From 7512933c656bc8b2242ee4dec8a1d7e6113e0707 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Thu, 25 Dec 2025 16:26:08 +0530 Subject: [PATCH] AI Image and Audio Generation Improvements. AI Video Generation Pre-Flight Checklist. Cost Estimate Improvements. --- .gitignore | 5 +- COST_ESTIMATE_IMPROVEMENTS.md | 337 +++++ IMPLEMENTATION_VALIDATION.md | 266 ---- PRE_FLIGHT_CHECKLIST.md | 402 ++++++ .../ai_writers/ai_agents_crew_writer.py | 192 --- .../ai_writers/ai_blog_faqs_writer/README.md | 192 --- .../faqs_generator_blog.py | 444 ------- .../ai_writers/ai_blog_faqs_writer/faqs_ui.py | 312 ----- .../ai_writers/ai_copywriter/4c_copywriter.py | 226 ---- .../ai_writers/ai_copywriter/4r_copywriter.py | 214 ---- .../ai_writers/ai_copywriter/README.md | 141 --- .../ai_writers/ai_copywriter/README_TBD.md | 97 -- .../ai_copywriter/acca_copywriter.py | 182 --- .../ai_copywriter/ai_emotional_copywriter.py | 168 --- .../ai_copywriter/aida_copywriter.py | 211 ---- .../ai_copywriter/aidppc_copywriter.py | 191 --- .../ai_copywriter/app_copywriter.py | 176 --- .../ai_copywriter/copywriter_dashboard.py | 674 ---------- .../ai_copywriter/fab_copywriter.py | 212 ---- .../ai_copywriter/oath_copywriter.py | 186 --- .../ai_copywriter/pas_copywriter.py | 213 ---- .../ai_copywriter/quest_copywriter.py | 191 --- .../ai_copywriter/star_copywriter.py | 182 --- .../ai_finance_report_generator/README.md | 190 --- .../ai_financial_dashboard.py | 358 ------ .../reports/README.md | 265 ---- .../reports/fundamental_analysis/__init__.py | 34 - .../reports/market_research/__init__.py | 29 - .../reports/news_analysis/__init__.py | 33 - .../reports/options_analysis/__init__.py | 33 - .../reports/portfolio_analysis/__init__.py | 32 - .../reports/technical_analysis/__init__.py | 314 ----- .../utils/__init__.py | 62 - .../utils/storage.py | 208 ---- .../ai_writers/ai_story_illustrator/README.md | 75 -- .../ai_story_illustrator/__init__.py | 7 - .../ai_story_illustrator/story_illustrator.py | 727 ----------- .../ai_writers/ai_story_illustrator/utils.py | 450 ------- .../ai_story_video_generator/README.md | 31 - .../ai_story_video_generator/__init__.py | 4 - .../story_video_generator.py | 1063 ---------------- .../ai_story_video_generator/utils.py | 64 - .../ai_writers/ai_story_writer/README.md | 103 -- .../ai_story_writer/ai_story_generator.py | 238 ---- .../ai_story_writer/story_writer.py | 134 -- .../ai_writers/data_img_slides_analyst.py | 0 .../ai_writers/github_blogs/README.md | 259 ---- .../github_blogs/github_getting_started.py | 254 ---- .../main_getting_started_blogs.py | 157 --- .../github_blogs/scrape_github_readme.py | 427 ------- ToBeMigrated/ai_writers/gpt_blog_sections.py | 50 - ToBeMigrated/ai_writers/image_ai_writer.py | 109 -- .../speech_to_blog/main_audio_to_blog.py | 143 --- .../ai_writers/twitter_writers/README.md | 165 --- .../ai_writers/twitter_writers/__init__.py | 9 - .../twitter_writers/tweet_generator/README.md | 163 --- .../tweet_generator/__init__.py | 9 - .../tweet_generator/smart_tweet_generator.py | 1081 ----------------- .../twitter_writers/twitter_dashboard.py | 729 ----------- .../twitter_streamlit_ui/README.md | 203 ---- .../twitter_streamlit_ui/__init__.py | 66 - .../twitter_streamlit_ui/components/cards.py | 634 ---------- .../twitter_streamlit_ui/components/forms.py | 1041 ---------------- .../components/navigation.py | 554 --------- .../twitter_streamlit_ui/dashboard.py | 278 ----- .../twitter_streamlit_ui/styles/theme.py | 173 --- .../twitter_streamlit_ui/twitter_dashboard.py | 503 -------- .../twitter_streamlit_ui/utils/helpers.py | 194 --- ToBeMigrated/ai_writers/web_url_ai_writer.py | 121 -- .../ai_writers/youtube_writers/README | 225 ---- .../modules/README_Thumbnail_Generator.md | 96 -- .../modules/README_endScreen.md | 108 -- .../modules/README_yt_shorts_scripts.md | 273 ----- .../youtube_writers/modules/__init__.py | 5 - .../modules/channel_trailer_generator.py | 1079 ---------------- .../modules/community_post_generator.py | 591 --------- .../modules/description_generator.py | 404 ------ .../modules/end_screen_generator.py | 740 ----------- .../modules/script_generator.py | 556 --------- .../modules/shorts_script_generator.py | 314 ----- .../modules/shorts_video_generator.py | 972 --------------- .../youtube_writers/modules/tags_generator.py | 406 ------- .../modules/thumbnail_generator.py | 622 ---------- .../modules/title_generator.py | 452 ------- .../youtube_writers/youtube_ai_writer.py | 237 ---- .../alwrity_ui/alwrity_researcher/README.md | 169 --- .../alwrity_researcher/README_DASHBOARD.md | 98 -- .../alwrity_ui/alwrity_researcher/config.py | 66 - .../alwrity_researcher/dashboard.py | 729 ----------- .../alwrity_ui/alwrity_researcher/main.py | 14 - .../alwrity_ui/alwrity_researcher/style.css | 517 -------- .../alwrity_ui/alwrity_researcher/utils.py | 380 ------ .../content_performance_predictor_ui.py | 684 ----------- ToBeMigrated/alwrity_ui/dashboard_styles.py | 510 -------- .../alwrity_ui/display_google_serp_results.py | 277 ----- ToBeMigrated/alwrity_ui/google_trends_ui.py | 458 ------- .../alwrity_ui/keyword_web_researcher.py | 585 --------- ToBeMigrated/alwrity_ui/navigation_styles.py | 331 ----- ToBeMigrated/alwrity_ui/settings_page.py | 973 --------------- ToBeMigrated/alwrity_ui/similar_analysis.py | 374 ------ .../alwrity_ui/social_media_dashboard.py | 116 -- .../competitive_intelligence/README.md | 488 -------- .../ai_competitive_intelligence.py | 725 ----------- ToBeMigrated/content_scheduler/README.md | 804 ------------ .../core/conflict_resolver.py | 403 ------ .../content_scheduler/core/health_checker.py | 584 --------- .../core/schedule_optimizer.py | 597 --------- .../core/schedule_validator.py | 611 ---------- .../content_scheduler/core/scheduler.py | 402 ------ .../integrations/calendar_integration.py | 651 ---------- ToBeMigrated/content_scheduler/models/job.py | 112 -- .../content_scheduler/models/job_status.py | 15 - .../content_scheduler/models/schedule.py | 153 --- .../content_scheduler/models/timeline.py | 75 -- .../content_scheduler/requirements.txt | 26 - .../content_scheduler/utils/date_utils.py | 201 --- .../content_scheduler/utils/error_handling.py | 134 -- .../content_scheduler/utils/logging.py | 11 - .../content_scheduler/utils/notification.py | 285 ----- .../content_scheduler/utils/timeline_utils.py | 381 ------ .../content_scheduler/utils/validation.py | 162 --- backend/api/podcast/handlers/images.py | 46 +- backend/api/youtube/handlers/audio.py | 376 ++++++ backend/api/youtube/handlers/images.py | 343 +++++- backend/api/youtube/router.py | 620 +++++++++- .../llm_providers/main_audio_generation.py | 6 +- .../llm_providers/main_image_generation.py | 365 +++++- backend/services/youtube/renderer.py | 65 +- backend/services/youtube/scene_builder.py | 98 +- .../YouTubeCreator/YouTubeCreator.tsx | 324 ++++- .../components/AssetGenerationCostCard.tsx | 363 ++++++ .../components/AudioSettingsModal.tsx | 512 ++++++++ .../components/CombinedSceneOverview.tsx | 85 +- .../components/CostEstimateCard.tsx | 459 +++++-- .../YouTubeCreator/components/RenderStep.tsx | 442 +++++-- .../YouTubeCreator/components/SceneCard.tsx | 816 ++++++------- .../SceneCard/GenerationButtons.tsx | 164 +++ .../components/SceneCard/GenerationModals.tsx | 53 + .../components/SceneCard/InfoAlert.tsx | 48 + .../components/SceneCard/SceneContent.tsx | 333 +++++ .../components/SceneCard/SceneEditForm.tsx | 82 ++ .../components/SceneCard/SceneHeader.tsx | 203 ++++ .../components/SceneCard/hooks/index.ts | 2 + .../SceneCard/hooks/useGenerationState.ts | 67 + .../SceneCard/hooks/useSceneMedia.ts | 54 + .../components/SceneGenerationStep.tsx | 215 ++++ .../components/ScenePreviewModal.tsx | 249 ++++ .../components/VideoRenderQueue.tsx | 179 +++ .../components/YouTubeCreator/constants.ts | 2 +- .../YouTubeCreator/hooks/useCostEstimate.ts | 26 +- .../hooks/useGenerationState.ts | 55 + .../hooks/useImageGenerationPolling.ts | 188 +++ .../YouTubeCreator/hooks/useSceneMedia.ts | 80 ++ .../hooks/useVideoRenderQueue.ts | 232 ++++ .../hooks/useYouTubeRenderQueue.ts | 268 ++++ .../shared/YouTubeImageGenerationModal.tsx | 687 +++++++++++ .../components/YouTubeCreator/shared/index.ts | 2 + .../YouTubeCreator/utils/operationHelpers.ts | 1 + .../components/shared/AudioSettingsModal.tsx | 648 ++++++++++ frontend/src/components/shared/index.ts | 6 +- frontend/src/hooks/useYouTubeCreatorState.ts | 2 +- frontend/src/services/youtubeApi.ts | 198 ++- frontend/src/utils/fetchMediaBlobUrl.ts | 4 +- 163 files changed, 8938 insertions(+), 37401 deletions(-) create mode 100644 COST_ESTIMATE_IMPROVEMENTS.md delete mode 100644 IMPLEMENTATION_VALIDATION.md create mode 100644 PRE_FLIGHT_CHECKLIST.md delete mode 100644 ToBeMigrated/ai_writers/ai_agents_crew_writer.py delete mode 100644 ToBeMigrated/ai_writers/ai_blog_faqs_writer/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_generator_blog.py delete mode 100644 ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_ui.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/4c_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/4r_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/README_TBD.md delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/acca_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/ai_emotional_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/aida_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/aidppc_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/app_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/copywriter_dashboard.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/fab_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/oath_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/pas_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/quest_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_copywriter/star_copywriter.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/utils/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_finance_report_generator/utils/storage.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_illustrator/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_story_illustrator/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_illustrator/story_illustrator.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_illustrator/utils.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_video_generator/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_story_video_generator/__init__.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_video_generator/story_video_generator.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_video_generator/utils.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_writer/README.md delete mode 100644 ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py delete mode 100644 ToBeMigrated/ai_writers/ai_story_writer/story_writer.py delete mode 100644 ToBeMigrated/ai_writers/data_img_slides_analyst.py delete mode 100644 ToBeMigrated/ai_writers/github_blogs/README.md delete mode 100644 ToBeMigrated/ai_writers/github_blogs/github_getting_started.py delete mode 100644 ToBeMigrated/ai_writers/github_blogs/main_getting_started_blogs.py delete mode 100644 ToBeMigrated/ai_writers/github_blogs/scrape_github_readme.py delete mode 100644 ToBeMigrated/ai_writers/gpt_blog_sections.py delete mode 100644 ToBeMigrated/ai_writers/image_ai_writer.py delete mode 100644 ToBeMigrated/ai_writers/speech_to_blog/main_audio_to_blog.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/README.md delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/__init__.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/tweet_generator/README.md delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/tweet_generator/__init__.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_dashboard.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/README.md delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/twitter_dashboard.py delete mode 100644 ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py delete mode 100644 ToBeMigrated/ai_writers/web_url_ai_writer.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/README delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/README_endScreen.md delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/README_yt_shorts_scripts.md delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/__init__.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/channel_trailer_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/community_post_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/description_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/end_screen_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/script_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/shorts_script_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/shorts_video_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/tags_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/thumbnail_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/modules/title_generator.py delete mode 100644 ToBeMigrated/ai_writers/youtube_writers/youtube_ai_writer.py delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/README.md delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/README_DASHBOARD.md delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/config.py delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/dashboard.py delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/main.py delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/style.css delete mode 100644 ToBeMigrated/alwrity_ui/alwrity_researcher/utils.py delete mode 100644 ToBeMigrated/alwrity_ui/content_performance_predictor_ui.py delete mode 100644 ToBeMigrated/alwrity_ui/dashboard_styles.py delete mode 100644 ToBeMigrated/alwrity_ui/display_google_serp_results.py delete mode 100644 ToBeMigrated/alwrity_ui/google_trends_ui.py delete mode 100644 ToBeMigrated/alwrity_ui/keyword_web_researcher.py delete mode 100644 ToBeMigrated/alwrity_ui/navigation_styles.py delete mode 100644 ToBeMigrated/alwrity_ui/settings_page.py delete mode 100644 ToBeMigrated/alwrity_ui/similar_analysis.py delete mode 100644 ToBeMigrated/alwrity_ui/social_media_dashboard.py delete mode 100644 ToBeMigrated/competitive_intelligence/README.md delete mode 100644 ToBeMigrated/competitive_intelligence/ai_competitive_intelligence.py delete mode 100644 ToBeMigrated/content_scheduler/README.md delete mode 100644 ToBeMigrated/content_scheduler/core/conflict_resolver.py delete mode 100644 ToBeMigrated/content_scheduler/core/health_checker.py delete mode 100644 ToBeMigrated/content_scheduler/core/schedule_optimizer.py delete mode 100644 ToBeMigrated/content_scheduler/core/schedule_validator.py delete mode 100644 ToBeMigrated/content_scheduler/core/scheduler.py delete mode 100644 ToBeMigrated/content_scheduler/integrations/calendar_integration.py delete mode 100644 ToBeMigrated/content_scheduler/models/job.py delete mode 100644 ToBeMigrated/content_scheduler/models/job_status.py delete mode 100644 ToBeMigrated/content_scheduler/models/schedule.py delete mode 100644 ToBeMigrated/content_scheduler/models/timeline.py delete mode 100644 ToBeMigrated/content_scheduler/requirements.txt delete mode 100644 ToBeMigrated/content_scheduler/utils/date_utils.py delete mode 100644 ToBeMigrated/content_scheduler/utils/error_handling.py delete mode 100644 ToBeMigrated/content_scheduler/utils/logging.py delete mode 100644 ToBeMigrated/content_scheduler/utils/notification.py delete mode 100644 ToBeMigrated/content_scheduler/utils/timeline_utils.py delete mode 100644 ToBeMigrated/content_scheduler/utils/validation.py create mode 100644 backend/api/youtube/handlers/audio.py create mode 100644 frontend/src/components/YouTubeCreator/components/AssetGenerationCostCard.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/AudioSettingsModal.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/GenerationButtons.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/GenerationModals.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/InfoAlert.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/SceneContent.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/SceneEditForm.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/SceneHeader.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/hooks/index.ts create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/hooks/useGenerationState.ts create mode 100644 frontend/src/components/YouTubeCreator/components/SceneCard/hooks/useSceneMedia.ts create mode 100644 frontend/src/components/YouTubeCreator/components/SceneGenerationStep.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/ScenePreviewModal.tsx create mode 100644 frontend/src/components/YouTubeCreator/components/VideoRenderQueue.tsx create mode 100644 frontend/src/components/YouTubeCreator/hooks/useGenerationState.ts create mode 100644 frontend/src/components/YouTubeCreator/hooks/useImageGenerationPolling.ts create mode 100644 frontend/src/components/YouTubeCreator/hooks/useSceneMedia.ts create mode 100644 frontend/src/components/YouTubeCreator/hooks/useVideoRenderQueue.ts create mode 100644 frontend/src/components/YouTubeCreator/hooks/useYouTubeRenderQueue.ts create mode 100644 frontend/src/components/YouTubeCreator/shared/YouTubeImageGenerationModal.tsx create mode 100644 frontend/src/components/YouTubeCreator/shared/index.ts create mode 100644 frontend/src/components/shared/AudioSettingsModal.tsx diff --git a/.gitignore b/.gitignore index ac7e963b..7c2ce73d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,10 @@ backend/podcast_videos/ youtube_avatars/ -youtube_avatars +youtube_avatars/* +youtube_videos/* +youtube_images/ +youtube_audio .cursorignore story_videos diff --git a/COST_ESTIMATE_IMPROVEMENTS.md b/COST_ESTIMATE_IMPROVEMENTS.md new file mode 100644 index 00000000..110b6054 --- /dev/null +++ b/COST_ESTIMATE_IMPROVEMENTS.md @@ -0,0 +1,337 @@ +# ๐Ÿ’ฐ Cost Estimate Improvements - YouTube Creator + +## Summary of Changes + +Enhanced cost estimation display with user-friendly messaging, clear explanations, and accurate calculations to help users understand exactly what they're paying for. + +--- + +## โœ… Completed Improvements + +### 1. **OperationButton Integration** (Already Implemented) +- โœ… The "Generate Video Plan" button in `PlanStep.tsx` already uses `OperationButton` with `showCost={true}` +- โœ… Shows cost estimate on hover using the `videoPlanningOperation` +- โœ… Validates subscription limits before allowing the action +- โœ… Displays user-friendly error messages if limits exceeded + +**Current Implementation:** +```typescript +} + onClick={onGeneratePlan} + disabled={loading || !userIdea.trim()} + loading={loading} + checkOnHover={true} + checkOnMount={false} + showCost={true} // โœ… Already showing cost! + sx={{ alignSelf: 'flex-start', px: 4 }} +/> +``` + +--- + +### 2. **Enhanced CostEstimateCard Component** + +#### **Before:** +- Basic cost display with technical jargon +- Simple breakdown without context +- No explanation of what's included +- Dry, accounting-style presentation + +#### **After:** +- ๐ŸŽจ **Beautiful visual design** with gradients and icons +- ๐Ÿ’ก **Clear explanations** in simple, non-technical language +- ๐Ÿ“Š **Detailed breakdown** of what's included in the price +- ๐ŸŽฏ **User-focused messaging** explaining the value + +--- + +## ๐ŸŽจ Key Improvements in Detail + +### A. **Header Section - More Engaging** +```typescript + + + ๐Ÿ’ฐ Total Cost Estimate + + + What you'll pay to create this video + +``` + +**Why:** Immediately clarifies what the user is looking at and sets expectations. + +--- + +### B. **Total Cost Display - More Prominent** +```typescript + + ${costEstimate.total_cost.toFixed(2)} + + + Estimated range: $X.XX - $X.XX + + + Final cost may vary by ยฑ10% based on actual processing + +``` + +**Why:** Large, clear pricing builds trust. The range and disclaimer manage expectations. + +--- + +### C. **"What's Included" Section - Educational** + +**1. AI Video Generation** +```typescript + AI Video Generation [$X.XX] +Creating 5 video scenes (45 seconds total) at 720p quality +Rate: $0.10/second โ€ข Using advanced AI to transform your narration into engaging video scenes +``` + +**2. Scene Images (if applicable)** +```typescript + Scene Images [$X.XX] +Generating 5 custom images for your video scenes using ideogram-v3-turbo +Rate: $0.10/image โ€ข High-quality AI-generated visuals tailored to your content +``` + +**Why:** +- Users understand exactly what they're paying for +- Clear breakdown by cost component +- Explains the value (AI processing, custom generation) +- Shows rates for transparency + +--- + +### D. **"Good to Know" Summary Box** +```typescript +๐Ÿ’ก Good to know: You only pay for the AI processing to create your video. +There are no hidden fees, subscription requirements, or storage charges. +Once created, your video is yours to download and use forever! +``` + +**Why:** +- Addresses common user concerns (hidden fees, subscriptions) +- Builds trust with transparency +- Emphasizes ownership (video is yours forever) +- Reduces anxiety about unexpected charges + +--- + +### E. **Per-Scene Breakdown - Interactive** +```typescript +๐Ÿ“Š Cost Per Scene [5 scenes] + +Scene 1 +5s video (optimized from 7s) [$0.50] + +Scene 2 +10s video [$1.00] + ++ 3 more scenes +(scroll down after rendering to see all scenes) +``` + +**Why:** +- Shows cost per scene for granular understanding +- Indicates optimization (7s โ†’ 5s) to demonstrate value +- Hover effects make it interactive +- "Show more" messaging for long lists + +--- + +### F. **Educational Help Section** +```typescript + + Why does video creation cost money? + + Creating videos with AI requires powerful computing resources. Each second of video is + generated by advanced AI models that analyze your script, create visuals, and synchronize + everything perfectly. The cost covers the actual AI processing time needed to bring your + content to life. + +``` + +**Why:** +- Educates users on why AI costs money +- Justifies the pricing with clear reasoning +- Builds understanding and reduces objections +- Positions the service as fair and valuable + +--- + +## ๐ŸŽฏ User Experience Benefits + +### **Before:** +- โŒ User sees technical cost breakdown +- โŒ No context for what they're paying for +- โŒ Unclear if there are hidden fees +- โŒ No explanation of AI processing costs +- โŒ Dry, accounting-style presentation + +### **After:** +- โœ… User sees beautiful, engaging cost card +- โœ… Clear explanation of every cost component +- โœ… Reassurance about no hidden fees +- โœ… Educational content about AI processing +- โœ… Professional, trust-building presentation + +--- + +## ๐Ÿ“Š Calculation Accuracy + +### **Video Rendering Cost** +```typescript +const videoRenderCost = useMemo(() => { + if (!costEstimate) return 0; + return costEstimate.total_cost - totalImageCost; +}, [costEstimate, totalImageCost]); +``` + +### **Image Generation Cost** +```typescript +const totalImageCost = useMemo(() => { + if (!costEstimate) return 0; + return costEstimate.total_image_cost || + (costEstimate.image_cost_per_scene ? costEstimate.num_scenes * costEstimate.image_cost_per_scene : 0); +}, [costEstimate]); +``` + +**Why:** +- Separates video and image costs for clarity +- Uses memoization for performance +- Handles missing data gracefully (fallbacks) +- Ensures accurate totals + +--- + +## ๐ŸŽจ Visual Design Improvements + +### **Color Palette:** +- Primary: `#667eea` (Purple-blue - trust, creativity) +- Success: `#10b981` (Green - value, savings) +- Text: `#1e293b` (Dark slate - readability) +- Muted: `#64748b` (Gray - secondary info) + +### **Layout:** +- Gradient background for visual appeal +- White cards with shadows for depth +- Icons for visual hierarchy +- Chips for cost highlights +- Hover effects for interactivity + +### **Typography:** +- Large, bold total cost (2.5rem) +- Clear hierarchy (h6 โ†’ body2 โ†’ caption) +- Weighted text for emphasis (600-800) +- Reduced letter spacing (-0.01em) for modern look + +--- + +## ๐Ÿ’ก Key User-Facing Messages + +### **1. Transparency** +> "What you'll pay to create this video" + +### **2. Trust** +> "No hidden fees, subscription requirements, or storage charges" + +### **3. Ownership** +> "Once created, your video is yours to download and use forever!" + +### **4. Education** +> "Creating videos with AI requires powerful computing resources" + +### **5. Value** +> "Using advanced AI to transform your narration into engaging video scenes" + +--- + +## ๐Ÿš€ Impact on User Conversion + +### **Expected Improvements:** + +1. **Reduced Anxiety** + - Clear pricing eliminates "hidden cost" fears + - Educational content justifies the expense + +2. **Increased Trust** + - Transparent breakdown builds credibility + - "No hidden fees" messaging removes barriers + +3. **Better Understanding** + - Users know exactly what they're buying + - Per-scene breakdown shows granular value + +4. **Professional Presentation** + - Beautiful UI signals quality service + - Attention to detail builds confidence + +5. **Reduced Support Inquiries** + - Comprehensive explanations answer questions upfront + - Clear messaging reduces confusion + +--- + +## ๐Ÿ“ Future Enhancements (Optional) + +### **1. Cost Comparison** +```typescript +๐Ÿ’ฐ This video: $4.50 +๐Ÿ“Š Industry average: $15-50 per video +โœ… You save: ~70-90% +``` + +### **2. Volume Discounts** +```typescript +๐ŸŽฏ Create 10+ videos/month +๐Ÿ’ธ Get 20% off all video creation +``` + +### **3. Cost History** +```typescript +๐Ÿ“ˆ Your last 5 videos +Average: $3.80/video +Trend: โ†“ 15% (you're optimizing!) +``` + +### **4. Interactive Cost Calculator** +```typescript +๐Ÿงฎ Adjust settings to see cost changes: +- Resolution: [480p] [720p] [1080p] +- Scenes: [3] [5] [8] +Real-time cost update: $X.XX +``` + +--- + +## โœ… Testing Checklist + +- [x] Cost calculation accuracy verified +- [x] All cost components displayed +- [x] No linter errors +- [x] Responsive design works on mobile +- [x] Loading states handled gracefully +- [x] Error states display user-friendly messages +- [x] OperationButton integration confirmed +- [x] User messaging is clear and accurate + +--- + +## ๐ŸŽ‰ Conclusion + +The enhanced cost estimation provides: +- โœ… **Clarity**: Users know exactly what they're paying for +- โœ… **Trust**: Transparent pricing with no hidden fees +- โœ… **Education**: Explains why AI costs money +- โœ… **Value**: Shows the quality and ownership benefits +- โœ… **Beauty**: Professional, engaging visual design + +**Result:** Users feel confident, informed, and motivated to create their videos! ๐Ÿš€ + diff --git a/IMPLEMENTATION_VALIDATION.md b/IMPLEMENTATION_VALIDATION.md deleted file mode 100644 index bad97b60..00000000 --- a/IMPLEMENTATION_VALIDATION.md +++ /dev/null @@ -1,266 +0,0 @@ -# YouTube Creator Avatar System - Implementation Validation - -## โœ… Implementation Status: COMPLETE - -All components from the plan have been successfully implemented and validated. - ---- - -## Phase 1: Backend - YouTube Avatar Handlers โœ… - -### File: `backend/api/youtube/handlers/avatar.py` - -**Status**: โœ… Fully Implemented - -**Endpoints Verified**: - -1. **`POST /api/youtube/avatar/upload`** โœ… - - Accepts file upload (max 5MB validation) - - Saves to `youtube_avatars/` directory - - Returns avatar URL - - Includes asset tracking via `save_asset_to_library` - - **Location**: Lines 44-113 - -2. **`POST /api/youtube/avatar/make-presentable`** โœ… - - Uses `edit_image()` from `main_image_editing.py` (includes preflight checks) - - YouTube-specific transformation prompt implemented: - ``` - Transform this image into a professional YouTube creator: - - Half-length portrait, looking at camera - - Modern YouTube creator appearance - - Confident, energetic, engaging expression - - Professional studio lighting, clean background - - Suitable for video generation and thumbnails - - Maintain person's appearance and identity - - Ultra realistic, 4k quality - ``` - - **Location**: Lines 115-194 - -3. **`POST /api/youtube/avatar/generate`** โœ… - - Uses `generate_image()` from `main_image_generation.py` (includes preflight checks) - - YouTube-specific prompt with context-aware variations (content_type, audience) - - **Location**: Lines 197-297 - -4. **`GET /api/youtube/images/avatars/{filename}`** โœ… - - Serves avatar images with security validation - - **Location**: Lines 300-319 - -**Key Features**: -- โœ… Uses shared services (`main_image_generation`, `main_image_editing`) -- โœ… Preflight checks via `user_id` parameter (automatic in shared services) -- โœ… Separate storage in `youtube_avatars/` directory -- โœ… YouTube-specific prompts -- โœ… Asset tracking integration - ---- - -## Phase 2: Backend - YouTube Scene Images โœ… - -### File: `backend/api/youtube/handlers/images.py` - -**Status**: โœ… Fully Implemented - -**Endpoints Verified**: - -1. **`POST /api/youtube/image`** โœ… - - If `base_avatar_url` provided: Uses WaveSpeed Ideogram Character API for consistency - - Otherwise: Generates from scratch with YouTube-optimized prompts - - Uses `validate_image_generation_operations()` for preflight checks - - Saves to `youtube_images/` directory - - **Location**: Lines 77-195 - -2. **`GET /api/youtube/images/scenes/{filename}`** โœ… - - Serves scene images with security validation - - **Location**: Lines 196-216 - -**Key Features**: -- โœ… Character consistency via WaveSpeed `generate_character_image()` -- โœ… Preflight validation via `validate_image_generation_operations()` -- โœ… Separate storage in `youtube_images/` directory -- โœ… YouTube-optimized prompts for both avatar-based and scratch generation - ---- - -## Phase 3: Backend - Router Integration โœ… - -### File: `backend/api/youtube/router.py` - -**Status**: โœ… Fully Implemented - -**Verification**: -- โœ… Imports handlers: Lines 26-27 - ```python - from .handlers import avatar as avatar_handlers - from .handlers import images as image_handlers - ``` - -- โœ… Directory constants: Lines 36-39 - ```python - YOUTUBE_AVATARS_DIR = base_dir / "youtube_avatars" - YOUTUBE_AVATARS_DIR.mkdir(parents=True, exist_ok=True) - YOUTUBE_IMAGES_DIR = base_dir / "youtube_images" - YOUTUBE_IMAGES_DIR.mkdir(parents=True, exist_ok=True) - ``` - -- โœ… Router includes: Lines 42-43 - ```python - router.include_router(avatar_handlers.router) - router.include_router(image_handlers.router) - ``` - -**Route Resolution**: -- Avatar router uses `prefix="/avatar"` โ†’ Final routes: `/api/youtube/avatar/*` -- Images router uses no prefix, individual routes โ†’ Final routes: `/api/youtube/image`, `/api/youtube/images/*` - ---- - -## Phase 4: Frontend - API Service โœ… - -### File: `frontend/src/services/youtubeApi.ts` - -**Status**: โœ… Fully Implemented - -**Methods Verified**: - -1. **`uploadAvatar(file: File)`** โœ… - - **Location**: Lines 228-240 - - Returns `AvatarUploadResponse` - -2. **`makeAvatarPresentable(avatarUrl, projectId?)`** โœ… - - **Location**: Lines 245-258 - - Returns `AvatarTransformResponse` - -3. **`generateCreatorAvatar(params)`** โœ… - - **Location**: Lines 263-277 - - Returns `AvatarTransformResponse` - -4. **`generateSceneImage(params)`** โœ… - - **Location**: Lines 282-302 - - Returns `SceneImageResponse` - -5. **`getAvatarUrl(filename)`** โœ… - - **Location**: Lines 307-309 - -6. **`getSceneImageUrl(filename)`** โœ… - - **Location**: Lines 314-316 - -**Interfaces Defined**: -- โœ… `AvatarUploadResponse` (Lines 93-97) -- โœ… `AvatarTransformResponse` (Lines 99-103) -- โœ… `SceneImageRequest` (Lines 105-117) -- โœ… `SceneImageResponse` (Lines 119-126) - ---- - -## Phase 5: Frontend - PlanStep UI Enhancement โœ… - -### File: `frontend/src/components/YouTubeCreator/components/PlanStep.tsx` - -**Status**: โœ… Fully Implemented - -**Features Verified**: - -1. **State Variables** โœ… - - `avatarPreview`, `avatarUrl`, `uploadingAvatar`, `makingPresentable` (Lines 32-33, 50-51) - -2. **Upload Handler** โœ… - - File validation (max 5MB, image types) - - **Location**: Lines 64-68 - -3. **"Make Presentable" Button** โœ… - - AI transformation trigger - - **Location**: Lines 136-142 - -4. **Visual Preview** โœ… - - Image preview with remove option - - **Location**: Lines 104-143 - -5. **Props Integration** โœ… - - All handlers passed from parent - - **Location**: Lines 40-42, 58-60 - -**UI Components**: -- โœ… Upload area with drag-and-drop styling (Lines 144-177) -- โœ… Preview with delete button (Lines 104-143) -- โœ… "Make Presentable" button with loading state (Lines 136-142) -- โœ… Helpful tooltips and descriptions (Lines 179-195) - ---- - -## Phase 6: Parent Component Integration โœ… - -### File: `frontend/src/components/YouTubeCreator/YouTubeCreator.tsx` - -**Status**: โœ… Fully Implemented - -**State Management** โœ…: -- `avatarPreview`, `avatarUrl`, `uploadingAvatar`, `makingPresentable` (Lines 44-47) - -**Handlers** โœ…: -- `handleAvatarUpload` (Lines 129-144) -- `handleRemoveAvatar` (Lines 146-149) -- `handleMakePresentable` (Lines 151-164) - -**Props Passing** โœ…: -- All avatar-related props passed to `PlanStep` (Lines 445-454) - ---- - -## Separation of Concerns Validation โœ… - -| Component | Podcast | YouTube | Shared | Status | -|-----------|---------|---------|--------|--------| -| Avatar handlers | `podcast/handlers/avatar.py` | `youtube/handlers/avatar.py` | - | โœ… Separate | -| Image handlers | `podcast/handlers/images.py` | `youtube/handlers/images.py` | - | โœ… Separate | -| Image generation | - | - | `main_image_generation.py` | โœ… Shared | -| Image editing | - | - | `main_image_editing.py` | โœ… Shared | -| Preflight validation | - | - | `preflight_validator.py` | โœ… Shared | -| File storage | `podcast_avatars/` | `youtube_avatars/` | - | โœ… Separate | -| Prompts | Podcast-specific | YouTube-specific | - | โœ… Separate | - -**Verification**: โœ… No changes made to podcast code. All YouTube functionality is isolated. - ---- - -## Testing Checklist - -- [x] Avatar upload works and saves to correct directory -- [x] "Make Presentable" transforms image with YouTube-specific prompt -- [x] Auto-generate creates appropriate YouTube creator avatar -- [x] Preflight checks integrated (via shared services) -- [x] Scene images maintain character consistency when avatar provided -- [x] Podcast maker code remains unchanged -- [x] No shared state between podcast and YouTube modules -- [x] Router integration correct (no duplicate prefixes) -- [x] Frontend API methods implemented -- [x] UI components integrated - ---- - -## Implementation Quality Notes - -### โœ… Strengths: -1. **Clean separation**: No cross-contamination between podcast and YouTube code -2. **Shared services**: Proper reuse of `main_image_generation` and `main_image_editing` -3. **Preflight checks**: Automatically included via `user_id` parameter -4. **Security**: Filename validation, path traversal protection -5. **Asset tracking**: Integrated with `save_asset_to_library` -6. **Error handling**: Comprehensive try-catch blocks with proper logging - -### โœ… URL Path Consistency Fixed: -1. **Image serving**: โœ… Fixed - Unified serving endpoint in `images.py` router: - - Route: `/images/{category}/{filename}` where category is "avatars" or "scenes" - - Final path: `/api/youtube/images/{category}/{filename}` - - Matches upload URL generation: `/api/youtube/images/avatars/{filename}` - - Removed duplicate serving endpoint from `avatar.py` - -2. **Directory initialization**: `YOUTUBE_AVATARS_DIR` is initialized in both `avatar.py` and `router.py`. This is fine (defensive), but could be centralized. - ---- - -## Final Validation Result: โœ… IMPLEMENTATION COMPLETE - -All planned features have been implemented according to the specification. The system maintains strict separation of concerns, properly integrates with shared services, and includes all required endpoints and UI components. - -**Ready for testing and deployment.** - diff --git a/PRE_FLIGHT_CHECKLIST.md b/PRE_FLIGHT_CHECKLIST.md new file mode 100644 index 00000000..ae7b1a11 --- /dev/null +++ b/PRE_FLIGHT_CHECKLIST.md @@ -0,0 +1,402 @@ +# ๐Ÿš€ YouTube Creator Video Generation - Pre-Flight Checklist + +## Status: โœ… GREEN LIGHT FOR TESTING + +This document confirms that all critical implementation areas have been reviewed and validated to prevent wasting AI video generation calls during testing. + +--- + +## 1. โœ… Polling for Results - **IMPLEMENTED & ROBUST** + +### Image Generation Polling (`useImageGenerationPolling.ts`) +- **Status**: โœ… **FULLY IMPLEMENTED** +- **Features**: + - โœ… Proper cleanup on unmount (prevents memory leaks) + - โœ… useRef for interval management (prevents race conditions) + - โœ… Retry logic with exponential backoff (max 3 retries) + - โœ… Timeout handling (5-minute max poll time) + - โœ… Error classification (network/server/not-found errors) + - โœ… Graceful degradation (stops polling on task not found) + - โœ… Progress reporting callback support + - โœ… Active polling map to track and cleanup multiple tasks + +### Integration in YouTubeCreator.tsx +- **Status**: โœ… **CORRECTLY INTEGRATED** +- โœ… `startImagePolling` called with proper callbacks +- โœ… `onComplete` updates scene state atomically +- โœ… `onError` displays user-friendly error messages +- โœ… `onProgress` logs progress for debugging +- โœ… Guards prevent duplicate polling for same scene + +--- + +## 2. โœ… Frontend Display Issues - **RESOLVED** + +### Scene Media Loading (`useSceneMedia.ts`) +- **Status**: โœ… **FULLY FUNCTIONAL** +- **Features**: + - โœ… Fetches media as authenticated blob URLs + - โœ… Proper cleanup (revokes blob URLs on unmount) + - โœ… Separate loading states for image and audio + - โœ… Fallback to direct URL if blob creation fails + - โœ… Error handling with console logging + - โœ… Reactive to imageUrl/audioUrl changes + +### SceneCard Display +- **Status**: โœ… **REFACTORED & ROBUST** +- **Features**: + - โœ… Modular sub-components (SceneHeader, SceneContent, etc.) + - โœ… Custom hooks for media loading and generation state + - โœ… Synchronizes local generation status with parent props + - โœ… Race condition handling (500ms delay check for imageUrl arrival) + - โœ… Detailed console logging for debugging + - โœ… Loading skeletons and progress indicators + - โœ… Proper display of both generated and uploaded avatars + +### Image/Audio Blob URL Loading +- **Status**: โœ… **AUTHENTICATED & WORKING** +- **Features**: + - โœ… Uses `fetchMediaBlobUrl` with auth token + - โœ… Fallback token query parameter for endpoints that support it + - โœ… Handles 404s gracefully (files might not exist yet) + - โœ… Proper error logging and fallback to direct URLs + +--- + +## 3. โœ… Previous Steps Generated Assets Loading - **VALIDATED** + +### Backend Validation (router.py) +- **Status**: โœ… **COMPREHENSIVE VALIDATION** +- **Validation Points**: + 1. โœ… **Line 495-498**: Checks for `imageUrl` and `audioUrl` on all enabled scenes + 2. โœ… **Line 606-609**: Validates `imageUrl` and `audioUrl` before single scene render + 3. โœ… Clear error messages guide users to generate missing assets + 4. โœ… Prevents expensive video API calls if assets are missing + +### Frontend Validation (RenderStep.tsx) +- **Status**: โœ… **REAL-TIME READINESS CHECK** +- **Features**: + - โœ… **Lines 129-145**: `sceneReadiness` memo tracks missing images/audio + - โœ… **Line 147**: `canStartRender` disabled until all scenes ready + - โœ… **Lines 167-228**: Visual alerts show: + - Success when all scenes are ready + - Warning with counts of missing images/audio + - Lists scene numbers with missing assets + - โœ… **Render button** shows readiness status in text + - โœ… Prevents user from wasting API calls on incomplete scenes + +### Backend Asset Reuse (renderer.py) +- **Status**: โœ… **EXISTING ASSETS PRIORITIZED** +- **Audio Reuse (Lines 101-131)**: + - โœ… Checks for `scene.get("audioUrl")` first + - โœ… Extracts filename from URL + - โœ… Loads audio from `youtube_audio/` directory + - โœ… Falls back to generation only if file not found + - โœ… Logs when using existing audio vs generating new + +- **Image Reuse (Lines not shown but referenced in summary)**: + - โœ… Similar pattern for `imageUrl` + - โœ… Prioritizes existing character-consistent images + - โœ… Only generates if missing + +--- + +## 4. โœ… State Management - **ATOMIC & SAFE** + +### Scene State Updates +- **Status**: โœ… **FUNCTIONAL STATE UPDATES** +- **Implementation**: + - โœ… Uses functional state updates: `scenes.map(s => s.scene_number === scene.scene_number ? { ...s, imageUrl } : s)` + - โœ… Prevents race conditions by reading current state + - โœ… Atomic updates ensure consistency + - โœ… `updateState({ scenes: updatedScenes })` persists to global state + +### Generation State Guards +- **Status**: โœ… **DUPLICATE PREVENTION** +- **Guards**: + - โœ… `if (generatingImageSceneId === scene.scene_number) return;` + - โœ… `if (generatingAudioSceneId === scene.scene_number) return;` + - โœ… `if (generatingImage || loading) return;` + - โœ… Prevents duplicate API calls during active generation + +--- + +## 5. โœ… Error Handling - **COMPREHENSIVE** + +### Backend Error Handling +- **Status**: โœ… **USER-FRIENDLY & DETAILED** +- **Features**: + - โœ… HTTPException with structured `detail` objects + - โœ… Clear `error`, `message`, and `user_action` fields + - โœ… Scene-specific error messages (e.g., "Scene 3: Missing image") + - โœ… Validation errors prevent expensive API calls + - โœ… Timeout errors with actionable suggestions + - โœ… Network error retry logic with exponential backoff + +### Frontend Error Display +- **Status**: โœ… **CLEAR USER FEEDBACK** +- **Features**: + - โœ… Error state displayed in SceneCard + - โœ… Toast notifications for success/error + - โœ… Detailed error messages extracted from API responses + - โœ… Fallback error messages for unknown errors + - โœ… Auto-dismiss success messages after 3 seconds + +--- + +## 6. โœ… Asset Library Integration - **WORKING** + +### Modal Implementation +- **Status**: โœ… **FULLY FUNCTIONAL** +- **Features**: + - โœ… Searches and filters by `source_module` (youtube_creator, podcast_maker) + - โœ… Displays images in responsive grid + - โœ… Authenticated image loading (no 401 errors) + - โœ… Loading, error, and empty states + - โœ… Favorites toggle support + +### Backend Asset Tracking +- **Status**: โœ… **ALL GENERATIONS TRACKED** +- **Tracked Assets**: + - โœ… YouTube avatars โ†’ `youtube_avatars/` + asset library + - โœ… Scene images โ†’ `youtube_images/` + asset library + - โœ… Scene audio โ†’ `youtube_audio/` + asset library + - โœ… Scene videos โ†’ `youtube_videos/` + asset library + - โœ… All with proper metadata (provider, model, cost, tags) + +--- + +## 7. โœ… Audio Settings Modal - **COMPREHENSIVE** + +### Modal Features +- **Status**: โœ… **FULLY IMPLEMENTED** +- **Parameters Exposed**: + - โœ… Voice selection (17 voices with descriptions) + - โœ… Speaking speed (0.5-2.0) + - โœ… Volume (0.1-10.0) + - โœ… Pitch (-12 to +12) + - โœ… Emotion (happy, neutral, sad, etc.) + - โœ… English normalization toggle + - โœ… Sample rate (8kHz-44.1kHz) + - โœ… Bitrate (32kbps-256kbps) + - โœ… Channel (mono/stereo) + - โœ… Format (mp3, wav, pcm, flac) + - โœ… Language boost + - โœ… Sync mode toggle + +### User Guidance +- **Status**: โœ… **EXCELLENT UX** +- โœ… Tooltips for every parameter +- โœ… Help icons with detailed explanations +- โœ… "Pro Tips" section +- โœ… Real-time settings preview +- โœ… Professional gradient design + +--- + +## 8. โœ… Image Settings Modal - **COMPREHENSIVE** + +### Modal Features +- **Status**: โœ… **FULLY IMPLEMENTED** +- **Parameters Exposed**: + - โœ… Custom prompt input + - โœ… Style selection (Auto, Fiction, Realistic) + - โœ… Rendering speed (Default, Turbo, Quality) + - โœ… Aspect ratio (16:9, 9:16, 1:1, etc.) + - โœ… Model selection (Ideogram V3 Turbo, Qwen Image) + - โœ… Dynamic cost estimation based on model + - โœ… YouTube-specific presets (Engaging Host, Cinematic, etc.) + +### Cost Transparency +- **Status**: โœ… **CLEAR PRICING** +- โœ… Cost per image displayed for each model +- โœ… Ideogram V3 Turbo: $0.10/image +- โœ… Qwen Image: $0.05/image +- โœ… Cost estimate updates with model selection + +--- + +## 9. โœ… Cost Estimation - **ACCURATE** + +### Backend Cost Calculation +- **Status**: โœ… **COMPREHENSIVE** +- **Components** (renderer.py `estimate_render_cost`): + - โœ… Video rendering cost (per scene, per second, per resolution) + - โœ… Image generation cost (per scene, per model) + - โœ… Model-specific breakdown (Ideogram vs Qwen) + - โœ… Total cost and cost range (ยฑ10% buffer) + +### Frontend Display +- **Status**: โœ… **PROFESSIONAL UI** +- **CostEstimateCard Features**: + - โœ… Large, readable total cost display + - โœ… Cost range for uncertainty + - โœ… Per-scene cost breakdown + - โœ… Image generation cost section + - โœ… Model-specific cost breakdown + - โœ… Scene-by-scene details (first 5 shown) + - โœ… Loading skeleton during calculation + +--- + +## 10. โœ… Video Rendering Workflow - **VALIDATED** + +### Pre-Render Validation +- **Status**: โœ… **MULTI-LAYER VALIDATION** +- **Validation Steps**: + 1. โœ… **Frontend (RenderStep.tsx)**: Button disabled until all scenes ready + 2. โœ… **Backend (router.py L495-498)**: Validates `imageUrl` and `audioUrl` exist + 3. โœ… **Backend (router.py L841-879)**: Pre-validates all scenes before starting + 4. โœ… **Backend (renderer.py L70-86)**: Validates visual prompts before API calls + +### Asset Utilization During Render +- **Status**: โœ… **EXISTING ASSETS USED FIRST** +- **Renderer Logic**: + - โœ… Checks for `scene.audioUrl` โ†’ loads existing audio + - โœ… Checks for `scene.imageUrl` โ†’ uses for character consistency + - โœ… Only generates new assets if missing + - โœ… Logs which assets are reused vs generated + - โœ… Prevents duplicate generation during render + +--- + +## 11. โœ… Background Task Management - **ROBUST** + +### Task Manager +- **Status**: โœ… **PRODUCTION-READY** +- **Features**: + - โœ… In-memory task tracking (persistent across requests) + - โœ… Task status updates (pending, processing, completed, failed) + - โœ… Progress tracking (0-100%) + - โœ… Result storage + - โœ… Error messages + - โœ… Auto-cleanup (tasks expire after 1 hour) + +### Image Generation Tasks +- **Status**: โœ… **NON-BLOCKING** +- **Implementation**: + - โœ… FastAPI BackgroundTasks for async execution + - โœ… Task initiated with immediate response (task_id) + - โœ… Frontend polls for status using `getImageGenerationStatus` + - โœ… Result includes `image_url` when completed + - โœ… Proper error handling and status updates + +--- + +## 12. โœ… Logging & Debugging - **COMPREHENSIVE** + +### Backend Logging +- **Status**: โœ… **DETAILED & STRUCTURED** +- **Logs Include**: + - โœ… Scene-specific identifiers + - โœ… Asset usage status (has_existing_image, has_existing_audio) + - โœ… Generation vs reuse decisions + - โœ… API call results and errors + - โœ… Cost tracking + - โœ… File paths and URLs + +### Frontend Logging +- **Status**: โœ… **VERBOSE FOR DEBUGGING** +- **Logs Include**: + - โœ… Render cycle tracking + - โœ… Image/audio URL changes + - โœ… Blob URL loading status + - โœ… Generation state transitions + - โœ… Polling progress and errors + - โœ… API response handling + +--- + +## 13. โœ… Per-Scene Generation - **FULLY IMPLEMENTED** + +### User Control +- **Status**: โœ… **GRANULAR CONTROL** +- **Features**: + - โœ… "Generate Image" button per scene + - โœ… "Generate Audio" button per scene + - โœ… "Regenerate" buttons for existing assets + - โœ… Scene enable/disable toggle + - โœ… Scene editing (title, narration, visual prompt) + - โœ… Visual feedback (loading, progress, success, error) + +### State Management +- **Status**: โœ… **INDIVIDUAL SCENE STATE** +- **Features**: + - โœ… `imageUrl` stored per scene + - โœ… `audioUrl` stored per scene + - โœ… `generatingImage` flag per scene + - โœ… `generatingAudio` flag per scene + - โœ… Independent generation for each scene + - โœ… No batch operations (prevents waste on failure) + +--- + +## 14. โœ… Testing Safeguards - **IN PLACE** + +### Development Guards +- **Status**: โœ… **PREVENTS DUPLICATE CALLS** +- **Safeguards**: + - โœ… **Line 275-279 (YouTubeCreator.tsx)**: Prevents duplicate scene building + ```typescript + if (scenes.length > 0) { + console.warn('[YouTubeCreator] Scenes already exist, skipping build to prevent duplicate AI calls'); + setError('Scenes have already been generated. Please refresh the page if you want to regenerate.'); + return; + } + ``` + - โœ… Generation guards prevent concurrent requests for same scene + - โœ… Validation prevents render without assets + - โœ… Clear error messages guide user to fix issues + +### Asset Reuse Strategy +- **Status**: โœ… **OPTIMIZED FOR TESTING** +- **Strategy**: + - โœ… Backend tries to reuse existing avatars from asset library (Line 283-317 in router.py) + - โœ… Existing scene images/audio loaded from disk + - โœ… Only generates when absolutely necessary + - โœ… Reduces cost during iterative testing + +--- + +## ๐ŸŽฏ FINAL VERDICT: **GREEN LIGHT โœ…** + +### All Critical Systems Validated โœ… +1. โœ… **Polling**: Robust with retry logic, timeout handling, and cleanup +2. โœ… **Display**: Authenticated blob URLs, proper loading states, race condition handling +3. โœ… **Asset Loading**: Backend validates and reuses existing images/audio +4. โœ… **State Management**: Atomic updates, functional state, duplicate prevention +5. โœ… **Error Handling**: Comprehensive backend validation, user-friendly messages +6. โœ… **Cost Transparency**: Accurate estimation with model-specific breakdown +7. โœ… **User Control**: Per-scene generation, regeneration, granular settings +8. โœ… **Testing Safeguards**: Guards prevent duplicate calls, asset reuse reduces cost + +### Recommended Testing Approach ๐Ÿงช + +1. **Start Small**: Test with 1-2 scenes first +2. **Verify Assets**: Confirm images and audio appear correctly +3. **Check Validation**: Try to render without assets (should be blocked) +4. **Test Regeneration**: Regenerate a single image/audio +5. **Full Workflow**: Generate plan โ†’ build scenes โ†’ per-scene generation โ†’ render +6. **Monitor Logs**: Watch console for any unexpected behavior + +### Known Good Paths โœ… +- โœ… Plan generation with avatar auto-generation (reuses existing avatars) +- โœ… Scene building (properly disabled if scenes already exist) +- โœ… Per-scene image generation with polling +- โœ… Per-scene audio generation with settings modal +- โœ… Video rendering with existing assets (no regeneration) + +### What to Watch For ๐Ÿ‘€ +- โš ๏ธ First time generation may be slower (polling every 3s for up to 5 mins) +- โš ๏ธ Network errors will retry up to 3 times with exponential backoff +- โš ๏ธ Task not found errors stop polling immediately (check backend logs) +- โš ๏ธ Image/audio blob loading issues fallback to direct URLs (check browser console) + +--- + +## ๐Ÿš€ YOU ARE CLEARED FOR TAKEOFF! + +All systems are **GO** for testing. The implementation is robust, validated, and production-ready. Proceed with confidence! ๐ŸŽ‰ + +**Good luck with testing! ๐Ÿ€** + diff --git a/ToBeMigrated/ai_writers/ai_agents_crew_writer.py b/ToBeMigrated/ai_writers/ai_agents_crew_writer.py deleted file mode 100644 index 2ff42baa..00000000 --- a/ToBeMigrated/ai_writers/ai_agents_crew_writer.py +++ /dev/null @@ -1,192 +0,0 @@ -import os -import configparser -import streamlit as st -from langchain_google_genai import ChatGoogleGenerativeAI - -# Initialize session state variables if not already done -if 'progress' not in st.session_state: - st.session_state.progress = 0 - - -def create_agents(search_keywords): - """Create agents for content creation.""" - try: - from crewai import Agent - from crewai_tools import SerperDevTool - except ImportError: - raise ImportError("The 'crewai' and/or 'crewai_tools' package is not installed. Please install them to use AI Agents Crew Writer features.") - search_tool = SerperDevTool() - google_api_key = os.getenv("GEMINI_API_KEY") - - llm = ChatGoogleGenerativeAI( - model="gemini-1.5-flash-latest", verbose=True, temperature=0.6, google_api_key=google_api_key - ) - - try: - role, goal, backstory = read_config("content_researcher") - content_researcher = Agent( - role=role, goal=goal, backstory=backstory, tools=[search_tool], memory=True, - verbose=True, max_rpm=None, max_iter=10, allow_delegation=False, llm=llm - ) - - role, goal, backstory = read_config("content_outliner") - content_outliner = Agent( - role=role, goal=goal, backstory=backstory, memory=True, - verbose=True, tools=[search_tool], max_rpm=10, max_iter=10, allow_delegation=False, llm=llm - ) - - role, goal, backstory = read_config("content_writer") - content_writer = Agent( - role=role, goal=goal, backstory=backstory, memory=True, - verbose=True, max_rpm=10, max_iter=15, allow_delegation=False, llm=llm - ) - - role, goal, backstory = read_config("content_reviewer") - content_reviewer = Agent( - role=role, goal=goal, backstory=backstory, memory=True, - verbose=True, max_rpm=10, max_iter=10, allow_delegation=False, llm=llm - ) - - except Exception as err: - st.error(f"Error creating agents: {err}") - st.stop() - - return [content_researcher, content_outliner, content_writer, content_reviewer] - -def create_tasks(agents, search_keywords): - """Create tasks for the agents.""" - try: - from crewai import Task - except ImportError: - raise ImportError("The 'crewai' package is not installed. Please install it to use AI Agents Crew Writer features.") - try: - task_description, expected_output = read_config("research_task") - research_task = Task( - description=f"The main focus keywords are: '{search_keywords}'.\n{task_description}.", - expected_output=expected_output, - agent=agents[0] - ) - - task_description, expected_output = read_config("outline_task") - outline_task = Task( - description=f"{task_description}.\nThe main focus keywords are {search_keywords}", - expected_output=expected_output, - agent=agents[1] - ) - - task_description, expected_output = read_config("writer_task") - writer_task = Task( - description=f"{task_description}\nThe main focus keywords are {search_keywords}.", - expected_output=expected_output, - agent=agents[2] - ) - - task_description, expected_output = read_config("review_task") - proofread_task = Task( - description=f"{task_description}.\nThe main focus keywords are: {search_keywords}.", - expected_output=expected_output, - agent=agents[3] - ) - - except Exception as err: - st.error(f"Error creating tasks: {err}") - st.stop() - - return [research_task, outline_task, writer_task, proofread_task] - -def execute_tasks(agents, tasks, lang): - """Execute tasks with the agents.""" - try: - from crewai import Crew - except ImportError: - raise ImportError("The 'crewai' package is not installed. Please install it to use AI Agents Crew Writer features.") - crew = Crew( - agents=agents, - tasks=tasks, - verbose=2, - language=lang - ) - try: - result = crew.kickoff() - except Exception as err: - st.error(f"Error executing tasks: {err}") - st.stop() - return result - -def read_config(which_member): - """Reads configuration for the specified agent or task.""" - team_dir = os.path.join(os.getcwd(), "lib", "workspace", "my_content_team") - config_file = None - - if 'content_researcher' in which_member or 'research_task' in which_member: - config_file = os.path.join(team_dir, "content_researcher.txt") - elif 'content_writer' in which_member or 'writer_task' in which_member: - config_file = os.path.join(team_dir, "content_writer.txt") - elif 'content_reviewer' in which_member or 'review_task' in which_member: - config_file = os.path.join(team_dir, "content_reviewer.txt") - elif 'content_outliner' in which_member or 'outline_task' in which_member: - config_file = os.path.join(team_dir, "content_outliner.txt") - - try: - config = configparser.ConfigParser() - config.read(config_file) - role = config.get('main', 'role') - goal = config.get('main', 'goal') - backstory = config.get('backstory', 'text') - except Exception as err: - st.error(f"Error reading config: {err}") - st.stop() - - if 'task' not in which_member: - return role, goal, backstory - else: - try: - task_description = config.get('task', 'task_description') - expected_output = config.get('task', 'task_expected_output') - except Exception as err: - st.error(f"Error reading task config: {err}") - st.stop() - return task_description, expected_output - - -def ai_agents_writers(search_keywords, lang="en"): - """Main function to kickoff AI Agents content team.""" - - progress_bar = st.progress(0) - status_text = st.empty() - - st.session_state.progress = 0 - status_text.text("Setting up environment...") - status_text.text("Creating Agents team...") - try: - agents = create_agents(search_keywords) - st.session_state.progress += 10 - progress_bar.progress(st.session_state.progress) - except Exception as err: - st.error(f"Failed in creating Agents team: {err}") - st.stop() - - status_text.text("Creating tasks for Agents team...") - try: - tasks = create_tasks(agents, search_keywords) - st.session_state.progress += 25 - progress_bar.progress(st.session_state.progress) - except Exception as err: - st.error(f"Failed in creating tasks for Agents team: {err}") - st.stop() - - status_text.text("AI Agents busy writing your content...") - try: - result = execute_tasks(agents, tasks, lang) - st.session_state.progress += 60 - progress_bar.progress(st.session_state.progress) - status_text.text("Tasks executed successfully.") - st.success("Successfully executed tasks.") - - # Display result with an option to copy the content - st.markdown("### Result") - st.code(result, language='markdown') - st.download_button('Download Content', data=result, file_name='alwrity_result.md') - except Exception as err: - st.error(f"Failed to execute tasks: {err}") - diff --git a/ToBeMigrated/ai_writers/ai_blog_faqs_writer/README.md b/ToBeMigrated/ai_writers/ai_blog_faqs_writer/README.md deleted file mode 100644 index 6023e0c7..00000000 --- a/ToBeMigrated/ai_writers/ai_blog_faqs_writer/README.md +++ /dev/null @@ -1,192 +0,0 @@ -# AI-Powered FAQ Generator - -A sophisticated FAQ generation system that creates comprehensive, well-researched FAQs from various content sources. This tool leverages AI to analyze content, conduct web research, and generate detailed FAQs with customizable options. - -## Features - -### Content Processing -- **Multiple Input Sources** - - Direct text input - - File uploads (DOCX, TXT) - - URL content extraction - - Support for any content type (general, technical, educational, etc.) - -### Research Capabilities -- **Multi-level Search Depth** - - **Basic**: Google Search for quick, general information - - **Comprehensive**: Tavily AI for detailed, in-depth research - - **Expert**: Metaphor AI for specialized, expert-level content - -### Customization Options -- **Target Audience** - - Beginner - - Intermediate - - Expert - -- **FAQ Style** - - Technical - - Conversational - - Professional - -- **Advanced Features** - - Emoji inclusion - - Code example generation - - Reference integration - - Customizable time range for research - - Multi-language support - -### Output Formats -- Interactive preview -- Markdown -- HTML -- JSON - -## Installation - -1. Clone the repository -2. Install dependencies: -```bash -pip install -r requirements.txt -``` - -## Usage - -### Basic Usage -```python -from lib.ai_writers.ai_blog_faqs_writer.faqs_generator_blog import FAQGenerator, FAQConfig - -# Initialize with default configuration -generator = FAQGenerator() - -# Generate FAQs from content -faqs = await generator.generate_faqs("Your content here") -``` - -### Advanced Configuration -```python -from lib.ai_writers.ai_blog_faqs_writer.faqs_generator_blog import ( - FAQGenerator, FAQConfig, TargetAudience, FAQStyle, SearchDepth -) - -# Custom configuration -config = FAQConfig( - num_faqs=10, - target_audience=TargetAudience.INTERMEDIATE, - faq_style=FAQStyle.TECHNICAL, - include_emojis=True, - include_code_examples=True, - include_references=True, - search_depth=SearchDepth.COMPREHENSIVE, - time_range="last_6_months", - language="English" -) - -generator = FAQGenerator(config) -``` - -### Web Interface -Run the Streamlit interface: -```bash -streamlit run lib/ai_writers/ai_blog_faqs_writer/faqs_ui.py -``` - -## Research Process - -1. **Content Analysis** - - Identifies key topics and concepts - - Extracts potential questions - - Determines research requirements - -2. **Web Research** - - Selects appropriate search function based on depth - - Gathers relevant information - - Validates and cross-references data - -3. **FAQ Generation** - - Creates comprehensive questions - - Provides detailed answers - - Includes code examples (if applicable) - - Adds references and citations - -## Output Structure - -Each FAQ item includes: -- Question -- Detailed answer -- Category -- Code example (if applicable) -- References -- Confidence score -- Last updated timestamp - -## Configuration Options - -### FAQConfig Parameters -- `num_faqs`: Number of FAQs to generate (default: 5) -- `target_audience`: Target audience level (default: INTERMEDIATE) -- `faq_style`: Writing style (default: PROFESSIONAL) -- `include_emojis`: Whether to include emojis (default: True) -- `include_code_examples`: Whether to include code examples (default: True) -- `include_references`: Whether to include references (default: True) -- `search_depth`: Research depth level (default: COMPREHENSIVE) -- `time_range`: Time range for research (default: "last_6_months") -- `language`: Output language (default: "English") - -## Research Depth Options - -### Basic (Google Search) -- Quick, general information -- Broad coverage -- Suitable for basic topics - -### Comprehensive (Tavily AI) -- Detailed, in-depth research -- Multiple source integration -- Best for most use cases - -### Expert (Metaphor AI) -- Specialized, expert-level content -- Advanced topic coverage -- Technical and academic focus - -## Best Practices - -1. **Content Preparation** - - Provide clear, well-structured content - - Include key terms and concepts - - Specify target audience and style - -2. **Research Selection** - - Use Basic for general topics - - Choose Comprehensive for detailed analysis - - Select Expert for technical subjects - -3. **Output Review** - - Verify accuracy of information - - Check code examples - - Validate references - -## Contributing - -1. Fork the repository -2. Create a feature branch -3. Commit your changes -4. Push to the branch -5. Create a Pull Request - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## Support - -For support, please open an issue in the repository or contact the maintainers. - -## Acknowledgments - -- OpenAI for GPT integration -- Google Search API -- Tavily AI -- Metaphor AI -- BeautifulSoup for web scraping -- Streamlit for UI \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_generator_blog.py b/ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_generator_blog.py deleted file mode 100644 index cefab088..00000000 --- a/ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_generator_blog.py +++ /dev/null @@ -1,444 +0,0 @@ -""" -Enhanced FAQ Generator - -This module provides a comprehensive FAQ generation system that can create detailed, -well-researched FAQs from various content sources with customizable options. -""" - -import sys -import json -import re -from typing import Dict, List, Optional, Union -from pathlib import Path -from enum import Enum -from dataclasses import dataclass -from loguru import logger - -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from lib.ai_web_researcher.google_serp_search import google_search -from lib.ai_web_researcher.tavily_ai_search import do_tavily_ai_search -from lib.ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles - -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}") - -class TargetAudience(Enum): - BEGINNER = "beginner" - INTERMEDIATE = "intermediate" - EXPERT = "expert" - -class FAQStyle(Enum): - TECHNICAL = "technical" - CONVERSATIONAL = "conversational" - PROFESSIONAL = "professional" - -class SearchDepth(Enum): - BASIC = "basic" - COMPREHENSIVE = "comprehensive" - EXPERT = "expert" - -@dataclass -class FAQConfig: - """Configuration for FAQ generation.""" - num_faqs: int = 5 - target_audience: TargetAudience = TargetAudience.INTERMEDIATE - faq_style: FAQStyle = FAQStyle.PROFESSIONAL - include_emojis: bool = True - include_code_examples: bool = True - include_references: bool = True - search_depth: SearchDepth = SearchDepth.COMPREHENSIVE - time_range: str = "last_6_months" - exclude_domains: List[str] = None - language: str = "English" - selected_search_queries: List[str] = None - -@dataclass -class FAQItem: - """Individual FAQ item with metadata.""" - question: str - answer: str - category: str - code_example: Optional[str] = None - references: List[Dict[str, str]] = None - confidence_score: float = 0.0 - last_updated: str = None - -class FAQGenerator: - """Enhanced FAQ Generator with research capabilities.""" - - def __init__(self, config: Optional[FAQConfig] = None): - """Initialize the FAQ generator with optional configuration.""" - self.config = config or FAQConfig() - self.faqs: List[FAQItem] = [] - self.research_results = {} - self.search_queries = [] - - def generate_search_queries(self, content: str) -> List[str]: - """Generate search queries based on the content.""" - try: - prompt = f"""Based on the following content, generate 5 specific search queries that would help create comprehensive FAQs. - Content: {content} - - Guidelines for search queries: - 1. Focus on key concepts and terms - 2. Include common questions users might have - 3. Cover technical aspects that need clarification - 4. Include best practices and recommendations - 5. Make queries specific and focused - - Please provide exactly 5 search queries, one per line. - Do not include numbers or bullet points in the queries. - """ - - response = llm_text_gen(prompt) - # Clean up the queries by removing numbers and extra spaces - queries = [] - for line in response.split('\n'): - # Remove any leading numbers, dots, or spaces - cleaned = re.sub(r'^\d+\.\s*', '', line.strip()) - if cleaned: - queries.append(cleaned) - - self.search_queries = queries[:5] # Ensure we only get 5 queries - return self.search_queries - - except Exception as err: - logger.error(f"Failed to generate search queries: {err}") - return [] - - def _clean_search_query(self, query: str) -> str: - """Clean up a search query by removing numbers and extra formatting.""" - # Remove any leading numbers, dots, or spaces - cleaned = re.sub(r'^\d+\.\s*', '', query.strip()) - # Remove any quotes - cleaned = cleaned.replace('"', '').replace("'", '') - # Remove any extra spaces - cleaned = ' '.join(cleaned.split()) - return cleaned - - def generate_faqs(self, content: str, content_type: str = "general") -> List[FAQItem]: - """Generate FAQs from the given content with research integration.""" - try: - if not self.config.selected_search_queries: - raise ValueError("No search queries selected. Please select queries to proceed.") - - # Clean up selected queries - cleaned_queries = [self._clean_search_query(q) for q in self.config.selected_search_queries] - self.config.selected_search_queries = cleaned_queries - - # Step 1: Research the topic using selected queries - research_results = self._conduct_research(content) - - # Step 2: Generate initial FAQs - initial_faqs = self._generate_initial_faqs(content, research_results) - - # Step 3: Enhance FAQs with research - enhanced_faqs = self._enhance_faqs_with_research(initial_faqs, research_results) - - # Step 4: Add code examples if requested - if self.config.include_code_examples: - enhanced_faqs = self._add_code_examples(enhanced_faqs) - - # Step 5: Add references if requested - if self.config.include_references: - enhanced_faqs = self._add_references(enhanced_faqs, research_results) - - self.faqs = enhanced_faqs - return enhanced_faqs - - except Exception as err: - logger.error(f"Failed to generate FAQs: {err}") - raise - - def _conduct_research(self, content: str) -> Dict: - """Conduct online research based on the selected search queries.""" - try: - research_results = {} - - for query in self.config.selected_search_queries: - try: - # Clean the query before searching - cleaned_query = self._clean_search_query(query) - logger.info(f"Researching query: {cleaned_query}") - - # Select search function based on search depth - if self.config.search_depth == SearchDepth.BASIC: - results = google_search(cleaned_query) - elif self.config.search_depth == SearchDepth.COMPREHENSIVE: - results = do_tavily_ai_search(cleaned_query) - elif self.config.search_depth == SearchDepth.EXPERT: - results = metaphor_search_articles(cleaned_query) - else: - logger.warning(f"Unknown search depth: {self.config.search_depth}, defaulting to Google search") - results = google_search(cleaned_query) - - research_results[query] = results - logger.info(f"Research completed for query: {query}") - - except Exception as err: - logger.error(f"Failed to research query '{query}': {err}") - continue - - return research_results - - except Exception as err: - logger.error(f"Failed to conduct research: {err}") - return {} - - def _generate_initial_faqs(self, content: str, research_results: Dict) -> List[FAQItem]: - """Generate initial FAQs using LLM.""" - try: - system_prompt = f"""You are an expert FAQ generator with deep knowledge in content creation and technical writing. - Your task is to create comprehensive FAQs based on the given content and research. - - Guidelines: - 1. Target Audience: {self.config.target_audience.value} - 2. Style: {self.config.faq_style.value} - 3. Include emojis: {self.config.include_emojis} - 4. Language: {self.config.language} - 5. Number of FAQs: {self.config.num_faqs} - - Create FAQs that are: - - Clear and concise - - Well-structured - - Technically accurate - - Engaging and informative - - Based on the provided research - - Relevant to the target audience - - Written in the specified style - - Format each FAQ exactly as follows: - Q: [Your question here] - A: [Your detailed answer here] - Category: [Category name] - Confidence: [Score between 0 and 1] - --- - """ - - prompt = f"""Content to generate FAQs from: - {content} - - Research Results: - {json.dumps(research_results, indent=2)} - - Please generate {self.config.num_faqs} FAQs following the guidelines above. - Each FAQ must be separated by '---' and include all required fields. - """ - - response = llm_text_gen(prompt, system_prompt=system_prompt) - logger.info(f"LLM Response: {response}") - - # Parse the response into FAQItem objects - faqs = [] - current_faq = None - - for line in response.split('\n'): - line = line.strip() - if not line or line == '---': - if current_faq and current_faq.question and current_faq.answer: - faqs.append(current_faq) - current_faq = None - continue - - if line.startswith('Q:'): - if current_faq and current_faq.question and current_faq.answer: - faqs.append(current_faq) - current_faq = FAQItem(question=line[2:].strip(), answer="", category="") - elif line.startswith('A:'): - if current_faq: - current_faq.answer = line[2:].strip() - elif line.startswith('Category:'): - if current_faq: - current_faq.category = line[9:].strip() - elif line.startswith('Confidence:'): - if current_faq: - try: - current_faq.confidence_score = float(line[11:].strip()) - except ValueError: - current_faq.confidence_score = 0.5 - - # Add the last FAQ if it exists and is complete - if current_faq and current_faq.question and current_faq.answer: - faqs.append(current_faq) - - logger.info(f"Generated {len(faqs)} FAQs") - return faqs - - except Exception as err: - logger.error(f"Failed to generate initial FAQs: {err}") - raise - - def _enhance_faqs_with_research(self, faqs: List[FAQItem], research_results: Dict) -> List[FAQItem]: - """Enhance FAQs with research findings.""" - try: - enhanced_faqs = [] - - for faq in faqs: - # Find relevant research for this FAQ - relevant_research = self._find_relevant_research(faq, research_results) - - if relevant_research: - # Enhance the answer with research findings - enhancement_prompt = f"""Enhance the following FAQ answer with the provided research: - - Question: {faq.question} - Current Answer: {faq.answer} - - Research: - {json.dumps(relevant_research, indent=2)} - - Please enhance the answer while: - 1. Maintaining the original style and tone - 2. Adding relevant information from the research - 3. Ensuring technical accuracy - 4. Keeping the answer concise and clear - """ - - enhanced_answer = llm_text_gen(enhancement_prompt) - faq.answer = enhanced_answer - - enhanced_faqs.append(faq) - - return enhanced_faqs - - except Exception as err: - logger.error(f"Failed to enhance FAQs with research: {err}") - return faqs - - def _add_code_examples(self, faqs: List[FAQItem]) -> List[FAQItem]: - """Add code examples to FAQs where applicable.""" - try: - for faq in faqs: - if self._is_technical_question(faq.question): - code_prompt = f"""Generate a code example for the following FAQ: - Question: {faq.question} - Answer: {faq.answer} - - Please provide a relevant code example that demonstrates the concept. - Include comments and explanations where necessary. - """ - - code_example = llm_text_gen(code_prompt) - faq.code_example = code_example - - return faqs - - except Exception as err: - logger.error(f"Failed to add code examples: {err}") - return faqs - - def _add_references(self, faqs: List[FAQItem], research_results: Dict) -> List[FAQItem]: - """Add references to FAQs based on research results.""" - try: - for faq in faqs: - relevant_research = self._find_relevant_research(faq, research_results) - if relevant_research: - references = [] - for source, content in relevant_research.items(): - references.append({ - "source": source, - "content": content - }) - faq.references = references - - return faqs - - except Exception as err: - logger.error(f"Failed to add references: {err}") - return faqs - - def _find_relevant_research(self, faq: FAQItem, research_results: Dict) -> Dict: - """Find research results relevant to a specific FAQ.""" - relevant_research = {} - for topic, results in research_results.items(): - if any(keyword in faq.question.lower() for keyword in topic.lower().split()): - relevant_research[topic] = results - return relevant_research - - def _is_technical_question(self, question: str) -> bool: - """Determine if a question is technical and might benefit from a code example.""" - technical_keywords = ["code", "program", "function", "method", "class", "api", "syntax", "error", "debug"] - return any(keyword in question.lower() for keyword in technical_keywords) - - def to_markdown(self) -> str: - """Convert FAQs to markdown format.""" - markdown = "# Frequently Asked Questions\n\n" - - for faq in self.faqs: - markdown += f"## {faq.question}\n\n" - markdown += f"{faq.answer}\n\n" - - if faq.code_example: - markdown += "```\n" - markdown += f"{faq.code_example}\n" - markdown += "```\n\n" - - if faq.references: - markdown += "### References\n" - for ref in faq.references: - markdown += f"- {ref['source']}\n" - markdown += "\n" - - return markdown - - def to_html(self) -> str: - """Convert FAQs to HTML format.""" - html = """ - - - - Frequently Asked Questions - - - -

Frequently Asked Questions

- """ - - for faq in self.faqs: - html += f""" -
-
{faq.question}
-
{faq.answer}
- """ - - if faq.code_example: - html += f""" -
-
{faq.code_example}
-
- """ - - if faq.references: - html += """ -
-

References

-
    - """ - for ref in faq.references: - html += f""" -
  • {ref['source']}
  • - """ - html += """ -
-
- """ - - html += """ -
- """ - - html += """ - - - """ - - return html diff --git a/ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_ui.py b/ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_ui.py deleted file mode 100644 index ab938dcf..00000000 --- a/ToBeMigrated/ai_writers/ai_blog_faqs_writer/faqs_ui.py +++ /dev/null @@ -1,312 +0,0 @@ -""" -Streamlit UI for FAQ Generator - -This module provides a user-friendly interface for generating FAQs from various content sources. -""" - -import streamlit as st -from pathlib import Path -from typing import Optional -import json -import requests -from bs4 import BeautifulSoup -import logging -import pyperclip - -from .faqs_generator_blog import FAQGenerator, FAQConfig, TargetAudience, FAQStyle, SearchDepth - -# Set up logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -def copy_to_clipboard(text: str) -> None: - """Copy text to clipboard and show success message.""" - try: - pyperclip.copy(text) - st.success("Copied to clipboard!") - except Exception as e: - st.error(f"Failed to copy to clipboard: {str(e)}") - -def fetch_url_content(url): - """Fetch and extract content from a URL.""" - try: - response = requests.get(url) - response.raise_for_status() - soup = BeautifulSoup(response.text, 'html.parser') - - # Remove script and style elements - for script in soup(["script", "style"]): - script.decompose() - - # Get text - text = soup.get_text() - - # Break into lines and remove leading and trailing space - lines = (line.strip() for line in text.splitlines()) - # Break multi-headlines into a line each - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - # Drop blank lines - text = '\n'.join(chunk for chunk in chunks if chunk) - - return text - except Exception as e: - st.error(f"Error fetching URL content: {str(e)}") - return None - -def main(): - st.title("FAQ Generator") - st.markdown("Generate comprehensive FAQs from your content with research integration.") - - # Initialize session state variables if they don't exist - if 'search_queries' not in st.session_state: - st.session_state.search_queries = [] - if 'selected_queries' not in st.session_state: - st.session_state.selected_queries = [] - if 'research_completed' not in st.session_state: - st.session_state.research_completed = False - if 'research_results' not in st.session_state: - st.session_state.research_results = {} - if 'faq_config' not in st.session_state: - st.session_state.faq_config = None - if 'generator' not in st.session_state: - st.session_state.generator = FAQGenerator() - if 'generated_faqs' not in st.session_state: - st.session_state.generated_faqs = None - if 'output_format' not in st.session_state: - st.session_state.output_format = "Preview" - - # Sidebar for configuration - with st.sidebar: - st.header("Configuration") - - # Basic settings - num_faqs = st.slider("Number of FAQs", 1, 20, 5) - target_audience = st.selectbox( - "Target Audience", - [audience.value for audience in TargetAudience] - ) - faq_style = st.selectbox( - "FAQ Style", - [style.value for style in FAQStyle] - ) - - # Advanced settings - with st.expander("Advanced Settings"): - include_emojis = st.checkbox("Include Emojis", value=True) - include_code_examples = st.checkbox("Include Code Examples", value=True) - include_references = st.checkbox("Include References", value=True) - - search_depth = st.selectbox( - "Search Depth", - [depth.value for depth in SearchDepth] - ) - time_range = st.selectbox( - "Time Range", - ["last_month", "last_6_months", "last_year", "all_time"] - ) - language = st.text_input("Language", value="English") - - # Main content area - content_type = st.radio( - "Content Source", - ["Direct Input", "File Upload", "URL"] - ) - - content = "" - if content_type == "Direct Input": - content = st.text_area("Enter your content", height=300) - - elif content_type == "URL": - url = st.text_input("Enter URL") - if url: - content = fetch_url_content(url) - if content: - st.text_area("Extracted Content", content, height=300) - - # Step 1: Generate search queries - if content and not st.session_state.search_queries: - if st.button("Generate Search Queries"): - with st.spinner("Generating search queries..."): - search_queries = st.session_state.generator.generate_search_queries(content) - if search_queries: - st.session_state.search_queries = search_queries - st.session_state.selected_queries = [] # Reset selected queries - st.session_state.research_completed = False # Reset research status - st.session_state.research_results = {} # Reset research results - st.session_state.faq_config = None # Reset config - st.session_state.generated_faqs = None # Reset generated FAQs - st.success("Search queries generated successfully!") - - # Step 2: Display and select search queries - if st.session_state.search_queries: - st.subheader("Select Search Queries") - st.info("Select the queries you want to use for web research. You can select multiple queries.") - - # Create checkboxes for each search query - selected_queries = [] - for query in st.session_state.search_queries: - if st.checkbox(query, key=f"query_{query}", value=query in st.session_state.selected_queries): - selected_queries.append(query) - - # Update selected queries in session state - st.session_state.selected_queries = selected_queries - - # Step 3: Do web research - if st.session_state.selected_queries and not st.session_state.research_completed: - if st.button("Do Web Research"): - try: - # Create config with selected queries - config = FAQConfig( - num_faqs=num_faqs, - target_audience=TargetAudience(target_audience), - faq_style=FAQStyle(faq_style), - include_emojis=include_emojis, - include_code_examples=include_code_examples, - include_references=include_references, - search_depth=SearchDepth(search_depth), - time_range=time_range, - language=language, - selected_search_queries=selected_queries - ) - - # Store config in session state - st.session_state.faq_config = config - - # Update generator with config - st.session_state.generator.config = config - - # Do research - with st.spinner("Conducting web research..."): - research_results = st.session_state.generator._conduct_research(content) - st.session_state.research_completed = True - st.session_state.research_results = research_results - st.success("Web research completed successfully!") - - # Display research results - st.subheader("Research Results") - for query, results in research_results.items(): - with st.expander(f"Results for: {query}"): - if isinstance(results, dict): - st.json(results) - else: - st.text(results) - - except Exception as e: - st.error(f"Error during web research: {str(e)}") - st.error("Please try again with different search queries or adjust the search depth.") - - # Step 4: Generate FAQs - if st.session_state.research_completed and st.session_state.research_results and st.session_state.faq_config: - if st.button("Generate FAQs"): - try: - # Update generator with stored config - st.session_state.generator.config = st.session_state.faq_config - - # Generate FAQs - with st.spinner("Generating FAQs..."): - logger.info("Starting FAQ generation...") - faqs = st.session_state.generator.generate_faqs(content) - logger.info(f"Generated {len(faqs) if faqs else 0} FAQs") - - if not faqs: - st.error("No FAQs were generated. Please try again.") - return - - st.session_state.generated_faqs = faqs - st.success("FAQs generated successfully!") - - except Exception as e: - logger.error(f"Error generating FAQs: {str(e)}") - st.error(f"Error generating FAQs: {str(e)}") - st.error("Please try again or adjust your settings.") - - # Display generated FAQs if they exist - if st.session_state.generated_faqs: - st.subheader("Generated FAQs") - - # Output format selection - output_format = st.radio( - "Output Format", - ["Preview", "Markdown", "HTML", "JSON"], - key="output_format" - ) - - # Create columns for copy and download buttons - col1, col2 = st.columns(2) - - if output_format == "Preview": - # Create a formatted text for copying - preview_text = "" - for i, faq in enumerate(st.session_state.generated_faqs, 1): - preview_text += f"{i}. {faq.question}\n" - preview_text += f"{faq.answer}\n\n" - if faq.code_example: - preview_text += f"Code Example:\n{faq.code_example}\n\n" - if faq.references: - preview_text += "References:\n" - for ref in faq.references: - preview_text += f"- {ref['source']}\n" - preview_text += "\n" - - with col1: - if st.button("Copy to Clipboard", key="copy_preview"): - copy_to_clipboard(preview_text) - - # Display the FAQs - for i, faq in enumerate(st.session_state.generated_faqs, 1): - with st.expander(f"{i}. {faq.question}"): - st.markdown(faq.answer) - if faq.code_example: - st.code(faq.code_example) - if faq.references: - st.markdown("**References:**") - for ref in faq.references: - st.markdown(f"- {ref['source']}") - - elif output_format == "Markdown": - markdown_output = st.session_state.generator.to_markdown() - st.code(markdown_output, language="markdown") - - with col1: - if st.button("Copy to Clipboard", key="copy_markdown"): - copy_to_clipboard(markdown_output) - with col2: - st.download_button( - "Download Markdown", - markdown_output, - file_name="faqs.md", - mime="text/markdown" - ) - - elif output_format == "HTML": - html_output = st.session_state.generator.to_html() - st.code(html_output, language="html") - - with col1: - if st.button("Copy to Clipboard", key="copy_html"): - copy_to_clipboard(html_output) - with col2: - st.download_button( - "Download HTML", - html_output, - file_name="faqs.html", - mime="text/html" - ) - - elif output_format == "JSON": - json_output = json.dumps([faq.__dict__ for faq in st.session_state.generated_faqs], indent=2) - st.code(json_output, language="json") - - with col1: - if st.button("Copy to Clipboard", key="copy_json"): - copy_to_clipboard(json_output) - with col2: - st.download_button( - "Download JSON", - json_output, - file_name="faqs.json", - mime="application/json" - ) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/4c_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/4c_copywriter.py deleted file mode 100644 index 0511d537..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/4c_copywriter.py +++ /dev/null @@ -1,226 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐ŸŽฏ 4C Copywriting Generator

-

Create compelling copy that follows the 4C (Clear, Concise, Credible, Compelling) framework to drive conversions.

-
- """, unsafe_allow_html=True) - - # Educational content about 4C copywriting - with st.expander("๐Ÿ“š What is 4C Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the 4C Copywriting Framework - - The 4C framework is a powerful copywriting approach that ensures your message is effective and persuasive: - - - **Clear**: Your message is easy to understand, with no ambiguity or confusion - - **Concise**: Your copy is brief and to the point, without unnecessary words - - **Credible**: Your claims are backed by evidence, testimonials, or authority - - **Compelling**: Your message is interesting and persuasive, motivating action - - ### Why 4C Copywriting Works - - The 4C framework works because it: - - - Improves readability and comprehension - - Respects the reader's time and attention - - Builds trust and credibility - - Increases the likelihood of conversion - - Creates a professional, polished impression - - Works across all marketing channels and platforms - - ### When to Use 4C Copywriting - - The 4C framework is particularly effective for: - - - Email marketing campaigns - - Landing pages and sales pages - - Social media posts and ads - - Product descriptions - - Service offerings - - Any marketing content where clarity and persuasion are essential - """) - - # Main input form - with st.expander("โœ๏ธ Create Your 4C Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity AI Writer", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Content marketers", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - campaign_description = st.text_input('**๐Ÿ“ Campaign Description** (In 3-4 words)', - placeholder="e.g., AI writing assistant", - help="Describe your campaign briefly.") - - clear_message = st.text_area('**๐Ÿ” Clear Message**', - placeholder="e.g., Our AI writing assistant helps you create high-quality content in minutes", - help="What is the main message you want to convey? Make it easy to understand.") - - with col2: - brand_description = st.text_input('**๐Ÿ“‹ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing platform", - help="Describe what your company does briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., All-in-one AI copywriting platform", - help="What makes your product/service different from competitors?") - - concise_content = st.text_area('**๐Ÿ“ Concise Content**', - placeholder="e.g., Create content 10x faster with our AI assistant", - help="How can you express your message in the fewest words possible?") - - credible_elements = st.text_area('**โœ… Credible Elements**', - placeholder="e.g., Trusted by 10,000+ businesses, 4.8/5 star rating, 30-day money-back guarantee", - help="What evidence, testimonials, or authority can you use to build credibility?") - - compelling_hook = st.text_area('**๐ŸŽฃ Compelling Hook**', - placeholder="e.g., Stop struggling with writer's block. Our AI assistant helps you create engaging content in minutes.", - help="What will grab attention and motivate action?") - - call_to_action = st.text_area('**๐Ÿš€ Call to Action**', - placeholder="e.g., Start creating high-converting content today with our 14-day free trial...", - help="Prompt your audience to take action with a strong call to action.") - - landing_page_url = st.text_input('**๐ŸŒ Landing Page URL** (Optional)', - placeholder="e.g., https://alwrity.com", - help="Provide a URL to include in your call to action.") - - col1, col2 = st.columns([1, 1]) - with col1: - platform = st.selectbox( - '**๐Ÿ“ฑ Content Platform**', - options=['Social media copy', 'Email copy', 'Website copy', 'Ad copy', 'Product copy'], - help="Select the platform where your copy will be used." - ) - - with col2: - language = st.selectbox( - '**๐ŸŒ Language**', - options=['English', 'Hindustani', 'Chinese', 'Hindi', 'Spanish'], - help="Select the language for your copy." - ) - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate 4C Copy**', type="primary"): - if not brand_name or not brand_description or not campaign_description or not clear_message or not concise_content or not credible_elements or not compelling_hook: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, Campaign Description, Clear Message, Concise Content, Credible Elements, and Compelling Hook)!") - else: - with st.spinner("โœจ Crafting compelling 4C copy..."): - four_cs_copy = generate_four_cs_copy( - brand_name, - brand_description, - campaign_description, - clear_message, - concise_content, - credible_elements, - compelling_hook, - target_audience, - unique_selling_point, - call_to_action, - landing_page_url, - platform, - language, - tone_style - ) - - if four_cs_copy: - st.markdown(""" -
-

๐ŸŽฏ Your 4C Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(four_cs_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your 4C Copy", expanded=False): - st.markdown(""" - ### How to Use Your 4C Copy Effectively - - 1. **Test for clarity**: Ask someone unfamiliar with your product to read your copy and explain what they understand - - 2. **Edit ruthlessly**: Review your copy to eliminate unnecessary words and phrases - - 3. **Add specific details**: Include concrete numbers, statistics, and examples to enhance credibility - - 4. **Create urgency**: Add time-sensitive elements to make your compelling hook even more effective - - 5. **Consider the context**: Adapt the copy based on where it will appear (landing page, email, social media, etc.) - - 6. **Measure results**: Track conversion metrics to see how your 4C copy performs - - 7. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate 4C Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_four_cs_copy(brand_name, brand_description, campaign_description, clear_message, - concise_content, credible_elements, compelling_hook, target_audience, - unique_selling_point, call_to_action, landing_page_url, platform, - language, tone_style): - system_prompt = """You are an expert copywriter specializing in the 4C (Clear, Concise, Credible, Compelling) framework. - Your expertise is in creating effective, persuasive marketing copy that communicates clearly, builds credibility, and drives action. - Your copy is authentic, specific to the brand, and focused on driving measurable results.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {brand_description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - PLATFORM: {platform} - LANGUAGE: {language} - TONE & STYLE: {tone_style} - - Use the 4C framework with these elements: - - **Clear Message**: {clear_message} - - **Concise Content**: {concise_content} - - **Credible Elements**: {credible_elements} - - **Compelling Hook**: {compelling_hook} - - **Call to Action**: {call_to_action} - """ - - if landing_page_url: - prompt += f"\nInclude the landing page URL ({landing_page_url}) in your call to action." - - prompt += """ - For each campaign: - 1. Start with a compelling hook that grabs attention - 2. Present your clear message in a concise way - 3. Support your claims with credible elements - 4. End with a strong call to action - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/4r_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/4r_copywriter.py deleted file mode 100644 index 252ce56f..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/4r_copywriter.py +++ /dev/null @@ -1,214 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐ŸŽฏ 4R Copywriting Generator

-

Create compelling copy that follows the 4R (Relevance, Resonance, Response, Results) framework to drive conversions.

-
- """, unsafe_allow_html=True) - - # Educational content about 4R copywriting - with st.expander("๐Ÿ“š What is 4R Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the 4R Copywriting Framework - - The 4R framework is a powerful copywriting approach that ensures your message connects with your audience and drives action: - - - **Relevance**: Your message addresses the specific needs, interests, or pain points of your target audience - - **Resonance**: Your copy creates an emotional connection with the audience, making them feel understood - - **Response**: Your message prompts the audience to take a specific action - - **Results**: Your copy clearly communicates the positive outcomes or benefits the audience will experience - - ### Why 4R Copywriting Works - - The 4R framework works because it: - - - Ensures your message is targeted to the right audience - - Creates emotional connections that build trust and loyalty - - Drives specific actions that lead to conversions - - Focuses on the outcomes that matter most to your audience - - Creates a complete journey from awareness to action - - Works across all marketing channels and platforms - - ### When to Use 4R Copywriting - - The 4R framework is particularly effective for: - - - Email marketing campaigns - - Landing pages and sales pages - - Social media posts and ads - - Product descriptions - - Service offerings - - Any marketing content where audience connection and action are essential - """) - - # Main input form - with st.expander("โœ๏ธ Create Your 4R Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity AI Writer", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Content marketers", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - relevance = st.text_area('**๐ŸŽฏ Relevance**', - placeholder="e.g., Struggling with writer's block? Our AI assistant helps you create high-quality content in minutes", - help="How does your product/service address the specific needs or pain points of your target audience?") - - with col2: - brand_description = st.text_input('**๐Ÿ“‹ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing platform", - help="Describe what your company does briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., All-in-one AI copywriting platform", - help="What makes your product/service different from competitors?") - - resonance = st.text_area('**๐Ÿ’– Resonance**', - placeholder="e.g., We understand the frustration of staring at a blank page. Our AI assistant feels like having a professional writer by your side", - help="How can you create an emotional connection with your audience? What language or imagery will resonate with them?") - - response = st.text_area('**๐Ÿš€ Response**', - placeholder="e.g., Start creating high-converting content today with our 14-day free trial", - help="What specific action do you want your audience to take?") - - results = st.text_area('**โœจ Results**', - placeholder="e.g., Save 20+ hours per week on content creation, increase conversion rates by 35%, improve SEO rankings", - help="What positive outcomes or benefits will your audience experience?") - - landing_page_url = st.text_input('**๐ŸŒ Landing Page URL** (Optional)', - placeholder="e.g., https://alwrity.com", - help="Provide a URL to include in your call to action.") - - col1, col2 = st.columns([1, 1]) - with col1: - platform = st.selectbox( - '**๐Ÿ“ฑ Content Platform**', - options=['Social media copy', 'Email copy', 'Website copy', 'Ad copy', 'Product copy'], - help="Select the platform where your copy will be used." - ) - - with col2: - language = st.selectbox( - '**๐ŸŒ Language**', - options=['English', 'Hindustani', 'Chinese', 'Hindi', 'Spanish'], - help="Select the language for your copy." - ) - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate 4R Copy**', type="primary"): - if not brand_name or not brand_description or not relevance or not resonance or not response or not results: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, Relevance, Resonance, Response, and Results)!") - else: - with st.spinner("โœจ Crafting compelling 4R copy..."): - four_r_copy = generate_four_r_copy( - brand_name, - brand_description, - relevance, - resonance, - response, - results, - target_audience, - unique_selling_point, - landing_page_url, - platform, - language, - tone_style - ) - - if four_r_copy: - st.markdown(""" -
-

๐ŸŽฏ Your 4R Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(four_r_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your 4R Copy", expanded=False): - st.markdown(""" - ### How to Use Your 4R Copy Effectively - - 1. **Test for relevance**: Ensure your copy speaks directly to your target audience's needs and interests - - 2. **Enhance emotional resonance**: Use language and imagery that creates a deeper connection with your audience - - 3. **Clarify the response**: Make sure your call to action is clear, specific, and compelling - - 4. **Quantify results**: Use specific numbers, statistics, and examples to make your results more tangible - - 5. **Consider the context**: Adapt the copy based on where it will appear (landing page, email, social media, etc.) - - 6. **Measure performance**: Track conversion metrics to see how your 4R copy performs - - 7. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate 4R Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_four_r_copy(brand_name, brand_description, relevance, resonance, response, results, - target_audience, unique_selling_point, landing_page_url, platform, - language, tone_style): - system_prompt = """You are an expert copywriter specializing in the 4R (Relevance, Resonance, Response, Results) framework. - Your expertise is in creating compelling marketing copy that connects with audiences on a deep level and drives specific actions. - Your copy is authentic, specific to the brand, and focused on driving measurable results.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {brand_description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - PLATFORM: {platform} - LANGUAGE: {language} - TONE & STYLE: {tone_style} - - Use the 4R framework with these elements: - - **Relevance**: {relevance} - - **Resonance**: {resonance} - - **Response**: {response} - - **Results**: {results} - """ - - if landing_page_url: - prompt += f"\nInclude the landing page URL ({landing_page_url}) in your call to action." - - prompt += """ - For each campaign: - 1. Start by establishing relevance to your target audience's needs or pain points - 2. Create emotional resonance by connecting with your audience's feelings and experiences - 3. Clearly communicate the specific action you want your audience to take - 4. End by highlighting the positive results or benefits they will experience - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/README.md b/ToBeMigrated/ai_writers/ai_copywriter/README.md deleted file mode 100644 index 3a7a9bef..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# AI Copywriting Tools - -A comprehensive collection of AI-powered copywriting tools designed to help create compelling, conversion-focused content using various proven frameworks and approaches. - -## Available Copywriting Tools - -### 1. AIDA Copywriter -The AIDA (Attention-Interest-Desire-Action) framework is a classic copywriting approach that guides your audience through a complete journey: -- **Attention**: Captures attention with compelling headlines -- **Interest**: Generates interest through benefits and pain points -- **Desire**: Creates desire by showcasing solutions -- **Action**: Prompts specific actions with strong CTAs - -Best for: Landing pages, sales pages, email campaigns, and direct response advertising. - -### 2. 4C Copywriter -The 4C framework ensures your message is effective and persuasive through: -- **Clear**: Easy to understand messaging -- **Concise**: Brief and to-the-point content -- **Credible**: Evidence-backed claims -- **Compelling**: Interesting and persuasive messaging - -Best for: Email marketing, landing pages, social media, and product descriptions. - -### 3. 4R Copywriter -The 4R framework focuses on building relationships with your audience through: -- **Relevance**: Content that matters to your audience -- **Receptivity**: Timing and context optimization -- **Response**: Clear calls to action -- **Return**: Value-driven content - -Best for: Content marketing, blog posts, and relationship-building campaigns. - -### 4. PAS Copywriter -The PAS (Problem-Agitation-Solution) framework addresses customer pain points: -- **Problem**: Identifies the customer's issue -- **Agitation**: Amplifies the problem's impact -- **Solution**: Presents your offering as the answer - -Best for: Problem-solving content, product launches, and service offerings. - -### 5. FAB Copywriter -The FAB (Features-Advantages-Benefits) framework focuses on product value: -- **Features**: Product characteristics -- **Advantages**: How features stand out -- **Benefits**: Customer value proposition - -Best for: Product descriptions, sales pages, and feature highlights. - -### 6. QUEST Copywriter -The QUEST framework creates engaging storytelling: -- **Qualify**: Identify the right audience -- **Understand**: Show empathy -- **Educate**: Provide value -- **Stimulate**: Create desire -- **Transition**: Guide to action - -Best for: Story-based marketing, brand storytelling, and content marketing. - -### 7. STAR Copywriter -The STAR framework focuses on social proof and testimonials: -- **Situation**: Context of the problem -- **Task**: Challenge faced -- **Action**: Solution implemented -- **Result**: Outcome achieved - -Best for: Case studies, testimonials, and success stories. - -### 8. OATH Copywriter -The OATH framework addresses customer objections: -- **Objection**: Identify common concerns -- **Acknowledge**: Show understanding -- **Transform**: Turn negatives to positives -- **Handle**: Provide solutions - -Best for: Sales pages, product launches, and objection handling. - -### 9. AIDPPC Copywriter -The AIDPPC framework extends AIDA with additional elements: -- **Attention**: Initial hook -- **Interest**: Generate curiosity -- **Desire**: Create want -- **Proof**: Provide evidence -- **Push**: Create urgency -- **Close**: Final call to action - -Best for: Long-form sales pages and comprehensive marketing materials. - -### 10. Emotional Copywriter -Focuses on creating emotional connections through: -- Emotional triggers (FOMO, trust, joy, urgency) -- Personal connections -- Pain point addressing -- Trust building -- Community creation - -Best for: Brand storytelling, emotional marketing, and relationship building. - -## Features - -All copywriting tools include: -- User-friendly interface with Streamlit -- Educational content about each framework -- Customizable input parameters -- Multiple language support -- Tone and style options -- Target audience customization -- Brand-specific content generation -- Retry mechanism for reliable API calls - -## Usage - -1. Select your desired copywriting framework -2. Fill in the required information: - - Brand/Company details - - Target audience - - Unique selling points - - Desired tone and style - - Platform-specific requirements -3. Generate your copy -4. Review and refine the output - -## Best Practices - -1. **Know Your Audience**: Always provide detailed target audience information -2. **Be Specific**: Include clear unique selling points and value propositions -3. **Choose the Right Framework**: Match the framework to your content goals -4. **Maintain Consistency**: Keep brand voice and messaging consistent -5. **Test and Optimize**: Use different frameworks for A/B testing -6. **Review and Edit**: Always review AI-generated content for accuracy and tone - -## Technical Requirements - -- Python 3.7+ -- Streamlit -- GPT API access -- Required Python packages (see requirements.txt) - -## Support - -For technical support or questions about specific frameworks, please refer to the documentation or contact the development team. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/README_TBD.md b/ToBeMigrated/ai_writers/ai_copywriter/README_TBD.md deleted file mode 100644 index b726744a..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/README_TBD.md +++ /dev/null @@ -1,97 +0,0 @@ -# Brainstorming for Copywriting Tools UI and Features (TBD) - -## Showing All Copywriting Tools in a Single UI - -1. **Unified Dashboard Approach** - - Create a central dashboard with cards/tiles for each copywriting formula - - Use visual icons and brief descriptions to distinguish each formula - - Implement a consistent color scheme and design language across all tools - -2. **Categorization System** - - Group formulas by purpose (e.g., "Emotional Appeal," "Problem-Solution," "Storytelling") - - Allow users to filter by category or search by keyword - - Include a "Featured" or "Popular" section for commonly used formulas - -3. **Interactive Selection Interface** - - Create a decision tree or guided selection process - - Ask users a few key questions to recommend the most appropriate formula - - Show a comparison view of multiple formulas side-by-side - -4. **Progressive Disclosure** - - Start with a simplified view showing just the formula names and basic descriptions - - Allow users to expand each formula for more details and to start using it - - Implement a "Recently Used" section for quick access to frequently used formulas - -## Presenting the Right Formula for User Needs - -1. **Guided Selection Wizard** - - Create a multi-step wizard that asks about the user's marketing goals - - Include questions about target audience, industry, content type, and desired outcome - - Provide recommendations based on user responses with explanations - -2. **Formula Comparison Tool** - - Create a comparison matrix showing strengths of each formula - - Include use cases and examples for each formula - - Allow users to see side-by-side comparisons of different formulas - -3. **Educational Content Integration** - - Add a "Learn More" section for each formula with detailed explanations - - Include case studies showing successful applications of each formula - - Provide templates and examples for common use cases - -4. **Contextual Recommendations** - - Analyze the user's input and automatically suggest the most appropriate formula - - Show a confidence score for each recommendation - - Allow users to easily switch between formulas if the recommendation isn't right - -## Using AI to Pre-fill Inputs Based on Brief Requirements - -1. **Smart Input Generation** - - Create an initial input field where users can describe their copywriting needs in natural language - - Use AI to analyze this input and extract key information (brand, audience, goals, etc.) - - Pre-fill the formula-specific fields with AI-generated content - - Allow users to edit and refine the pre-filled content - -2. **Contextual Understanding** - - Implement industry-specific templates and prompts - - Use AI to recognize industry terminology and adapt suggestions accordingly - - Provide multiple options for each field based on the user's brief description - -3. **Progressive Refinement** - - Start with AI-generated suggestions for all fields - - Allow users to focus on refining specific fields while keeping others - - Implement a "regenerate" option for individual fields if the AI suggestion isn't suitable - -4. **Learning from User Edits** - - Track which AI-generated suggestions users keep vs. modify - - Use this data to improve future suggestions - - Implement a feedback mechanism for users to rate the quality of AI suggestions - -## AI-Generated Images as a Feature - -1. **Complementary Visual Content** - - Generate images that match the tone and message of the copy - - Create multiple image options for different platforms (social media, email, website) - - Ensure images align with the copywriting formula being used - -2. **Integrated Workflow** - - Add an "Generate Matching Images" button after copy is created - - Allow users to specify image style, mood, and key elements - - Provide options to customize generated images further - -3. **Platform-Specific Optimization** - - Automatically size and format images for different platforms - - Generate variations optimized for different aspect ratios - - Include text overlay options that complement the copy - -4. **Brand Consistency** - - Allow users to upload brand assets (logos, colors, fonts) - - Generate images that maintain brand identity - - Create a visual style guide based on user preferences - -5. **Enhanced Engagement** - - A/B test different image options with the same copy - - Provide analytics on which image-copy combinations perform best - - Suggest image improvements based on performance data - -These enhancements would create a more comprehensive, user-friendly copywriting platform that guides users to the right formula, simplifies the input process, and delivers complete marketing assets ready for deployment. diff --git a/ToBeMigrated/ai_writers/ai_copywriter/acca_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/acca_copywriter.py deleted file mode 100644 index 4eea9ea5..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/acca_copywriter.py +++ /dev/null @@ -1,182 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -def input_section(): - st.markdown(""" -
-

๐Ÿš€ ACCA Copywriting Generator

-

Create persuasive marketing copy using the proven ACCA (Awareness-Curiosity-Conviction-Action) formula.

-
- """, unsafe_allow_html=True) - - # Educational content about ACCA copywriting - with st.expander("๐Ÿ“š What is ACCA Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the ACCA Copywriting Formula - - The ACCA formula is a powerful copywriting framework that guides your audience through a journey from problem recognition to action: - - - **Awareness**: Highlight the problem or pain point your audience faces - - **Curiosity**: Agitate the problem by emphasizing its negative impact - - **Conviction**: Present your solution and build confidence in it - - **Action**: Provide a clear, compelling call to action - - ### Why ACCA Copywriting Works - - The ACCA formula works because it: - - - Follows the natural decision-making process of your audience - - Creates a logical progression from problem to solution - - Builds emotional investment before asking for commitment - - Addresses objections before they arise - - Ends with a clear next step - - ### When to Use ACCA Copywriting - - The ACCA formula is particularly effective for: - - - Product launches - - Service promotions - - Problem-solving offers - - Educational content - - Sales pages - - Email marketing sequences - """) - - # Main input form - with st.expander("โœ๏ธ Create Your ACCA Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - awareness = st.text_input('โ“ **Awareness (Problem)**', - placeholder="e.g., Struggling to manage finances", - help="What problem or pain point does your audience face?") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 5-6 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - curiosity = st.text_input('๐Ÿ”ฅ **Curiosity (Agitation)**', - placeholder="e.g., Leads to financial instability and stress", - help="Why is this problem serious for your audience? Highlight the negative impact.") - - conviction = st.text_input('๐Ÿ’ก **Conviction (Solution)**', - placeholder="e.g., Provides easy-to-use budgeting tools with AI insights", - help="How does your product/service solve this problem? Explain the benefits.") - - call_to_action = st.text_input('๐ŸŽฏ **Action (Call to Action)**', - placeholder="e.g., Start your free trial today", - help="What specific action do you want your audience to take?") - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate ACCA Copy**', type="primary"): - if not brand_name or not description or not awareness or not curiosity or not conviction: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, Awareness, Curiosity, and Conviction)!") - else: - with st.spinner("โœจ Crafting persuasive ACCA copy..."): - acca_copy = generate_acca_copy( - brand_name, - description, - awareness, - curiosity, - conviction, - target_audience, - unique_selling_point, - call_to_action, - tone_style - ) - - if acca_copy: - st.markdown(""" -
-

โœจ Your ACCA Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(acca_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - using a container instead of an expander - st.markdown(""" -
-

๐Ÿ’ก Tips for Using Your ACCA Copy

-
- """, unsafe_allow_html=True) - - st.markdown(""" - ### How to Use Your ACCA Copy Effectively - - 1. **Test different versions**: A/B test your copy to see which version resonates most with your audience - - 2. **Pair with visuals**: Combine your copy with images that reinforce each stage of the ACCA formula - - 3. **Consider the platform**: Adapt your copy based on where it will appear (social media, email, website, etc.) - - 4. **Measure results**: Track conversion metrics to see how your ACCA copy performs - - 5. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate ACCA Copy. Please try again!**") - - -def generate_acca_copy(brand_name, description, awareness, curiosity, conviction, target_audience, - unique_selling_point, call_to_action, tone_style): - system_prompt = """You are an expert copywriter specializing in the ACCA (Awareness-Curiosity-Conviction-Action) formula. - Your expertise is in creating compelling, persuasive marketing copy that guides audiences through a journey from problem - recognition to taking action. Your copy is authentic, specific to the brand, and focused on the target audience's needs.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - TONE & STYLE: {tone_style} - - Use the ACCA formula with these elements: - - **Awareness**: {awareness} - - **Curiosity**: {curiosity} - - **Conviction**: {conviction} - - **Action**: {call_to_action} - - For each campaign: - 1. Create a compelling headline that captures attention - 2. Write 2-3 paragraphs that follow the ACCA formula - 3. End with a strong call to action - 4. Explain how each element of the ACCA formula is used in the copy - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/ai_emotional_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/ai_emotional_copywriter.py deleted file mode 100644 index 3249872c..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/ai_emotional_copywriter.py +++ /dev/null @@ -1,168 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -def input_section(): - st.markdown(""" -
-

๐ŸŽญ Emotional Copywriting Generator

-

Create compelling copy that resonates with your audience's emotions and drives action.

-
- """, unsafe_allow_html=True) - - # Educational content about emotional copywriting - with st.expander("๐Ÿ“š What is Emotional Copywriting?", expanded=False): - st.markdown(""" - ### Understanding Emotional Copywriting - - Emotional copywriting is a powerful marketing technique that connects with your audience on a deeper level by: - - - **Triggering specific emotions** (joy, fear, urgency, trust, etc.) - - **Creating personal connections** with your audience - - **Addressing pain points** and offering solutions - - **Building trust and credibility** - - **Creating a sense of belonging** or exclusivity - - ### Why Emotional Copywriting Works - - Research shows that people make purchasing decisions based on emotions first, then justify with logic. By tapping into the right emotions, you can: - - - Increase engagement and response rates - - Build stronger brand loyalty - - Drive more conversions - - Create memorable brand experiences - - ### Common Emotional Triggers - - - **Fear of Missing Out (FOMO)**: Limited time offers, exclusive access - - **Trust**: Testimonials, guarantees, social proof - - **Joy/Happiness**: Benefits, positive outcomes, aspirational messaging - - **Urgency**: Time-sensitive offers, countdown timers - - **Belonging**: Community, exclusivity, shared values - """) - - # Main input form - with st.expander("โœ๏ธ Create Your Emotional Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**Brand/Company Name**', - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**Target Audience**', - help="Who is your ideal customer? (e.g., 'busy moms', 'tech-savvy millennials')") - - emotional_trigger = st.selectbox( - '**Primary Emotional Trigger**', - options=['Trust', 'Fear of Missing Out', 'Joy/Happiness', 'Urgency', 'Belonging', 'Exclusivity'], - help="Select the primary emotion you want to evoke in your audience." - ) - - with col2: - description = st.text_input('**Brand Description** (In 5-6 words)', - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**Unique Selling Point**', - help="What makes your product/service different from competitors?") - - call_to_action = st.text_input('**Desired Call to Action**', - help="What action do you want your audience to take? (e.g., 'Sign up now', 'Buy today')") - - trust_elements = st.text_area('**Trust Elements**', - help="Build trust and credibility by showcasing testimonials, guarantees, or endorsements.", - placeholder="Testimonials from satisfied customers...\nOur guarantee that...\nIndustry certifications...") - - tone_style = st.selectbox( - '**Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**Generate Emotional Copy**', type="primary"): - if not brand_name or not description or not trust_elements: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, and Trust Elements)!") - else: - with st.spinner("โœจ Crafting emotionally compelling copy..."): - emotional_copy = generate_emotional_copy( - brand_name, - description, - trust_elements, - target_audience, - emotional_trigger, - unique_selling_point, - call_to_action, - tone_style - ) - - if emotional_copy: - st.markdown(""" -
-

๐ŸŽฏ Your Emotional Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(emotional_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - using a container instead of an expander - st.markdown(""" -
-

๐Ÿ’ก Tips for Using Your Emotional Copy

-
- """, unsafe_allow_html=True) - - st.markdown(""" - ### How to Use Your Emotional Copy Effectively - - 1. **Test different versions**: A/B test your copy to see which emotional triggers resonate most with your audience - - 2. **Pair with visuals**: Combine your copy with images that reinforce the emotional message - - 3. **Consider the context**: Adapt the copy based on where it will appear (social media, email, website, etc.) - - 4. **Measure results**: Track engagement metrics to see how your emotional copy performs - - 5. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate Emotional Copy. Please try again!**") - - -def generate_emotional_copy(brand_name, description, trust_elements, target_audience, emotional_trigger, - unique_selling_point, call_to_action, tone_style): - system_prompt = """You are an expert emotional copywriter with years of experience in creating compelling marketing copy - that resonates with audiences on a deep emotional level. Your specialty is crafting copy that triggers specific emotions - and drives action while maintaining authenticity and credibility.""" - - prompt = f"""Create 3 different emotional marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - PRIMARY EMOTIONAL TRIGGER: {emotional_trigger} - UNIQUE SELLING POINT: {unique_selling_point} - DESIRED CALL TO ACTION: {call_to_action} - TONE & STYLE: {tone_style} - TRUST ELEMENTS: {trust_elements} - - For each campaign: - 1. Create a compelling headline that captures attention - 2. Write 2-3 paragraphs of body copy that builds emotional connection - 3. End with a strong call to action - 4. Explain which emotional triggers you used and why they're effective for this audience - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/aida_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/aida_copywriter.py deleted file mode 100644 index 80ca3cfd..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/aida_copywriter.py +++ /dev/null @@ -1,211 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐ŸŽฏ AIDA Copywriting Generator

-

Create compelling copy that follows the AIDA (Attention-Interest-Desire-Action) framework to drive conversions.

-
- """, unsafe_allow_html=True) - - # Educational content about AIDA copywriting - with st.expander("๐Ÿ“š What is AIDA Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the AIDA Copywriting Framework - - AIDA is an acronym for Attention-Interest-Desire-Action. It's a classic copywriting framework that guides your audience through a complete journey: - - - **Attention**: Capturing the audience's attention with a compelling headline or hook - - **Interest**: Generating interest by highlighting benefits or addressing pain points - - **Desire**: Creating desire by showcasing how the product/service solves problems or fulfills needs - - **Action**: Prompting the audience to take a specific action with a strong call to action - - ### Why AIDA Copywriting Works - - The AIDA framework works because it: - - - Follows the natural decision-making process of consumers - - Addresses all key elements needed for conversion - - Creates a complete journey from awareness to action - - Balances emotional and rational appeals - - Focuses on the customer's journey rather than just product features - - ### When to Use AIDA Copywriting - - The AIDA framework is particularly effective for: - - - Landing pages and sales pages - - Email marketing campaigns - - Product descriptions - - Direct response advertising - - Content that needs to drive specific actions - - Marketing materials that need to address objections - """) - - # Main input form - with st.expander("โœ๏ธ Create Your AIDA Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - attention = st.text_area('**๐Ÿ”” Attention-Grabbing Hook**', - placeholder="e.g., Tired of spending hours writing content that doesn't convert?", - help="Create a compelling headline or hook that captures attention.") - - interest = st.text_area('**๐Ÿ’ก Generate Interest**', - placeholder="e.g., Imagine creating high-quality content in minutes instead of hours...", - help="Highlight benefits or address pain points to generate interest.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 5-6 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - desire = st.text_area('**โค๏ธ Create Desire**', - placeholder="e.g., Our AI analyzes top-performing content to ensure your copy resonates with your target audience...", - help="Showcase how your product/service solves problems or fulfills needs.") - - action = st.text_area('**๐Ÿš€ Call to Action**', - placeholder="e.g., Start creating converting content today with our 14-day free trial...", - help="Prompt your audience to take action with a strong call to action.") - - landing_page_url = st.text_input('**๐ŸŒ Landing Page URL** (Optional)', - placeholder="e.g., https://alwrity.com", - help="Provide a URL to include in your call to action.") - - col1, col2 = st.columns([1, 1]) - with col1: - platform = st.selectbox( - '**๐Ÿ“ฑ Content Platform**', - options=['Social media copy', 'Email copy', 'Website copy', 'Ad copy', 'Product copy'], - help="Select the platform where your copy will be used." - ) - - with col2: - language = st.selectbox( - '**๐ŸŒ Language**', - options=['English', 'Hindustani', 'Chinese', 'Hindi', 'Spanish'], - help="Select the language for your copy." - ) - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate AIDA Copy**', type="primary"): - if not brand_name or not description or not attention or not interest or not desire or not action: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, and all AIDA elements)!") - else: - with st.spinner("โœจ Crafting compelling AIDA copy..."): - aida_copy = generate_aida_copy( - brand_name, - description, - attention, - interest, - desire, - action, - target_audience, - unique_selling_point, - landing_page_url, - platform, - language, - tone_style - ) - - if aida_copy: - st.markdown(""" -
-

๐ŸŽฏ Your AIDA Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(aida_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your AIDA Copy", expanded=False): - st.markdown(""" - ### How to Use Your AIDA Copy Effectively - - 1. **Follow the sequence**: The AIDA framework creates a natural progression - make sure your copy maintains this flow - - 2. **Test different hooks**: A/B test different attention-grabbing headlines to see which resonates most with your audience - - 3. **Pair with visuals**: Combine your copy with images that reinforce each stage of the AIDA journey - - 4. **Consider the context**: Adapt the copy based on where it will appear (landing page, email, social media, etc.) - - 5. **Measure results**: Track conversion metrics to see how your AIDA copy performs - - 6. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate AIDA Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_aida_copy(brand_name, description, attention, interest, desire, action, - target_audience, unique_selling_point, landing_page_url, - platform, language, tone_style): - system_prompt = """You are an expert copywriter specializing in the AIDA (Attention-Interest-Desire-Action) framework. - Your expertise is in creating compelling, conversion-focused marketing copy that guides readers through a complete journey from awareness to action. - Your copy is authentic, specific to the brand, and focused on driving measurable results.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - PLATFORM: {platform} - LANGUAGE: {language} - TONE & STYLE: {tone_style} - - Use the AIDA framework with these elements: - - **Attention**: {attention} - - **Interest**: {interest} - - **Desire**: {desire} - - **Action**: {action} - """ - - if landing_page_url: - prompt += f"\nInclude the landing page URL ({landing_page_url}) in your call to action." - - prompt += """ - For each campaign: - 1. Start with the attention-grabbing hook to capture the audience's attention - 2. Generate interest by highlighting benefits or addressing pain points - 3. Create desire by showcasing how the product/service solves problems or fulfills needs - 4. End with a strong call to action - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/aidppc_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/aidppc_copywriter.py deleted file mode 100644 index 5b1ca375..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/aidppc_copywriter.py +++ /dev/null @@ -1,191 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐ŸŽฏ AIDPPC Copywriting Generator

-

Create compelling copy that follows the AIDPPC (Attention-Interest-Description-Persuasion-Proof-Close) framework to drive conversions.

-
- """, unsafe_allow_html=True) - - # Educational content about AIDPPC copywriting - with st.expander("๐Ÿ“š What is AIDPPC Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the AIDPPC Copywriting Framework - - AIDPPC is an acronym for Attention-Interest-Description-Persuasion-Proof-Close. It's a comprehensive copywriting framework that guides your audience through a complete journey: - - - **Attention**: Capturing the audience's attention with a compelling headline or hook - - **Interest**: Generating interest by highlighting benefits or addressing pain points - - **Description**: Describing your product or service in detail - - **Persuasion**: Presenting compelling arguments or incentives to persuade - - **Proof**: Providing social proof, testimonials, or guarantees to build credibility - - **Close**: Prompting the audience to take action with a strong call to action - - ### Why AIDPPC Copywriting Works - - The AIDPPC framework works because it: - - - Follows the natural decision-making process of consumers - - Addresses all key elements needed for conversion - - Builds credibility through multiple stages - - Creates a complete journey from awareness to action - - Balances emotional and rational appeals - - ### When to Use AIDPPC Copywriting - - The AIDPPC framework is particularly effective for: - - - Landing pages and sales pages - - Email marketing campaigns - - Product descriptions - - Direct response advertising - - Content that needs to drive specific actions - - Marketing materials that need to address objections - """) - - # Main input form - with st.expander("โœ๏ธ Create Your AIDPPC Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - attention = st.text_area('**๐Ÿ”” Attention-Grabbing Hook**', - placeholder="e.g., Tired of spending hours writing content that doesn't convert?", - help="Create a compelling headline or hook that captures attention.") - - interest = st.text_area('**๐Ÿ’ก Generate Interest**', - placeholder="e.g., Imagine creating high-quality content in minutes instead of hours...", - help="Highlight benefits or address pain points to generate interest.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - persuasion = st.text_area('**๐Ÿ’ช Persuasive Arguments**', - placeholder="e.g., Our AI analyzes top-performing content to ensure your copy resonates with your target audience...", - help="Present compelling arguments or incentives to persuade your audience.") - - proof = st.text_area('**โœ… Social Proof**', - placeholder="e.g., Join 10,000+ satisfied customers who have transformed their content strategy...", - help="Provide testimonials, statistics, or guarantees to build credibility.") - - close = st.text_area('**๐Ÿš€ Call to Action**', - placeholder="e.g., Start creating converting content today with our 14-day free trial...", - help="Prompt your audience to take action with a strong call to action.") - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate AIDPPC Copy**', type="primary"): - if not brand_name or not description or not attention or not interest or not persuasion or not proof or not close: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, and all AIDPPC elements)!") - else: - with st.spinner("โœจ Crafting compelling AIDPPC copy..."): - aidppc_copy = generate_aidppc_copy( - brand_name, - description, - attention, - interest, - persuasion, - proof, - close, - target_audience, - unique_selling_point, - tone_style - ) - - if aidppc_copy: - st.markdown(""" -
-

๐ŸŽฏ Your AIDPPC Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(aidppc_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your AIDPPC Copy", expanded=False): - st.markdown(""" - ### How to Use Your AIDPPC Copy Effectively - - 1. **Follow the sequence**: The AIDPPC framework creates a natural progression - make sure your copy maintains this flow - - 2. **Test different hooks**: A/B test different attention-grabbing headlines to see which resonates most with your audience - - 3. **Pair with visuals**: Combine your copy with images that reinforce each stage of the AIDPPC journey - - 4. **Consider the context**: Adapt the copy based on where it will appear (landing page, email, social media, etc.) - - 5. **Measure results**: Track conversion metrics to see how your AIDPPC copy performs - - 6. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate AIDPPC Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_aidppc_copy(brand_name, description, attention, interest, persuasion, proof, close, - target_audience, unique_selling_point, tone_style): - system_prompt = """You are an expert copywriter specializing in the AIDPPC (Attention-Interest-Description-Persuasion-Proof-Close) framework. - Your expertise is in creating compelling, conversion-focused marketing copy that guides readers through a complete journey from awareness to action. - Your copy is authentic, specific to the brand, and focused on driving measurable results.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - TONE & STYLE: {tone_style} - - Use the AIDPPC framework with these elements: - - **Attention**: {attention} - - **Interest**: {interest} - - **Persuasion**: {persuasion} - - **Proof**: {proof} - - **Close**: {close} - - For each campaign: - 1. Start with the attention-grabbing hook to capture the audience's attention - 2. Generate interest by highlighting benefits or addressing pain points - 3. Describe your product or service in detail - 4. Present persuasive arguments or incentives - 5. Provide social proof, testimonials, or guarantees - 6. End with a strong call to action - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/app_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/app_copywriter.py deleted file mode 100644 index 8bcfe6d7..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/app_copywriter.py +++ /dev/null @@ -1,176 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -def input_section(): - st.markdown(""" -
-

๐Ÿ” APP Copywriting Generator

-

Create compelling marketing copy using the proven APP (Agree-Promise-Preview) formula.

-
- """, unsafe_allow_html=True) - - # Educational content about APP copywriting - with st.expander("๐Ÿ“š What is APP Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the APP Copywriting Formula - - The APP formula is a powerful copywriting framework that creates a natural connection with your audience: - - - **Agree**: Acknowledge a shared problem or pain point your audience faces - - **Promise**: Make a compelling promise or offer a solution to that problem - - **Preview**: Provide a preview of how your solution will deliver on that promise - - ### Why APP Copywriting Works - - The APP formula works because it: - - - Creates immediate rapport by showing you understand your audience's challenges - - Builds trust by acknowledging problems before selling solutions - - Reduces resistance by connecting on a human level first - - Demonstrates empathy and understanding - - Follows a natural conversation flow that feels authentic - - ### When to Use APP Copywriting - - The APP formula is particularly effective for: - - - Building trust with new audiences - - Introducing new products or services - - Addressing common objections - - Creating relatable content - - Establishing your brand as a solution provider - - Email marketing sequences - """) - - # Main input form - with st.expander("โœ๏ธ Create Your APP Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - agree = st.text_area('**๐Ÿค Agree (Shared Problem)**', - placeholder="We all face..., Like you, I've..., Safety, Unprofessionalism..", - help="Connect with the audience by acknowledging a shared problem or pain point they face.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - promise = st.text_area('**โœจ Promise (Solution)**', - placeholder="We guarantee..., Our solution ensures..., You'll never have to worry about...", - help="Make a compelling promise or offer a solution to the problem.") - - preview = st.text_area('**๐Ÿ”ฎ Preview (Proof)**', - placeholder="Here's how..., Our customers have experienced..., You'll see results like...", - help="Provide a preview of how your solution will deliver on the promise.") - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate APP Copy**', type="primary"): - if not brand_name or not description or not agree or not promise or not preview: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, Agree, Promise, and Preview)!") - else: - with st.spinner("โœจ Crafting compelling APP copy..."): - app_copy = generate_app_copy( - brand_name, - description, - agree, - target_audience, - unique_selling_point, - promise, - preview, - tone_style - ) - - if app_copy: - st.markdown(""" -
-

โœจ Your APP Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(app_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - using a container instead of an expander - st.markdown(""" -
-

๐Ÿ’ก Tips for Using Your APP Copy

-
- """, unsafe_allow_html=True) - - st.markdown(""" - ### How to Use Your APP Copy Effectively - - 1. **Test different versions**: A/B test your copy to see which version resonates most with your audience - - 2. **Pair with visuals**: Combine your copy with images that reinforce each stage of the APP formula - - 3. **Consider the platform**: Adapt your copy based on where it will appear (social media, email, website, etc.) - - 4. **Measure results**: Track engagement metrics to see how your APP copy performs - - 5. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate APP Copy. Please try again!**") - - -def generate_app_copy(brand_name, description, agree, target_audience, unique_selling_point, - promise, preview, tone_style): - system_prompt = """You are an expert copywriter specializing in the APP (Agree-Promise-Preview) formula. - Your expertise is in creating compelling, persuasive marketing copy that builds rapport with audiences by - acknowledging their problems, making promises, and providing previews of solutions. Your copy is authentic, - specific to the brand, and focused on the target audience's needs.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - TONE & STYLE: {tone_style} - - Use the APP formula with these elements: - - **Agree**: {agree} - - **Promise**: {promise} - - **Preview**: {preview} - - For each campaign: - 1. Create a compelling headline that captures attention - 2. Write 2-3 paragraphs that follow the APP formula - 3. End with a strong call to action - 4. Explain how each element of the APP formula is used in the copy - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/copywriter_dashboard.py b/ToBeMigrated/ai_writers/ai_copywriter/copywriter_dashboard.py deleted file mode 100644 index e04571dc..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/copywriter_dashboard.py +++ /dev/null @@ -1,674 +0,0 @@ -import streamlit as st -import importlib -import sys -import os -from pathlib import Path -import time -import json -from typing import Dict, List, Callable, Optional, Tuple - -# Add the parent directory to the path to allow importing from lib -current_dir = Path(__file__).parent -root_dir = current_dir.parent.parent.parent -sys.path.append(str(root_dir)) - -# Dictionary to store the input section functions -input_sections = {} - -# List of copywriter modules to import -copywriter_modules = [ - "ai_emotional_copywriter", - "acca_copywriter", - "app_copywriter", - "star_copywriter", - "oath_copywriter", - "quest_copywriter", - "aidppc_copywriter", - "aida_copywriter", - "pas_copywriter", - "fab_copywriter", - "4c_copywriter", - "4r_copywriter" -] - -# Define formula categories for better organization -formula_categories = { - "Emotional Appeal": ["ai_emotional_copywriter", "oath_copywriter"], - "Structured Framework": ["acca_copywriter", "app_copywriter", "star_copywriter", "quest_copywriter"], - "Sales Funnel": ["aidppc_copywriter", "aida_copywriter"], - "Problem-Solution": ["pas_copywriter"], - "Feature-Benefit": ["fab_copywriter"], - "Messaging Framework": ["4c_copywriter", "4r_copywriter"] -} - -# Define formula metadata for better display and filtering -formula_metadata = { - "ai_emotional_copywriter": { - "name": "Emotional Copywriter", - "icon": "๐ŸŽญ", - "description": "Create copy that resonates with your audience's emotions and drives action.", - "color": "#FF6B6B", - "difficulty": "Intermediate", - "best_for": ["Landing Pages", "Email", "Social Media"], - "tags": ["emotional", "persuasive", "engagement"] - }, - "acca_copywriter": { - "name": "ACCA Copywriter", - "icon": "๐ŸŽฏ", - "description": "Use the ACCA (Attention, Context, Content, Action) framework to create compelling copy.", - "color": "#4ECDC4", - "difficulty": "Beginner", - "best_for": ["Ads", "Email", "Landing Pages"], - "tags": ["structured", "conversion", "clear"] - }, - "app_copywriter": { - "name": "APP Copywriter", - "icon": "๐Ÿค", - "description": "Implement the APP (Agree, Promise, Preview) formula to create persuasive copy.", - "color": "#45B7D1", - "difficulty": "Beginner", - "best_for": ["Blog Posts", "Sales Pages", "Email"], - "tags": ["persuasive", "agreement", "preview"] - }, - "star_copywriter": { - "name": "STAR Copywriter", - "icon": "โญ", - "description": "Use the STAR (Situation, Task, Action, Result) framework to tell compelling stories.", - "color": "#FFD166", - "difficulty": "Intermediate", - "best_for": ["Case Studies", "Testimonials", "About Pages"], - "tags": ["storytelling", "results", "case-study"] - }, - "oath_copywriter": { - "name": "OATH Copywriter", - "icon": "๐Ÿ“œ", - "description": "Apply the OATH (Oblivious, Apathetic, Thinking, Hurting) framework to target specific audience mindsets.", - "color": "#06D6A0", - "difficulty": "Advanced", - "best_for": ["Ads", "Landing Pages", "Email Sequences"], - "tags": ["audience", "mindset", "targeting"] - }, - "quest_copywriter": { - "name": "QUEST Copywriter", - "icon": "๐Ÿ”", - "description": "Use the QUEST (Question, Unpack, Emphasize, Solution, Transform) framework for narrative-driven copy.", - "color": "#118AB2", - "difficulty": "Intermediate", - "best_for": ["Long-form Content", "Sales Pages", "Video Scripts"], - "tags": ["narrative", "transformation", "solution"] - }, - "aidppc_copywriter": { - "name": "AIDPPC Copywriter", - "icon": "๐Ÿ’ฐ", - "description": "Implement the AIDPPC (Attention, Interest, Desire, Proof, Persuasion, Call to Action) framework for PPC ads.", - "color": "#073B4C", - "difficulty": "Advanced", - "best_for": ["PPC Ads", "Social Ads", "Display Ads"], - "tags": ["advertising", "ppc", "conversion"] - }, - "aida_copywriter": { - "name": "AIDA Copywriter", - "icon": "๐ŸŽฌ", - "description": "Use the AIDA (Attention, Interest, Desire, Action) framework to guide customers through the sales funnel.", - "color": "#EF476F", - "difficulty": "Beginner", - "best_for": ["Sales Pages", "Email", "Product Descriptions"], - "tags": ["sales", "funnel", "conversion"] - }, - "pas_copywriter": { - "name": "PAS Copywriter", - "icon": "๐Ÿ”ง", - "description": "Apply the PAS (Problem, Agitate, Solution) formula to address pain points and offer solutions.", - "color": "#7209B7", - "difficulty": "Beginner", - "best_for": ["Ads", "Email", "Landing Pages"], - "tags": ["problem-solving", "pain-points", "solutions"] - }, - "fab_copywriter": { - "name": "FAB Copywriter", - "icon": "๐Ÿ’Ž", - "description": "Use the FAB (Features, Advantages, Benefits) framework to highlight product value.", - "color": "#3A0CA3", - "difficulty": "Beginner", - "best_for": ["Product Descriptions", "Sales Pages", "Brochures"], - "tags": ["product", "features", "benefits"] - }, - "4c_copywriter": { - "name": "4C Copywriter", - "icon": "๐Ÿ“", - "description": "Implement the 4C (Clear, Concise, Credible, Compelling) framework for effective messaging.", - "color": "#4361EE", - "difficulty": "Intermediate", - "best_for": ["Brand Messaging", "Mission Statements", "Value Propositions"], - "tags": ["clarity", "concise", "credibility"] - }, - "4r_copywriter": { - "name": "4R Copywriter", - "icon": "๐Ÿ”„", - "description": "Use the 4R (Relevance, Resonance, Response, Results) framework to connect with your audience.", - "color": "#F72585", - "difficulty": "Intermediate", - "best_for": ["Content Marketing", "Email", "Social Media"], - "tags": ["relevance", "resonance", "results"] - } -} - -def load_user_preferences() -> Dict: - """Load user preferences from session state or initialize if not present.""" - if "copywriter_preferences" not in st.session_state: - st.session_state.copywriter_preferences = { - "recent_formulas": [], - "favorite_formulas": [], - "comparison_formulas": [], - "view_mode": "grid" # or "list" - } - return st.session_state.copywriter_preferences - -def save_user_preferences(preferences: Dict) -> None: - """Save user preferences to session state.""" - st.session_state.copywriter_preferences = preferences - -def add_recent_formula(module_name: str) -> None: - """Add a formula to the recent formulas list.""" - preferences = load_user_preferences() - - # Remove if already exists - if module_name in preferences["recent_formulas"]: - preferences["recent_formulas"].remove(module_name) - - # Add to the beginning of the list - preferences["recent_formulas"].insert(0, module_name) - - # Keep only the 5 most recent - preferences["recent_formulas"] = preferences["recent_formulas"][:5] - - save_user_preferences(preferences) - -def toggle_favorite_formula(module_name: str) -> bool: - """Toggle a formula as favorite and return the new state.""" - preferences = load_user_preferences() - - if module_name in preferences["favorite_formulas"]: - preferences["favorite_formulas"].remove(module_name) - is_favorite = False - else: - preferences["favorite_formulas"].append(module_name) - is_favorite = True - - save_user_preferences(preferences) - return is_favorite - -def is_favorite_formula(module_name: str) -> bool: - """Check if a formula is in the favorites list.""" - preferences = load_user_preferences() - return module_name in preferences["favorite_formulas"] - -def add_to_comparison(module_name: str) -> None: - """Add a formula to the comparison list.""" - preferences = load_user_preferences() - - if module_name not in preferences["comparison_formulas"]: - preferences["comparison_formulas"].append(module_name) - - # Keep only up to 3 formulas for comparison - preferences["comparison_formulas"] = preferences["comparison_formulas"][:3] - - save_user_preferences(preferences) - -def remove_from_comparison(module_name: str) -> None: - """Remove a formula from the comparison list.""" - preferences = load_user_preferences() - - if module_name in preferences["comparison_formulas"]: - preferences["comparison_formulas"].remove(module_name) - - save_user_preferences(preferences) - -def clear_comparison() -> None: - """Clear the comparison list.""" - preferences = load_user_preferences() - preferences["comparison_formulas"] = [] - save_user_preferences(preferences) - -def lazy_load_module(module_name: str) -> Optional[Callable]: - """Lazily load a module and return its input_section function.""" - if module_name in input_sections: - return input_sections[module_name] - - try: - module_path = f"lib.ai_writers.ai_copywriter.{module_name}" - module = importlib.import_module(module_path) - if hasattr(module, "input_section"): - input_sections[module_name] = module.input_section - return module.input_section - else: - st.warning(f"Module {module_name} does not have an input_section function.") - return None - except Exception as e: - st.error(f"Error loading module {module_name}: {str(e)}") - return None - -def render_formula_card(module_name: str, index: int, view_mode: str = "grid") -> None: - """Render a formula card with its details.""" - metadata = formula_metadata.get(module_name, {}) - - if not metadata: - return - - is_favorite = is_favorite_formula(module_name) - favorite_icon = "โ˜…" if is_favorite else "โ˜†" - favorite_tooltip = "Remove from favorites" if is_favorite else "Add to favorites" - - if view_mode == "grid": - with st.container(): - st.markdown(f""" -
-
{favorite_icon}
-

{metadata["icon"]} {metadata["name"]}

-

{metadata["description"]}

-
- - {metadata["difficulty"]} - -
-
- """, unsafe_allow_html=True) - - col1, col2, col3 = st.columns(3) - with col1: - if st.button(f"Use {metadata['name']}", key=f"use_btn_{index}", use_container_width=True): - add_recent_formula(module_name) - st.session_state.selected_formula = { - "module": module_name, - "name": metadata["name"], - "icon": metadata["icon"], - "function": lazy_load_module(module_name) - } - st.rerun() - - with col2: - if st.button(f"{favorite_icon} Favorite", key=f"fav_btn_{index}", help=favorite_tooltip, use_container_width=True): - toggle_favorite_formula(module_name) - st.rerun() - - with col3: - if module_name in load_user_preferences()["comparison_formulas"]: - if st.button("Remove from Compare", key=f"comp_btn_{index}", use_container_width=True): - remove_from_comparison(module_name) - st.rerun() - else: - if st.button("Add to Compare", key=f"comp_btn_{index}", use_container_width=True): - add_to_comparison(module_name) - st.rerun() - else: # list view - with st.container(): - col1, col2 = st.columns([3, 1]) - - with col1: - st.markdown(f""" -
-

{metadata["icon"]} {metadata["name"]} {favorite_icon}

-

{metadata["description"]}

-
- - {metadata["difficulty"]} - - Best for: {", ".join(metadata["best_for"][:2])} -
-
- """, unsafe_allow_html=True) - - with col2: - if st.button(f"Use", key=f"use_list_btn_{index}", use_container_width=True): - add_recent_formula(module_name) - st.session_state.selected_formula = { - "module": module_name, - "name": metadata["name"], - "icon": metadata["icon"], - "function": lazy_load_module(module_name) - } - st.rerun() - - if st.button(f"{favorite_icon}", key=f"fav_list_btn_{index}", help=favorite_tooltip): - toggle_favorite_formula(module_name) - st.rerun() - - if module_name in load_user_preferences()["comparison_formulas"]: - if st.button("- Compare", key=f"comp_list_btn_{index}"): - remove_from_comparison(module_name) - st.rerun() - else: - if st.button("+ Compare", key=f"comp_list_btn_{index}"): - add_to_comparison(module_name) - st.rerun() - -def render_formula_comparison() -> None: - """Render a comparison of selected formulas.""" - preferences = load_user_preferences() - comparison_formulas = preferences["comparison_formulas"] - - if not comparison_formulas: - st.info("Add formulas to compare them side by side.") - return - - # Create a table for comparison - comparison_data = [] - for module_name in comparison_formulas: - metadata = formula_metadata.get(module_name, {}) - if metadata: - comparison_data.append({ - "Name": f"{metadata['icon']} {metadata['name']}", - "Description": metadata["description"], - "Difficulty": metadata["difficulty"], - "Best For": ", ".join(metadata["best_for"][:3]), - "Tags": ", ".join(metadata["tags"]) - }) - - # Display the comparison table - st.markdown("### Formula Comparison") - - # Create columns for each formula - cols = st.columns(len(comparison_data)) - - # Display headers - for i, col in enumerate(cols): - with col: - st.markdown(f"#### {comparison_data[i]['Name']}") - - # Display description - st.markdown("##### Description") - for i, col in enumerate(cols): - with col: - st.write(comparison_data[i]["Description"]) - - # Display difficulty - st.markdown("##### Difficulty") - for i, col in enumerate(cols): - with col: - st.write(comparison_data[i]["Difficulty"]) - - # Display best for - st.markdown("##### Best For") - for i, col in enumerate(cols): - with col: - st.write(comparison_data[i]["Best For"]) - - # Display tags - st.markdown("##### Tags") - for i, col in enumerate(cols): - with col: - st.write(comparison_data[i]["Tags"]) - - # Add buttons to use each formula - st.markdown("##### Actions") - for i, col in enumerate(cols): - with col: - module_name = comparison_formulas[i] - if st.button(f"Use {formula_metadata[module_name]['name']}", key=f"use_comp_btn_{i}"): - add_recent_formula(module_name) - st.session_state.selected_formula = { - "module": module_name, - "name": formula_metadata[module_name]["name"], - "icon": formula_metadata[module_name]["icon"], - "function": lazy_load_module(module_name) - } - st.rerun() - - # Add a button to clear the comparison - if st.button("Clear Comparison", key="clear_comparison"): - clear_comparison() - st.rerun() - -def filter_formulas(formulas: List[str], search_term: str, category: str, difficulty: str) -> List[str]: - """Filter formulas based on search term, category, and difficulty.""" - filtered_formulas = [] - - for module_name in formulas: - metadata = formula_metadata.get(module_name, {}) - if not metadata: - continue - - # Check if the formula matches the search term - name_match = search_term.lower() in metadata["name"].lower() - desc_match = search_term.lower() in metadata["description"].lower() - tags_match = any(search_term.lower() in tag.lower() for tag in metadata.get("tags", [])) - - # Check if the formula matches the category - category_match = True - if category != "All Categories": - category_match = module_name in formula_categories.get(category, []) - - # Check if the formula matches the difficulty - difficulty_match = True - if difficulty != "All Difficulties": - difficulty_match = metadata.get("difficulty", "") == difficulty - - # Add the formula if it matches all criteria - if (name_match or desc_match or tags_match) and category_match and difficulty_match: - filtered_formulas.append(module_name) - - return filtered_formulas - -def copywriter_dashboard(): - """ - Main function to display the copywriting dashboard. - This function can be called from content_generator.py when the user selects "AI Copywriter". - """ - # Load user preferences - preferences = load_user_preferences() - - # Initialize session state for selected formula if it doesn't exist - if "selected_formula" not in st.session_state: - st.session_state.selected_formula = None - - # Initialize session state for search and filter options - if "search_term" not in st.session_state: - st.session_state.search_term = "" - if "selected_category" not in st.session_state: - st.session_state.selected_category = "All Categories" - if "selected_difficulty" not in st.session_state: - st.session_state.selected_difficulty = "All Difficulties" - if "view_mode" not in st.session_state: - st.session_state.view_mode = preferences["view_mode"] - - # Create a container for the formula input section - formula_container = st.container() - - # If a formula is selected, show its input section - if st.session_state.selected_formula is not None: - with formula_container: - # Display the selected formula's input section - st.markdown("---") - st.markdown(f"# {st.session_state.selected_formula['icon']} {st.session_state.selected_formula['name']}") - - # Add a back button - if st.button("โ† Back to Dashboard", key="back_to_dashboard"): - # Clear the selected formula from session state - st.session_state.selected_formula = None - st.rerun() - - # Call the input section function for the selected formula - if st.session_state.selected_formula["function"]: - st.session_state.selected_formula["function"]() - else: - st.error(f"The {st.session_state.selected_formula['name']} module is not available.") - else: - # Create a container for the dashboard - dashboard_container = st.container() - - with dashboard_container: - # Display the dashboard - # Header - st.markdown(""" -
-

โœ๏ธ AI Copywriting Tools

-

Choose the perfect copywriting formula for your marketing needs

-
- """, unsafe_allow_html=True) - - # Create tabs for different sections - tab1, tab2, tab3, tab4 = st.tabs(["All Formulas", "Recent & Favorites", "Compare Formulas", "Help & Guide"]) - - with tab1: - # Search and filter options - col1, col2, col3, col4 = st.columns([3, 2, 2, 1]) - - with col1: - search_term = st.text_input("๐Ÿ” Search formulas", value=st.session_state.search_term) - if search_term != st.session_state.search_term: - st.session_state.search_term = search_term - - with col2: - categories = ["All Categories"] + list(formula_categories.keys()) - selected_category = st.selectbox("Category", categories, index=categories.index(st.session_state.selected_category)) - if selected_category != st.session_state.selected_category: - st.session_state.selected_category = selected_category - - with col3: - difficulties = ["All Difficulties", "Beginner", "Intermediate", "Advanced"] - selected_difficulty = st.selectbox("Difficulty", difficulties, index=difficulties.index(st.session_state.selected_difficulty)) - if selected_difficulty != st.session_state.selected_difficulty: - st.session_state.selected_difficulty = selected_difficulty - - with col4: - view_options = {"Grid": "grid", "List": "list"} - view_mode = st.selectbox("View", list(view_options.keys()), index=list(view_options.values()).index(st.session_state.view_mode)) - st.session_state.view_mode = view_options[view_mode] - preferences["view_mode"] = st.session_state.view_mode - save_user_preferences(preferences) - - # Filter formulas based on search and filter options - filtered_formulas = filter_formulas( - copywriter_modules, - st.session_state.search_term, - st.session_state.selected_category, - st.session_state.selected_difficulty - ) - - if not filtered_formulas: - st.info("No formulas match your search criteria. Try adjusting your filters.") - else: - # Display the formula cards - if st.session_state.view_mode == "grid": - # Create a 3-column layout for the formula cards - col1, col2, col3 = st.columns(3) - - # Display the formula cards - for i, module_name in enumerate(filtered_formulas): - # Determine which column to use - col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3 - - with col: - render_formula_card(module_name, i, st.session_state.view_mode) - else: # list view - for i, module_name in enumerate(filtered_formulas): - render_formula_card(module_name, i, st.session_state.view_mode) - - with tab2: - # Recent formulas - st.subheader("Recently Used Formulas") - recent_formulas = preferences["recent_formulas"] - - if not recent_formulas: - st.info("You haven't used any formulas yet. Start by selecting a formula from the 'All Formulas' tab.") - else: - # Create a 3-column layout for the recent formula cards - col1, col2, col3 = st.columns(3) - - # Display the recent formula cards - for i, module_name in enumerate(recent_formulas): - # Determine which column to use - col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3 - - with col: - render_formula_card(module_name, i + 100, "grid") # Use a different index to avoid key conflicts - - # Favorite formulas - st.subheader("Favorite Formulas") - favorite_formulas = preferences["favorite_formulas"] - - if not favorite_formulas: - st.info("You haven't added any formulas to your favorites yet. Click the star icon on a formula card to add it to your favorites.") - else: - # Create a 3-column layout for the favorite formula cards - col1, col2, col3 = st.columns(3) - - # Display the favorite formula cards - for i, module_name in enumerate(favorite_formulas): - # Determine which column to use - col = col1 if i % 3 == 0 else col2 if i % 3 == 1 else col3 - - with col: - render_formula_card(module_name, i + 200, "grid") # Use a different index to avoid key conflicts - - with tab3: - # Formula comparison - render_formula_comparison() - - with tab4: - # Help and guide - st.subheader("Copywriting Formula Guide") - st.write(""" - This dashboard provides access to a variety of copywriting formulas, each designed for specific marketing needs. - Here's how to make the most of these powerful tools: - """) - - st.markdown(""" - #### How to Use This Dashboard - - 1. **Browse Formulas**: Explore the available copywriting formulas in the "All Formulas" tab - 2. **Search & Filter**: Use the search box and filters to find the perfect formula for your needs - 3. **Compare Formulas**: Add up to 3 formulas to the comparison tab to see them side by side - 4. **Save Favorites**: Click the star icon to save formulas you use frequently - 5. **Access Recent**: Quickly access your recently used formulas in the "Recent & Favorites" tab - - #### Choosing the Right Formula - - Different formulas work best for different marketing goals: - - - **Emotional Appeal**: Use when you want to connect with your audience on an emotional level - - **Structured Framework**: Great for organizing complex information in a compelling way - - **Sales Funnel**: Designed to guide prospects through the buying journey - - **Problem-Solution**: Effective for highlighting pain points and positioning your solution - - **Feature-Benefit**: Perfect for product descriptions and technical offerings - - **Messaging Framework**: Helps create clear, consistent messaging across channels - - #### Formula Difficulty Levels - - - **Beginner**: Easy to use with minimal copywriting experience - - **Intermediate**: Requires some understanding of copywriting principles - - **Advanced**: Most effective when used by experienced copywriters - """) - - # Add a section about how to use the generated copy - st.subheader("Using Your Generated Copy") - st.write(""" - After generating copy with your chosen formula: - - 1. **Review & Edit**: Always review and personalize the generated content - 2. **Test Different Versions**: Try multiple formulas for the same product/service - 3. **A/B Test**: Use different versions in your marketing to see which performs best - 4. **Adapt for Channels**: Modify the copy as needed for different marketing channels - """) - - # Add a feedback section - st.subheader("Feedback & Suggestions") - st.write("We're constantly improving our copywriting tools. If you have feedback or suggestions, please let us know!") - - feedback = st.text_area("Your feedback", placeholder="Share your thoughts, suggestions, or report any issues...") - if st.button("Submit Feedback"): - if feedback: - st.success("Thank you for your feedback! We'll use it to improve our tools.") - # In a real implementation, you would save this feedback somewhere - else: - st.warning("Please enter your feedback before submitting.") - -# For standalone execution -if __name__ == "__main__": - st.set_page_config( - page_title="AI Copywriting Tools", - page_icon="โœ๏ธ", - layout="wide", - initial_sidebar_state="expanded" - ) - copywriter_dashboard() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/fab_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/fab_copywriter.py deleted file mode 100644 index 6f6dad8b..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/fab_copywriter.py +++ /dev/null @@ -1,212 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐ŸŽฏ FAB Copywriting Generator

-

Create compelling copy that follows the FAB (Features-Advantages-Benefits) framework to drive conversions.

-
- """, unsafe_allow_html=True) - - # Educational content about FAB copywriting - with st.expander("๐Ÿ“š What is FAB Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the FAB Copywriting Framework - - FAB is an acronym for Features-Advantages-Benefits. It's a powerful copywriting framework that focuses on translating product features into customer benefits: - - - **Features**: The specific characteristics, attributes, or capabilities of your product or service - - **Advantages**: How these features compare to or outperform competitors - - **Benefits**: The positive outcomes or results that customers will experience when using your product or service - - ### Why FAB Copywriting Works - - The FAB framework works because it: - - - Focuses on customer value rather than just product specifications - - Translates technical features into meaningful benefits - - Addresses the "what's in it for me" question that customers ask - - Creates a clear connection between product capabilities and customer outcomes - - Helps customers understand why they should choose your product over alternatives - - ### When to Use FAB Copywriting - - The FAB framework is particularly effective for: - - - Product descriptions and specifications - - Technical products with complex features - - Comparison marketing - - B2B marketing where features matter - - Content that needs to explain product capabilities - - Marketing materials that need to address feature-based objections - """) - - # Main input form - with st.expander("โœ๏ธ Create Your FAB Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - product_name = st.text_input('**๐Ÿข Product/Service Name**', - placeholder="e.g., Alwrity AI Writer", - help="Enter the name of your product or service.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Content marketers", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - features = st.text_area('**๐Ÿ”ง Features**', - placeholder="e.g., AI-powered content generation, Multiple copywriting frameworks, SEO optimization", - help="List the specific characteristics, attributes, or capabilities of your product or service.") - - advantages = st.text_area('**๐Ÿ’ช Advantages**', - placeholder="e.g., 10x faster than manual writing, Supports 12+ copywriting frameworks, Built-in SEO analysis", - help="How do these features compare to or outperform competitors?") - - with col2: - product_description = st.text_input('**๐Ÿ“ Product Description** (In 5-6 words)', - placeholder="e.g., AI writing assistant", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., All-in-one AI copywriting platform", - help="What makes your product/service different from competitors?") - - benefits = st.text_area('**โœจ Benefits**', - placeholder="e.g., Save 20+ hours per week on content creation, Increase conversion rates by 35%, Improve SEO rankings", - help="What positive outcomes or results will customers experience when using your product or service?") - - call_to_action = st.text_area('**๐Ÿš€ Call to Action**', - placeholder="e.g., Start creating high-converting content today with our 14-day free trial...", - help="Prompt your audience to take action with a strong call to action.") - - landing_page_url = st.text_input('**๐ŸŒ Landing Page URL** (Optional)', - placeholder="e.g., https://alwrity.com", - help="Provide a URL to include in your call to action.") - - col1, col2 = st.columns([1, 1]) - with col1: - platform = st.selectbox( - '**๐Ÿ“ฑ Content Platform**', - options=['Social media copy', 'Email copy', 'Website copy', 'Ad copy', 'Product copy'], - help="Select the platform where your copy will be used." - ) - - with col2: - language = st.selectbox( - '**๐ŸŒ Language**', - options=['English', 'Hindustani', 'Chinese', 'Hindi', 'Spanish'], - help="Select the language for your copy." - ) - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate FAB Copy**', type="primary"): - if not product_name or not product_description or not features or not advantages or not benefits: - st.error("โš ๏ธ Please fill in all required fields (Product Name, Description, Features, Advantages, and Benefits)!") - else: - with st.spinner("โœจ Crafting compelling FAB copy..."): - fab_copy = generate_fab_copy( - product_name, - product_description, - features, - advantages, - benefits, - target_audience, - unique_selling_point, - call_to_action, - landing_page_url, - platform, - language, - tone_style - ) - - if fab_copy: - st.markdown(""" -
-

๐ŸŽฏ Your FAB Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(fab_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your FAB Copy", expanded=False): - st.markdown(""" - ### How to Use Your FAB Copy Effectively - - 1. **Follow the sequence**: The FAB framework creates a natural progression - make sure your copy maintains this flow - - 2. **Balance features and benefits**: While benefits are most important, don't neglect features for technical audiences - - 3. **Be specific**: Use concrete numbers, statistics, and examples to make your advantages and benefits more compelling - - 4. **Pair with visuals**: Combine your copy with images that showcase your product features and the resulting benefits - - 5. **Consider the context**: Adapt the copy based on where it will appear (landing page, email, social media, etc.) - - 6. **Measure results**: Track conversion metrics to see how your FAB copy performs - - 7. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate FAB Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_fab_copy(product_name, product_description, features, advantages, benefits, - target_audience, unique_selling_point, call_to_action, - landing_page_url, platform, language, tone_style): - system_prompt = """You are an expert copywriter specializing in the FAB (Features-Advantages-Benefits) framework. - Your expertise is in creating compelling, conversion-focused marketing copy that translates product features into meaningful customer benefits. - Your copy is authentic, specific to the brand, and focused on driving measurable results.""" - - prompt = f"""Create 3 different marketing campaigns for {product_name}, which is a {product_description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - PLATFORM: {platform} - LANGUAGE: {language} - TONE & STYLE: {tone_style} - - Use the FAB framework with these elements: - - **Features**: {features} - - **Advantages**: {advantages} - - **Benefits**: {benefits} - - **Call to Action**: {call_to_action} - """ - - if landing_page_url: - prompt += f"\nInclude the landing page URL ({landing_page_url}) in your call to action." - - prompt += """ - For each campaign: - 1. Start by highlighting the key features of the product or service - 2. Explain the advantages these features provide compared to alternatives - 3. Connect these advantages to specific benefits that customers will experience - 4. End with a strong call to action - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/oath_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/oath_copywriter.py deleted file mode 100644 index d5375cd6..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/oath_copywriter.py +++ /dev/null @@ -1,186 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐Ÿ“‹ OATH Copywriting Generator

-

Create compelling copy that addresses different audience mindsets using the OATH (Oblivious-Apathetic-Thinking-Hurting) framework.

-
- """, unsafe_allow_html=True) - - # Educational content about OATH copywriting - with st.expander("๐Ÿ“š What is OATH Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the OATH Copywriting Framework - - The OATH framework is a powerful copywriting approach that recognizes different audience mindsets: - - - **Oblivious**: People who don't know they have a problem or need - - **Apathetic**: People who know about the problem but don't care enough to act - - **Thinking**: People who are actively considering solutions - - **Hurting**: People who are experiencing pain and urgently need a solution - - ### Why OATH Copywriting Works - - The OATH framework works because it: - - - Addresses the full spectrum of audience awareness - - Creates targeted messaging for each mindset - - Increases conversion rates by meeting people where they are - - Helps you craft the right message for the right audience - - Allows for more personalized and effective marketing campaigns - - ### When to Use OATH Copywriting - - The OATH framework is particularly effective for: - - - New product launches - - Educational content - - Problem-solution marketing - - Awareness campaigns - - Multi-channel marketing strategies - - Content that needs to address different audience segments - """) - - # Main input form - with st.expander("โœ๏ธ Create Your OATH Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - oblivious = st.text_area('**๐Ÿ” Oblivious Audience**', - placeholder="People who don't know they have this problem...", - help="Describe the audience who doesn't know they have a problem or need your solution.") - - apathetic = st.text_area('**๐Ÿ˜ Apathetic Audience**', - placeholder="People who know about the problem but don't care enough to act...", - help="Describe the audience who knows about the problem but isn't motivated to solve it.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - thinking = st.text_area('**๐Ÿค” Thinking Audience**', - placeholder="People who are actively considering solutions...", - help="Describe the audience who is actively researching solutions to their problem.") - - hurting = st.text_area('**๐Ÿ˜ซ Hurting Audience**', - placeholder="People who are experiencing pain and urgently need a solution...", - help="Describe the audience who is experiencing significant pain and urgently needs a solution.") - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate OATH Copy**', type="primary"): - if not brand_name or not description or not oblivious or not apathetic or not thinking or not hurting: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, and all audience segments)!") - else: - with st.spinner("โœจ Crafting compelling OATH copy..."): - oath_copy = generate_oath_copy( - brand_name, - description, - oblivious, - apathetic, - thinking, - hurting, - target_audience, - unique_selling_point, - tone_style - ) - - if oath_copy: - st.markdown(""" -
-

๐Ÿ“‹ Your OATH Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(oath_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - using a container instead of an expander - st.markdown(""" -
-

๐Ÿ’ก Tips for Using Your OATH Copy

-
- """, unsafe_allow_html=True) - - st.markdown(""" - ### How to Use Your OATH Copy Effectively - - 1. **Target the right audience**: Use the appropriate OATH segment copy based on your target audience's current mindset - - 2. **Create a journey**: Consider how to move audiences from one mindset to another (e.g., from Oblivious to Thinking) - - 3. **Test different versions**: A/B test your copy to see which OATH segment resonates most with your audience - - 4. **Pair with visuals**: Combine your copy with images that reinforce the message for each audience segment - - 5. **Measure results**: Track engagement metrics to see how your OATH copy performs across different audience segments - - 6. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate OATH Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_oath_copy(brand_name, description, oblivious, apathetic, thinking, hurting, - target_audience, unique_selling_point, tone_style): - system_prompt = """You are an expert copywriter specializing in the OATH (Oblivious-Apathetic-Thinking-Hurting) framework. - Your expertise is in creating compelling, targeted marketing copy that addresses different audience mindsets and awareness levels. - Your copy is authentic, specific to the brand, and focused on meeting audiences where they are in their journey.""" - - prompt = f"""Create 4 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - TONE & STYLE: {tone_style} - - Use the OATH framework with these audience segments: - - **Oblivious**: {oblivious} - - **Apathetic**: {apathetic} - - **Thinking**: {thinking} - - **Hurting**: {hurting} - - For each campaign: - 1. Create a compelling headline that captures attention - 2. Write 2-3 paragraphs that address the specific audience mindset - 3. End with a strong call to action - 4. Explain how the copy is tailored to that specific audience mindset - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/pas_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/pas_copywriter.py deleted file mode 100644 index 5e1a5ecf..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/pas_copywriter.py +++ /dev/null @@ -1,213 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def input_section(): - st.markdown(""" -
-

๐ŸŽฏ PAS Copywriting Generator

-

Create compelling copy that follows the PAS (Problem-Agitate-Solution) framework to drive conversions.

-
- """, unsafe_allow_html=True) - - # Educational content about PAS copywriting - with st.expander("๐Ÿ“š What is PAS Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the PAS Copywriting Framework - - PAS is an acronym for Problem-Agitate-Solution. It's a powerful copywriting framework that focuses on identifying and solving customer pain points: - - - **Problem**: Identifying a specific problem or pain point that your target audience faces - - **Agitate**: Amplifying the problem by highlighting its negative consequences and emotional impact - - **Solution**: Presenting your product or service as the ideal solution to the problem - - ### Why PAS Copywriting Works - - The PAS framework works because it: - - - Addresses real customer pain points and needs - - Creates emotional resonance by highlighting the consequences of inaction - - Positions your product/service as the hero that solves the problem - - Follows a natural problem-solving narrative that readers can relate to - - Focuses on the customer's journey rather than just product features - - ### When to Use PAS Copywriting - - The PAS framework is particularly effective for: - - - Products or services that solve specific problems - - Marketing to audiences with clear pain points - - Content that needs to drive specific actions - - Landing pages and sales pages - - Email marketing campaigns - - Direct response advertising - """) - - # Main input form - with st.expander("โœ๏ธ Create Your PAS Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - problem = st.text_area('**โŒ Problem**', - placeholder="e.g., Struggling to create high-quality content that converts", - help="Identify a specific problem or pain point that your target audience faces.") - - agitate = st.text_area('**๐Ÿ˜ซ Agitate**', - placeholder="e.g., Without effective content, you're losing potential customers and revenue every day...", - help="Amplify the problem by highlighting its negative consequences and emotional impact.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 5-6 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - solution = st.text_area('**โœจ Solution**', - placeholder="e.g., Our AI-powered platform creates high-converting content in minutes...", - help="Present your product or service as the ideal solution to the problem.") - - call_to_action = st.text_area('**๐Ÿš€ Call to Action**', - placeholder="e.g., Start creating converting content today with our 14-day free trial...", - help="Prompt your audience to take action with a strong call to action.") - - landing_page_url = st.text_input('**๐ŸŒ Landing Page URL** (Optional)', - placeholder="e.g., https://alwrity.com", - help="Provide a URL to include in your call to action.") - - col1, col2 = st.columns([1, 1]) - with col1: - platform = st.selectbox( - '**๐Ÿ“ฑ Content Platform**', - options=['Social media copy', 'Email copy', 'Website copy', 'Ad copy', 'Product copy'], - help="Select the platform where your copy will be used." - ) - - with col2: - language = st.selectbox( - '**๐ŸŒ Language**', - options=['English', 'Hindustani', 'Chinese', 'Hindi', 'Spanish'], - help="Select the language for your copy." - ) - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate PAS Copy**', type="primary"): - if not brand_name or not description or not problem or not agitate or not solution: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, Problem, Agitate, and Solution)!") - else: - with st.spinner("โœจ Crafting compelling PAS copy..."): - pas_copy = generate_pas_copy( - brand_name, - description, - problem, - agitate, - solution, - target_audience, - unique_selling_point, - call_to_action, - landing_page_url, - platform, - language, - tone_style - ) - - if pas_copy: - st.markdown(""" -
-

๐ŸŽฏ Your PAS Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(pas_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your PAS Copy", expanded=False): - st.markdown(""" - ### How to Use Your PAS Copy Effectively - - 1. **Follow the sequence**: The PAS framework creates a natural progression - make sure your copy maintains this flow - - 2. **Be specific about the problem**: The more specific and relatable the problem, the more effective your copy will be - - 3. **Balance agitation**: Don't over-agitate to the point of creating anxiety; find the right balance to motivate action - - 4. **Pair with visuals**: Combine your copy with images that reinforce each stage of the PAS journey - - 5. **Consider the context**: Adapt the copy based on where it will appear (landing page, email, social media, etc.) - - 6. **Measure results**: Track conversion metrics to see how your PAS copy performs - - 7. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate PAS Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_pas_copy(brand_name, description, problem, agitate, solution, - target_audience, unique_selling_point, call_to_action, - landing_page_url, platform, language, tone_style): - system_prompt = """You are an expert copywriter specializing in the PAS (Problem-Agitate-Solution) framework. - Your expertise is in creating compelling, conversion-focused marketing copy that identifies customer pain points, - amplifies their impact, and positions your product or service as the ideal solution. - Your copy is authentic, specific to the brand, and focused on driving measurable results.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - PLATFORM: {platform} - LANGUAGE: {language} - TONE & STYLE: {tone_style} - - Use the PAS framework with these elements: - - **Problem**: {problem} - - **Agitate**: {agitate} - - **Solution**: {solution} - - **Call to Action**: {call_to_action} - """ - - if landing_page_url: - prompt += f"\nInclude the landing page URL ({landing_page_url}) in your call to action." - - prompt += """ - For each campaign: - 1. Start by identifying the specific problem or pain point - 2. Amplify the problem by highlighting its negative consequences and emotional impact - 3. Present your product or service as the ideal solution to the problem - 4. End with a strong call to action - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/quest_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/quest_copywriter.py deleted file mode 100644 index 2552d637..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/quest_copywriter.py +++ /dev/null @@ -1,191 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from tenacity import retry, wait_random_exponential, stop_after_attempt - -def title_and_description(): - st.markdown(""" -
-

๐Ÿ” QUEST Copywriting Generator

-

Create compelling copy that guides your audience through a journey using the QUEST (Question-Unpack-Emphasize-Solution-Transform) framework.

-
- """, unsafe_allow_html=True) - - # Educational content about QUEST copywriting - with st.expander("๐Ÿ“š What is QUEST Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the QUEST Copywriting Framework - - QUEST is an acronym for Question-Unpack-Emphasize-Solution-Transform. It's a copywriting framework that focuses on guiding the audience through different stages: - - - **Question**: Presenting a thought-provoking question to engage the audience - - **Unpack**: Unpacking the question by elaborating on its implications and relevance - - **Emphasize**: Emphasizing the importance or significance of the topic - - **Solution**: Presenting your product or service as the solution to the question - - **Transform**: Describing the transformation or improvement your solution offers - - ### Why QUEST Copywriting Works - - The QUEST framework works because it: - - - Creates a natural flow that guides readers through a journey - - Engages readers by starting with a question they care about - - Builds credibility by showing deep understanding of the problem - - Demonstrates value by clearly connecting the solution to the problem - - Inspires action by showing the transformation that's possible - - ### When to Use QUEST Copywriting - - The QUEST framework is particularly effective for: - - - Educational content and blog posts - - Product launches and feature announcements - - Problem-solution marketing - - Thought leadership content - - Content that needs to guide readers through a journey - - Marketing materials that need to explain complex solutions - """) - -def input_section(): - # Main input form - with st.expander("โœ๏ธ Create Your QUEST Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - question = st.text_area('**โ“ Thought-Provoking Question**', - placeholder="e.g., What if you could create content 10x faster without sacrificing quality?", - help="Pose a question that resonates with your audience and highlights a problem they face.") - - unpack = st.text_area('**๐Ÿ“ฆ Unpack the Question**', - placeholder="e.g., Content creation is time-consuming and often results in inconsistent quality...", - help="Elaborate on the implications of the question and provide context that your audience can relate to.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - emphasize = st.text_area('**๐Ÿ’ช Emphasize Importance**', - placeholder="e.g., In today's fast-paced digital world, efficient content creation is essential for business growth...", - help="Highlight the relevance and impact of addressing this problem.") - - solution = st.text_area('**๐Ÿ”ง Present Your Solution**', - placeholder="e.g., Our AI-powered writing assistant helps you create high-quality content in a fraction of the time...", - help="Introduce your product or service as the solution to the question.") - - transform = st.text_area('**โœจ Describe the Transformation**', - placeholder="e.g., Imagine having more time to focus on strategy while maintaining consistent, high-quality content...", - help="Describe the transformation or improvement your solution offers to your audience.") - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate QUEST Copy**', type="primary"): - if not brand_name or not description or not question or not unpack or not emphasize or not solution or not transform: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, and all QUEST elements)!") - else: - with st.spinner("โœจ Crafting compelling QUEST copy..."): - quest_copy = generate_quest_copy( - brand_name, - description, - question, - unpack, - emphasize, - solution, - transform, - target_audience, - unique_selling_point, - tone_style - ) - - if quest_copy: - st.markdown(""" -
-

๐Ÿ” Your QUEST Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(quest_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - with st.expander("๐Ÿ’ก Tips for Using Your QUEST Copy", expanded=False): - st.markdown(""" - ### How to Use Your QUEST Copy Effectively - - 1. **Follow the journey**: The QUEST framework creates a natural flow - make sure your copy maintains this progression - - 2. **Test different questions**: A/B test different opening questions to see which resonates most with your audience - - 3. **Pair with visuals**: Combine your copy with images that reinforce each stage of the QUEST journey - - 4. **Consider the context**: Adapt the copy based on where it will appear (blog post, landing page, email, etc.) - - 5. **Measure results**: Track engagement metrics to see how your QUEST copy performs - - 6. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate QUEST Copy. Please try again!**") - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def generate_quest_copy(brand_name, description, question, unpack, emphasize, solution, transform, - target_audience, unique_selling_point, tone_style): - system_prompt = """You are an expert copywriter specializing in the QUEST (Question-Unpack-Emphasize-Solution-Transform) framework. - Your expertise is in creating compelling, narrative-driven marketing copy that guides readers through a journey. - Your copy is authentic, specific to the brand, and focused on connecting with the audience's needs and desires.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - TONE & STYLE: {tone_style} - - Use the QUEST framework with these elements: - - **Question**: {question} - - **Unpack**: {unpack} - - **Emphasize**: {emphasize} - - **Solution**: {solution} - - **Transform**: {transform} - - For each campaign: - 1. Start with the thought-provoking question to engage the audience - 2. Unpack the question by elaborating on its implications - 3. Emphasize the importance of addressing this issue - 4. Present your solution clearly and convincingly - 5. Describe the transformation that your solution offers - 6. End with a strong call to action - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_copywriter/star_copywriter.py b/ToBeMigrated/ai_writers/ai_copywriter/star_copywriter.py deleted file mode 100644 index 01e1b855..00000000 --- a/ToBeMigrated/ai_writers/ai_copywriter/star_copywriter.py +++ /dev/null @@ -1,182 +0,0 @@ -import streamlit as st -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -def input_section(): - st.markdown(""" -
-

โญ STAR Copywriting Generator

-

Create compelling marketing copy using the proven STAR (Situation-Task-Action-Result) framework.

-
- """, unsafe_allow_html=True) - - # Educational content about STAR copywriting - with st.expander("๐Ÿ“š What is STAR Copywriting?", expanded=False): - st.markdown(""" - ### Understanding the STAR Copywriting Framework - - The STAR framework is a powerful storytelling structure that creates compelling narratives: - - - **Situation**: Set the context and background for the problem or need - - **Task**: Describe the specific challenge or objective that needs to be addressed - - **Action**: Explain the specific actions taken to address the challenge - - **Result**: Highlight the positive outcomes and benefits achieved - - ### Why STAR Copywriting Works - - The STAR framework works because it: - - - Creates a complete narrative arc that engages readers - - Demonstrates problem-solving capabilities - - Shows concrete results and benefits - - Builds credibility through specific examples - - Makes abstract benefits tangible through storytelling - - ### When to Use STAR Copywriting - - The STAR framework is particularly effective for: - - - Case studies and success stories - - Product or service demonstrations - - Customer testimonials - - Company achievements and milestones - - Problem-solution marketing - - Portfolio showcases - """) - - # Main input form - with st.expander("โœ๏ธ Create Your STAR Copy", expanded=True): - col1, col2 = st.columns([1, 1]) - - with col1: - brand_name = st.text_input('**๐Ÿข Brand/Company Name**', - placeholder="e.g., Alwrity", - help="Enter the name of your brand or company.") - - target_audience = st.text_input('**๐Ÿ‘ฅ Target Audience**', - placeholder="e.g., Small business owners, Tech professionals", - help="Who is your ideal customer? Be specific about demographics and psychographics.") - - situation = st.text_area('**๐ŸŒ Situation (Context)**', - placeholder="In a busy city, Late Delivery, Unsafe Activities, Unprofessional Service..", - help="Describe the background context or problem that needs to be addressed.") - - action = st.text_area('**โšก Action (Solution)**', - placeholder="New strategy, launched campaign, better service, New product...", - help="Describe the specific actions taken to address the challenge or objective.") - - with col2: - description = st.text_input('**๐Ÿ“ Brand Description** (In 2-3 words)', - placeholder="e.g., AI writing tools", - help="Describe your product or service briefly.") - - unique_selling_point = st.text_input('**๐Ÿ’Ž Unique Selling Point**', - placeholder="e.g., 10x faster content creation", - help="What makes your product/service different from competitors?") - - task = st.text_area('**๐ŸŽฏ Task (Challenge)**', - placeholder="Increase website traffic by 30%, improve customer satisfaction, Safe Travels...", - help="Describe the specific challenge or objective that needs to be addressed.") - - result = st.text_area('**โœจ Result (Outcome)**', - placeholder="Improved customer engagement, sales revenue, Happy customers, Improved Service X...", - help="Highlight the positive outcomes and benefits achieved from the actions taken.") - - tone_style = st.selectbox( - '**๐ŸŽญ Copy Tone & Style**', - options=['Professional', 'Conversational', 'Humorous', 'Authoritative', 'Empathetic', 'Aspirational'], - help="Select the tone and style for your copy." - ) - - if st.button('**๐Ÿš€ Generate STAR Copy**', type="primary"): - if not brand_name or not description or not situation or not task or not action or not result: - st.error("โš ๏ธ Please fill in all required fields (Brand Name, Description, Situation, Task, Action, and Result)!") - else: - with st.spinner("โœจ Crafting compelling STAR copy..."): - star_copy = generate_star_copy( - brand_name, - description, - situation, - task, - action, - result, - target_audience, - unique_selling_point, - tone_style - ) - - if star_copy: - st.markdown(""" -
-

โญ Your STAR Copy

-
- """, unsafe_allow_html=True) - - # Display the copy with a nice format - st.markdown(star_copy) - - # Add copy button - st.markdown(""" -
- -
- """, unsafe_allow_html=True) - - # Add tips for using the copy - using a container instead of an expander - st.markdown(""" -
-

๐Ÿ’ก Tips for Using Your STAR Copy

-
- """, unsafe_allow_html=True) - - st.markdown(""" - ### How to Use Your STAR Copy Effectively - - 1. **Test different versions**: A/B test your copy to see which version resonates most with your audience - - 2. **Pair with visuals**: Combine your copy with images that illustrate each stage of the STAR framework - - 3. **Consider the platform**: Adapt your copy based on where it will appear (social media, email, website, etc.) - - 4. **Measure results**: Track engagement metrics to see how your STAR copy performs - - 5. **Refine over time**: Continuously improve your copy based on audience feedback and performance data - """) - else: - st.error("๐Ÿ’ฅ **Failed to generate STAR Copy. Please try again!**") - - -def generate_star_copy(brand_name, description, situation, task, action, result, target_audience, - unique_selling_point, tone_style): - system_prompt = """You are an expert copywriter specializing in the STAR (Situation-Task-Action-Result) framework. - Your expertise is in creating compelling, narrative-driven marketing copy that tells a complete story from problem to solution. - Your copy is authentic, specific to the brand, and focused on demonstrating concrete results and benefits.""" - - prompt = f"""Create 3 different marketing campaigns for {brand_name}, which is a {description}. - - TARGET AUDIENCE: {target_audience} - UNIQUE SELLING POINT: {unique_selling_point} - TONE & STYLE: {tone_style} - - Use the STAR framework with these elements: - - **Situation**: {situation} - - **Task**: {task} - - **Action**: {action} - - **Result**: {result} - - For each campaign: - 1. Create a compelling headline that captures attention - 2. Write 2-3 paragraphs that follow the STAR framework - 3. End with a strong call to action - 4. Explain how each element of the STAR framework is used in the copy - - Format each campaign clearly with "CAMPAIGN 1:", "CAMPAIGN 2:", etc. as headers. - Make the copy authentic, specific to the brand, and focused on the target audience's needs and desires. - """ - - try: - return llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as e: - st.error(f"Error generating copy: {str(e)}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/README.md b/ToBeMigrated/ai_writers/ai_finance_report_generator/README.md deleted file mode 100644 index 554e9ba2..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# AI Finance Report Generator - -An advanced AI-powered financial analysis and report generation system that combines data collection, technical analysis, visualization, and automated report generation. - -## Project Structure - -``` -ai_finance_report_generator/ -โ”œโ”€โ”€ ai_financial_dashboard.py # Main dashboard interface -โ”œโ”€โ”€ utils/ # Utility functions -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ””โ”€โ”€ storage.py # Data persistence -โ”œโ”€โ”€ reports/ # Report generation modules -โ”‚ โ”œโ”€โ”€ technical_analysis/ # Technical analysis reports -โ”‚ โ”œโ”€โ”€ fundamental_analysis/ # Fundamental analysis reports -โ”‚ โ”œโ”€โ”€ options_analysis/ # Options analysis reports -โ”‚ โ”œโ”€โ”€ portfolio_analysis/ # Portfolio analysis reports -โ”‚ โ”œโ”€โ”€ market_research/ # Market research reports -โ”‚ โ””โ”€โ”€ news_analysis/ # News analysis reports -โ””โ”€โ”€ README.md # This file -``` - -## Features - -### Current Features -- Unified dashboard interface for all financial analysis tools -- Technical Analysis report generation -- Options analysis report generation -- User preferences management -- Recent reports tracking -- Data persistence with JSON storage -- Financial data collection from various sources -- Integration with LLM for report generation - -### Planned Features - -#### 1. Data Collection Module -- Web scraping for financial news and data -- API integrations (Yahoo Finance, Alpha Vantage, Financial Modeling Prep) -- Real-time market data collection -- Historical data retrieval -- Company financial statements -- Market sentiment data -- Economic indicators -- Sector analysis data - -#### 2. Technical Analysis Module -- Moving averages (SMA, EMA, WMA) -- RSI, MACD, Bollinger Bands -- Volume analysis -- Support/Resistance levels -- Trend analysis -- Pattern recognition -- Fibonacci retracements -- Momentum indicators - -#### 3. Fundamental Analysis Module -- Financial ratios calculation -- Company valuation metrics -- Growth analysis -- Profitability analysis -- Debt analysis -- Cash flow analysis -- Industry comparison -- Peer analysis - -#### 4. Data Visualization Module -- Candlestick charts -- Technical indicator overlays -- Volume charts -- Price action patterns -- Correlation matrices -- Heat maps -- Interactive charts -- Custom chart templates - -#### 5. Report Generation Module -- Technical analysis reports -- Fundamental analysis reports -- Market research reports -- Investment recommendations -- Risk assessment reports -- Sector analysis reports -- News impact analysis -- Custom report templates - -#### 6. News and Sentiment Analysis Module -- News aggregation -- Sentiment scoring -- Social media analysis -- Market sentiment indicators -- News impact analysis -- Event correlation -- Trend detection -- Sentiment visualization - -#### 7. Portfolio Analysis Module -- Portfolio performance analysis -- Risk assessment -- Asset allocation -- Correlation analysis -- Diversification metrics -- Performance attribution -- Portfolio optimization -- Rebalancing suggestions - -## Usage - -### Basic Usage - -```python -from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard - -# Get dashboard instance -dashboard = get_dashboard() - -# Generate technical analysis report -ta_report = dashboard.generate_technical_analysis("AAPL") - -# Generate options analysis report -options_report = dashboard.generate_options_analysis("AAPL") - -# Get recent reports -recent_reports = dashboard.get_recent_reports() -``` - -### User Preferences - -```python -# Update user preferences -dashboard.update_preferences({ - "report_format": "markdown", - "include_charts": True, - "chart_style": "dark", - "language": "en" -}) - -# Get current preferences -preferences = dashboard.get_preferences() -``` - -### Portfolio Analysis - -```python -# Create portfolio -portfolio = [ - {"symbol": "AAPL", "shares": 100}, - {"symbol": "GOOGL", "shares": 50} -] - -# Generate portfolio report -portfolio_report = dashboard.generate_portfolio_analysis(portfolio) -``` - -## Installation - -```bash -pip install -r requirements.txt -``` - -## Dependencies - -1. **Data Collection** - - `finance_data_researcher` - - `web_scraping_tools` - -2. **Analysis Tools** - - `pandas_ta` - - `numpy` - - `scipy` - -3. **Visualization** - - `matplotlib` - - `plotly` - -4. **Text Generation** - - `llm_text_gen` - - `gpt_providers` - -## Contributing - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/AmazingFeature`) -3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) -4. Push to the branch (`git push origin feature/AmazingFeature`) -5. Open a Pull Request - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py deleted file mode 100644 index b08b706f..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/ai_financial_dashboard.py +++ /dev/null @@ -1,358 +0,0 @@ -""" -AI Financial Dashboard Module - -This module combines the financial dashboard interface with financial report generation capabilities. -It provides a unified interface for managing financial analysis tools and generating reports. -""" - -import sys -import os -from textwrap import dedent -from pathlib import Path -from datetime import datetime -from typing import Dict, List, Any, Optional, Union - -from loguru import logger -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}" - ) - -from ...ai_web_researcher.finance_data_researcher import get_finance_data, get_fin_options_data -from ...gpt_providers.text_generation.main_text_generation import llm_text_gen -from .utils import get_feature_status -from .utils.storage import get_storage_manager - -class UserPreferences: - """Class to manage user preferences and settings.""" - - def __init__(self): - self.default_settings = { - "theme": "light", - "currency": "USD", - "timezone": "UTC", - "date_format": "%Y-%m-%d", - "default_symbols": [], - "notifications": True, - "auto_refresh": False, - "refresh_interval": 300, # 5 minutes - "report_format": "markdown", - "include_charts": True, - "chart_style": "default", - "language": "en" - } - self.settings = self.default_settings.copy() - self.storage = get_storage_manager() - self.load_settings() - - def update_setting(self, key: str, value: Any) -> None: - """Update a specific setting.""" - if key in self.default_settings: - self.settings[key] = value - self.save_settings() - - def get_setting(self, key: str) -> Any: - """Get a specific setting value.""" - return self.settings.get(key, self.default_settings.get(key)) - - def reset_settings(self) -> None: - """Reset all settings to default values.""" - self.settings = self.default_settings.copy() - self.save_settings() - - def save_settings(self) -> None: - """Save current settings to storage.""" - self.storage.save_user_preferences(self.settings) - - def load_settings(self) -> None: - """Load settings from storage.""" - stored_settings = self.storage.load_user_preferences() - if stored_settings: - self.settings.update(stored_settings) - -class RecentReport: - """Class to represent a recently generated report.""" - - def __init__(self, report_type: str, symbol: Optional[str], timestamp: datetime, content: Optional[str] = None): - self.report_type = report_type - self.symbol = symbol - self.timestamp = timestamp - self.content = content - self.id = f"{report_type}_{symbol}_{timestamp.strftime('%Y%m%d%H%M%S')}" - - def to_dict(self) -> Dict[str, Any]: - """Convert report to dictionary format.""" - return { - "id": self.id, - "type": self.report_type, - "symbol": self.symbol, - "timestamp": self.timestamp.isoformat(), - "content": self.content - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> 'RecentReport': - """Create report from dictionary format.""" - return cls( - report_type=data["type"], - symbol=data["symbol"], - timestamp=datetime.fromisoformat(data["timestamp"]), - content=data.get("content") - ) - -class FinancialDashboard: - """Main dashboard class for managing financial analysis tools and generating reports.""" - - def __init__(self): - self.features = { - "technical_analysis": { - "name": "Technical Analysis", - "description": "Generate technical analysis reports with indicators and patterns", - "icon": "๐Ÿ“Š", - "route": "/technical-analysis", - "category": "analysis", - "dependencies": ["data_collection"], - "version": "1.0.0" - }, - "fundamental_analysis": { - "name": "Fundamental Analysis", - "description": "Analyze company financials and valuation metrics", - "icon": "๐Ÿ“ˆ", - "route": "/fundamental-analysis", - "category": "analysis", - "dependencies": ["data_collection"], - "version": "0.1.0" - }, - "options_analysis": { - "name": "Options Analysis", - "description": "Analyze options chains and generate trading strategies", - "icon": "โšก", - "route": "/options-analysis", - "category": "analysis", - "dependencies": ["data_collection", "options_data"], - "version": "1.0.0" - }, - "portfolio_analysis": { - "name": "Portfolio Analysis", - "description": "Analyze portfolio performance and risk metrics", - "icon": "๐Ÿ“‘", - "route": "/portfolio-analysis", - "category": "portfolio", - "dependencies": ["data_collection", "portfolio_data"], - "version": "0.1.0" - }, - "market_research": { - "name": "Market Research", - "description": "Generate market research reports and sector analysis", - "icon": "๐Ÿ”", - "route": "/market-research", - "category": "research", - "dependencies": ["data_collection", "news_data"], - "version": "0.1.0" - }, - "news_analysis": { - "name": "News Analysis", - "description": "Analyze news impact and market sentiment", - "icon": "๐Ÿ“ฐ", - "route": "/news-analysis", - "category": "research", - "dependencies": ["data_collection", "news_data"], - "version": "0.1.0" - } - } - - self.user_preferences = UserPreferences() - self.storage = get_storage_manager() - self.recent_reports: List[RecentReport] = [] - self.max_recent_reports = 10 - self.load_recent_reports() - - def get_all_features(self) -> List[Dict[str, Any]]: - """Get all available features with their status.""" - features_list = [] - for feature_id, feature_info in self.features.items(): - status = get_feature_status(feature_id) - feature_info.update(status) - features_list.append(feature_info) - return features_list - - def get_feature(self, feature_id: str) -> Dict[str, Any]: - """Get information about a specific feature.""" - if feature_id not in self.features: - raise ValueError(f"Feature {feature_id} not found") - - feature_info = self.features[feature_id].copy() - status = get_feature_status(feature_id) - feature_info.update(status) - return feature_info - - def get_implemented_features(self) -> List[Dict[str, Any]]: - """Get only the implemented features.""" - return [f for f in self.get_all_features() if f["implemented"]] - - def get_coming_soon_features(self) -> List[Dict[str, Any]]: - """Get features that are coming soon.""" - return [f for f in self.get_all_features() if f["coming_soon"]] - - def get_features_by_category(self, category: str) -> List[Dict[str, Any]]: - """Get features filtered by category.""" - return [f for f in self.get_all_features() if f["category"] == category] - - def add_recent_report(self, report_type: str, symbol: Optional[str] = None, content: Optional[str] = None) -> None: - """Add a report to the recent reports list.""" - report = RecentReport(report_type, symbol, datetime.now(), content) - self.recent_reports.insert(0, report) - if len(self.recent_reports) > self.max_recent_reports: - self.recent_reports.pop() - self.save_recent_reports() - - def get_recent_reports(self, limit: Optional[int] = None) -> List[Dict[str, Any]]: - """Get recent reports.""" - reports = self.recent_reports[:limit] if limit else self.recent_reports - return [{ - **r.to_dict(), - "feature_info": self.get_feature(r.report_type) - } for r in reports] - - def save_recent_reports(self) -> None: - """Save recent reports to storage.""" - reports_data = [r.to_dict() for r in self.recent_reports] - self.storage.save_recent_reports(reports_data) - - def load_recent_reports(self) -> None: - """Load recent reports from storage.""" - reports_data = self.storage.load_recent_reports() - self.recent_reports = [RecentReport.from_dict(r) for r in reports_data] - - def get_dashboard_summary(self) -> Dict[str, Any]: - """Get a summary of the dashboard state.""" - return { - "total_features": len(self.features), - "implemented_features": len(self.get_implemented_features()), - "coming_soon_features": len(self.get_coming_soon_features()), - "recent_reports": len(self.recent_reports), - "categories": list(set(f["category"] for f in self.features.values())), - "user_preferences": self.user_preferences.settings - } - - def check_feature_dependencies(self, feature_id: str) -> Dict[str, bool]: - """Check if all dependencies for a feature are met.""" - if feature_id not in self.features: - raise ValueError(f"Feature {feature_id} not found") - - feature = self.features[feature_id] - dependencies = feature.get("dependencies", []) - - return { - dep: get_feature_status(dep)["implemented"] - for dep in dependencies - } - - def backup_data(self, backup_dir: Optional[str] = None) -> None: - """Create a backup of all dashboard data.""" - self.storage.backup_storage(backup_dir) - - def restore_from_backup(self, backup_file: str) -> None: - """Restore dashboard data from a backup file.""" - self.storage.restore_from_backup(backup_file) - self.user_preferences.load_settings() - self.load_recent_reports() - - def generate_technical_analysis(self, symbol: str) -> str: - """Generate a technical analysis report for the given symbol.""" - try: - # Get financial data - symbol_fin_data = get_finance_data(symbol) - - # Generate report - report_content = self._generate_ta_report(symbol_fin_data, symbol) - - # Add to recent reports - self.add_recent_report("technical_analysis", symbol, report_content) - - logger.info(f"Done: Final Technical Analysis for {symbol}") - return report_content - - except Exception as err: - logger.error(f"Error: Failed to generate Technical Analysis report: {err}") - raise - - def generate_options_analysis(self, symbol: str) -> str: - """Generate an options analysis report for the given symbol.""" - try: - # Get options data - options_data = get_fin_options_data(symbol) - - # Generate report - report_content = self._generate_options_report(options_data, symbol) - - # Add to recent reports - self.add_recent_report("options_analysis", symbol, report_content) - - logger.info(f"Done: Options Analysis for {symbol}") - return report_content - - except Exception as err: - logger.error(f"Error: Failed to generate Options Analysis report: {err}") - raise - - def _generate_ta_report(self, last_day_summary: str, symbol: str) -> str: - """Generate technical analysis report using LLM.""" - prompt = f""" - You are a seasoned Technical Analysis (TA) expert, rivaling legends like Charles Dow, John Bollinger, and Alan Andrews. - Your deep understanding of market dynamics, coupled with mastery of technical indicators, - allows you to decipher complex patterns and offer precise predictions. - - Your expertise extends to practical tools like the pandas_ta module, enabling you to extract valuable insights from raw data. - - **Objective:** - Analyze the provided technical indicators for {symbol} on its last trading day and predict its price movement over the next few trading sessions. - - **Instructions:** - 1. **Identify Potential Trading Signals:** Highlight specific indicators suggesting bullish, bearish, or neutral signals. Explain the rationale behind each signal, referencing historical patterns or comparable market scenarios. - 2. **Detect Patterns and Divergences:** Analyze the interplay between different indicators. Detect patterns like moving average crossovers, candlestick formations, or divergences between price action and indicators. Explain the significance of each pattern. - 3. **Price Movement Prediction:** Based on your analysis, provide a clear prediction for {symbol}'s price movement in the next few days. State the expected direction (up, down, sideways) and potential price targets if identifiable. - 4. **Risk Assessment:** Briefly discuss any potential risks or factors that could invalidate your predictions, promoting a balanced and informed perspective. - - **Technical Indicators for {symbol} on the Last Trading Day:** - {last_day_summary} - - Remember, your analysis should be detailed, insightful, and actionable for traders seeking to capitalize on market movements. - """ - - try: - return llm_text_gen(prompt) - except Exception as err: - logger.error(f"Failed to generate TA report: {err}") - raise - - def _generate_options_report(self, results_sentences: List[str], ticker: str) -> str: - """Generate options analysis report using LLM.""" - prompt = f""" - You are a financial expert specializing in options trading and market sentiment analysis. - You have been provided with the following technical analysis of options data for the ticker symbol {ticker} with the nearest expiry date: - - {chr(10).join(results_sentences)} - - Based on this data, provide a comprehensive analysis of the options market for {ticker}. - - Your analysis should include: - - 1. **Implied Volatility Interpretation:** Discuss the significance of the average implied volatility for both call and put options. What does it suggest about market expectations of future price movements? - 2. **Volume and Open Interest Insights:** Analyze the volume and open interest for call and put options. What does this data reveal about current market positioning and potential future trading activity? - 3. **Sentiment Analysis:** Evaluate the put-call ratio, implied volatility skew, and overall market sentiment. What do these indicators suggest about trader sentiment and potential future price direction? - 4. **Potential Trading Strategies:** Based on your analysis, suggest potential options trading strategies that could be employed for {ticker}, considering the current market conditions and sentiment. - - Please provide your analysis in a clear and concise manner, suitable for someone with a good understanding of options trading. - """ - - try: - return llm_text_gen(prompt) - except Exception as err: - logger.error(f"Failed to generate options report: {err}") - raise - -def get_dashboard() -> FinancialDashboard: - """Get the financial dashboard instance.""" - return FinancialDashboard() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/README.md b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/README.md deleted file mode 100644 index 6fc26864..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/README.md +++ /dev/null @@ -1,265 +0,0 @@ -# Financial Reports Module - -This directory contains the core report generation modules for different types of financial analysis. Each module is designed to handle a specific type of financial report and can be accessed through the main dashboard interface. - -## Directory Structure - -``` -reports/ -โ”œโ”€โ”€ technical_analysis/ # Technical analysis reports -โ”œโ”€โ”€ fundamental_analysis/ # Fundamental analysis reports -โ”œโ”€โ”€ options_analysis/ # Options analysis reports -โ”œโ”€โ”€ portfolio_analysis/ # Portfolio analysis reports -โ”œโ”€โ”€ market_research/ # Market research reports -โ””โ”€โ”€ news_analysis/ # News analysis reports -``` - -## Report Types - -### 1. Technical Analysis Reports -Location: `technical_analysis/` - -Generates technical analysis reports including: -- Moving averages (SMA, EMA, WMA) -- RSI, MACD, Bollinger Bands -- Volume analysis -- Support/Resistance levels -- Trend analysis -- Pattern recognition - -Usage: -```python -from lib.ai_writers.ai_finance_report_generator.reports.technical_analysis import generate_ta_report - -report = generate_ta_report("AAPL") -``` - -### 2. Fundamental Analysis Reports -Location: `fundamental_analysis/` - -Generates fundamental analysis reports including: -- Financial ratios -- Company valuation metrics -- Growth analysis -- Profitability analysis -- Debt analysis -- Cash flow analysis - -Usage: -```python -from lib.ai_writers.ai_finance_report_generator.reports.fundamental_analysis import generate_fa_report - -report = generate_fa_report("AAPL") -``` - -### 3. Options Analysis Reports -Location: `options_analysis/` - -Generates options analysis reports including: -- Options chain analysis -- Implied volatility analysis -- Options strategies -- Risk metrics -- Greeks analysis - -Usage: -```python -from lib.ai_writers.ai_finance_report_generator.reports.options_analysis import generate_options_report - -report = generate_options_report("AAPL") -``` - -### 4. Portfolio Analysis Reports -Location: `portfolio_analysis/` - -Generates portfolio analysis reports including: -- Portfolio performance analysis -- Risk assessment -- Asset allocation -- Correlation analysis -- Diversification metrics -- Performance attribution - -Usage: -```python -from lib.ai_writers.ai_finance_report_generator.reports.portfolio_analysis import generate_portfolio_report - -portfolio = [ - {"symbol": "AAPL", "shares": 100}, - {"symbol": "GOOGL", "shares": 50} -] -report = generate_portfolio_report(portfolio) -``` - -### 5. Market Research Reports -Location: `market_research/` - -Generates market research reports including: -- Sector analysis -- Industry trends -- Market overview -- Competitive analysis -- Market opportunities -- Risk factors - -Usage: -```python -from lib.ai_writers.ai_finance_report_generator.reports.market_research import generate_market_research_report - -report = generate_market_research_report(sectors=["Technology", "Healthcare"]) -``` - -### 6. News Analysis Reports -Location: `news_analysis/` - -Generates news analysis reports including: -- News sentiment analysis -- Market impact analysis -- Event correlation -- Trend detection -- Social media analysis -- News aggregation - -Usage: -```python -from lib.ai_writers.ai_finance_report_generator.reports.news_analysis import generate_news_analysis_report - -report = generate_news_analysis_report("AAPL") -``` - -## Common Features - -All report modules share the following features: - -1. **Data Validation** - - Input validation for symbols and parameters - - Error handling for invalid inputs - - Data type checking - -2. **Report Formatting** - - Markdown formatting - - Chart generation (when applicable) - - Customizable templates - -3. **Storage Integration** - - Automatic report storage - - Recent reports tracking - - Report versioning - -4. **User Preferences** - - Customizable report formats - - Language selection - - Chart style preferences - -## Integration with Dashboard - -All report modules are integrated with the main dashboard and can be accessed through the `FinancialDashboard` class: - -```python -from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard - -dashboard = get_dashboard() - -# Generate reports through dashboard -ta_report = dashboard.generate_technical_analysis("AAPL") -options_report = dashboard.generate_options_analysis("AAPL") - -# Get recent reports -recent_reports = dashboard.get_recent_reports() -``` - -## Adding New Report Types - -To add a new report type: - -1. Create a new directory in the `reports/` folder -2. Create an `__init__.py` file with the report generation function -3. Add the report type to the dashboard features -4. Implement the report generation logic -5. Add appropriate error handling and validation - -Example: -```python -# reports/new_analysis/__init__.py -from typing import Dict, Any -from ...utils import validate_symbol - -def generate_new_analysis_report(symbol: str) -> Dict[str, Any]: - """ - Generate a new type of analysis report. - - Args: - symbol (str): Stock symbol to analyze - - Returns: - Dict[str, Any]: Analysis report - """ - if not validate_symbol(symbol): - raise ValueError("Invalid symbol provided") - - # Implement report generation logic - return { - "symbol": symbol, - "analysis": "Report content" - } -``` - -## Error Handling - -All report modules implement consistent error handling: - -1. **Input Validation** - - Symbol validation - - Parameter validation - - Data type checking - -2. **Data Collection Errors** - - API errors - - Network errors - - Data format errors - -3. **Report Generation Errors** - - LLM errors - - Template errors - - Formatting errors - -4. **Storage Errors** - - File system errors - - Database errors - - Backup errors - -## Contributing - -When contributing to the reports module: - -1. Follow the existing code structure -2. Add appropriate type hints -3. Include comprehensive docstrings -4. Add error handling -5. Update the dashboard integration -6. Add tests for new functionality - -## Dependencies - -The reports module depends on: - -1. **Data Collection** - - `finance_data_researcher` - - `web_scraping_tools` - -2. **Analysis Tools** - - `pandas_ta` - - `numpy` - - `scipy` - -3. **Visualization** - - `matplotlib` - - `plotly` - -4. **Text Generation** - - `llm_text_gen` - - `gpt_providers` - -## License - -This module is part of the AI Finance Report Generator project and is licensed under the MIT License. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py deleted file mode 100644 index af152a18..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/fundamental_analysis/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Fundamental Analysis Reports Module - -This module handles the generation of fundamental analysis reports including: -- Financial ratios -- Company valuation metrics -- Growth analysis -- Profitability analysis -- Debt analysis -- Cash flow analysis -""" - -from typing import Dict, Any -from ...utils import validate_symbol - -def generate_fa_report(symbol: str) -> Dict[str, Any]: - """ - Generate a fundamental analysis report for the given symbol. - - Args: - symbol (str): Stock symbol to analyze - - Returns: - Dict[str, Any]: Fundamental analysis report - """ - if not validate_symbol(symbol): - raise ValueError("Invalid symbol provided") - - # TODO: Implement fundamental analysis report generation - return { - "symbol": symbol, - "status": "coming_soon", - "message": "Fundamental analysis report generation is coming soon" - } \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py deleted file mode 100644 index 4e276969..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/market_research/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Market Research Reports Module - -This module handles the generation of market research reports including: -- Sector analysis -- Industry trends -- Market overview -- Competitive analysis -- Market opportunities -- Risk factors -""" - -from typing import Dict, Any, List - -def generate_market_research_report(sectors: List[str] = None) -> Dict[str, Any]: - """ - Generate a market research report. - - Args: - sectors (List[str], optional): List of sectors to analyze - - Returns: - Dict[str, Any]: Market research report - """ - # TODO: Implement market research report generation - return { - "status": "coming_soon", - "message": "Market research report generation is coming soon" - } \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py deleted file mode 100644 index 8006c8da..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/news_analysis/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -News Analysis Reports Module - -This module handles the generation of news analysis reports including: -- News sentiment analysis -- Market impact analysis -- Event correlation -- Trend detection -- Social media analysis -- News aggregation -""" - -from typing import Dict, Any, List -from ...utils import validate_symbol - -def generate_news_analysis_report(symbol: str = None) -> Dict[str, Any]: - """ - Generate a news analysis report. - - Args: - symbol (str, optional): Stock symbol to analyze news for - - Returns: - Dict[str, Any]: News analysis report - """ - if symbol and not validate_symbol(symbol): - raise ValueError("Invalid symbol provided") - - # TODO: Implement news analysis report generation - return { - "status": "coming_soon", - "message": "News analysis report generation is coming soon" - } \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py deleted file mode 100644 index 89eddb7a..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/options_analysis/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Options Analysis Reports Module - -This module handles the generation of options analysis reports including: -- Options chain analysis -- Implied volatility analysis -- Options strategies -- Risk metrics -- Greeks analysis -""" - -from typing import Dict, Any -from ...utils import validate_symbol - -def generate_options_report(symbol: str) -> Dict[str, Any]: - """ - Generate an options analysis report for the given symbol. - - Args: - symbol (str): Stock symbol to analyze - - Returns: - Dict[str, Any]: Options analysis report - """ - if not validate_symbol(symbol): - raise ValueError("Invalid symbol provided") - - # TODO: Implement options analysis report generation - return { - "symbol": symbol, - "status": "coming_soon", - "message": "Options analysis report generation is coming soon" - } \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py deleted file mode 100644 index 1f7d5e88..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/portfolio_analysis/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Portfolio Analysis Reports Module - -This module handles the generation of portfolio analysis reports including: -- Portfolio performance analysis -- Risk assessment -- Asset allocation -- Correlation analysis -- Diversification metrics -- Performance attribution -""" - -from typing import Dict, Any, List - -def generate_portfolio_report(portfolio: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - Generate a portfolio analysis report. - - Args: - portfolio (List[Dict[str, Any]]): List of portfolio positions - - Returns: - Dict[str, Any]: Portfolio analysis report - """ - if not portfolio: - raise ValueError("Portfolio cannot be empty") - - # TODO: Implement portfolio analysis report generation - return { - "status": "coming_soon", - "message": "Portfolio analysis report generation is coming soon" - } \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py deleted file mode 100644 index 8cc609d9..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/reports/technical_analysis/__init__.py +++ /dev/null @@ -1,314 +0,0 @@ -""" -Technical Analysis Reports Module - -This module handles the generation of technical analysis reports using yfinance data and pandas_ta for indicators. -""" - -from typing import Dict, Any, List, Optional -import yfinance as yf -import pandas as pd -import pandas_ta as ta -import plotly.graph_objects as go -from datetime import datetime, timedelta -from loguru import logger -from ...utils import validate_symbol -from ...ai_financial_dashboard import get_dashboard - -class TechnicalAnalysis: - def __init__(self, symbol: str, timeframe: str = "1d", period: str = "1y"): - """ - Initialize Technical Analysis. - - Args: - symbol (str): Stock symbol to analyze - timeframe (str): Data timeframe (1m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo) - period (str): Data period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max) - """ - logger.info(f"Initializing Technical Analysis for {symbol} with timeframe {timeframe} and period {period}") - self.symbol = symbol - self.timeframe = timeframe - self.period = period - self.data = None - self.indicators = {} - self.stock = yf.Ticker(symbol) - - def fetch_data(self) -> None: - """Fetch historical price data using yfinance""" - try: - logger.info(f"Fetching historical data for {self.symbol}") - # Get historical data - self.data = self.stock.history(period=self.period, interval=self.timeframe) - logger.debug(f"Retrieved {len(self.data)} data points") - - # Get additional info - logger.info("Fetching company information") - self.info = self.stock.info - - # Calculate basic metrics - logger.debug("Calculating basic metrics") - self.data['Returns'] = self.data['Close'].pct_change() - self.data['Volatility'] = self.data['Returns'].rolling(window=20).std() - - logger.success(f"Successfully fetched data for {self.symbol}") - - except Exception as e: - logger.error(f"Error fetching data for {self.symbol}: {str(e)}") - raise ValueError(f"Error fetching data for {self.symbol}: {str(e)}") - - def calculate_indicators(self) -> None: - """Calculate technical indicators using pandas_ta""" - if self.data is None: - logger.error("Data not fetched. Call fetch_data() first.") - raise ValueError("Data not fetched. Call fetch_data() first.") - - logger.info("Calculating technical indicators") - - # Moving Averages - logger.debug("Calculating Moving Averages") - self.indicators['sma_20'] = self.data.ta.sma(length=20) - self.indicators['sma_50'] = self.data.ta.sma(length=50) - self.indicators['sma_200'] = self.data.ta.sma(length=200) - self.indicators['ema_20'] = self.data.ta.ema(length=20) - - # RSI - logger.debug("Calculating RSI") - self.indicators['rsi'] = self.data.ta.rsi() - - # MACD - logger.debug("Calculating MACD") - macd = self.data.ta.macd() - self.indicators['macd'] = macd['MACD_12_26_9'] - self.indicators['macd_signal'] = macd['MACDs_12_26_9'] - self.indicators['macd_hist'] = macd['MACDh_12_26_9'] - - # Bollinger Bands - logger.debug("Calculating Bollinger Bands") - bbands = self.data.ta.bbands() - self.indicators['bb_upper'] = bbands['BBU_20_2.0'] - self.indicators['bb_middle'] = bbands['BBM_20_2.0'] - self.indicators['bb_lower'] = bbands['BBL_20_2.0'] - - # Volume Analysis - logger.debug("Calculating Volume indicators") - self.indicators['volume_sma'] = self.data['Volume'].rolling(window=20).mean() - self.indicators['obv'] = self.data.ta.obv() - - # Additional Indicators - logger.debug("Calculating additional indicators") - self.indicators['stoch'] = self.data.ta.stoch() - self.indicators['adx'] = self.data.ta.adx() - self.indicators['atr'] = self.data.ta.atr() - - logger.success("Successfully calculated all technical indicators") - - def identify_patterns(self) -> List[Dict[str, Any]]: - """Identify chart patterns""" - logger.info("Identifying chart patterns") - patterns = [] - - # Candlestick Patterns - if len(self.data) >= 3: - logger.debug("Analyzing candlestick patterns") - # Doji - doji = self.data.ta.cdl_doji() - if doji['CDL_DOJI'].iloc[-1] != 0: - logger.debug("Doji pattern detected") - patterns.append({ - 'type': 'doji', - 'date': self.data.index[-1], - 'significance': 'neutral' - }) - - # Engulfing - engulfing = self.data.ta.cdl_engulfing() - if engulfing['CDL_ENGULFING'].iloc[-1] != 0: - logger.debug("Engulfing pattern detected") - patterns.append({ - 'type': 'engulfing', - 'date': self.data.index[-1], - 'significance': 'bullish' if engulfing['CDL_ENGULFING'].iloc[-1] > 0 else 'bearish' - }) - - logger.info(f"Identified {len(patterns)} patterns") - return patterns - - def find_support_resistance(self) -> Dict[str, List[float]]: - """Find support and resistance levels using price action""" - logger.info("Finding support and resistance levels") - levels = { - 'support': [], - 'resistance': [] - } - - # Use recent price action to identify levels - recent_data = self.data.tail(100) - logger.debug(f"Analyzing {len(recent_data)} recent data points for S/R levels") - - # Find local minima and maxima - for i in range(2, len(recent_data) - 2): - # Support level - if (recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i-1] and - recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i-2] and - recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i+1] and - recent_data['Low'].iloc[i] < recent_data['Low'].iloc[i+2]): - levels['support'].append(recent_data['Low'].iloc[i]) - - # Resistance level - if (recent_data['High'].iloc[i] > recent_data['High'].iloc[i-1] and - recent_data['High'].iloc[i] > recent_data['High'].iloc[i-2] and - recent_data['High'].iloc[i] > recent_data['High'].iloc[i+1] and - recent_data['High'].iloc[i] > recent_data['High'].iloc[i+2]): - levels['resistance'].append(recent_data['High'].iloc[i]) - - # Remove duplicates and sort - levels['support'] = sorted(list(set(levels['support']))) - levels['resistance'] = sorted(list(set(levels['resistance']))) - - logger.info(f"Found {len(levels['support'])} support and {len(levels['resistance'])} resistance levels") - return levels - - def generate_chart(self) -> go.Figure: - """Generate interactive chart using plotly""" - logger.info("Generating interactive chart") - fig = go.Figure() - - # Candlestick chart - logger.debug("Adding candlestick chart") - fig.add_trace(go.Candlestick( - x=self.data.index, - open=self.data['Open'], - high=self.data['High'], - low=self.data['Low'], - close=self.data['Close'], - name='Price' - )) - - # Moving Averages - logger.debug("Adding moving averages") - fig.add_trace(go.Scatter( - x=self.data.index, - y=self.indicators['sma_20'], - name='SMA 20', - line=dict(color='blue') - )) - - fig.add_trace(go.Scatter( - x=self.data.index, - y=self.indicators['sma_50'], - name='SMA 50', - line=dict(color='orange') - )) - - # Bollinger Bands - logger.debug("Adding Bollinger Bands") - fig.add_trace(go.Scatter( - x=self.data.index, - y=self.indicators['bb_upper'], - name='BB Upper', - line=dict(color='gray', dash='dash') - )) - - fig.add_trace(go.Scatter( - x=self.data.index, - y=self.indicators['bb_lower'], - name='BB Lower', - line=dict(color='gray', dash='dash'), - fill='tonexty' - )) - - # Volume - logger.debug("Adding volume bars") - fig.add_trace(go.Bar( - x=self.data.index, - y=self.data['Volume'], - name='Volume', - marker_color='rgba(0,0,255,0.3)' - )) - - # Layout - logger.debug("Setting chart layout") - fig.update_layout( - title=f'{self.symbol} Technical Analysis', - yaxis_title='Price', - xaxis_title='Date', - template='plotly_dark' - ) - - logger.success("Successfully generated chart") - return fig - - def _generate_summary(self) -> Dict[str, Any]: - """Generate summary of technical analysis""" - logger.info("Generating analysis summary") - current_price = self.data['Close'].iloc[-1] - sma_20 = self.indicators['sma_20'].iloc[-1] - sma_50 = self.indicators['sma_50'].iloc[-1] - rsi = self.indicators['rsi'].iloc[-1] - - summary = { - 'current_price': current_price, - 'price_change': self.data['Returns'].iloc[-1] * 100, - 'trend': 'bullish' if current_price > sma_20 > sma_50 else 'bearish', - 'rsi_signal': 'overbought' if rsi > 70 else 'oversold' if rsi < 30 else 'neutral', - 'volatility': self.data['Volatility'].iloc[-1], - 'volume_trend': 'increasing' if self.data['Volume'].iloc[-1] > self.indicators['volume_sma'].iloc[-1] else 'decreasing' - } - - logger.debug(f"Analysis summary: {summary}") - return summary - - def generate_report(self) -> Dict[str, Any]: - """Generate comprehensive technical analysis report""" - logger.info(f"Generating comprehensive report for {self.symbol}") - - self.fetch_data() - self.calculate_indicators() - patterns = self.identify_patterns() - levels = self.find_support_resistance() - chart = self.generate_chart() - summary = self._generate_summary() - - report = { - 'symbol': self.symbol, - 'timestamp': datetime.now(), - 'company_info': self.info, - 'indicators': self.indicators, - 'patterns': patterns, - 'levels': levels, - 'chart': chart, - 'summary': summary - } - - logger.success(f"Successfully generated report for {self.symbol}") - return report - -def generate_ta_report(symbol: str) -> Dict[str, Any]: - """ - Generate a technical analysis report for the given symbol. - - Args: - symbol (str): Stock symbol to analyze - - Returns: - Dict[str, Any]: Technical analysis report - """ - logger.info(f"Generating technical analysis report for {symbol}") - - if not validate_symbol(symbol): - logger.error(f"Invalid symbol provided: {symbol}") - raise ValueError("Invalid symbol provided") - - try: - analysis = TechnicalAnalysis(symbol) - report = analysis.generate_report() - - # Add to dashboard's recent reports - dashboard = get_dashboard() - dashboard.add_recent_report("technical_analysis", symbol, report) - - logger.success(f"Successfully completed technical analysis for {symbol}") - return report - - except Exception as e: - logger.error(f"Error generating technical analysis report for {symbol}: {str(e)}") - raise \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/utils/__init__.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/utils/__init__.py deleted file mode 100644 index 285be4bb..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/utils/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Utility functions and helpers for the AI Finance Report Generator. -""" - -from typing import Dict, List, Any -import logging - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) - -logger = logging.getLogger(__name__) - -def validate_symbol(symbol: str) -> bool: - """ - Validate if the given symbol is in correct format. - - Args: - symbol (str): Stock symbol to validate - - Returns: - bool: True if valid, False otherwise - """ - if not isinstance(symbol, str): - return False - return len(symbol.strip()) > 0 - -def format_currency(value: float) -> str: - """ - Format number as currency. - - Args: - value (float): Number to format - - Returns: - str: Formatted currency string - """ - return f"${value:,.2f}" - -def get_feature_status(feature_name: str) -> Dict[str, Any]: - """ - Get the status of a feature. - - Args: - feature_name (str): Name of the feature - - Returns: - Dict[str, Any]: Feature status information - """ - # This will be expanded as we implement more features - implemented_features = { - "technical_analysis": True, - "options_analysis": True, - } - - return { - "name": feature_name, - "implemented": implemented_features.get(feature_name, False), - "coming_soon": not implemented_features.get(feature_name, False) - } \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_finance_report_generator/utils/storage.py b/ToBeMigrated/ai_writers/ai_finance_report_generator/utils/storage.py deleted file mode 100644 index 52e81b1e..00000000 --- a/ToBeMigrated/ai_writers/ai_finance_report_generator/utils/storage.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -Storage Module for AI Finance Report Generator - -This module handles the persistence of user preferences and recent reports using JSON files. -""" - -import json -import os -from typing import Dict, List, Any, Optional -from datetime import datetime -from pathlib import Path - -class StorageManager: - """Manages storage operations for user preferences and recent reports.""" - - def __init__(self, base_dir: Optional[str] = None): - """ - Initialize the storage manager. - - Args: - base_dir (Optional[str]): Base directory for storage files - """ - if base_dir is None: - # Use user's home directory by default - self.base_dir = Path.home() / ".ai_finance" - else: - self.base_dir = Path(base_dir) - - # Create storage directory if it doesn't exist - self.base_dir.mkdir(parents=True, exist_ok=True) - - # Define file paths - self.prefs_file = self.base_dir / "preferences.json" - self.reports_file = self.base_dir / "recent_reports.json" - - # Initialize files if they don't exist - self._initialize_storage() - - def _initialize_storage(self) -> None: - """Initialize storage files if they don't exist.""" - if not self.prefs_file.exists(): - self._save_preferences({}) - - if not self.reports_file.exists(): - self._save_reports([]) - - def _save_preferences(self, preferences: Dict[str, Any]) -> None: - """ - Save user preferences to file. - - Args: - preferences (Dict[str, Any]): User preferences to save - """ - with open(self.prefs_file, 'w') as f: - json.dump(preferences, f, indent=4) - - def _load_preferences(self) -> Dict[str, Any]: - """ - Load user preferences from file. - - Returns: - Dict[str, Any]: User preferences - """ - try: - with open(self.prefs_file, 'r') as f: - return json.load(f) - except (json.JSONDecodeError, FileNotFoundError): - return {} - - def _save_reports(self, reports: List[Dict[str, Any]]) -> None: - """ - Save recent reports to file. - - Args: - reports (List[Dict[str, Any]]): Recent reports to save - """ - with open(self.reports_file, 'w') as f: - json.dump(reports, f, indent=4) - - def _load_reports(self) -> List[Dict[str, Any]]: - """ - Load recent reports from file. - - Returns: - List[Dict[str, Any]]: Recent reports - """ - try: - with open(self.reports_file, 'r') as f: - return json.load(f) - except (json.JSONDecodeError, FileNotFoundError): - return [] - - def save_user_preferences(self, preferences: Dict[str, Any]) -> None: - """ - Save user preferences. - - Args: - preferences (Dict[str, Any]): User preferences to save - """ - self._save_preferences(preferences) - - def load_user_preferences(self) -> Dict[str, Any]: - """ - Load user preferences. - - Returns: - Dict[str, Any]: User preferences - """ - return self._load_preferences() - - def save_recent_reports(self, reports: List[Dict[str, Any]]) -> None: - """ - Save recent reports. - - Args: - reports (List[Dict[str, Any]]): Recent reports to save - """ - # Convert datetime objects to ISO format strings - serialized_reports = [] - for report in reports: - serialized_report = report.copy() - if isinstance(report.get('timestamp'), datetime): - serialized_report['timestamp'] = report['timestamp'].isoformat() - serialized_reports.append(serialized_report) - - self._save_reports(serialized_reports) - - def load_recent_reports(self) -> List[Dict[str, Any]]: - """ - Load recent reports. - - Returns: - List[Dict[str, Any]]: Recent reports with datetime objects - """ - reports = self._load_reports() - - # Convert ISO format strings back to datetime objects - for report in reports: - if isinstance(report.get('timestamp'), str): - report['timestamp'] = datetime.fromisoformat(report['timestamp']) - - return reports - - def clear_storage(self) -> None: - """Clear all stored data.""" - self._save_preferences({}) - self._save_reports([]) - - def backup_storage(self, backup_dir: Optional[str] = None) -> None: - """ - Create a backup of the storage files. - - Args: - backup_dir (Optional[str]): Directory to store backup files - """ - if backup_dir is None: - backup_dir = self.base_dir / "backups" - else: - backup_dir = Path(backup_dir) - - backup_dir.mkdir(parents=True, exist_ok=True) - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - - # Backup preferences - if self.prefs_file.exists(): - backup_prefs = backup_dir / f"preferences_{timestamp}.json" - with open(self.prefs_file, 'r') as src, open(backup_prefs, 'w') as dst: - dst.write(src.read()) - - # Backup reports - if self.reports_file.exists(): - backup_reports = backup_dir / f"recent_reports_{timestamp}.json" - with open(self.reports_file, 'r') as src, open(backup_reports, 'w') as dst: - dst.write(src.read()) - - def restore_from_backup(self, backup_file: str) -> None: - """ - Restore storage from a backup file. - - Args: - backup_file (str): Path to the backup file - """ - backup_path = Path(backup_file) - if not backup_path.exists(): - raise FileNotFoundError(f"Backup file not found: {backup_file}") - - # Determine which type of backup file it is - if "preferences" in backup_path.name: - with open(backup_path, 'r') as src, open(self.prefs_file, 'w') as dst: - dst.write(src.read()) - elif "recent_reports" in backup_path.name: - with open(backup_path, 'r') as src, open(self.reports_file, 'w') as dst: - dst.write(src.read()) - else: - raise ValueError(f"Unknown backup file type: {backup_file}") - -def get_storage_manager(base_dir: Optional[str] = None) -> StorageManager: - """ - Get a storage manager instance. - - Args: - base_dir (Optional[str]): Base directory for storage files - - Returns: - StorageManager: Storage manager instance - """ - return StorageManager(base_dir) \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_illustrator/README.md b/ToBeMigrated/ai_writers/ai_story_illustrator/README.md deleted file mode 100644 index ad1afbe8..00000000 --- a/ToBeMigrated/ai_writers/ai_story_illustrator/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# AI Story Illustrator - -The AI Story Illustrator is a powerful tool that generates beautiful illustrations for stories using Google's Gemini AI. This module allows users to input stories via text, file upload, or URL, and automatically generates appropriate illustrations for different scenes in the story. - -## Features - -- **Multiple Input Methods**: Input stories via direct text entry, file upload, or URL extraction -- **Intelligent Scene Segmentation**: Automatically divides stories into logical segments for illustration -- **Customizable Illustration Styles**: Choose from various artistic styles or define your own -- **Scene Element Extraction**: Analyzes story segments to identify key visual elements -- **Multiple Export Options**: Export as PDF storybook or ZIP archive of individual images -- **Customizable Aspect Ratios**: Support for different image dimensions (16:9, 4:3, 1:1) -- **Advanced Settings**: Control the number of segments to illustrate and other parameters - -## Usage - -The Story Illustrator is integrated into the Alwrity platform and can be accessed through the main interface. The workflow consists of three main steps: - -1. **Story Input**: Enter your story text, upload a file, or provide a URL -2. **Illustration Settings**: Configure the style, aspect ratio, and other parameters -3. **Generate & Export**: Generate illustrations for all or individual segments and export the results - -## Technical Details - -### Dependencies - -- Streamlit: For the user interface -- Gemini AI: For image generation -- BeautifulSoup: For URL text extraction -- ReportLab: For PDF generation (optional) -- PIL: For image processing - -### Key Functions - -- `segment_story()`: Divides a story into logical segments for illustration -- `extract_scene_elements()`: Analyzes story segments to identify key visual elements -- `generate_illustration_prompt()`: Creates detailed prompts for the AI image generator -- `create_illustration()`: Generates an illustration for a story segment -- `create_storybook_pdf()`: Combines story text and illustrations into a PDF -- `create_zip_archive()`: Creates a ZIP archive of individual illustrations - -## Example - -```python -from lib.ai_writers.ai_story_illustrator.story_illustrator import write_story_illustrator - -# Run the Story Illustrator app -write_story_illustrator() -``` - -## Best Practices - -- **Provide Clear Segments**: The system works best with stories that have clear scene transitions -- **Be Specific with Styles**: More specific style descriptions yield better results -- **Balance Text and Images**: For best results, aim for segments of 100-500 words per illustration -- **Review and Regenerate**: If an illustration doesn't capture the scene well, use the regenerate option - -## Future Enhancements - -- Support for more export formats (EPUB, HTML) -- Enhanced character consistency across illustrations -- Animation options for digital storytelling -- Voice narration integration -- Custom character design options - -## Troubleshooting - -- If illustrations are not generating, check your internet connection and API access -- If PDF export fails, ensure ReportLab is installed (`pip install reportlab`) -- If URL extraction fails, try copying the text manually -- For large stories, consider processing in smaller batches - -## Credits - -This module uses Google's Gemini AI for image generation and leverages various open-source libraries for text processing and document generation. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_illustrator/__init__.py b/ToBeMigrated/ai_writers/ai_story_illustrator/__init__.py deleted file mode 100644 index cd765890..00000000 --- a/ToBeMigrated/ai_writers/ai_story_illustrator/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -AI Story Illustrator module for generating illustrations for stories using AI. -""" - -from .story_illustrator import write_story_illustrator - -__all__ = ['write_story_illustrator'] \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_illustrator/story_illustrator.py b/ToBeMigrated/ai_writers/ai_story_illustrator/story_illustrator.py deleted file mode 100644 index 189e9430..00000000 --- a/ToBeMigrated/ai_writers/ai_story_illustrator/story_illustrator.py +++ /dev/null @@ -1,727 +0,0 @@ -""" -AI Story Illustrator - Generate illustrations for stories using Gemini AI - -This module provides functionality to generate illustrations for stories using Google's Gemini AI. -Users can input stories via text, file upload, or URL, and the system will generate appropriate -illustrations for different scenes in the story. - -Based on: https://github.com/google-gemini/cookbook/blob/main/examples/Book_illustration.ipynb -""" - -import streamlit as st -import os -import re -import time -import tempfile -import requests -from pathlib import Path -import io -import base64 -import json -import uuid -import logging -from urllib.parse import urlparse -from bs4 import BeautifulSoup -import zipfile - -# Configure logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger('story_illustrator') - -# Constants -MAX_STORY_LENGTH = 10000 # Maximum story length in characters -MIN_SEGMENT_LENGTH = 100 # Minimum segment length for illustration -MAX_SEGMENTS = 20 # Maximum number of segments to illustrate -DEFAULT_STYLE = "digital art" # Default illustration style -DEFAULT_ASPECT_RATIO = "16:9" # Default aspect ratio - - -def extract_text_from_url(url): - """Extract text content from a URL.""" - try: - headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' - } - response = requests.get(url, headers=headers, timeout=10) - response.raise_for_status() - - soup = BeautifulSoup(response.content, 'html.parser') - - # Remove script and style elements - for script in soup(["script", "style"]): - script.extract() - - # Get text - text = soup.get_text(separator='\\n') - - # Break into lines and remove leading and trailing space on each - lines = (line.strip() for line in text.splitlines()) - # Break multi-headlines into a line each - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - # Drop blank lines - text = '\\n'.join(chunk for chunk in chunks if chunk) - - return text - except Exception as e: - logger.error(f"Error extracting text from URL: {e}") - return None - - -def segment_story(story_text, min_segment_length=MIN_SEGMENT_LENGTH, max_segments=MAX_SEGMENTS): - """ - Segment a story into logical parts for illustration. - Uses paragraph breaks, scene changes, and other indicators to create segments. - """ - # Clean up the text - story_text = story_text.strip() - - # Split by paragraphs first - paragraphs = re.split(r'\\n\s*\\n', story_text) - - # Initialize segments - segments = [] - current_segment = "" - - for paragraph in paragraphs: - # Skip empty paragraphs - if not paragraph.strip(): - continue - - # If adding this paragraph would make the segment too long, start a new segment - if len(current_segment) + len(paragraph) > 1000: # Limit segment size - if current_segment: - segments.append(current_segment.strip()) - current_segment = paragraph - else: - # Add paragraph to current segment - if current_segment: - current_segment += "\\n\\n" + paragraph - else: - current_segment = paragraph - - # Add the last segment if it exists - if current_segment: - segments.append(current_segment.strip()) - - # Combine very short segments - i = 0 - while i < len(segments) - 1: - if len(segments[i]) < min_segment_length: - segments[i] += "\\n\\n" + segments[i+1] - segments.pop(i+1) - else: - i += 1 - - # Limit the number of segments - if len(segments) > max_segments: - # Combine segments to reduce the total number - new_segments = [] - segment_size = len(segments) / max_segments - - for i in range(max_segments): - start_idx = int(i * segment_size) - end_idx = int((i + 1) * segment_size) - combined_segment = "\\n\\n".join(segments[start_idx:end_idx]) - new_segments.append(combined_segment) - - segments = new_segments - - return segments - - -def extract_scene_elements(segment): - """ - Extract key scene elements from a story segment using LLM. - This helps create more accurate illustration prompts. - """ - from ...gpt_providers.text_generation.main_text_generation import llm_text_gen - - prompt = f""" - Analyze the following story segment and extract key visual elements for an illustration: - - {segment} - - Please provide: - 1. Main characters present (with brief visual descriptions) - 2. Setting/location details - 3. Key action or emotional moment to illustrate - 4. Important objects or props - 5. Time of day and lighting - 6. Weather or atmospheric conditions (if applicable) - - Format your response as JSON with these keys: "characters", "setting", "key_moment", "objects", "lighting", "atmosphere" - """ - - try: - response = llm_text_gen(prompt) - - # Try to extract JSON from the response - try: - # Find JSON content between triple backticks if present - json_match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL) - if json_match: - json_str = json_match.group(1) - else: - # Otherwise try to parse the whole response as JSON - json_str = response - - scene_elements = json.loads(json_str) - return scene_elements - except json.JSONDecodeError: - # If JSON parsing fails, extract information using regex - characters = re.search(r'"characters":\s*"([^"]*)"', response) - setting = re.search(r'"setting":\s*"([^"]*)"', response) - - return { - "characters": characters.group(1) if characters else "", - "setting": setting.group(1) if setting else "", - "key_moment": "", - "objects": "", - "lighting": "", - "atmosphere": "" - } - except Exception as e: - logger.error(f"Error extracting scene elements: {e}") - return { - "characters": "", - "setting": "", - "key_moment": "", - "objects": "", - "lighting": "", - "atmosphere": "" - } - - -def generate_illustration_prompt(segment, style, characters=None, setting=None): - """ - Generate a prompt for the illustration based on the segment content. - - Args: - segment: The story segment to illustrate - style: The artistic style for the illustration - characters: Optional character descriptions - setting: Optional setting description - - Returns: - A prompt string for the image generation model - """ - # Create a base prompt - base_prompt = f""" - Create a detailed illustration for the following story segment in {style} style: - - {segment[:500]} # Limit segment length for prompt - - The illustration should capture the key elements, mood, and action of this scene. - """ - - # Add character information if provided - if characters: - base_prompt += f"\\n\\nThe main characters in this scene are: {characters}" - - # Add setting information if provided - if setting: - base_prompt += f"\\n\\nThe setting is: {setting}" - - # Add style-specific instructions - if "watercolor" in style.lower(): - base_prompt += "\\n\\nUse soft, flowing watercolor techniques with visible brush strokes and color blending." - elif "digital art" in style.lower(): - base_prompt += "\\n\\nCreate a polished digital illustration with clean lines and vibrant colors." - elif "pencil sketch" in style.lower(): - base_prompt += "\\n\\nUse pencil sketch techniques with visible hatching, shading, and line work." - - # Add final quality instructions - base_prompt += """ - - Make the illustration: - - Visually engaging and detailed - - Appropriate for a storybook - - Focused on the main action or emotion of the scene - - With good composition and visual storytelling - """ - - return base_prompt.strip() - - -def create_illustration(segment, style, aspect_ratio="16:9"): - """ - Create an illustration for a story segment. - - Args: - segment: The story segment to illustrate - style: The artistic style for the illustration - aspect_ratio: The aspect ratio for the illustration - - Returns: - Path to the generated image - """ - # Import here to avoid circular imports - from ...gpt_providers.text_to_image_generation.gen_gemini_images import generate_gemini_image - - # Extract scene elements to enhance the prompt - scene_elements = extract_scene_elements(segment) - - # Create a detailed prompt for the illustration - prompt = generate_illustration_prompt( - segment, - style, - characters=scene_elements.get("characters", ""), - setting=scene_elements.get("setting", "") - ) - - # Add key elements to the prompt - key_moment = scene_elements.get("key_moment", "") - objects = scene_elements.get("objects", "") - lighting = scene_elements.get("lighting", "") - atmosphere = scene_elements.get("atmosphere", "") - - if key_moment: - prompt += f"\\n\\nFocus on this key moment: {key_moment}" - - if objects: - prompt += f"\\n\\nInclude these important objects: {objects}" - - if lighting: - prompt += f"\\n\\nThe lighting is: {lighting}" - - if atmosphere: - prompt += f"\\n\\nThe atmosphere/weather is: {atmosphere}" - - # Generate the illustration - try: - # Parse aspect ratio - if aspect_ratio == "16:9": - width, height = 16, 9 - elif aspect_ratio == "4:3": - width, height = 4, 3 - elif aspect_ratio == "1:1": - width, height = 1, 1 - else: - width, height = 16, 9 # Default - - # Generate image using Gemini - image_path = generate_gemini_image( - prompt=prompt, - style=style.lower() if style else None, - aspect_ratio=aspect_ratio - ) - - return image_path - except Exception as e: - logger.error(f"Error creating illustration: {e}") - return None - - -def create_storybook_pdf(segments, illustrations, title, author, output_path): - """ - Create a PDF storybook with text and illustrations. - - Args: - segments: List of story segments - illustrations: List of paths to illustrations - title: Book title - author: Book author - output_path: Path to save the PDF - - Returns: - Path to the created PDF - """ - try: - from reportlab.lib.pagesizes import letter, A4 - from reportlab.lib import colors - from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage, PageBreak - from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle - from reportlab.lib.units import inch - - # Create a PDF document - doc = SimpleDocTemplate(output_path, pagesize=A4) - story = [] - - # Get styles - styles = getSampleStyleSheet() - title_style = styles['Title'] - author_style = styles['Normal'] - author_style.alignment = 1 # Center alignment - normal_style = styles['Normal'] - - # Add title page - story.append(Paragraph(title, title_style)) - story.append(Spacer(1, 0.5*inch)) - story.append(Paragraph(f"by {author}", author_style)) - story.append(PageBreak()) - - # Add content pages - for i, (segment, illustration_path) in enumerate(zip(segments, illustrations)): - if illustration_path and os.path.exists(illustration_path): - # Add illustration - img = ReportLabImage(illustration_path, width=6*inch, height=4*inch) - story.append(img) - story.append(Spacer(1, 0.25*inch)) - - # Add text - for paragraph in segment.split('\\n\\n'): - if paragraph.strip(): - story.append(Paragraph(paragraph, normal_style)) - story.append(Spacer(1, 0.1*inch)) - - # Add page break between segments - if i < len(segments) - 1: - story.append(PageBreak()) - - # Build the PDF - doc.build(story) - return output_path - except Exception as e: - logger.error(f"Error creating PDF: {e}") - return None - - -def create_zip_archive(files, output_path): - """ - Create a ZIP archive containing the provided files. - - Args: - files: Dictionary of {filename: file_path} to include in the archive - output_path: Path to save the ZIP file - - Returns: - Path to the created ZIP file - """ - try: - with zipfile.ZipFile(output_path, 'w') as zipf: - for filename, file_path in files.items(): - if os.path.exists(file_path): - zipf.write(file_path, arcname=filename) - return output_path - except Exception as e: - logger.error(f"Error creating ZIP archive: {e}") - return None - - -def write_story_illustrator(): - """Main function for the Story Illustrator Streamlit app.""" - st.title("AI Story Illustrator") - st.write("Generate beautiful illustrations for your stories using AI") - - # Create tabs for different sections - tab1, tab2, tab3 = st.tabs(["Story Input", "Illustration Settings", "Generate & Export"]) - - # Initialize session state variables if they don't exist - if "story_text" not in st.session_state: - st.session_state.story_text = "" - if "segments" not in st.session_state: - st.session_state.segments = [] - if "illustrations" not in st.session_state: - st.session_state.illustrations = [] - if "book_title" not in st.session_state: - st.session_state.book_title = "" - if "book_author" not in st.session_state: - st.session_state.book_author = "" - if "illustration_style" not in st.session_state: - st.session_state.illustration_style = DEFAULT_STYLE - if "aspect_ratio" not in st.session_state: - st.session_state.aspect_ratio = DEFAULT_ASPECT_RATIO - if "temp_files" not in st.session_state: - st.session_state.temp_files = [] - - # Tab 1: Story Input - with tab1: - st.header("Step 1: Input Your Story") - - # Input method selection - input_method = st.radio( - "Choose input method:", - ["Text Input", "File Upload", "URL"] - ) - - if input_method == "Text Input": - st.session_state.story_text = st.text_area( - "Enter your story text:", - value=st.session_state.story_text, - height=300, - max_chars=MAX_STORY_LENGTH, - help="Enter the story text you want to illustrate (max 10,000 characters)" - ) - - elif input_method == "File Upload": - uploaded_file = st.file_uploader("Upload a text file:", type=["txt", "md"]) - if uploaded_file is not None: - try: - st.session_state.story_text = uploaded_file.getvalue().decode("utf-8") - st.success(f"Successfully loaded file: {uploaded_file.name}") - st.text_area("Preview:", value=st.session_state.story_text[:500] + "...", height=200, disabled=True) - except Exception as e: - st.error(f"Error reading file: {e}") - - elif input_method == "URL": - url = st.text_input("Enter URL containing the story:") - if url: - if st.button("Extract Text from URL"): - with st.spinner("Extracting text from URL..."): - extracted_text = extract_text_from_url(url) - if extracted_text: - st.session_state.story_text = extracted_text - st.success("Successfully extracted text from URL") - st.text_area("Preview:", value=st.session_state.story_text[:500] + "...", height=200, disabled=True) - else: - st.error("Failed to extract text from URL") - - # Book metadata - st.subheader("Book Metadata") - col1, col2 = st.columns(2) - with col1: - st.session_state.book_title = st.text_input("Book Title:", value=st.session_state.book_title) - with col2: - st.session_state.book_author = st.text_input("Author:", value=st.session_state.book_author) - - # Process story into segments - if st.session_state.story_text: - if st.button("Process Story into Segments"): - with st.spinner("Processing story into segments..."): - st.session_state.segments = segment_story(st.session_state.story_text) - st.success(f"Story processed into {len(st.session_state.segments)} segments") - - # Initialize illustrations list with None values - st.session_state.illustrations = [None] * len(st.session_state.segments) - - # Display segments - st.subheader("Story Segments") - for i, segment in enumerate(st.session_state.segments): - with st.expander(f"Segment {i+1}"): - st.write(segment) - - # Tab 2: Illustration Settings - with tab2: - st.header("Step 2: Configure Illustration Settings") - - # Style selection - st.subheader("Illustration Style") - style_options = [ - "Digital Art", - "Watercolor Painting", - "Pencil Sketch", - "Oil Painting", - "Cartoon", - "Anime", - "3D Render", - "Pixel Art", - "Children's Book Illustration", - "Comic Book Style", - "Fantasy Art", - "Realistic" - ] - - st.session_state.illustration_style = st.selectbox( - "Choose an illustration style:", - style_options, - index=style_options.index(st.session_state.illustration_style) if st.session_state.illustration_style in style_options else 0 - ) - - # Custom style input - use_custom_style = st.checkbox("Use custom style") - if use_custom_style: - custom_style = st.text_input("Describe your custom style:", - placeholder="e.g., Impressionist painting with vibrant colors and visible brushstrokes") - if custom_style: - st.session_state.illustration_style = custom_style - - # Display style examples - st.info("๐Ÿ’ก The style you choose will significantly impact the look and feel of your illustrations.") - - # Aspect ratio selection - st.subheader("Image Settings") - aspect_ratio_options = { - "16:9 (Widescreen)": "16:9", - "4:3 (Standard)": "4:3", - "1:1 (Square)": "1:1" - } - - selected_ratio = st.selectbox( - "Choose aspect ratio:", - list(aspect_ratio_options.keys()), - index=list(aspect_ratio_options.values()).index(st.session_state.aspect_ratio) if st.session_state.aspect_ratio in aspect_ratio_options.values() else 0 - ) - st.session_state.aspect_ratio = aspect_ratio_options[selected_ratio] - - # Advanced settings - with st.expander("Advanced Settings"): - st.slider("Number of segments to illustrate:", 1, - max(len(st.session_state.segments), 1) if st.session_state.segments else 1, - min(len(st.session_state.segments), MAX_SEGMENTS) if st.session_state.segments else 1, - key="num_segments_to_illustrate") - - st.checkbox("Generate cover image", value=True, key="generate_cover") - - st.checkbox("Add text to illustrations", value=False, key="add_text_to_illustrations") - - # Tab 3: Generate & Export - with tab3: - st.header("Step 3: Generate Illustrations & Export") - - if not st.session_state.segments: - st.warning("Please process your story into segments in Step 1 before generating illustrations.") - else: - # Generate illustrations - st.subheader("Generate Illustrations") - - num_segments = min(len(st.session_state.segments), st.session_state.get("num_segments_to_illustrate", len(st.session_state.segments))) - - if st.button("Generate All Illustrations"): - with st.spinner(f"Generating {num_segments} illustrations... This may take a while."): - progress_bar = st.progress(0) - - for i in range(num_segments): - # Update progress - progress_bar.progress((i) / num_segments) - st.write(f"Generating illustration {i+1} of {num_segments}...") - - # Generate illustration - illustration_path = create_illustration( - st.session_state.segments[i], - st.session_state.illustration_style, - st.session_state.aspect_ratio - ) - - # Store the illustration path - if illustration_path: - st.session_state.illustrations[i] = illustration_path - st.session_state.temp_files.append(illustration_path) - - # Complete progress - progress_bar.progress(1.0) - st.success(f"Generated {num_segments} illustrations!") - - # Generate individual illustrations - st.subheader("Generate Individual Illustrations") - - for i in range(num_segments): - col1, col2 = st.columns([3, 1]) - - with col1: - with st.expander(f"Segment {i+1}"): - st.write(st.session_state.segments[i][:300] + "..." if len(st.session_state.segments[i]) > 300 else st.session_state.segments[i]) - - with col2: - if st.button(f"Generate #{i+1}", key=f"gen_btn_{i}"): - with st.spinner(f"Generating illustration {i+1}..."): - illustration_path = create_illustration( - st.session_state.segments[i], - st.session_state.illustration_style, - st.session_state.aspect_ratio - ) - - if illustration_path: - st.session_state.illustrations[i] = illustration_path - st.session_state.temp_files.append(illustration_path) - st.success(f"Generated illustration {i+1}!") - - # Display generated illustrations - st.subheader("Preview Illustrations") - - if any(st.session_state.illustrations): - for i, illustration_path in enumerate(st.session_state.illustrations[:num_segments]): - if illustration_path and os.path.exists(illustration_path): - with st.expander(f"Illustration {i+1}"): - st.image(illustration_path, caption=f"Illustration for Segment {i+1}", use_column_width=True) - - # Regenerate button - if st.button(f"Regenerate", key=f"regen_btn_{i}"): - with st.spinner(f"Regenerating illustration {i+1}..."): - new_illustration_path = create_illustration( - st.session_state.segments[i], - st.session_state.illustration_style, - st.session_state.aspect_ratio - ) - - if new_illustration_path: - st.session_state.illustrations[i] = new_illustration_path - st.session_state.temp_files.append(new_illustration_path) - st.rerun() - else: - st.info("No illustrations generated yet. Click 'Generate All Illustrations' or generate individual illustrations.") - - # Export options - st.subheader("Export Options") - - if any(st.session_state.illustrations): - export_format = st.radio( - "Export format:", - ["PDF Storybook", "Individual Images (ZIP)", "Both"] - ) - - if st.button("Export"): - with st.spinner("Preparing export..."): - # Create temporary directory for exports - with tempfile.TemporaryDirectory() as temp_dir: - # Filter out None values from illustrations - valid_illustrations = [path for path in st.session_state.illustrations[:num_segments] if path and os.path.exists(path)] - valid_segments = st.session_state.segments[:len(valid_illustrations)] - - # Prepare filenames - safe_title = "".join(c if c.isalnum() else "_" for c in st.session_state.book_title) if st.session_state.book_title else "story" - timestamp = int(time.time()) - - # Export as PDF - if export_format in ["PDF Storybook", "Both"]: - pdf_path = os.path.join(temp_dir, f"{safe_title}_{timestamp}.pdf") - - try: - pdf_result = create_storybook_pdf( - valid_segments, - valid_illustrations, - st.session_state.book_title or "Untitled Story", - st.session_state.book_author or "Anonymous", - pdf_path - ) - - if pdf_result: - with open(pdf_path, "rb") as f: - st.download_button( - label="Download PDF Storybook", - data=f, - file_name=f"{safe_title}.pdf", - mime="application/pdf" - ) - except Exception as e: - st.error(f"Error creating PDF: {e}") - st.info("Please install ReportLab to enable PDF export: pip install reportlab") - - # Export as ZIP of images - if export_format in ["Individual Images (ZIP)", "Both"]: - zip_path = os.path.join(temp_dir, f"{safe_title}_illustrations_{timestamp}.zip") - - # Prepare files for ZIP - files_to_zip = {} - for i, img_path in enumerate(valid_illustrations): - if img_path and os.path.exists(img_path): - files_to_zip[f"illustration_{i+1}.png"] = img_path - - zip_result = create_zip_archive(files_to_zip, zip_path) - - if zip_result: - with open(zip_path, "rb") as f: - st.download_button( - label="Download Illustrations ZIP", - data=f, - file_name=f"{safe_title}_illustrations.zip", - mime="application/zip" - ) - else: - st.info("Generate illustrations before exporting.") - - # Cleanup temporary files when the session ends - def cleanup_temp_files(): - for file_path in st.session_state.temp_files: - try: - if file_path and os.path.exists(file_path): - os.remove(file_path) - except Exception as e: - logger.error(f"Error removing temporary file {file_path}: {e}") - - # Register the cleanup function to run when the session ends - import atexit - atexit.register(cleanup_temp_files) - - -if __name__ == "__main__": - write_story_illustrator() diff --git a/ToBeMigrated/ai_writers/ai_story_illustrator/utils.py b/ToBeMigrated/ai_writers/ai_story_illustrator/utils.py deleted file mode 100644 index f1c05ecb..00000000 --- a/ToBeMigrated/ai_writers/ai_story_illustrator/utils.py +++ /dev/null @@ -1,450 +0,0 @@ -""" -Utility functions for the AI Story Illustrator module. - -This module provides helper functions for file operations, string manipulation, -and simple text analysis relevant to story processing. -""" - -import os -import re -import tempfile -import uuid -import logging -import shutil -from pathlib import Path -from typing import List, Tuple, Optional, Union - -# Attempt to import Pillow for image dimensions, but don't fail if not installed -# unless the specific function is called. -try: - from PIL import Image - _PIL_AVAILABLE = True -except ImportError: - _PIL_AVAILABLE = False - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", -) -logger = logging.getLogger('story_illustrator_utils') - -# --- Constants --- -IMAGE_EXTENSIONS = frozenset(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']) -TEXT_EXTENSIONS = frozenset(['.txt', '.md', '.text']) -# Common English words that often start sentences, excluded from simple name detection -COMMON_START_WORDS = frozenset([ - 'The', 'A', 'An', 'And', 'But', 'Or', 'For', 'Nor', 'So', 'Yet', 'He', 'She', - 'It', 'They', 'We', 'You', 'I', 'In', 'On', 'At', 'To', 'From', 'With', - 'About', 'As', 'Is', 'Was', 'Were', 'Be', 'Been', 'Being', 'Have', 'Has', - 'Had', 'Do', 'Does', 'Did', 'Will', 'Would', 'Shall', 'Should', 'May', - 'Might', 'Must', 'Can', 'Could' -]) - - -# --- File/Directory Operations --- - -def create_temp_directory(prefix: str = "story_illustrator_") -> str: - """ - Creates a temporary directory using tempfile.mkdtemp. - - Args: - prefix: A prefix for the temporary directory name. - - Returns: - The absolute path to the created temporary directory. - """ - try: - temp_dir = tempfile.mkdtemp(prefix=prefix) - logger.info(f"Created temporary directory: {temp_dir}") - return temp_dir - except Exception as e: - logger.error(f"Failed to create temporary directory: {e}", exc_info=True) - raise # Re-raise the exception after logging - - -def sanitize_filename(filename: str) -> str: - """ - Sanitizes a filename by removing/replacing invalid characters for common filesystems. - - Args: - filename: The original filename string. - - Returns: - A sanitized filename string suitable for use in file paths. - """ - if not isinstance(filename, str): - logger.warning("sanitize_filename received non-string input, converting.") - filename = str(filename) - - # Remove characters invalid for Windows/Unix filenames - # Replace them with an underscore. - sanitized = re.sub(r'[\\/*?:"<>|\']', "_", filename) - # Replace consecutive underscores/spaces with a single underscore - sanitized = re.sub(r'[_ ]+', '_', sanitized) - # Remove leading/trailing spaces, dots, and underscores - sanitized = sanitized.strip("._ ") - - # Ensure the filename is not empty after sanitization - if not sanitized: - sanitized = "unnamed_file" - logger.warning("Filename was empty after sanitization, using default.") - - # Limit filename length (optional, adjust as needed) - # max_len = 255 # Example limit - # if len(sanitized) > max_len: - # name, ext = os.path.splitext(sanitized) - # sanitized = name[:max_len - len(ext) - 1] + "_" + ext - # logger.warning(f"Filename truncated to maximum length: {sanitized}") - - return sanitized - - -def get_temp_file_path( - directory: str, prefix: str = "file_", suffix: str = ".tmp" -) -> str: - """ - Generates a unique temporary file path within the specified directory. - - Args: - directory: The directory where the temporary file should be located. - prefix: A prefix for the filename. - suffix: A suffix (extension) for the filename. - - Returns: - The full path for the unique temporary file. - """ - # Ensure suffix starts with a dot if it's meant to be an extension - if suffix and not suffix.startswith("."): - suffix = "." + suffix - - unique_id = uuid.uuid4().hex[:12] # Longer hex UUID for better uniqueness - filename = f"{prefix}{unique_id}{suffix}" - return os.path.join(directory, filename) - - -def ensure_directory_exists(directory: Union[str, Path]) -> str: - """ - Ensures that a directory exists, creating it recursively if necessary. - - Args: - directory: The path to the directory (string or Path object). - - Returns: - The absolute path to the directory as a string. - - Raises: - OSError: If the directory cannot be created (e.g., permission issues). - """ - dir_path = Path(directory).resolve() # Use Pathlib for robust handling - try: - dir_path.mkdir(parents=True, exist_ok=True) - # Log only if it needed creation (or if verbose logging is on) - # logger.info(f"Ensured directory exists: {dir_path}") - return str(dir_path) - except OSError as e: - logger.error(f"Failed to create or access directory {dir_path}: {e}", exc_info=True) - raise - - -def cleanup_directory(directory: Union[str, Path]) -> None: - """ - Removes a directory and all its contents recursively. Handles errors gracefully. - - Args: - directory: The path to the directory to remove (string or Path object). - """ - dir_path = Path(directory) - if not dir_path.exists(): - logger.debug(f"Cleanup skipped: Directory '{directory}' does not exist.") - return - - if not dir_path.is_dir(): - logger.warning(f"Cleanup warning: Path '{directory}' is not a directory.") - return - - try: - shutil.rmtree(dir_path) - logger.info(f"Successfully removed directory: {directory}") - except OSError as e: - logger.error(f"Error removing directory {directory}: {e}", exc_info=True) - except Exception as e: - logger.error( - f"Unexpected error removing directory {directory}: {e}", exc_info=True - ) - - -# --- File Type Checks --- - -def get_file_extension(file_path: Union[str, Path]) -> str: - """ - Gets the lowercased file extension (including the dot) from a file path. - - Args: - file_path: The path to the file (string or Path object). - - Returns: - The file extension (e.g., '.txt', '.png') or an empty string if no extension. - """ - return Path(file_path).suffix.lower() - - -def is_image_file(file_path: Union[str, Path]) -> bool: - """ - Checks if a file is likely an image based on its extension. - - Args: - file_path: The path to the file (string or Path object). - - Returns: - True if the file extension is in IMAGE_EXTENSIONS, False otherwise. - """ - return get_file_extension(file_path) in IMAGE_EXTENSIONS - - -def is_text_file(file_path: Union[str, Path]) -> bool: - """ - Checks if a file is likely a text file based on its extension. - - Args: - file_path: The path to the file (string or Path object). - - Returns: - True if the file extension is in TEXT_EXTENSIONS, False otherwise. - """ - return get_file_extension(file_path) in TEXT_EXTENSIONS - - -# --- Text Analysis (Simple Heuristics) --- - -def extract_story_title_from_text(text: str) -> str: - """ - Attempts to extract a title from story text using simple heuristics. - - Looks for patterns (in order): - 1. Markdown headers (#, ##, etc.) at the start of a line. - 2. The first non-empty line if it's short (< 100 chars) and followed by - a blank line or is the only line. - 3. The first non-empty line if it's entirely in uppercase (< 100 chars). - - Args: - text: The story text content. - - Returns: - An extracted title string, or "Untitled Story" if no pattern matches. - """ - if not isinstance(text, str) or not text.strip(): - return "Untitled Story" - - # 1. Check for markdown headers ( # Title, ## Title ) - # Needs to match start of line (^) with optional whitespace before # - header_match = re.search(r'^\s*#+\s+(.+)$', text.strip(), re.MULTILINE) - if header_match: - title = header_match.group(1).strip() - if title: return title - - lines = text.strip().split('\n') - if not lines: - return "Untitled Story" - - first_line = lines[0].strip() - if not first_line: # Skip if first line is blank - if len(lines) > 1: - first_line = lines[1].strip() # Try second line - else: - return "Untitled Story" - - if not first_line: # Still no title found - return "Untitled Story" - - # 2. Check if first line is short and potentially a title - is_short = len(first_line) < 100 - is_followed_by_blank = len(lines) > 1 and not lines[1].strip() - is_only_line = len(lines) == 1 - - if is_short and (is_followed_by_blank or is_only_line): - return first_line - - # 3. Check if first line is all caps (and short) - is_all_caps = first_line == first_line.upper() and first_line.isalpha() # Check if it contains letters - if is_short and is_all_caps: - return first_line - - # Default if no other pattern matched - return "Untitled Story" - - -def estimate_reading_time(text: str, words_per_minute: int = 200) -> float: - """ - Estimates the reading time of a text in minutes. - - Args: - text: The text content. - words_per_minute: The assumed average reading speed. - - Returns: - The estimated reading time in minutes. Returns 0.0 for empty text. - """ - if not isinstance(text, str) or not text.strip(): - return 0.0 - if words_per_minute <= 0: - raise ValueError("words_per_minute must be positive.") - - word_count = len(text.split()) - minutes = word_count / words_per_minute - return minutes - - -def count_sentences(text: str) -> int: - """ - Counts the number of sentences in a text using a very simple heuristic. - - Note: This is a basic implementation counting sentence-ending punctuation - (. ! ?). It will be inaccurate with abbreviations (Mr., Mrs., etc.), - ellipses, and complex sentence structures. - - Args: - text: The text content. - - Returns: - An estimated count of sentences. Returns 0 for empty text. - """ - if not isinstance(text, str) or not text.strip(): - return 0 - - # Find sequences of one or more sentence-ending punctuation marks - sentence_endings = re.findall(r'[.!?]+', text) - count = len(sentence_endings) - - # Handle edge case where text might not end with punctuation but isn't empty - if count == 0 and len(text.strip()) > 0: - return 1 # Assume at least one sentence if text exists but no terminators found - return count - - -def extract_character_names(text: str, min_occurrences: int = 2) -> List[str]: - """ - Attempts to extract potential character names from story text. - - Note: This is a simple heuristic based on finding capitalized words - (excluding common sentence starters) that appear multiple times. It has - limitations and may produce false positives or miss actual names. - - Args: - text: The story text content. - min_occurrences: The minimum number of times a capitalized word must - appear to be considered a potential name. - - Returns: - A list of potential character name strings. - """ - if not isinstance(text, str) or not text.strip(): - return [] - if min_occurrences < 1: - min_occurrences = 1 # Ensure at least one occurrence is required - - # Find words starting with an uppercase letter, potentially followed by lowercase - # Allows for single-letter names like 'X' but focuses on typical Name structure - capitalized_words = re.findall(r'\b[A-Z][a-zA-Z]*\b', text) - - # Count occurrences, excluding common words - word_counts: Dict[str, int] = {} - for word in capitalized_words: - if word not in COMMON_START_WORDS: - word_counts[word] = word_counts.get(word, 0) + 1 - - # Filter for words that meet the minimum occurrence threshold - potential_names = [ - word for word, count in word_counts.items() if count >= min_occurrences - ] - - # Sort for consistency (optional) - potential_names.sort() - - return potential_names - - -def extract_setting_details(text: str) -> List[str]: - """ - Attempts to extract potential setting details using simple regex patterns. - - Note: This is a very basic heuristic looking for common prepositional - phrases (e.g., "in the forest", "at the castle"). It is highly limited - and likely to miss many setting details or extract irrelevant phrases. - - Args: - text: The story text content. - - Returns: - A list of potential setting phrases found. - """ - if not isinstance(text, str) or not text.strip(): - return [] - - # Patterns looking for prepositions followed by nouns/adjectives - # Making patterns slightly more general: - # (\b\w+\b) captures single words - # (\b\w+\s+\w+\b) captures two-word phrases - # (\b[A-Z]\w*\b) captures capitalized words (potential proper nouns) - setting_patterns = [ - r'\b(?:in|on|at|near|beside|inside|outside|under|over|through)\s+(?:the|a|an)\s+((?:[A-Z]\w*|\w+)(?:\s+\w+){0,2})\b', # e.g., in the old house - r'\b(?:in|on|at)\s+((?:[A-Z]\w+)(?:\s+[A-Z]\w+)*)\b', # e.g., in New York City - r'\b(?:during|before|after)\s+(?:the|a|an)\s+(\w+(?:\s+\w+){0,2})\b', # e.g., during the storm - ] - - settings_found = set() # Use a set to avoid duplicates - for pattern in setting_patterns: - try: - matches = re.findall(pattern, text, re.IGNORECASE) # Ignore case - for match in matches: - # If match is tuple due to multiple capture groups, join them? - # For these patterns, it should be single strings. - if isinstance(match, str): - phrase = match.strip() - if phrase and len(phrase.split()) <= 5: # Limit phrase length - settings_found.add(phrase) - except re.error as e: - logger.warning(f"Regex error in extract_setting_details: {e} with pattern: {pattern}") - - - # Convert set back to list and sort for consistency - sorted_settings = sorted(list(settings_found)) - return sorted_settings - - -# --- Image Operations --- - -def get_image_dimensions(image_path: Union[str, Path]) -> Optional[Tuple[int, int]]: - """ - Gets the (width, height) dimensions of an image file using Pillow. - - Args: - image_path: The path to the image file (string or Path object). - - Returns: - A tuple (width, height) if successful, or None if the file is not - a valid image, Pillow is not installed, or an error occurs. - """ - if not _PIL_AVAILABLE: - logger.warning("Pillow (PIL) library not installed. Cannot get image dimensions.") - return None - - img_path = Path(image_path) - if not img_path.is_file(): - logger.error(f"Image file not found or is not a file: {image_path}") - return None - - try: - with Image.open(img_path) as img: - width, height = img.size - logger.debug(f"Dimensions for {image_path}: {width}x{height}") - return width, height - except FileNotFoundError: - logger.error(f"Image file not found at path: {image_path}") - return None - except UnidentifiedImageError: # Specific Pillow error for invalid images - logger.error(f"Could not identify image file (invalid format or corrupted): {image_path}") - return None - except Exception as e: - logger.error(f"Error getting dimensions for image {image_path}: {e}", exc_info=True) - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_video_generator/README.md b/ToBeMigrated/ai_writers/ai_story_video_generator/README.md deleted file mode 100644 index edc451cb..00000000 --- a/ToBeMigrated/ai_writers/ai_story_video_generator/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# AI Story Video Generator - -This module allows users to generate animated story videos using AI. It leverages Google's Gemini model to create stories and generate images for each scene, then combines them into a video. - -## Features - -- Generate complete stories based on user prompts -- Create scene-by-scene storyboards -- Generate images for each scene using Gemini -- Compile images into an animated video -- Add background music and text overlays -- Export videos in MP4 format - -## How It Works - -1. User provides a story prompt and preferences -2. AI generates a complete story with multiple scenes -3. For each scene, an image is generated -4. Images are compiled into a video with transitions -5. Optional background music and text overlays are added -6. The final video is available for download - -## Requirements - -- Google Gemini API key -- FFmpeg for video processing -- Python libraries: moviepy, pillow, requests - -## Usage - -Access this tool through the Streamlit interface by selecting "AI Story Video Generator" from the main menu. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_video_generator/__init__.py b/ToBeMigrated/ai_writers/ai_story_video_generator/__init__.py deleted file mode 100644 index 6432e465..00000000 --- a/ToBeMigrated/ai_writers/ai_story_video_generator/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# AI Story Video Generator module -from .story_video_generator import write_story_video_generator - -__all__ = ["write_story_video_generator"] \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_video_generator/story_video_generator.py b/ToBeMigrated/ai_writers/ai_story_video_generator/story_video_generator.py deleted file mode 100644 index 910a6514..00000000 --- a/ToBeMigrated/ai_writers/ai_story_video_generator/story_video_generator.py +++ /dev/null @@ -1,1063 +0,0 @@ -""" -AI Story Video Generator - -This module provides functionality to generate animated story videos using AI. -It adapts the Google Gemini cookbook example for Streamlit. -""" - -import os -import re -import time -import json -import uuid -import tempfile -import logging -from pathlib import Path -from typing import List, Dict, Any, Tuple, Optional, Union - -import streamlit as st -import numpy as np -from PIL import Image, ImageDraw, ImageFont -import requests -from moviepy.editor import ( - ImageSequenceClip, - TextClip, - CompositeVideoClip, - AudioFileClip, -) - -# Import Gemini functionality (Ensure these paths are correct in your project) -try: - from lib.gpt_providers.text_generation.main_text_generation import ( - llm_text_gen, - ) - from lib.gpt_providers.text_to_image_generation.gen_gemini_images import ( - generate_gemini_image, - ) -except ImportError as e: - st.error( - f"Failed to import custom libraries: {e}. " - "Please ensure 'lib/gpt_providers/...' structure is correct and accessible." - ) - # You might want to exit or disable functionality if imports fail - llm_text_gen = None - generate_gemini_image = None - -# Configure logging -logging.basicConfig( - level=logging.DEBUG, # Set to DEBUG for maximum verbosity - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.StreamHandler(), # Console handler - logging.FileHandler('story_video_generator.log') # File handler - ] -) -logger = logging.getLogger(__name__) - -# Constants -DEFAULT_FPS = 1 -DEFAULT_DURATION = 3 # seconds per image -DEFAULT_TRANSITION_DURATION = 1 # seconds for transition (Currently unused, potential future feature) -DEFAULT_FONT_SIZE = 24 -DEFAULT_FONT_COLOR = "white" -DEFAULT_MUSIC_URL = "https://freepd.com/music/Magical%20Transition.mp3" # Example free music URL -DEFAULT_IMAGE_WIDTH = 1024 -DEFAULT_IMAGE_HEIGHT = 768 -TEXT_AREA_HEIGHT_RATIO = 1 / 3 -TEXT_PADDING = 20 -TEXT_OVERLAY_ALPHA = 160 # Semi-transparent overlay (0-255) - -class StoryVideoGenerator: - """Class to handle the generation of animated story videos.""" - - def __init__(self): - """Initialize the StoryVideoGenerator.""" - logger.info("Initializing StoryVideoGenerator") - self.temp_dir = tempfile.mkdtemp() - logger.debug(f"Created temporary directory: {self.temp_dir}") - # Register cleanup on program exit - import atexit - atexit.register(self.cleanup) - logger.info("StoryVideoGenerator initialized successfully") - - def cleanup(self): - """Clean up temporary files and resources.""" - logger.info("Starting cleanup process") - try: - import shutil - if os.path.exists(self.temp_dir): - shutil.rmtree(self.temp_dir) - logger.info(f"Successfully cleaned up temporary directory: {self.temp_dir}") - else: - logger.warning(f"Temporary directory not found: {self.temp_dir}") - except Exception as e: - logger.error(f"Error during cleanup: {str(e)}", exc_info=True) - - def __del__(self): - """Destructor to ensure cleanup.""" - logger.debug("Destructor called") - self.cleanup() - - def generate_story( - self, prompt: str, num_scenes: int = 5, style: str = "children's story" - ) -> Dict[str, Any]: - """ - Generate a story based on the given prompt using an LLM. - - Args: - prompt: The story prompt. - num_scenes: Number of scenes to generate. - style: Style of the story (e.g., "children's story", "sci-fi"). - - Returns: - A dictionary containing the story title and a list of scenes. - - Raises: - Exception: If story generation or parsing fails. - """ - logger.info(f"Generating story with parameters: prompt='{prompt}', num_scenes={num_scenes}, style='{style}'") - - if not llm_text_gen: - logger.error("LLM text generation function not available") - raise RuntimeError("LLM text generation function not available.") - - try: - system_prompt = f"""You are a creative story writer specializing in {style} stories. - Create a short story based on the prompt below. - The story should have exactly {num_scenes} scenes. - Format your response STRICTLY as a JSON object with the following structure: - {{ - "title": "Story Title", - "scenes": [ - {{ - "scene_number": 1, - "description": "Brief visual description of the scene suitable for image generation", - "narration": "The narration text for this scene" - }}, - ... - ] - }} - Ensure each scene has a clear visual description and corresponding narration. - Do not include any text outside the JSON structure itself (e.g., no '```json' markers). - """ - logger.debug(f"Generated system prompt: {system_prompt}") - - user_prompt = f"Create a {style} story about: {prompt}" - logger.debug(f"Generated user prompt: {user_prompt}") - - response = llm_text_gen(user_prompt, system_prompt=system_prompt) - logger.debug(f"Raw LLM response received: {response}") - - # Parse and validate the response - try: - cleaned_response = re.sub(r'^```(json)?\s*|\s*```$', '', response, flags=re.DOTALL | re.IGNORECASE).strip() - story_data = json.loads(cleaned_response) - logger.info("Successfully parsed JSON response") - except json.JSONDecodeError as json_err: - logger.error(f"JSONDecodeError: {json_err}. Raw response was: {response}") - json_match = re.search(r'\{\s*"title":.*\}\s*$', cleaned_response, re.DOTALL) - if json_match: - json_str = json_match.group(0) - try: - story_data = json.loads(json_str) - logger.info("Successfully parsed JSON using regex fallback") - except json.JSONDecodeError as fallback_err: - logger.error(f"Fallback JSON parsing failed: {fallback_err}") - raise Exception(f"Failed to parse LLM response as JSON. Response:\n{response}") from fallback_err - else: - raise Exception(f"Could not find valid JSON in LLM response. Response:\n{response}") from json_err - - # Validate structure - if "title" not in story_data or "scenes" not in story_data: - logger.error("Generated JSON missing 'title' or 'scenes' key") - raise ValueError("Generated JSON missing 'title' or 'scenes' key") - if not isinstance(story_data["scenes"], list): - logger.error("'scenes' key must contain a list") - raise ValueError("'scenes' key must contain a list") - - logger.info(f"Successfully generated story: {story_data.get('title', 'Untitled')}") - return story_data - - except Exception as e: - logger.error(f"Error generating story: {str(e)}", exc_info=True) - raise Exception(f"Failed to generate or parse story: {str(e)}") from e - - def generate_scene_image( - self, scene: Dict[str, Any], style: str = "digital art" - ) -> str: - """ - Generate an image for a single scene using an image generation model. - - Args: - scene: The scene dictionary containing "scene_number" and "description". - style: The visual style for the image (e.g., "digital art", "cartoon"). - - Returns: - Path to the generated image file. Falls back to a placeholder on error. - """ - scene_num = scene.get("scene_number", "unknown") - description = scene.get("description", "No description provided.") - logger.info(f"Generating image for scene {scene_num}: '{description}', style: '{style}'") - - if not generate_gemini_image: - logger.error("Image generation function not available") - raise RuntimeError("Image generation function not available.") - - prompt = f"Create a {style} image representing this scene: {description}. Image should be visually clear and focus on the core elements described." - logger.debug(f"Generated image prompt: {prompt}") - - try: - # Generate image using the imported function - # This function should save the image and return its path - image_path = generate_gemini_image(prompt, style=style) # Assuming this function saves the image and returns path - - if not image_path or not os.path.exists(image_path): - logger.error(f"Image generation function did not return a valid path: {image_path}") - raise Exception(f"Image generation function did not return a valid path: {image_path}") - - logger.info(f"Successfully generated image for scene {scene_num}: {image_path}") - return image_path - - except Exception as e: - logger.error(f"Error generating image for scene {scene_num}: {str(e)}", exc_info=True) - logger.warning(f"Creating placeholder image for scene {scene_num}") - return self._create_placeholder_image(scene_num, description) - - def _create_placeholder_image( - self, scene_num: Union[int, str], description: str - ) -> str: - """Create a placeholder image with text when image generation fails.""" - logger.info(f"Creating placeholder image for scene {scene_num}") - width, height = DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT - image = Image.new("RGB", (width, height), color=(73, 109, 137)) - draw = ImageDraw.Draw(image) - - try: - # Try loading a common font, fall back to default - font_size = 36 - try: - font = ImageFont.truetype("arial.ttf", font_size) - except IOError: - try: - font = ImageFont.truetype("DejaVuSans.ttf", font_size) # Common on Linux - except IOError: - font = ImageFont.load_default() - logger.warning("Arial/DejaVuSans font not found. Using default PIL font.") - - text = f"Scene {scene_num}\n\nImage Generation Failed\n\nDescription:\n{description}" - - # Simple text wrapping - max_text_width = width - 2 * TEXT_PADDING - lines = [] - words = text.split() - current_line = "" - for word in words: - if not current_line: - test_line = word - else: - test_line = current_line + " " + word - - # Use textbbox for potentially more accurate width estimation - try: - bbox = draw.textbbox((0,0), test_line, font=font) - line_width = bbox[2] - bbox[0] - except AttributeError: # older Pillow versions might not have textbbox - line_width = draw.textlength(test_line, font=font) # Use textlength - - - if line_width <= max_text_width: - current_line = test_line - else: - lines.append(current_line) - current_line = word - lines.append(current_line) # Add the last line - - # Calculate text block height and starting position - total_text_height = 0 - line_heights = [] - for line in lines: - try: - bbox = draw.textbbox((0,0), line, font=font) - h = bbox[3] - bbox[1] - except AttributeError: - # Estimate height if textbbox not available - (_, h) = draw.textsize(line, font=font) - line_heights.append(h) - total_text_height += h + 5 # Add small spacing - - start_y = (height - total_text_height) // 2 - - # Draw text line by line - current_y = start_y - for i, line in enumerate(lines): - try: - bbox = draw.textbbox((0,0), line, font=font) - line_width = bbox[2] - bbox[0] - except AttributeError: - line_width = draw.textlength(line, font=font) - - x_position = (width - line_width) // 2 - draw.text((x_position, current_y), line, fill="white", font=font) - current_y += line_heights[i] + 5 # Move y for next line - - except Exception as font_err: - logger.error(f"Error drawing text on placeholder: {font_err}", exc_info=True) - # Draw a simple error message if font loading/drawing fails - draw.text((10, 10), f"Error creating placeholder text for Scene {scene_num}", fill="red") - - - # Save image - output_path = os.path.join( - self.temp_dir, f"placeholder_scene_{scene_num}_{uuid.uuid4()}.png" - ) - image.save(output_path) - logger.info(f"Saved placeholder image to {output_path}") - return output_path - - def add_text_to_image(self, image_path: str, text: str) -> str: - """ - Add narration text overlayed on an image. - - Args: - image_path: Path to the source image. - text: The narration text to add. - - Returns: - Path to the new image with text overlay. Returns original path on error. - """ - logger.info(f"Adding text overlay to image: {image_path}") - try: - image = Image.open(image_path).convert("RGBA") # Ensure RGBA for overlay - width, height = image.size - - # Create a semi-transparent overlay for the bottom part - overlay_height = int(height * TEXT_AREA_HEIGHT_RATIO) - overlay = Image.new( - "RGBA", (width, overlay_height), (0, 0, 0, TEXT_OVERLAY_ALPHA) - ) - - # Paste overlay onto a copy of the image - image_with_overlay = image.copy() - image_with_overlay.paste( - overlay, (0, height - overlay_height), overlay - ) - - # Prepare to draw text - draw = ImageDraw.Draw(image_with_overlay) - try: - font = ImageFont.truetype("arial.ttf", DEFAULT_FONT_SIZE) - except IOError: - try: - font = ImageFont.truetype("DejaVuSans.ttf", DEFAULT_FONT_SIZE) - except IOError: - font = ImageFont.load_default() - logger.warning("Arial/DejaVuSans font not found. Using default PIL font for overlay.") - - - # Wrap text - max_text_width = width - 2 * TEXT_PADDING - words = text.split() - lines = [] - current_line = "" - - if not words: # Handle empty narration - logger.warning(f"Empty narration text for image {image_path}. No text added.") - return image_path # Return original if no text - - for word in words: - if not current_line: - test_line = word - else: - test_line = current_line + " " + word - - try: - bbox = draw.textbbox((0,0), test_line, font=font) - line_width = bbox[2] - bbox[0] - except AttributeError: - line_width = draw.textlength(test_line, font=font) - - if line_width <= max_text_width: - current_line = test_line - else: - lines.append(current_line) - current_line = word - lines.append(current_line) # Add the last line - - # Calculate starting Y position for text - total_text_height = 0 - line_heights = [] - line_spacing = 10 - for line in lines: - try: - bbox = draw.textbbox((0,0), line, font=font) - h = bbox[3] - bbox[1] - except AttributeError: - (_, h) = draw.textsize(line, font=font) - line_heights.append(h) - total_text_height += h + line_spacing - - total_text_height -= line_spacing # Remove extra spacing after last line - - # Adjust starting position to center text vertically within the overlay area - text_area_top = height - overlay_height - start_y = text_area_top + (overlay_height - total_text_height) // 2 - if start_y < text_area_top + TEXT_PADDING: # Ensure padding from top of overlay - start_y = text_area_top + TEXT_PADDING - - # Draw text lines - current_y = start_y - for i, line in enumerate(lines): - try: - bbox = draw.textbbox((0,0), line, font=font) - line_width = bbox[2] - bbox[0] - except AttributeError: - line_width = draw.textlength(line, font=font) - - x_position = (width - line_width) // 2 # Center horizontally - draw.text( - (x_position, current_y), - line, - fill=DEFAULT_FONT_COLOR, - font=font, - ) - current_y += line_heights[i] + line_spacing - - # Save the new image (use PNG to preserve transparency) - base_name = os.path.basename(image_path) - name, ext = os.path.splitext(base_name) - output_path = os.path.join( - self.temp_dir, f"text_{name}_{uuid.uuid4()}.png" - ) - # Convert back to RGB before saving if target format doesn't need alpha - image_with_overlay.convert("RGB").save(output_path) - logger.info(f"Added text overlay to {image_path}, saved as {output_path}") - return output_path - - except FileNotFoundError: - logger.error(f"Error adding text: Image file not found at {image_path}") - return image_path # Return original path if file is missing - except Exception as e: - logger.error( - f"Error adding text to image {image_path}: {str(e)}", exc_info=True - ) - return image_path # Return original image path if text addition fails - - def download_audio(self, url: str) -> Optional[str]: - """ - Download audio file from a URL. - - Args: - url: URL of the audio file (expects MP3). - - Returns: - Path to the downloaded audio file, or None if download fails. - """ - logger.info(f"Downloading audio from URL: {url}") - if not url: - logger.warning("No audio URL provided.") - return None - - try: - response = requests.get(url, stream=True, timeout=30) # Add timeout - response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) - - # Check content type (optional but recommended) - content_type = response.headers.get('content-type') - if content_type and 'audio' not in content_type: - logger.warning(f"URL content type is '{content_type}', expected audio. Proceeding anyway.") - - audio_path = os.path.join(self.temp_dir, f"background_music_{uuid.uuid4()}.mp3") - with open(audio_path, "wb") as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - - logger.info(f"Successfully downloaded audio to {audio_path}") - return audio_path - - except requests.exceptions.RequestException as e: - logger.error(f"Error downloading audio from {url}: {str(e)}") - return None - except Exception as e: - logger.error(f"An unexpected error occurred during audio download: {str(e)}", exc_info=True) - return None - - - def create_video( - self, - image_paths: List[str], - audio_path: Optional[str] = None, - fps: int = DEFAULT_FPS, - duration_per_image: int = DEFAULT_DURATION, - ) -> str: - """ - Create a video from a sequence of images with optional audio. - - Args: - image_paths: List of paths to the image files (should include text overlays if added). - audio_path: Path to the background audio file (optional). - fps: Frames per second for the output video. - duration_per_image: How long each image should be displayed in seconds. - - Returns: - Path to the created video file. - - Raises: - Exception: If video creation fails. - FileNotFoundError: If any image path is invalid. - """ - logger.info(f"Creating video with {len(image_paths)} images, fps={fps}, duration={duration_per_image}s/image") - if not image_paths: - logger.error("Cannot create video with no images.") - raise ValueError("Cannot create video with no images.") - - # Verify all image paths exist before processing - for img_path in image_paths: - if not os.path.exists(img_path): - logger.error(f"Image file not found: {img_path}") - raise FileNotFoundError(f"Image file not found: {img_path}") - - try: - # Create a unique output filename - output_path = os.path.join( - self.temp_dir, f"story_video_{uuid.uuid4()}.mp4" - ) - - # Load images and create frames list - # Need to ensure all images are the same size, resize if necessary - frames = [] - target_size = None - - logger.info("Loading and processing images for video...") - for i, img_path in enumerate(image_paths): - try: - img = Image.open(img_path).convert("RGB") # Ensure RGB format for video - - if target_size is None: - target_size = img.size - logger.info(f"Video frame size set to: {target_size}") - elif img.size != target_size: - logger.warning(f"Image {i} ({img_path}) has size {img.size}, resizing to {target_size}") - img = img.resize(target_size, Image.LANCZOS) # Use high-quality resize filter - - # Duplicate frame for the duration - num_frames_per_image = duration_per_image * fps - img_array = np.array(img) - for _ in range(num_frames_per_image): - frames.append(img_array) - except Exception as img_err: - logger.error(f"Error processing image {img_path}: {img_err}", exc_info=True) - # Option: skip image, use placeholder, or raise error - raise Exception(f"Failed to load or process image: {img_path}") from img_err - - - if not frames: - logger.error("No valid frames could be generated from the images.") - raise ValueError("No valid frames could be generated from the images.") - - # Create video clip from image sequence - logger.info(f"Creating ImageSequenceClip with {len(frames)} total frames.") - clip = ImageSequenceClip(frames, fps=fps) - - # Add audio if provided and valid - final_audio_clip = None - if audio_path and os.path.exists(audio_path): - logger.info(f"Adding audio from: {audio_path}") - try: - audio_clip = AudioFileClip(audio_path) - video_duration = clip.duration - - # Loop or trim audio to match video duration - if audio_clip.duration < video_duration: - logger.info(f"Looping audio (duration {audio_clip.duration}s) for video (duration {video_duration}s)") - final_audio_clip = audio_clip.loop(duration=video_duration) - elif audio_clip.duration > video_duration: - logger.info(f"Trimming audio (duration {audio_clip.duration}s) to video duration ({video_duration}s)") - final_audio_clip = audio_clip.subclip(0, video_duration) - else: - final_audio_clip = audio_clip # Duration matches exactly - - clip = clip.set_audio(final_audio_clip) - except Exception as audio_err: - logger.error(f"Error processing audio file {audio_path}: {audio_err}. Proceeding without audio.", exc_info=True) - # Ensure audio clip resources are closed if error occurs mid-process - if 'audio_clip' in locals() and hasattr(audio_clip, 'close'): - audio_clip.close() - elif audio_path: - logger.warning(f"Audio path provided ({audio_path}) but file not found. Creating video without audio.") - - - # Write video file - logger.info(f"Writing video file to: {output_path}") - # Use sensible codecs and parameters - clip.write_videofile( - output_path, - codec="libx264", # Common and efficient codec - audio_codec="aac", # Standard audio codec - ffmpeg_params=["-pix_fmt", "yuv420p"], # Ensure compatibility - logger='bar' # Show progress bar - ) - - logger.info(f"Successfully created video: {output_path}") - - # Clean up moviepy resources - clip.close() - if final_audio_clip and hasattr(final_audio_clip, 'close'): - final_audio_clip.close() - - return output_path - - except Exception as e: - logger.error(f"Error creating video: {str(e)}", exc_info=True) - # Clean up partial resources if possible - if 'clip' in locals() and hasattr(clip, 'close'): - clip.close() - if 'final_audio_clip' in locals() and hasattr(final_audio_clip, 'close'): - final_audio_clip.close() - - raise Exception(f"Failed to create video: {str(e)}") from e - -# --- Streamlit UI --- - -def write_story_video_generator(): - """Main function to run the Streamlit application interface.""" - logger.info("Starting Story Video Generator UI") - - if not MOVIEPY_AVAILABLE: - logger.error("MoviePy is not available") - st.error( - "MoviePy is required for video generation but is not properly installed. " - "Please install it using:\n" - "```\n" - "pip install moviepy imageio imageio-ffmpeg\n" - "```" - ) - return - - # Check if dependencies are loaded - if not llm_text_gen or not generate_gemini_image: - logger.error("Core AI functionalities could not be loaded") - st.error("Core AI functionalities could not be loaded. Please check the logs and library paths.") - st.stop() - - # Initialize session state variables - logger.debug("Initializing session state variables") - if "story_data" not in st.session_state: - st.session_state.story_data = None - if "generated_images" not in st.session_state: - st.session_state.generated_images = [] - if "original_images" not in st.session_state: - st.session_state.original_images = [] - if "video_path" not in st.session_state: - st.session_state.video_path = None - - tab1, tab2, tab3, tab4 = st.tabs( - ["**1. Story Prompt**", "**2. Storyboard**", "**3. Generate Images**", "**4. Create Video**"] - ) - - # --- Step 1: Story Prompt --- - with tab1: - st.header("Step 1: Create Your Story") - - col1, col2 = st.columns([2, 1]) - - with col1: - story_prompt = st.text_area( - "Enter your story idea:", - placeholder="e.g., A brave squirrel who learns to fly with the help of an old owl.", - height=100, - key="story_prompt_input" - ) - - col1_1, col1_2 = st.columns(2) - with col1_1: - num_scenes = st.slider( - "Number of Scenes", min_value=2, max_value=10, value=4, key="num_scenes_slider" - ) - with col1_2: - story_style = st.selectbox( - "Story Style", - [ - "children's story", - "adventure story", - "fairy tale", - "sci-fi story", - "fantasy story", - "mystery story", - "fable", - ], - key="story_style_select" - ) - - with col2: - st.markdown("#### Tips for Good Prompts") - st.markdown( - """ - * **Be specific:** Mention characters, setting, and the main plot point. - * **Include conflict:** What challenge do the characters face? - * **Suggest a mood:** Happy, mysterious, exciting? - * **Target Audience:** Helps the AI tailor the tone (e.g., "for young children"). - * **Example:** "A funny children's story about a clumsy robot trying to bake a cake for its creator's birthday in a futuristic kitchen." - """ - ) - - if st.button("โœจ Generate Story", type="primary", key="generate_story_button"): - if not story_prompt: - st.error("Please enter a story prompt.") - else: - with st.spinner("โœ๏ธ Generating your story... This may take a moment."): - try: - generator = StoryVideoGenerator() # Create instance - story_data = generator.generate_story( - story_prompt, num_scenes, story_style - ) - st.session_state.story_data = story_data - # Reset downstream states - st.session_state.generated_images = [] - st.session_state.original_images = [] - st.session_state.video_path = None - st.success( - "Story generated successfully! Proceed to the **Storyboard** tab to review and edit." - ) - # Consider automatically switching tabs here if desired (more complex JS interaction) - except Exception as e: - st.error(f"Error generating story: {str(e)}") - logger.error("Story generation failed in UI", exc_info=True) - - - # --- Step 2: Storyboard --- - with tab2: - st.header("Step 2: Review Your Storyboard") - - if st.session_state.story_data: - story_data = st.session_state.story_data - - st.subheader(f"Title: {story_data.get('title', 'Untitled Story')}") - st.markdown("Review and edit the scene descriptions and narrations below.") - - # Use st.form to batch edits? Could be smoother but adds complexity. - # Simple sequential editing for now. - edited = False - for i, scene in enumerate(story_data["scenes"]): - st.markdown("---") - st.markdown(f"**๐ŸŽฌ Scene {scene.get('scene_number', i+1)}**") - - # Store original values for comparison/reset? - original_desc = scene.get("description", "") - original_narr = scene.get("narration", "") - - # Use unique keys for each text area - desc_key = f"desc_{scene.get('scene_number', i)}" - narr_key = f"narr_{scene.get('scene_number', i)}" - - new_description = st.text_area( - "Visual Description (for image generation)", - value=original_desc, - key=desc_key, - height=100 - ) - new_narration = st.text_area( - "Narration Text (for voiceover/overlay)", - value=original_narr, - key=narr_key, - height=100 - ) - - # Update the scene data in session state if changed - if new_description != original_desc: - st.session_state.story_data["scenes"][i]["description"] = new_description - edited = True - if new_narration != original_narr: - st.session_state.story_data["scenes"][i]["narration"] = new_narration - edited = True - - if edited: - # Use st.info for non-blocking notification - st.info("Changes saved in session. Proceed when ready.") - - if st.button("๐Ÿ–ผ๏ธ Proceed to Image Generation", type="primary", key="proceed_to_images_button"): - # Re-check story_data exists before proceeding - if not st.session_state.story_data or not st.session_state.story_data.get("scenes"): - st.error("No story data available. Please generate a story first.") - else: - # Reset image/video state if proceeding from edits - st.session_state.generated_images = [] - st.session_state.original_images = [] - st.session_state.video_path = None - st.success("Ready! Go to the **Generate Images** tab.") - - else: - st.info("Generate a story in **Step 1** first.") - - # --- Step 3: Generate Images --- - with tab3: - st.header("Step 3: Generate Scene Images") - - if st.session_state.story_data and st.session_state.story_data.get("scenes"): - story_data = st.session_state.story_data - - col1, col2 = st.columns([1, 2]) # Settings | Preview - - with col1: - st.subheader("Image Settings") - image_style = st.selectbox( - "Image Style", - [ - "digital art", - "cartoon", - "watercolor", - "photorealistic", # Changed 'realistic' - "anime", - "pixel art", - "oil painting", - "line art", - "cinematic", - ], - key="image_style_select" - ) - - include_text = st.checkbox( - "Overlay narration text on images", value=True, key="include_text_checkbox" - ) - - st.markdown("---") - - if st.button("๐ŸŽจ Generate All Images", type="primary", key="generate_images_button"): - # Check if images already exist for current story? Ask to regenerate? - # Simple approach: always regenerate when button is clicked. - with st.spinner("Generating images... This can take some time depending on the number of scenes."): - try: - generator = StoryVideoGenerator() # New instance for this task - generated_images = [] - original_images = [] # Store originals separately - - num_scenes_total = len(story_data["scenes"]) - progress_bar = st.progress(0.0) - status_text = st.empty() - - for i, scene in enumerate(story_data["scenes"]): - status_text.text(f"Generating image for scene {i+1}/{num_scenes_total}...") - # Generate the base image - original_image_path = generator.generate_scene_image( - scene, image_style - ) - original_images.append(original_image_path) - - # Add text if requested - if include_text: - status_text.text(f"Adding text overlay for scene {i+1}...") - final_image_path = generator.add_text_to_image( - original_image_path, scene.get("narration", "") - ) - # Check if text addition failed (returned original path) - if final_image_path == original_image_path and scene.get("narration", ""): - st.warning(f"Could not add text overlay to scene {i+1}. Using original image.") - generated_images.append(final_image_path) - else: - generated_images.append(original_image_path) # Use original if no text needed - - progress_bar.progress((i + 1) / num_scenes_total) - - status_text.text("Image generation complete!") - st.session_state.original_images = original_images - st.session_state.generated_images = generated_images - st.session_state.video_path = None # Reset video path - st.success( - "All images generated! Review them here and proceed to the **Create Video** tab." - ) - except FileNotFoundError as fnf_err: - st.error(f"Image Generation Error: A required file was not found. {fnf_err}") - logger.error("Image generation failed due to FileNotFoundError", exc_info=True) - except Exception as e: - st.error(f"Error generating images: {str(e)}") - logger.error("Image generation failed in UI", exc_info=True) - # Clear potentially partial results - st.session_state.generated_images = [] - st.session_state.original_images = [] - - - with col2: - st.subheader("Image Preview") - if st.session_state.generated_images: - # Display images (final versions with text if applicable) - for i, img_path in enumerate(st.session_state.generated_images): - scene_num = st.session_state.story_data["scenes"][i].get("scene_number", i+1) - st.image( - img_path, - caption=f"Scene {scene_num}", - use_column_width=True, - ) - else: - st.info( - "Click 'Generate All Images' after configuring settings." - ) - else: - st.info( - "Please generate or review a story in **Step 1** or **Step 2** first." - ) - - - # --- Step 4: Create Video --- - with tab4: - st.header("Step 4: Create Your Story Video") - - if st.session_state.generated_images: - col1, col2 = st.columns([1, 1]) # Settings | Video Player - - with col1: - st.subheader("Video Settings") - fps = st.slider( - "Frames Per Second (Video Smoothness)", - min_value=1, - max_value=30, - value=max(DEFAULT_FPS, 10), # Default to slightly smoother - key="fps_slider" - ) - duration_per_image = st.slider( - "Seconds Per Scene", - min_value=1, - max_value=15, - value=DEFAULT_DURATION, - key="duration_slider" - ) - - st.markdown("---") - st.subheader("Audio Settings") - use_music = st.checkbox("Add background music", value=True, key="use_music_checkbox") - - music_url_to_use = None - if use_music: - music_option = st.radio( - "Music Source", - ["Use default soothing music", "Provide music URL (MP3)"], - key="music_option_radio", - horizontal=True - ) - - if music_option == "Provide music URL (MP3)": - custom_music_url = st.text_input( - "Music URL (must be direct MP3 link)", - placeholder="https://example.com/path/to/music.mp3", - key="custom_music_url_input" - ) - if custom_music_url: - music_url_to_use = custom_music_url - else: - # Explicitly set to None if field is empty but option selected - music_url_to_use = None - else: - music_url_to_use = DEFAULT_MUSIC_URL - st.caption(f"Using default: {DEFAULT_MUSIC_URL}") - - st.markdown("---") - - if st.button("๐ŸŽž๏ธ Create Video", type="primary", key="create_video_button"): - if not st.session_state.generated_images: - st.error("No images found. Please generate images in Step 3.") - else: - with st.spinner("๐ŸŽฌ Creating your story video... This might take some time."): - try: - generator = StoryVideoGenerator() # New instance - - # Download audio if requested and URL is valid - audio_path = None - if use_music and music_url_to_use: - status_text = st.empty() - status_text.text("Downloading background music...") - audio_path = generator.download_audio(music_url_to_use) - if not audio_path: - st.warning("Failed to download music. Proceeding without audio.") - status_text.text("Music download failed. Continuing...") - else: - status_text.text("Music downloaded.") - - # Create video using the final generated images - status_text.text("Compiling video...") - video_path = generator.create_video( - st.session_state.generated_images, # Use images (potentially with text) - audio_path, - fps, - duration_per_image, - ) - - st.session_state.video_path = video_path - status_text.empty() # Clear status message - st.success("Video created successfully!") - - except FileNotFoundError as fnf_err: - st.error(f"Video Creation Error: A required file was not found. {fnf_err}") - logger.error("Video creation failed due to FileNotFoundError", exc_info=True) - except Exception as e: - st.error(f"Error creating video: {str(e)}") - logger.error("Video creation failed in UI", exc_info=True) - st.session_state.video_path = None # Clear video path on error - - with col2: - st.subheader("Video Preview") - if st.session_state.video_path: - try: - video_file = open(st.session_state.video_path, "rb") - video_bytes = video_file.read() - st.video(video_bytes) - video_file.close() # Close the file handle - - # Prepare download button - try: - video_title = st.session_state.story_data.get("title", "story") - safe_title = re.sub(r'[^\w\-]+', '_', video_title) # Sanitize title for filename - download_filename = f"{safe_title}_video.mp4" - - st.download_button( - label="โฌ‡๏ธ Download Video", - data=video_bytes, # Use the bytes already read - file_name=download_filename, - mime="video/mp4", - key="download_video_button" - ) - except Exception as download_err: - st.error(f"Error preparing download button: {download_err}") - - except FileNotFoundError: - st.error("The generated video file could not be found. Please try generating again.") - st.session_state.video_path = None # Reset state - except Exception as display_err: - st.error(f"Error displaying video: {display_err}") - logger.error("Error displaying video in UI", exc_info=True) - - - else: - st.info( - "Click 'Create Video' after generating images and configuring settings." - ) - else: - st.info( - "Please generate images in **Step 3** first." - ) - - # --- Footer --- - st.markdown("---") - st.markdown( - "Powered by AI | Story generation, image creation, and video compilation." - ) - # Add link to your repo or project if desired - # st.markdown("[GitHub Repository](your-link-here)") - - logger.info("Story Video Generator UI initialized successfully") - -if __name__ == "__main__": - # Ensure essential libraries are installed - try: - import streamlit - import numpy - import PIL - import requests - import moviepy - # Optionally check for google-generativeai if it's the backend - # import google.generativeai - except ImportError as e: - logger.error(f"Error: Missing required library: {e.name}") - st.error(f"Error: Missing required library: {e.name}") - st.error("Please install all required libraries: pip install streamlit numpy Pillow requests moviepy") - # Add other dependencies like google-generativeai if needed - exit(1) - - write_story_video_generator() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_video_generator/utils.py b/ToBeMigrated/ai_writers/ai_story_video_generator/utils.py deleted file mode 100644 index 81d5a96b..00000000 --- a/ToBeMigrated/ai_writers/ai_story_video_generator/utils.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Utility functions for the AI Story Video Generator. -""" - -import os -import tempfile -import uuid -from pathlib import Path -from typing import Optional - -# Constants -TEMP_DIR = Path(tempfile.gettempdir()) / "alwrity_story_generator" - -def ensure_temp_dir() -> Path: - """Ensure the temporary directory exists and return its path.""" - os.makedirs(TEMP_DIR, exist_ok=True) - return TEMP_DIR - -def get_temp_filepath(prefix: str, extension: str) -> str: - """Generate a temporary file path with the given prefix and extension.""" - temp_dir = ensure_temp_dir() - return str(temp_dir / f"{prefix}_{uuid.uuid4()}.{extension}") - -def clean_temp_files(older_than_hours: int = 24) -> int: - """ - Clean temporary files older than the specified number of hours. - - Args: - older_than_hours: Remove files older than this many hours - - Returns: - Number of files removed - """ - import time - from datetime import datetime, timedelta - - temp_dir = ensure_temp_dir() - cutoff_time = time.time() - (older_than_hours * 3600) - count = 0 - - for file_path in temp_dir.glob("*"): - if file_path.is_file() and file_path.stat().st_mtime < cutoff_time: - try: - file_path.unlink() - count += 1 - except Exception: - pass - - return count - -def format_duration(seconds: float) -> str: - """Format seconds into a MM:SS string.""" - minutes = int(seconds // 60) - remaining_seconds = int(seconds % 60) - return f"{minutes}:{remaining_seconds:02d}" - -def sanitize_filename(filename: str) -> str: - """Sanitize a string to be used as a filename.""" - import re - # Remove invalid characters - sanitized = re.sub(r'[^\w\s-]', '', filename) - # Replace spaces with underscores - sanitized = sanitized.strip().replace(' ', '_') - return sanitized \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/ai_story_writer/README.md b/ToBeMigrated/ai_writers/ai_story_writer/README.md deleted file mode 100644 index 5baaf26d..00000000 --- a/ToBeMigrated/ai_writers/ai_story_writer/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# AI Story Generator App - -In the age of AI, creativity and technology are intertwining in ways that are transforming how we tell stories. Imagine having the power to craft a captivating narrative tailored to your exact specifications with just a few clicks. Whether you're an aspiring writer, a seasoned novelist, or just someone who loves a good story, our new AI-powered story writing app is here to make storytelling easier and more engaging than ever before. - -## Why an AI Story Writing App? - -Storytelling has always been a cherished art form, but not everyone finds it easy to start from scratch. With the AI Story Generator App, you can create detailed and personalized stories by simply providing some key inputs. Our app uses advanced AI to turn your ideas into compelling narratives, helping you overcome writer's block and unleashing your creative potential. - -## Features of the AI Story Generator App - -### Genre -Choose from a variety of genres such as Fantasy, Sci-Fi, Mystery, Romance, and Horror to set the tone for your story. - -### Story Setting -Provide a detailed setting for your story, including location and time period. - -For example: -A bustling futuristic city with towering skyscrapers and flying cars, set in the year 2150. The city is known for its technological advancements but has a dark underbelly of crime and corruption. - - -### Main Characters -Input the names, descriptions, and roles of your main characters. - -For example: -Character Names: John, Xishan, Amol -Character Descriptions: John is a tall, muscular man with a kind heart. Xishan is a clever and resourceful woman. Amol is a mischievous and energetic young boy. -Character Roles: John - Hero, Xishan - Sidekick, Amol - Supporting Character - - -### Plot Elements -Outline the key plot elements including the story theme, key events, and main conflict. - -For example: -Story Theme: Love conquers all, The hero's journey, Good vs. evil - -Key Events or Plot Points: - -The hero meets the villain -The hero faces a challenge -The hero overcomes the conflict -Main Conflict or Problem: -The hero must save the world from a powerful enemy, The hero must overcome a personal obstacle to achieve their goal. - - -### Tone and Style -Choose the writing style, tone, and narrative point of view for your story. - -For example: -Writing Style: Formal, Casual, Poetic, Humorous -Story Tone: Dark - -### Perspective -Choose the narrative point of view from which the story is told (e.g., first person, third person limited, third person omniscient). - -### Target Audience -Specify the intended audience age group (Children, Young Adults, Adults) and set a content rating (G, PG, PG-13, R) for appropriateness. - -### Ending Preference -Select the type of ending you prefer for the story (e.g., happy, tragic, cliffhanger, twist). - -## How to Use - -Choose Genre: Select the genre that best fits your story idea. -Set Story Setting: Describe the setting and time period where your story unfolds. -Define Characters: Provide names, descriptions, and roles for your main characters. -Outline Plot Elements: Detail the story's theme, key events, and main conflict. -Select Tone and Style: Choose the writing style and tone that align with your story's mood. -Specify Perspective: Decide on the narrative point of view. -Target Audience: Specify the age group and content rating. -Choose Ending: Select the preferred type of story conclusion. -Generate Story: Click the "Generate Story" button to receive a customized story prompt based on your inputs. - - -### Example Prompt - -**Genre:** Fantasy -**Setting:** A mystical forest in a medieval realm, where magic thrives and mythical creatures roam freely. -**Characters:** -- Name: Elara - Description: Elara is a young elf with a mischievous glint in her emerald eyes, known for her ability to wield powerful spells. - Role: Protagonist -- Name: Thorne - Description: Thorne is a gruff dwarf with a heart of gold, skilled in forging enchanted weapons. - Role: Sidekick -- Name: Malachai - Description: Malachai is a cunning dragon with shimmering scales of azure, whose allegiance is uncertain. - Role: Antagonist - -**Plot Elements:** -- Theme: The power of friendship and bravery in the face of adversity. -- Key Events: Elara discovers an ancient prophecy that foretells a looming darkness threatening the realm. Thorne crafts a legendary sword to aid in their quest. Malachai challenges Elara's resolve, forcing her to make a difficult choice. -- Conflict: Elara must gather allies and confront the dark sorcerer who seeks to plunge the realm into eternal shadow. - -**Writing Style:** Poetic -**Tone:** Whimsical -**Point of View:** Third Person Limited - -**Audience:** Young Adults, **Content Rating:** PG -**Ending:** Happy - - - - diff --git a/ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py b/ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py deleted file mode 100644 index 826c6c0d..00000000 --- a/ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py +++ /dev/null @@ -1,238 +0,0 @@ -##################################################### -# -# google-gemini-cookbook - Story_Writing_with_Prompt_Chaining -# -##################################################### - -import os -from pathlib import Path -import streamlit as st -from loguru import logger -import sys - -from ...gpt_providers.text_generation.main_text_generation import llm_text_gen - - -def generate_with_retry(prompt, system_prompt=None): - """ - Generates content using the llm_text_gen function with retry handling for errors. - - Parameters: - prompt (str): The prompt to generate content from. - system_prompt (str, optional): Custom system prompt to use instead of the default one. - - Returns: - str: The generated content. - """ - try: - # Use llm_text_gen instead of directly calling the model - return llm_text_gen(prompt, system_prompt) - except Exception as e: - logger.error(f"Error generating content: {e}") - return "" - - -def ai_story(persona, story_setting, character_input, - plot_elements, writing_style, story_tone, narrative_pov, - audience_age_group, content_rating, ending_preference): - """ - Write a story using prompt chaining and iterative generation. - - Parameters: - persona (str): The persona statement for the author. - story_setting (str): The setting of the story. - character_input (str): The characters in the story. - plot_elements (str): The plot elements of the story. - writing_style (str): The writing style of the story. - story_tone (str): The tone of the story. - narrative_pov (str): The narrative point of view. - audience_age_group (str): The target audience age group. - content_rating (str): The content rating of the story. - ending_preference (str): The preferred ending of the story. - """ - st.info(f""" - You have chosen to create a story set in **{story_setting}**. - The main characters are: **{character_input}**. - The plot will revolve around the theme of **{plot_elements}**. - The story will be written in a **{writing_style}** style with a **{story_tone}** tone, from a **{narrative_pov}** perspective. - It is intended for a **{audience_age_group}** audience with a **{content_rating}** rating. - You prefer the story to have a **{ending_preference}** ending. - """) - try: - persona = f"""{persona} - Write a story with the following details: - - **The stroy Setting is:** - {story_setting} - - **The Characters of the story are:** - {character_input} - - **Plot Elements of the story:** - {plot_elements} - - **Story Writing Style:** - {writing_style} - - **The story Tone is:** - {story_tone} - - **Write story from the Point of View of:** - {narrative_pov} - - **Target Audience of the story:** - {audience_age_group}, **Content Rating:** {content_rating} - - **Story Ending:** - {ending_preference} - - Make sure the story is engaging and tailored to the specified audience and content rating. - Ensure the ending aligns with the preference indicated. - - """ - # Define persona and writing guidelines - guidelines = f'''\ - Writing Guidelines: - - Delve deeper. Lose yourself in the world you're building. Unleash vivid - descriptions to paint the scenes in your reader's mind. - Develop your characters โ€” let their motivations, fears, and complexities unfold naturally. - Weave in the threads of your outline, but don't feel constrained by it. - Allow your story to surprise you as you write. Use rich imagery, sensory details, and - evocative language to bring the setting, characters, and events to life. - Introduce elements subtly that can blossom into complex subplots, relationships, - or worldbuilding details later in the story. - Keep things intriguing but not fully resolved. - Avoid boxing the story into a corner too early. - Plant the seeds of subplots or potential character arc shifts that can be expanded later. - - Remember, your main goal is to write as much as you can. If you get through - the story too fast, that is bad. Expand, never summarize. - ''' - - # Generate prompts - premise_prompt = f'''\ - {persona} - - Write a single sentence premise for a {story_setting} story featuring {character_input}. - ''' - - outline_prompt = f'''\ - {persona} - - You have a gripping premise in mind: - - {{premise}} - - Write an outline for the plot of your story. - ''' - - starting_prompt = f'''\ - {persona} - - You have a gripping premise in mind: - - {{premise}} - - Your imagination has crafted a rich narrative outline: - - {{outline}} - - First, silently review the outline and the premise. Consider how to start the - story. - - Start to write the very beginning of the story. You are not expected to finish - the whole story now. Your writing should be detailed enough that you are only - scratching the surface of the first bullet of your outline. Try to write AT - MINIMUM 4000 WORDS. - - {guidelines} - ''' - - continuation_prompt = f'''\ - {persona} - - You have a gripping premise in mind: - - {{premise}} - - Your imagination has crafted a rich narrative outline: - - {{outline}} - - You've begun to immerse yourself in this world, and the words are flowing. - Here's what you've written so far: - - {{story_text}} - - ===== - - First, silently review the outline and story so far. Identify what the single - next part of your outline you should write. - - Your task is to continue where you left off and write the next part of the story. - You are not expected to finish the whole story now. Your writing should be - detailed enough that you are only scratching the surface of the next part of - your outline. Try to write AT MINIMUM 2000 WORDS. However, only once the story - is COMPLETELY finished, write IAMDONE. Remember, do NOT write a whole chapter - right now. - - {guidelines} - ''' - - # Generate prompts - try: - premise = generate_with_retry(premise_prompt) - st.info(f"The premise of the story is: {premise}") - except Exception as err: - st.error(f"Premise Generation Error: {err}") - return - - outline = generate_with_retry(outline_prompt.format(premise=premise)) - with st.expander("Click to Checkout the outline, writing still in progress.."): - st.markdown(f"The Outline of the story is: {outline}\n\n") - - if not outline: - st.error("Failed to generate outline. Exiting...") - return - - # Generate starting draft - try: - starting_draft = generate_with_retry( - starting_prompt.format(premise=premise, outline=outline)) - except Exception as err: - st.error(f"Failed to Generate Story draft: {err}") - return - - try: - draft = starting_draft - continuation = generate_with_retry( - continuation_prompt.format(premise=premise, outline=outline, story_text=draft)) - except Exception as err: - st.error(f"Failed to write the initial draft: {err}") - - # Add the continuation to the initial draft, keep building the story until we see 'IAMDONE' - try: - draft += '\n\n' + continuation - except Exception as err: - st.error(f"Failed as: {err} and {continuation}") - - with st.status("Story Writing in Progress..", expanded=True) as status: - status.update(label=f"Writing in progress... Current draft length: {len(draft)} characters") - while 'IAMDONE' not in continuation: - try: - status.update(label=f"Writing in progress... Current draft length: {len(draft)} characters") - continuation = generate_with_retry( - continuation_prompt.format(premise=premise, outline=outline, story_text=draft)) - draft += '\n\n' + continuation - except Exception as err: - st.error(f"Failed to continually write the story: {err}") - return - - # Remove 'IAMDONE' and print the final story - final = draft.replace('IAMDONE', '').strip() - return(final) - - except Exception as e: - st.error(f"Main Story writing: An error occurred: {e}") - return "" diff --git a/ToBeMigrated/ai_writers/ai_story_writer/story_writer.py b/ToBeMigrated/ai_writers/ai_story_writer/story_writer.py deleted file mode 100644 index 4f146c17..00000000 --- a/ToBeMigrated/ai_writers/ai_story_writer/story_writer.py +++ /dev/null @@ -1,134 +0,0 @@ -import time -import os -import json -import streamlit as st - -from .ai_story_generator import ai_story - - -def story_input_section(): - st.title("๐Ÿง• Alwrity - AI Story Writer") - personas = [ - ("Award-Winning Science Fiction Author", "๐Ÿ‘ฝ Award-Winning Science Fiction Author"), - ("Historical Fiction Author", "๐Ÿบ Historical Fiction Author"), - ("Fantasy World Builder", "๐Ÿง™ Fantasy World Builder"), - ("Mystery Novelist", "๐Ÿ•ต๏ธ Mystery Novelist"), - ("Romantic Poet", "๐Ÿ’Œ Romantic Poet"), - ("Thriller Writer", "๐Ÿ”ช Thriller Writer"), - ("Children's Book Author", "๐Ÿ“š Children's Book Author"), - ("Satirical Humorist", "๐Ÿ˜‚ Satirical Humorist"), - ("Biographical Writer", "๐Ÿ“œ Biographical Writer"), - ("Dystopian Visionary", "๐ŸŒ† Dystopian Visionary"), - ("Magical Realism Author", "๐Ÿช„ Magical Realism Author") - ] - - selected_persona_name = st.selectbox( - "Select Your Story Writing Persona Or Book Genre", - options=[persona[0] for persona in personas] - ) - - persona_descriptions = { - "Award-Winning Science Fiction Author": "You are an award-winning science fiction author with a penchant for expansive, intricately woven stories. Your ultimate goal is to write the next award-winning sci-fi novel.", - "Historical Fiction Author": "You are a seasoned historical fiction author, meticulously researching past eras to weave captivating narratives. Your goal is to transport readers to different times and places through your vivid storytelling.", - "Fantasy World Builder": "You are a world-building enthusiast, crafting intricate realms filled with magic, mythical creatures, and epic quests. Your ambition is to create the next immersive fantasy saga that captivates readers' imaginations.", - "Mystery Novelist": "You are a master of suspense and intrigue, intricately plotting out mysteries with unexpected twists and turns. Your aim is to keep readers on the edge of their seats, eagerly turning pages to unravel the truth.", - "Romantic Poet": "You are a romantic at heart, composing verses that capture the essence of love, longing, and human connections. Your dream is to write the next timeless love story that leaves readers swooning.", - "Thriller Writer": "You are a thrill-seeker, crafting adrenaline-pumping tales of danger, suspense, and high-stakes action. Your mission is to keep readers hooked from start to finish with heart-pounding thrills and unexpected twists.", - "Children's Book Author": "You are a storyteller for the young and young at heart, creating whimsical worlds and lovable characters that inspire imagination and wonder. Your goal is to spark joy and curiosity in young readers with enchanting tales.", - "Satirical Humorist": "You are a keen observer of society, using humor and wit to satirize the absurdities of everyday life. Your aim is to entertain and provoke thought, delivering biting social commentary through clever and humorous storytelling.", - "Biographical Writer": "You are a chronicler of lives, delving into the stories of real people and events to illuminate the human experience. Your passion is to bring history to life through richly detailed biographies that resonate with readers.", - "Dystopian Visionary": "You are a visionary writer, exploring dark and dystopian futures that reflect contemporary fears and anxieties. Your vision is to challenge societal norms and provoke reflection on the path humanity is heading.", - "Magical Realism Author": "You are a purveyor of magical realism, blending the ordinary with the extraordinary to create enchanting and thought-provoking tales. Your goal is to blur the lines between reality and fantasy, leaving readers enchanted and introspective." - } - - # Story Setting - st.subheader("๐ŸŒ Story Setting") - story_setting = st.text_area( - label="**Story Setting** (e.g., medieval kingdom in the past, futuristic city in the future, haunted house in the present):", - placeholder="""Enter settings for your story, like Location (e.g., medieval kingdom, futuristic city, haunted house), - Time period in which your story is set (e.g: Past, Present, Future) - Example: 'A bustling futuristic city with towering skyscrapers and flying cars, set in the year 2150. - The city is known for its technological advancements but has a dark underbelly of crime and corruption.'""", - help="Describe the main location and time period where the story will unfold in a detailed manner." - ) - - # Main Characters - st.subheader("๐Ÿ‘ฅ Main Characters") - character_input = st.text_area( - label="**Character Information** (Names, Descriptions, Roles)", - placeholder="""Example: - Character Names: John, Xishan, Amol - Character Descriptions: John is a tall, muscular man with a kind heart. Xishan is a clever and resourceful woman. Amol is a mischievous and energetic young boy. - Character Roles: John - Hero, Xishan - Sidekick, Amol - Supporting Character""", - help="Enter character information as specified in the placeholder." - ) - - # Plot Elements - st.subheader("๐Ÿ—บ๏ธ Plot Elements") - plot_elements = st.text_area( - "**Plot Elements** - (Theme, Key Events & Main Conflict)", - placeholder="""Example: - Story Theme: Love conquers all, The hero's journey, Good vs. evil. - Key Events: The hero meets the villain, The hero faces a challenge, The hero overcomes the conflict. - Main Conflict: The hero must save the world from a powerful enemy, The hero must overcome a personal obstacle to achieve their goal.""", - help="Enter plot elements as specified in the placeholder." - ) - - # Tone and Style - st.subheader("๐ŸŽจ Tone and Style") - col1, col2, col3 = st.columns(3) - with col1: - writing_style = st.selectbox( - "**Writing Style:**", - ["๐Ÿง Formal", "๐Ÿ˜Ž Casual", "๐ŸŽผ Poetic", "๐Ÿ˜‚ Humorous"], - help="Choose the writing style that fits your story." - ) - with col2: - story_tone = st.selectbox( - "**Story Tone:**", - ["๐ŸŒ‘ Dark", "โ˜€๏ธ Uplifting", "โณ Suspenseful", "๐ŸŽˆ Whimsical"], - help="Select the overall tone or mood of the story." - ) - with col3: - narrative_pov = st.selectbox( - "**Narrative Point of View:**", - ["๐Ÿ‘ค First Person", "๐Ÿ‘ฅ Third Person Limited", "๐Ÿ‘๏ธ Third Person Omniscient"], - help="Choose the point of view from which the story is told." - ) - - # Target Audience - st.subheader("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Target Audience") - col1, col2, col3 = st.columns(3) - with col1: - audience_age_group = st.selectbox( - "**Audience Age Group:**", - ["๐Ÿง’ Children", "๐Ÿ‘จโ€๐ŸŽ“ Young Adults", "๐Ÿง‘โ€๐Ÿฆณ Adults"], - help="Choose the intended audience age group." - ) - with col2: - content_rating = st.selectbox( - "**Content Rating:**", - ["๐ŸŸข G", "๐ŸŸก PG", "๐Ÿ”ต PG-13", "๐Ÿ”ด R"], - help="Select a content rating for appropriateness." - ) - with col3: - ending_preference = st.selectbox( - "Story Conclusion:", - ["๐Ÿ˜Š Happy", "๐Ÿ˜ข Tragic", "โ“ Cliffhanger", "๐Ÿ”€ Twist"], - help="Choose the type of ending you prefer for the story." - ) - - if st.button('AI, Write a Story..'): - if character_input.strip(): - with st.spinner("Generating Story...๐Ÿ’ฅ๐Ÿ’ฅ"): - story_content = ai_story(persona_descriptions[selected_persona_name], - story_setting, character_input, plot_elements, writing_style, - story_tone, narrative_pov, audience_age_group, content_rating, - ending_preference) - if story_content: - st.subheader('**๐Ÿง• Your Awesome Story:**') - st.markdown(story_content) - else: - st.error("๐Ÿ’ฅ **Failed to generate Story. Please try again!**") - else: - st.error("Describe the story you have in your mind.. !") diff --git a/ToBeMigrated/ai_writers/data_img_slides_analyst.py b/ToBeMigrated/ai_writers/data_img_slides_analyst.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ToBeMigrated/ai_writers/github_blogs/README.md b/ToBeMigrated/ai_writers/github_blogs/README.md deleted file mode 100644 index 194490a1..00000000 --- a/ToBeMigrated/ai_writers/github_blogs/README.md +++ /dev/null @@ -1,259 +0,0 @@ -# GitHub Blog Generator - -A powerful AI-powered content generation system that automatically creates comprehensive documentation, tutorials, and guides from GitHub repositories. This module transforms GitHub repository data into various types of high-quality technical content. - -## Features - -### 1. Content Generation Types - -The system can generate the following types of content from GitHub repositories: - -- **Getting Started Guides** - - Introduction and Overview - - Prerequisites and Setup - - Installation Instructions - - Basic Usage Examples - - Common Use Cases - - Best Practices - - Next Steps and Resources - -- **Technical Documentation** - - Architecture Overview - - Core Components - - Technical Specifications - - Integration Points - - Performance Considerations - - Security Features - - API Documentation - - Configuration Options - - Deployment Guidelines - - Troubleshooting Guide - -- **Tutorial Series** - - Beginner Tutorials - - Basic concepts - - Simple examples - - Step-by-step instructions - - Intermediate Tutorials - - Advanced features - - Real-world examples - - Best practices - - Advanced Tutorials - - Complex use cases - - Performance optimization - - Integration patterns - -- **Comparison Analysis** - - Feature Comparison - - Performance Analysis - - Use Case Suitability - - Community and Support - - Learning Curve - - Integration Capabilities - - Future Prospects - -- **Case Studies** - - Problem Statement - - Solution Implementation - - Technical Challenges - - Results and Benefits - - Lessons Learned - - Future Improvements - -- **Contribution Guides** - - Development Setup - - Code Style Guidelines - - Testing Requirements - - Documentation Standards - - Pull Request Process - - Review Guidelines - - Community Guidelines - -- **Security Guides** - - Security Architecture - - Authentication & Authorization - - Data Protection - - Secure Configuration - - Vulnerability Management - - Incident Response - - Compliance Requirements - -- **Performance Guides** - - Performance Metrics - - Optimization Techniques - - Benchmarking Guidelines - - Resource Management - - Scaling Strategies - - Monitoring Setup - - Troubleshooting - -### 2. GitHub Content Scraping - -The module includes a sophisticated GitHub content scraper with the following capabilities: - -- **Rate Limiting** - - Configurable API call limits - - Automatic request throttling - - Concurrent request management - -- **Caching System** - - Configurable cache duration (TTL) - - Automatic cache invalidation - - Efficient storage of scraped content - -- **Content Extraction** - - Repository metadata - - README content - - File contents - - Repository topics - - Contributor information - - License information - -### 3. Content Enhancement - -- **Online Research Integration** - - Automatic topic research - - Related content discovery - - Industry trend analysis - -- **FAQ Generation** - - Automatic FAQ creation - - Common question identification - - Comprehensive answers - -- **Metadata Generation** - - SEO-optimized titles - - Meta descriptions - - Tags and categories - - Content structuring - -## Usage Examples - -### Basic Usage - -```python -from lib.ai_writers.github_blogs import GitHubBlogGenerator - -# Initialize the generator -generator = GitHubBlogGenerator() - -# Generate content for a GitHub repository -content = await generator.generate_content( - github_url="https://github.com/owner/repo", - content_types=["getting_started", "technical_docs", "tutorials"] -) - -# Save the generated content -generator.save_content(content, "my_repository") -``` - -### Advanced Usage - -```python -from lib.ai_writers.github_blogs import GitHubBlogGenerator - -# Initialize with custom settings -generator = GitHubBlogGenerator( - cache_dir=".custom_cache", - ttl_hours=48 -) - -# Generate all content types -content_types = [ - "getting_started", - "technical_docs", - "tutorials", - "comparison", - "case_studies", - "contribution", - "security", - "performance" -] - -# Generate content for multiple repositories -urls = [ - "https://github.com/owner/repo1", - "https://github.com/owner/repo2" -] - -for url in urls: - content = await generator.generate_content(url, content_types) - generator.save_content(content, url.split("/")[-1]) -``` - -## Configuration Options - -### GitHubBlogGenerator - -- `cache_dir` (str): Directory for caching scraped content (default: ".github_cache") -- `ttl_hours` (int): Time-to-live for cached content in hours (default: 24) - -### Content Generation - -- `gpt_provider` (str): Choice of AI provider ("gemini" or "openai") -- `content_types` (List[str]): Types of content to generate -- `github_url` (str): URL of the GitHub repository - -## Output Format - -All generated content is saved in Markdown format with the following structure: - -```markdown -# [Title] - -[Generated content based on content type] - -## Metadata -- Title: [SEO-optimized title] -- Description: [Meta description] -- Tags: [Generated tags] -- Categories: [Generated categories] -``` - -## Best Practices - -1. **Rate Limiting** - - Configure appropriate rate limits based on your GitHub API quota - - Use caching to minimize API calls - - Implement proper error handling for rate limit exceeded scenarios - -2. **Content Generation** - - Start with basic content types before generating advanced content - - Review generated content for accuracy and completeness - - Customize prompts for specific repository types - -3. **Caching** - - Set appropriate TTL based on repository update frequency - - Clear cache when repository content changes significantly - - Monitor cache size and performance - -4. **Error Handling** - - Implement proper error handling for API failures - - Log errors for debugging - - Provide fallback mechanisms for failed content generation - -## Dependencies - -- Python 3.8+ -- aiohttp -- beautifulsoup4 -- loguru -- pydantic -- requests -- pandas - -## Contributing - -1. Fork the repository -2. Create a feature branch -3. Commit your changes -4. Push to the branch -5. Create a Pull Request - -## License - -[Your License Here] - -## Support - -For support, please [create an issue](https://github.com/your-repo/issues) or contact the maintainers. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/github_blogs/github_getting_started.py b/ToBeMigrated/ai_writers/github_blogs/github_getting_started.py deleted file mode 100644 index 81b247b5..00000000 --- a/ToBeMigrated/ai_writers/github_blogs/github_getting_started.py +++ /dev/null @@ -1,254 +0,0 @@ -""" -Enhanced GitHub Content Generator - -This module provides various content generation capabilities from GitHub repository data, -including getting started guides, technical documentation, tutorials, and more. -""" - -import sys -from typing import Dict, List, Optional -from loguru import logger - -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}") - -def generate_technical_documentation(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate comprehensive technical documentation from repository data.""" - prompt = f"""As an expert technical writer, create detailed technical documentation for the following GitHub repository: - -Repository Data: -{repo_data} - -Please create a comprehensive technical documentation that includes: -1. Architecture Overview -2. Core Components -3. Technical Specifications -4. Integration Points -5. Performance Considerations -6. Security Features -7. API Documentation (if applicable) -8. Configuration Options -9. Deployment Guidelines -10. Troubleshooting Guide - -Format the documentation in markdown with appropriate headers, code blocks, and diagrams. -Include real-world examples and best practices. -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_getting_started_guide(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate a beginner-friendly getting started guide.""" - prompt = f"""As an expert programmer and teacher, create a comprehensive getting started guide for the following GitHub repository: - -Repository Data: -{repo_data} - -Create a step-by-step guide that includes: -1. Introduction and Overview -2. Prerequisites and Setup -3. Installation Instructions -4. Basic Usage Examples -5. Common Use Cases -6. Best Practices -7. Next Steps and Resources - -Make the guide: -- Beginner-friendly with clear explanations -- Include practical examples with code snippets -- Add emojis for better readability -- Include troubleshooting tips -- Provide links to additional resources -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_tutorial_series(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate a series of tutorials for different skill levels.""" - prompt = f"""As an expert educator, create a series of tutorials for the following GitHub repository: - -Repository Data: -{repo_data} - -Create a structured tutorial series that includes: -1. Beginner Tutorial - - Basic concepts - - Simple examples - - Step-by-step instructions - -2. Intermediate Tutorial - - Advanced features - - Real-world examples - - Best practices - -3. Advanced Tutorial - - Complex use cases - - Performance optimization - - Integration patterns - -Each tutorial should: -- Be self-contained -- Include practical examples -- Have clear learning objectives -- Include exercises and challenges -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_comparison_analysis(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate a comparison analysis with similar tools/frameworks.""" - prompt = f"""As a technical analyst, create a comprehensive comparison analysis for the following GitHub repository: - -Repository Data: -{repo_data} - -Create a detailed comparison that includes: -1. Feature Comparison -2. Performance Analysis -3. Use Case Suitability -4. Community and Support -5. Learning Curve -6. Integration Capabilities -7. Future Prospects - -Include: -- Pros and Cons -- Real-world use cases -- Industry adoption -- Community feedback -- Future roadmap -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_case_studies(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate real-world case studies and success stories.""" - prompt = f"""As a technical writer, create compelling case studies for the following GitHub repository: - -Repository Data: -{repo_data} - -Create detailed case studies that include: -1. Problem Statement -2. Solution Implementation -3. Technical Challenges -4. Results and Benefits -5. Lessons Learned -6. Future Improvements - -Make the case studies: -- Based on real-world scenarios -- Include technical details -- Show measurable results -- Provide actionable insights -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_contribution_guide(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate a comprehensive contribution guide.""" - prompt = f"""As an open-source maintainer, create a detailed contribution guide for the following GitHub repository: - -Repository Data: -{repo_data} - -Create a contribution guide that includes: -1. Development Setup -2. Code Style Guidelines -3. Testing Requirements -4. Documentation Standards -5. Pull Request Process -6. Review Guidelines -7. Community Guidelines - -Make the guide: -- Clear and concise -- Include examples -- Cover all contribution types -- Provide templates -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_security_guide(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate a security best practices guide.""" - prompt = f"""As a security expert, create a comprehensive security guide for the following GitHub repository: - -Repository Data: -{repo_data} - -Create a security guide that includes: -1. Security Architecture -2. Authentication & Authorization -3. Data Protection -4. Secure Configuration -5. Vulnerability Management -6. Incident Response -7. Compliance Requirements - -Make the guide: -- Practical and actionable -- Include security checklists -- Provide code examples -- Cover common vulnerabilities -""" - return _get_llm_response(prompt, gpt_provider) - -def generate_performance_guide(repo_data: Dict, gpt_provider: str = "gemini") -> str: - """Generate a performance optimization guide.""" - prompt = f"""As a performance optimization expert, create a detailed performance guide for the following GitHub repository: - -Repository Data: -{repo_data} - -Create a performance guide that includes: -1. Performance Metrics -2. Optimization Techniques -3. Benchmarking Guidelines -4. Resource Management -5. Scaling Strategies -6. Monitoring Setup -7. Troubleshooting - -Make the guide: -- Data-driven -- Include benchmarks -- Provide optimization tips -- Cover different scales -""" - return _get_llm_response(prompt, gpt_provider) - -def _get_llm_response(prompt: str, gpt_provider: str) -> str: - """Get response from the specified LLM provider.""" - system_prompt = """You are an expert technical writer and GitHub repository analyst with deep expertise in software development, documentation, and technical communication. - - Your role is to create high-quality, accurate, and engaging content based on GitHub repository data. You should: - - 1. **Technical Accuracy** - - Ensure all technical information is precise and up-to-date - - Verify code examples and configurations - - Cross-reference documentation and source code - - Maintain consistency with repository standards - - 2. **Content Structure** - - Use clear hierarchical organization - - Include appropriate code blocks and examples - - Add relevant diagrams and visual aids - - Break complex topics into digestible sections - - 3. **Writing Style** - - Maintain a professional yet approachable tone - - Use active voice and clear language - - Include practical examples and use cases - - Add relevant emojis for better readability - - 4. **Best Practices** - - Follow industry-standard documentation practices - - Include troubleshooting sections - - Add performance considerations - - Address security implications -""" - try: - - llm_response = llm_text_gen(prompt, system_prompt=system_prompt) - except Exception as err: - logger.error(f"Failed to get response from {gpt_provider}: {err}") - raise diff --git a/ToBeMigrated/ai_writers/github_blogs/main_getting_started_blogs.py b/ToBeMigrated/ai_writers/github_blogs/main_getting_started_blogs.py deleted file mode 100644 index 5d84a565..00000000 --- a/ToBeMigrated/ai_writers/github_blogs/main_getting_started_blogs.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -Enhanced GitHub Blog Generator - -This module provides comprehensive content generation from GitHub repositories, -including technical documentation, tutorials, case studies, and more. -""" - -import os -import sys -import datetime -import json -from typing import Dict, List, Optional -from pathlib import Path - -from loguru import logger -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}") - -from .scrape_github_readme import GitHubScraper, GitHubContent -from .scrape_github_readme import get_gh_details_vision, get_readme_content -from .scrape_github_readme import research_github_topics, check_if_already_written -from .github_getting_started import ( - generate_technical_documentation, - generate_getting_started_guide, - generate_tutorial_series, - generate_comparison_analysis, - generate_case_studies, - generate_contribution_guide, - generate_security_guide, - generate_performance_guide -) - - -class GitHubBlogGenerator: - """Generator for various types of GitHub-related content.""" - - def __init__(self, cache_dir: str = ".github_cache", ttl_hours: int = 24): - """Initialize the blog generator.""" - self.cache_dir = Path(cache_dir) - self.scraper = GitHubScraper(cache_dir, ttl_hours) - self.output_dir = Path("generated_content") - self.output_dir.mkdir(exist_ok=True) - - async def generate_content(self, github_url: str, content_types: List[str] = None) -> Dict[str, str]: - """Generate various types of content from a GitHub repository.""" - if content_types is None: - content_types = ["getting_started", "technical_docs", "tutorials"] - - try: - # Scrape GitHub content - repo_content = await self.scraper.scrape_github_content(github_url) - - # Generate different types of content - generated_content = {} - - for content_type in content_types: - if content_type == "getting_started": - content = generate_getting_started_guide(repo_content.dict()) - elif content_type == "technical_docs": - content = generate_technical_documentation(repo_content.dict()) - elif content_type == "tutorials": - content = generate_tutorial_series(repo_content.dict()) - elif content_type == "comparison": - content = generate_comparison_analysis(repo_content.dict()) - elif content_type == "case_studies": - content = generate_case_studies(repo_content.dict()) - elif content_type == "contribution": - content = generate_contribution_guide(repo_content.dict()) - elif content_type == "security": - content = generate_security_guide(repo_content.dict()) - elif content_type == "performance": - content = generate_performance_guide(repo_content.dict()) - else: - logger.warning(f"Unknown content type: {content_type}") - continue - - generated_content[content_type] = content - - # Generate FAQs from online research - try: - research_report = do_online_research(repo_content.title, "gemini", github_url) - faqs = generate_blog_faq(research_report, "gemini") - generated_content["faqs"] = faqs - except Exception as err: - logger.error(f"Failed to generate FAQs: {err}") - - return generated_content - - except Exception as err: - logger.error(f"Failed to generate content: {err}") - raise - - def save_content(self, content: Dict[str, str], base_filename: str): - """Save generated content to files.""" - try: - for content_type, content_text in content.items(): - # Generate metadata for each content type - title, meta_desc, tags, categories = blog_metadata(content_text, "gemini") - - # Create filename with content type - filename = f"{base_filename}_{content_type}.md" - - # Save content to file - save_blog_to_file( - content_text, - title, - meta_desc, - tags, - categories, - None # No image path for now - ) - - logger.info(f"Saved {content_type} content to {filename}") - - except Exception as err: - logger.error(f"Failed to save content: {err}") - raise - -async def main(): - """Example usage of the GitHub blog generator.""" - generator = GitHubBlogGenerator() - - # Example GitHub URLs - urls = [ - "https://github.com/owner/repo", - "https://github.com/owner/another-repo" - ] - - content_types = [ - "getting_started", - "technical_docs", - "tutorials", - "comparison", - "case_studies", - "contribution", - "security", - "performance" - ] - - for url in urls: - try: - # Generate content - content = await generator.generate_content(url, content_types) - - # Create base filename from URL - base_filename = url.split("/")[-1] - - # Save content - generator.save_content(content, base_filename) - - except Exception as e: - logger.error(f"Error processing {url}: {e}") - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/ToBeMigrated/ai_writers/github_blogs/scrape_github_readme.py b/ToBeMigrated/ai_writers/github_blogs/scrape_github_readme.py deleted file mode 100644 index 98efd98a..00000000 --- a/ToBeMigrated/ai_writers/github_blogs/scrape_github_readme.py +++ /dev/null @@ -1,427 +0,0 @@ -""" -Enhanced GitHub Content Scraper with Rate Limiting and Caching - -This module provides functionality to scrape GitHub repositories, READMEs, and code files -for content marketing purposes. It includes async support, rate limiting, caching, -and comprehensive metadata collection. -""" - -import os -import sys -import json -import asyncio -import aiohttp -from datetime import datetime, timedelta -from typing import Dict, List, Optional, Union -from urllib.parse import urljoin, urlparse -import pandas as pd -from bs4 import BeautifulSoup -from loguru import logger -import requests -from pydantic import BaseModel, Field -import time -import pickle -from pathlib import Path - -# Configure logging -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}") - -class RateLimiter: - """Rate limiter for GitHub API requests.""" - - def __init__(self, calls_per_minute: int = 30): - self.calls_per_minute = calls_per_minute - self.interval = 60 / calls_per_minute # seconds between calls - self.last_call_time = 0 - self.lock = asyncio.Lock() - - async def acquire(self): - """Acquire rate limit token.""" - async with self.lock: - current_time = time.time() - time_since_last_call = current_time - self.last_call_time - - if time_since_last_call < self.interval: - await asyncio.sleep(self.interval - time_since_last_call) - - self.last_call_time = time.time() - -class Cache: - """Cache for GitHub content.""" - - def __init__(self, cache_dir: str = ".github_cache", ttl_hours: int = 24): - self.cache_dir = Path(cache_dir) - self.ttl = timedelta(hours=ttl_hours) - self.cache_dir.mkdir(exist_ok=True) - - def _get_cache_path(self, key: str) -> Path: - """Get cache file path for a key.""" - return self.cache_dir / f"{hash(key)}.cache" - - def get(self, key: str) -> Optional[Dict]: - """Get cached value for key.""" - cache_path = self._get_cache_path(key) - - if not cache_path.exists(): - return None - - try: - with open(cache_path, 'rb') as f: - data = pickle.load(f) - if datetime.now() - data['timestamp'] > self.ttl: - cache_path.unlink() - return None - return data['value'] - except Exception as e: - logger.warning(f"Cache read error for {key}: {e}") - return None - - def set(self, key: str, value: Dict): - """Set cache value for key.""" - cache_path = self._get_cache_path(key) - - try: - with open(cache_path, 'wb') as f: - pickle.dump({ - 'timestamp': datetime.now(), - 'value': value - }, f) - except Exception as e: - logger.warning(f"Cache write error for {key}: {e}") - -class GitHubContent(BaseModel): - """Model for GitHub content analysis.""" - title: str = Field("", description="Title of the content") - description: str = Field("", description="Description of the content") - content: str = Field("", description="Main content") - language: str = Field("", description="Programming language") - stars: int = Field(0, description="Number of stars") - forks: int = Field(0, description="Number of forks") - watchers: int = Field(0, description="Number of watchers") - last_updated: str = Field("", description="Last update date") - topics: List[str] = Field([], description="Repository topics") - contributors: List[str] = Field([], description="Contributor usernames") - readme_url: str = Field("", description="URL of the README") - raw_content_url: str = Field("", description="URL for raw content") - license: str = Field("", description="Repository license") - dependencies: List[str] = Field([], description="Project dependencies") - metadata: Dict = Field({}, description="Additional metadata") - -class GitHubScraper: - """Service for scraping GitHub content with rate limiting and caching.""" - - def __init__(self, cache_dir: str = ".github_cache", ttl_hours: int = 24, calls_per_minute: int = 30): - """Initialize the scraper service.""" - self.session = None - self.headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'application/vnd.github.v3+json' - } - self.rate_limiter = RateLimiter(calls_per_minute) - self.cache = Cache(cache_dir, ttl_hours) - - async def __aenter__(self): - """Create aiohttp session when entering context.""" - self.session = aiohttp.ClientSession(headers=self.headers) - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - """Close aiohttp session when exiting context.""" - if self.session: - await self.session.close() - - async def fetch_url(self, url: str, use_cache: bool = True) -> str: - """Fetch URL content asynchronously with rate limiting and caching.""" - if use_cache: - cached_content = self.cache.get(url) - if cached_content: - logger.debug(f"Cache hit for {url}") - return cached_content - - await self.rate_limiter.acquire() - - try: - async with self.session.get(url) as response: - if response.status == 200: - content = await response.text() - if use_cache: - self.cache.set(url, content) - return content - else: - error_msg = f"Failed to fetch URL: Status code {response.status}" - logger.error(error_msg) - raise Exception(error_msg) - except Exception as e: - logger.error(f"Error fetching URL {url}: {e}") - raise - - def parse_github_url(self, url: str) -> Dict[str, str]: - """Parse GitHub URL to extract repository information.""" - parsed = urlparse(url) - path_parts = parsed.path.strip('/').split('/') - - if len(path_parts) < 2: - raise ValueError("Invalid GitHub URL format") - - return { - 'owner': path_parts[0], - 'repo': path_parts[1], - 'branch': path_parts[3] if len(path_parts) > 3 else 'main', - 'path': '/'.join(path_parts[4:]) if len(path_parts) > 4 else '' - } - - async def get_repo_metadata(self, owner: str, repo: str) -> Dict: - """Get repository metadata from GitHub API with caching.""" - cache_key = f"metadata_{owner}_{repo}" - cached_metadata = self.cache.get(cache_key) - if cached_metadata: - return cached_metadata - - await self.rate_limiter.acquire() - - api_url = f"https://api.github.com/repos/{owner}/{repo}" - try: - async with self.session.get(api_url) as response: - if response.status == 200: - metadata = await response.json() - self.cache.set(cache_key, metadata) - return metadata - else: - logger.error(f"Failed to fetch repo metadata: {response.status}") - return {} - except Exception as e: - logger.error(f"Error fetching repo metadata: {e}") - return {} - - async def get_readme_content(self, owner: str, repo: str, branch: str = 'main') -> Dict: - """Get README content from GitHub with caching.""" - cache_key = f"readme_{owner}_{repo}_{branch}" - cached_content = self.cache.get(cache_key) - if cached_content: - return cached_content - - try: - # Try to get README from API first - await self.rate_limiter.acquire() - api_url = f"https://api.github.com/repos/{owner}/{repo}/readme" - async with self.session.get(api_url) as response: - if response.status == 200: - readme_data = await response.json() - content = { - 'content': readme_data.get('content', ''), - 'encoding': readme_data.get('encoding', 'base64'), - 'url': readme_data.get('html_url', '') - } - self.cache.set(cache_key, content) - return content - - # Fallback to scraping if API fails - readme_url = f"https://github.com/{owner}/{repo}/blob/{branch}/README.md" - html_content = await self.fetch_url(readme_url, use_cache=True) - soup = BeautifulSoup(html_content, 'html.parser') - - # Find the README content - readme_content = soup.find('div', {'class': 'markdown-body'}) - if readme_content: - content = { - 'content': readme_content.get_text(), - 'encoding': 'text', - 'url': readme_url - } - self.cache.set(cache_key, content) - return content - - return {} - except Exception as e: - logger.error(f"Error fetching README: {e}") - return {} - - async def get_file_content(self, owner: str, repo: str, path: str, branch: str = 'main') -> Dict: - """Get content of a specific file from GitHub with caching.""" - cache_key = f"file_{owner}_{repo}_{path}_{branch}" - cached_content = self.cache.get(cache_key) - if cached_content: - return cached_content - - try: - # Try to get file content from API first - await self.rate_limiter.acquire() - api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={branch}" - async with self.session.get(api_url) as response: - if response.status == 200: - file_data = await response.json() - content = { - 'content': file_data.get('content', ''), - 'encoding': file_data.get('encoding', 'base64'), - 'url': file_data.get('html_url', '') - } - self.cache.set(cache_key, content) - return content - - # Fallback to scraping if API fails - file_url = f"https://github.com/{owner}/{repo}/blob/{branch}/{path}" - html_content = await self.fetch_url(file_url, use_cache=True) - soup = BeautifulSoup(html_content, 'html.parser') - - # Find the file content - file_content = soup.find('div', {'class': 'file-content'}) - if file_content: - content = { - 'content': file_content.get_text(), - 'encoding': 'text', - 'url': file_url - } - self.cache.set(cache_key, content) - return content - - return {} - except Exception as e: - logger.error(f"Error fetching file content: {e}") - return {} - - async def get_repo_topics(self, owner: str, repo: str) -> List[str]: - """Get repository topics with caching.""" - cache_key = f"topics_{owner}_{repo}" - cached_topics = self.cache.get(cache_key) - if cached_topics: - return cached_topics - - try: - await self.rate_limiter.acquire() - api_url = f"https://api.github.com/repos/{owner}/{repo}/topics" - async with self.session.get(api_url, headers={'Accept': 'application/vnd.github.mercy-preview+json'}) as response: - if response.status == 200: - data = await response.json() - topics = data.get('names', []) - self.cache.set(cache_key, topics) - return topics - return [] - except Exception as e: - logger.error(f"Error fetching topics: {e}") - return [] - - async def get_contributors(self, owner: str, repo: str) -> List[str]: - """Get repository contributors with caching.""" - cache_key = f"contributors_{owner}_{repo}" - cached_contributors = self.cache.get(cache_key) - if cached_contributors: - return cached_contributors - - try: - await self.rate_limiter.acquire() - api_url = f"https://api.github.com/repos/{owner}/{repo}/contributors" - async with self.session.get(api_url) as response: - if response.status == 200: - contributors = await response.json() - contributor_list = [contributor['login'] for contributor in contributors] - self.cache.set(cache_key, contributor_list) - return contributor_list - return [] - except Exception as e: - logger.error(f"Error fetching contributors: {e}") - return [] - - async def scrape_github_content(self, url: str) -> GitHubContent: - """Main function to scrape GitHub content with caching.""" - cache_key = f"content_{url}" - cached_content = self.cache.get(cache_key) - if cached_content: - return GitHubContent(**cached_content) - - try: - # Parse the GitHub URL - repo_info = self.parse_github_url(url) - - # Get repository metadata - metadata = await self.get_repo_metadata(repo_info['owner'], repo_info['repo']) - - # Get content based on URL type - if not repo_info['path'] or repo_info['path'].lower() == 'readme.md': - content_data = await self.get_readme_content( - repo_info['owner'], - repo_info['repo'], - repo_info['branch'] - ) - else: - content_data = await self.get_file_content( - repo_info['owner'], - repo_info['repo'], - repo_info['path'], - repo_info['branch'] - ) - - # Get additional metadata - topics = await self.get_repo_topics(repo_info['owner'], repo_info['repo']) - contributors = await self.get_contributors(repo_info['owner'], repo_info['repo']) - - # Create GitHubContent object - content = GitHubContent( - title=metadata.get('name', ''), - description=metadata.get('description', ''), - content=content_data.get('content', ''), - language=metadata.get('language', ''), - stars=metadata.get('stargazers_count', 0), - forks=metadata.get('forks_count', 0), - watchers=metadata.get('watchers_count', 0), - last_updated=metadata.get('updated_at', ''), - topics=topics, - contributors=contributors, - readme_url=content_data.get('url', ''), - raw_content_url=metadata.get('html_url', ''), - license=metadata.get('license', {}).get('name', ''), - metadata={ - 'size': metadata.get('size', 0), - 'open_issues': metadata.get('open_issues_count', 0), - 'default_branch': metadata.get('default_branch', 'main'), - 'created_at': metadata.get('created_at', ''), - 'pushed_at': metadata.get('pushed_at', '') - } - ) - - # Cache the complete content - self.cache.set(cache_key, content.dict()) - - return content - - except Exception as e: - logger.error(f"Error scraping GitHub content: {e}") - raise - -async def main(): - """Example usage of the GitHub scraper with rate limiting and caching.""" - scraper = GitHubScraper( - cache_dir=".github_cache", - ttl_hours=24, - calls_per_minute=30 - ) - - async with scraper: - # Example URLs - urls = [ - "https://github.com/owner/repo", - "https://github.com/owner/repo/blob/main/README.md", - "https://github.com/owner/repo/blob/main/src/main.py" - ] - - for url in urls: - try: - content = await scraper.scrape_github_content(url) - print(f"Scraped content from {url}:") - print(json.dumps(content.dict(), indent=2)) - except Exception as e: - print(f"Error scraping {url}: {e}") - - -if __name__ == "__main__": - asyncio.run(main()) - - - - - - - - diff --git a/ToBeMigrated/ai_writers/gpt_blog_sections.py b/ToBeMigrated/ai_writers/gpt_blog_sections.py deleted file mode 100644 index a266a31c..00000000 --- a/ToBeMigrated/ai_writers/gpt_blog_sections.py +++ /dev/null @@ -1,50 +0,0 @@ -import sys -import os -import json - -from ..gpt_providers.text_generation.openai_text_gen import openai_text_generation -from ..gpt_providers.text_generation.gemini_pro_text import gemini_text_generation - -from loguru import logger -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}" - ) - - -# FIXME: Provide num_blogs, num_faqs as inputs. -def get_blog_sections_from_websearch(search_keyword, search_results): - """Combine the given online research and gpt blog content""" - gpt_providers = os.environ["GPT_PROVIDER"] - prompt = f""" - As a SEO expert and content writer, I will provide you with a search keyword and its google search result. - Your task is to write a blog title and 5 blog sub titles, from the given google search result. - The subtitles should be less than 40 characters and click worthy. - Do not explain, describe your response. Respond in json format, always name the key as 'blogSections'. - - Web Research Keyword: "{search_keyword}" - Google search Result: "{search_results}" - """ - - if 'gemini' in gpt_providers: - try: - response = gemini_text_response(prompt) - if '```' in response and '\n' in response: - response = response.strip().split('\n') - # Remove the first and last lines - response = '\n'.join(response[1:-1]) - response = json.loads(response) - return response - except Exception as err: - logger.error(f"Failed to get response from gemini: {err}") - logger.error(f"Gemini Error: {response.prompt_feedback}") - raise err - elif 'openai' in gpt_providers: - try: - logger.info("Calling OpenAI LLM.") - response = openai_chatgpt(prompt) - return response - except Exception as err: - logger.error(f"Failed to get response from Openai: {err}") - raise err diff --git a/ToBeMigrated/ai_writers/image_ai_writer.py b/ToBeMigrated/ai_writers/image_ai_writer.py deleted file mode 100644 index 64a4ae6f..00000000 --- a/ToBeMigrated/ai_writers/image_ai_writer.py +++ /dev/null @@ -1,109 +0,0 @@ -import sys -import os - -from textwrap import dedent -import json -import asyncio -from pathlib import Path -from datetime import datetime -import streamlit as st - -from dotenv import load_dotenv -load_dotenv(Path('../../.env')) -from loguru import logger -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}" - ) - -from ..ai_web_researcher.firecrawl_web_crawler import scrape_url -from ..blog_metadata.get_blog_metadata import blog_metadata -from ..blog_postprocessing.save_blog_to_file import save_blog_to_file -from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image -from ..gpt_providers.text_generation.main_text_generation import llm_text_gen -from ..gpt_providers.image_to_text_gen.gemini_image_describe import describe_image, analyze_image_with_prompt - - -def blog_from_image(prompt, uploaded_img): - """ - This function will take a blog Topic to first generate sections for it - and then generate content for each section. - """ - # Use to store the blog in a string, to save in a *.md file. - blog_markdown_str = None - logger.info(f"Researching and Writing Blog on {uploaded_img} and {prompt}") - # FIXME: Implement support for Openai. - if not os.getenv("GEMINI_API_KEY"): - st.error("Only Gemini supported, Open Issue ticket on github for Openai, others.") - st.stop() - - with st.status("Started Writing from Image..", expanded=True) as status: - st.empty() - status.update(label=f"Researching and Writing Blog on given Image") - try: - blog_markdown_str = write_blog_from_image(prompt, uploaded_img) - except Exception as err: - st.error(f"Failed to write blog from Image - Error: {err}") - logger.error(f"Failed to write blog from image: {err}") - st.stop() - status.update(label="Successfully wrote blog from image.", expanded=False, state="complete") - - try: - status.update(label="๐Ÿ™Ž Generating - Title, Meta Description, Tags, Categories for the content.") - blog_title, blog_meta_desc, blog_tags, blog_categories = asyncio.run(blog_metadata(blog_markdown_str)) - except Exception as err: - st.error(f"Failed to get blog metadata: {err}") - - try: - status.update(label="๐Ÿ™Ž Generating Image for the new blog.") - generated_image_filepath = generate_image(f"{blog_title} + ' ' + {blog_meta_desc}") - except Exception as err: - st.warning(f"Failed in Image generation: {err}") - - saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc, - blog_tags, blog_categories, generated_image_filepath) - status.update(label=f"Saved the content in this file: {saved_blog_to_file}") - logger.info(f"\n\n --------- Finished writing Blog -------------- \n") - st.image(generated_image_filepath, caption=blog_title) - st.markdown(f"{blog_markdown_str}") - status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}", state="complete") - - # Clean up the temporary file after processing (optional) - os.remove(uploaded_img) - - -def write_blog_from_image(prompt, uploaded_img): - """Combine the given online research and GPT blog content""" - try: - config_path = Path(os.environ["ALWRITY_CONFIG"]) - with open(config_path, 'r', encoding='utf-8') as file: - config = json.load(file) - except Exception as err: - logger.error(f"Error: Failed to read values from config: {err}") - exit(1) - - blog_characteristics = config['Blog Content Characteristics'] - - if not prompt: - prompt = f""" - As expert Creative Content writer, analyse the given image carefully. - I want you to write a detailed {blog_characteristics['Blog Type']} blog post including 5 FAQs. - - Below are the guidelines to follow: - 1). You must respond in {blog_characteristics['Blog Language']} language. - 2). Tone and Brand Alignment: Adjust your tone, voice, personality for {blog_characteristics['Blog Tone']} audience. - 3). Make sure your response content length is of {blog_characteristics['Blog Length']} words. - """ - logger.info("Generating blog and FAQs from image analysis.") - - try: - # Use the gemini_image_describe function to analyze the image with the custom prompt - response = analyze_image_with_prompt(uploaded_img, prompt) - if not response: - logger.error("Failed to get response from image analysis") - return "Failed to generate content from image." - return response - except Exception as err: - logger.error(f"Exit: Failed to get response from image analysis: {err}") - exit(1) diff --git a/ToBeMigrated/ai_writers/speech_to_blog/main_audio_to_blog.py b/ToBeMigrated/ai_writers/speech_to_blog/main_audio_to_blog.py deleted file mode 100644 index 677bb19f..00000000 --- a/ToBeMigrated/ai_writers/speech_to_blog/main_audio_to_blog.py +++ /dev/null @@ -1,143 +0,0 @@ -import os -import datetime #I wish -import sys -from textwrap import dedent -from tqdm import tqdm, trange -import time - -from pytubefix import YouTube -import tempfile -from html2image import Html2Image - -from loguru import logger -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}" - ) - -from ...ai_web_researcher.gpt_online_researcher import do_google_serp_search -from ..ai_blog_writer.blog_from_google_serp import blog_with_research -from ...blog_metadata.get_blog_metadata import blog_metadata -from ...blog_postprocessing.save_blog_to_file import save_blog_to_file -from ...gpt_providers.audio_to_text_generation.stt_audio_blog import speech_to_text -from ...gpt_providers.text_generation.main_text_generation import llm_text_gen - - -def youtube_to_blog(video_url): - """Function to transcribe a given youtube url """ - try: - # Starting the speech-to-text process - logger.info("Starting with Speech to Text.") - audio_text, audio_title = speech_to_text(video_url) - except Exception as e: - logger.error(f"Error in speech_to_text: {e}") - sys.exit(1) # Exit the program due to error in speech_to_text - - try: - # Summarizing the content of the YouTube video - audio_blog_content = summarize_youtube_video(audio_text) - logger.info("Successfully converted given URL to blog article.") - return audio_blog_content, audio_title - except Exception as e: - logger.error(f"Error in summarize_youtube_video: {e}") - return False - - -def summarize_youtube_video(user_content): - """Generates a summary of a YouTube video using OpenAI GPT-3 and displays a progress bar. - Args: - video_link: The URL of the YouTube video to summarize. - Returns: - A string containing the summary of the video. - """ - - logger.info("Start summarize_youtube_video..") - prompt = f""" - You are an expert copywriter specializing in digital content writing. I will provide you with a transcript. - Your task is to transform a given transcript into a well-structured and informative blog article. - Please follow the below objectives: - - 1. Master the Transcript: Understand main ideas, key points, and the core message. - 2. Sentence Structure: Rephrase while preserving logical flow and coherence. Dont quote anyone from video. - 3. Note: Check if the transcript is about programming, then include code examples and snippets in your article. - 4. Write Unique Content: Avoid direct copying; rewrite in your own words. - 5. REMEMBER to avoid direct quoting and maintain uniqueness. - 6. Proofread: Check for grammar, spelling, and punctuation errors. - 7. Use Creative and Human-like Style: Incorporate contractions, idioms, transitional phrases, interjections, and colloquialisms. 8. Avoid repetitive phrases and unnatural sentence structures. - 9. Ensure Uniqueness: Guarantee the article is plagiarism-free. - 10. Punctuation: Use appropriate question marks at the end of questions. - 11. Pass AI Detection Tools: Create content that easily passes AI plagiarism detection tools. - 12. Rephrase words like 'video, youtube, channel' with 'article, blog' and such suitable words. - - Follow the above guidelines to create a well-optimized, unique, and informative article, - that will rank well in search engine results and engage readers effectively. - Follow above guidelines to craft a blog content from the following transcript:\n{user_content} - """ - try: - response = llm_text_gen(prompt) - return response - except Exception as err: - logger.error(f"Failed to summarize_youtube_video: {err}") - exit(1) - - -def generate_audio_blog(audio_input): - """Takes a list of youtube videos and generates blog for each one of them. - """ - # Use to store the blog in a string, to save in a *.md file. - blog_markdown_str = "" - try: - logger.info(f"Starting to write blog on URL: {audio_input}") - yt_blog, yt_title = youtube_to_blog(audio_input) - except Exception as e: - logger.error(f"Error in youtube_to_blog: {e}") - sys.exit(1) - - try: - logger.info("Starting with online research for URL title.") - research_report = do_google_serp_search(yt_title) - print(research_report) - except Exception as e: - logger.error(f"Error in do_online_research: {e}") - sys.exit(1) - - try: - # Note: Check if the order of input matters for your function - logger.info("Preparing a blog content from audio script and online research content...") - blog_markdown_str = blog_with_research(research_report, yt_blog) - except Exception as e: - logger.error(f"Error in blog_with_research: {e}") - sys.exit(1) - - try: - import asyncio - # blog_metadata now returns 6 values: title, desc, tags, categories, hashtags, slug - blog_title, blog_meta_desc, blog_tags, blog_categories, blog_hashtags, blog_slug = asyncio.run(blog_metadata(blog_markdown_str)) - except Exception as err: - logger.error(f"Failed to generate blog metadata: {err}") - # Set defaults in case of failure - blog_title = "Blog Article" - blog_meta_desc = "An informative blog post" - blog_tags = "content, blog" - blog_categories = "General, Information" - blog_hashtags = "#content #blog" - blog_slug = "blog-article" - - try: - # TBD: Save the blog content as a .md file. Markdown or HTML ? - # Initialize generated_image_filepath to None since it's not generated in this function - generated_image_filepath = None - save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc, blog_tags, blog_categories, generated_image_filepath) - except Exception as err: - logger.error(f"Failed to save final blog in a file: {err}") - - blog_frontmatter = dedent(f"""\n\n\n\ - --- - title: {blog_title} - categories: [{blog_categories}] - tags: [{blog_tags}] - Meta description: {blog_meta_desc.replace(":", "-")} - ---\n\n""") - logger.info(f"{blog_frontmatter}{blog_markdown_str}") - logger.info(f"\n\n ################ Finished writing Blog for : {audio_input} #################### \n") diff --git a/ToBeMigrated/ai_writers/twitter_writers/README.md b/ToBeMigrated/ai_writers/twitter_writers/README.md deleted file mode 100644 index ea0c179c..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# Twitter AI Writer Module - -A comprehensive suite of AI-powered tools for Twitter/X content marketing and management. - -## Features - -### 1. Tweet Generation & Optimization -- **Smart Tweet Generator** - - Multiple tweet variations based on input parameters - - Character count optimization - - Hashtag suggestions and placement - - Emoji usage recommendations - - Thread creation capabilities - -- **Tweet Performance Predictor** - - Engagement rate estimation - - Best time to post suggestions - - Audience reach predictions - - Viral potential scoring - -### 2. Content Strategy Tools -- **Content Calendar Generator** - - Weekly/monthly content planning - - Theme-based content scheduling - - Event and holiday integration - - Content mix recommendations - -- **Hashtag Strategy Manager** - - Trending hashtag research - - Custom hashtag creation - - Hashtag performance tracking - - Competitor hashtag analysis - -### 3. Visual Content Creation -- **Image Generator** - - Tweet card creation - - Infographic templates - - Quote card designs - - Brand-consistent visuals - -- **Video Content Assistant** - - Video script generation - - Storyboard creation - - Caption optimization - - Thumbnail design suggestions - -### 4. Engagement & Community Management -- **Reply Generator** - - Context-aware responses - - Tone matching - - Crisis management templates - - Customer service responses - -- **Community Engagement Tools** - - Poll creation - - Q&A session planning - - Community highlight suggestions - - User-generated content prompts - -### 5. Analytics & Optimization -- **Performance Analytics** - - Tweet performance tracking - - Engagement metrics analysis - - Audience growth monitoring - - Content effectiveness scoring - -- **A/B Testing Assistant** - - Tweet variation testing - - Headline optimization - - CTA effectiveness analysis - - Best performing content identification - -### 6. Research & Intelligence -- **Market Research Tools** - - Competitor analysis - - Industry trend tracking - - Audience sentiment analysis - - Content gap identification - -- **Content Inspiration** - - Trending topic suggestions - - Content idea generation - - Viral content analysis - - Industry-specific insights - -## Best Practices Integration - -### Tweet Optimization -- Optimal character count (240-280) -- Strategic hashtag placement -- Effective use of mentions and links -- Engaging call-to-actions -- Visual content optimization - -### Content Strategy -- Consistent brand voice -- Regular posting schedule -- Content variety maintenance -- Engagement-driven approach -- Community building focus - -### Visual Content -- Image size optimization -- Brand color consistency -- Text overlay best practices -- Mobile-friendly design -- Visual hierarchy principles - -### Engagement -- Response time optimization -- Community management guidelines -- Crisis communication protocols -- User interaction best practices -- Content moderation assistance - -## Technical Integration - -### API Integration -- Twitter API v2 support -- Rate limit management -- Error handling -- Data synchronization - -### Performance Optimization -- Caching mechanisms -- Batch processing -- Resource optimization -- Response time improvement - -## Security & Compliance - -### Data Protection -- User data encryption -- Secure API key management -- Privacy compliance -- Data retention policies - -### Content Guidelines -- Platform policy compliance -- Copyright protection -- Brand safety measures -- Content moderation rules - -## Coming Soon -- Advanced thread generator -- AI-powered image editor -- Real-time trend analyzer -- Automated content scheduler -- Advanced analytics dashboard -- Multi-account management -- Custom AI model training -- Integration with other social platforms - -## Usage Guidelines -1. Ensure API keys are properly configured -2. Follow Twitter's terms of service -3. Maintain brand voice consistency -4. Regular content calendar updates -5. Monitor performance metrics -6. Engage with community regularly -7. Update content strategy based on analytics -8. Follow security best practices - -## Support -For technical support or feature requests, please contact the development team or raise an issue in the repository. https://github.com/AJaySi/AI-Writer/issues \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/__init__.py b/ToBeMigrated/ai_writers/twitter_writers/__init__.py deleted file mode 100644 index f2eca202..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Twitter AI Writer Module - -A comprehensive suite of AI-powered tools for Twitter/X content marketing and management. -""" - -from .twitter_dashboard import run_dashboard - -__all__ = ['run_dashboard'] \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/README.md b/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/README.md deleted file mode 100644 index cc45bf2f..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/README.md +++ /dev/null @@ -1,163 +0,0 @@ -Hereโ€™s an improved and enhanced version of your README. I've structured it for clarity, conciseness, and professionalism, while also making it more engaging and user-friendly. - ---- - -# ๐Ÿฆ Smart Tweet Generator - -**Create tweets that stand out!** The Smart Tweet Generator is a cutting-edge AI-powered tool designed to craft optimized, engaging tweets that maximize your audience reach and engagement. - ---- - -## โœจ Key Features - -### 1. **Multi-Variation Tweet Generation** -- Generate 1โ€“5 tweet variations from a single prompt. -- Each variation tailored to different engagement styles. -- Consistent tone and messaging across all versions. - -### 2. **Real-Time Character Optimization** -- Live character count tracking, including emoji support. -- Visual indicators to maintain the ideal tweet length. -- Alerts when nearing Twitter's 280-character limit. - -### 3. **Intelligent Hashtag Management** -- Auto-extract hashtags from generated tweets. -- Topic-based, AI-suggested hashtags to enhance discoverability. -- Recommendations for optimal hashtag count and placement. - -### 4. **Emoji Suggestions That Fit** -- Context-sensitive and tone-appropriate emoji suggestions. -- Categories include: - - **Humorous**: ๐Ÿ˜„ ๐Ÿ˜‚ ๐Ÿ˜‰ - - **Informative**: ๐Ÿ“Š ๐Ÿ” ๐Ÿ’ก - - **Inspirational**: โœจ ๐ŸŒŸ ๐Ÿ”ฅ - - **Serious**: ๐Ÿค” ๐Ÿ“ข ๐Ÿ”” - - **Casual**: ๐Ÿ‘‹ ๐Ÿ‘ ๐Ÿค— - -### 5. **Performance Prediction** -- Engagement score (0-100%) based on AI analysis. -- Metrics analyzed include: - - Character count optimization. - - Hashtag effectiveness. - - Emoji usage. - - Audience relevance. -- Categories: - - **Excellent** (80โ€“100%) - - **Good** (60โ€“79%) - - **Fair** (40โ€“59%) - - **Needs Improvement** (0โ€“39%) - -### 6. **Actionable Improvement Suggestions** -- Real-time feedback on tweet quality. -- Tailored recommendations to boost performance. -- Built-in best practices guidance for effective tweeting. - ---- - -## ๐ŸŽฏ How to Use - -### Step 1: **Enter Basic Information** -- Add your tweet topic or hook. -- Define the target audience. -- Choose the desired tone and tweet length. -- Optionally, include a call-to-action (CTA). - -### Step 2: **Customize Advanced Options** -- Select the number of tweet variations (1โ€“5). -- Input keywords or hashtags. -- Choose emoji preferences. -- Add @mentions or placeholders for links. - -### Step 3: **Generate and Refine** -- Click **Generate Tweets** to create variations. -- Review performance metrics and apply improvement suggestions. -- Copy, save, or export your favorite version. - ---- - -## ๐Ÿ“Š Performance Metrics - -**Your tweets are analyzed based on:** - -1. **Character Count** - - Optimal: 100โ€“200 characters. - - Short: <100 characters. - - Long: >200 characters. - -2. **Hashtag Usage** - - Optimal: 1โ€“3 hashtags. - - Too few: 0 hashtags. - - Too many: >3 hashtags. - -3. **Engagement Triggers** - - Questions, CTAs, or interactive elements. - -4. **Emoji Optimization** - - Ideal: 1โ€“3 emojis. - - Too few: 0 emojis. - - Too many: >3 emojis. - -5. **Audience Relevance** - - Alignment with keywords, tone, and context. - ---- - -## ๐Ÿ’ก Best Practices - -1. **Craft Attention-Grabbing Hooks** - - Start with bold statements or thought-provoking questions. - - Use stats or facts to capture attention. - -2. **Align Tone with Audience** - - Maintain consistency with your brand voice. - - Adapt tone to audience preferences (e.g., formal, casual). - -3. **Strategic Hashtag Usage** - - Use trending and relevant hashtags. - - Limit to 1โ€“3 for optimal engagement. - -4. **Effective Emoji Usage** - - Enhance meaning and context with emojis. - - Match the tone and avoid overuse. - -5. **Clear Calls-to-Action** - - Encourage action with clarity and urgency. - - Use action verbs like "Discover," "Join," or "Explore." - ---- - -## ๐Ÿ”„ Export Options - -- Copy individual tweets. -- Export all variations as a JSON file. -- Save performance metrics and recommendations. - ---- - -## ๐Ÿ› ๏ธ Technical Details - -- **Built with:** Streamlit for an intuitive user interface. -- **AI-powered:** Advanced natural language models for tweet generation. -- **Real-time:** Instant feedback and suggestions. -- **Cross-platform compatibility:** Works seamlessly across devices. - ---- - -## ๐Ÿ“ Notes - -- Tweets are optimized for Twitterโ€™s 280-character limit. -- Performance predictions are derived from AI insights and engagement patterns. -- Suggestions adapt to your audience, ensuring relevancy. -- Regular updates keep the tool current with Twitter trends. - ---- - -## ๐Ÿค Support - -Have questions or feature requests? Reach out to our support team or submit an issue on our GitHub repository. - ---- - -*Last updated: Yesterday* - ---- \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/__init__.py b/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/__init__.py deleted file mode 100644 index 5fa6c3b7..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Twitter Tweet Generator Module - -A comprehensive suite of tools for generating and optimizing tweets. -""" - -from .smart_tweet_generator import smart_tweet_generator - -__all__ = ['smart_tweet_generator'] \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py b/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py deleted file mode 100644 index 3849734f..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/tweet_generator/smart_tweet_generator.py +++ /dev/null @@ -1,1081 +0,0 @@ -""" -Enhanced Smart Tweet Generator with real AI integration and platform adapter connectivity. -""" - -import streamlit as st -import re -import json -import asyncio -from typing import Dict, List, Tuple, Optional, Any -import random -import emoji -from datetime import datetime -import plotly.express as px -import plotly.graph_objects as go - -from ....gpt_providers.text_generation.main_text_generation import llm_text_gen -from ....integrations.twitter_auth_bridge import ( - get_twitter_adapter, - is_twitter_authenticated, - validate_twitter_credentials, - save_twitter_credentials, - setup_twitter_session, - clear_twitter_session -) -from ..twitter_streamlit_ui import ( - TweetForm, - TweetCard, - Theme, - save_to_session, - get_from_session, - show_success_message, - show_error_message, - show_info_message, - show_warning_message, - validate_tweet_content, - validate_hashtags, - validate_emojis, - calculate_engagement_score, - generate_tweet_metrics, - copy_to_clipboard, - create_download_button -) - -# Constants -MAX_TWEET_LENGTH = 280 -EMOJI_CATEGORIES = { - "Humorous": ["๐Ÿ˜„", "๐Ÿ˜‚", "๐Ÿคฃ", "๐Ÿ˜Š", "๐Ÿ˜‰", "๐Ÿ˜Ž", "๐Ÿคช", "๐Ÿ˜œ", "๐Ÿค“", "๐Ÿ˜‡"], - "Informative": ["๐Ÿ“š", "๐Ÿ“Š", "๐Ÿ“ˆ", "๐Ÿ”", "๐Ÿ’ก", "๐Ÿ“", "๐Ÿ“‹", "๐Ÿ”Ž", "๐Ÿ“–", "๐Ÿ“‘"], - "Inspirational": ["โœจ", "๐ŸŒŸ", "๐Ÿ’ซ", "โญ", "๐Ÿ”ฅ", "๐Ÿ’ช", "๐Ÿ™Œ", "๐Ÿ‘", "๐Ÿ’ฏ", "๐ŸŽฏ"], - "Serious": ["๐Ÿค”", "๐Ÿ’ญ", "๐Ÿง", "๐Ÿ“ข", "๐Ÿ””", "โš–๏ธ", "๐ŸŽ“", "๐Ÿ“Š", "๐Ÿ”ฌ", "๐Ÿ“ฐ"], - "Casual": ["๐Ÿ‘‹", "๐Ÿ‘", "๐Ÿ™‹", "๐Ÿ’", "๐Ÿค—", "๐Ÿ‘Œ", "โœŒ๏ธ", "๐Ÿค", "๐Ÿ‘Š", "๐Ÿ™"] -} - -def get_current_user_id() -> str: - """Get current user ID for authentication.""" - # In a real app, this would come from your authentication system - # For now, we'll use a session-based approach - if 'user_id' not in st.session_state: - st.session_state.user_id = f"user_{hash(st.session_state.get('session_id', 'default'))}" - return st.session_state.user_id - -def render_twitter_authentication(): - """Render Twitter authentication interface.""" - st.markdown("### ๐Ÿ” Twitter Authentication") - - user_id = get_current_user_id() - - if is_twitter_authenticated(): - user_info = st.session_state.get('twitter_user_info', {}) - - # Show connected status - col1, col2, col3 = st.columns([1, 2, 1]) - - with col1: - if user_info.get('profile_image_url'): - st.image(user_info['profile_image_url'], width=60) - - with col2: - st.markdown(f"**{user_info.get('name', 'Unknown')}**") - st.markdown(f"@{user_info.get('screen_name', 'unknown')}") - st.markdown(f"Followers: {user_info.get('followers_count', 0):,}") - - with col3: - if st.button("๐Ÿ”“ Disconnect", type="secondary"): - clear_twitter_session() - st.rerun() - - return True - else: - st.warning("โš ๏ธ Connect your Twitter account to enable posting") - - with st.expander("๐Ÿ”ง Twitter API Configuration", expanded=True): - st.markdown(""" - **To connect your Twitter account:** - 1. Go to [Twitter Developer Portal](https://developer.twitter.com/) - 2. Create a new app or use existing one - 3. Get your API credentials - 4. Enter them below - """) - - # Input fields for credentials - api_key = st.text_input("API Key", type="password", help="Your Twitter API Key") - api_secret = st.text_input("API Secret", type="password", help="Your Twitter API Secret") - access_token = st.text_input("Access Token", type="password", help="Your Twitter Access Token") - access_token_secret = st.text_input("Access Token Secret", type="password", help="Your Twitter Access Token Secret") - - col1, col2 = st.columns(2) - - with col1: - if st.button("๐Ÿงช Test Connection", use_container_width=True): - if all([api_key, api_secret, access_token, access_token_secret]): - credentials = { - 'api_key': api_key, - 'api_secret': api_secret, - 'access_token': access_token, - 'access_token_secret': access_token_secret - } - - with st.spinner("Testing connection..."): - is_valid, message = validate_twitter_credentials(credentials) - - if is_valid: - st.success(f"โœ… {message}") - else: - st.error(f"โŒ {message}") - else: - st.error("Please fill in all credentials") - - with col2: - if st.button("๐Ÿ’พ Save & Connect", use_container_width=True, type="primary"): - if all([api_key, api_secret, access_token, access_token_secret]): - credentials = { - 'api_key': api_key, - 'api_secret': api_secret, - 'access_token': access_token, - 'access_token_secret': access_token_secret - } - - with st.spinner("Connecting to Twitter..."): - # Validate first - is_valid, message = validate_twitter_credentials(credentials) - - if is_valid: - # Save credentials - if save_twitter_credentials(user_id, credentials): - # Setup session - if setup_twitter_session(user_id): - st.success("โœ… Successfully connected to Twitter!") - st.rerun() - else: - st.error("โŒ Failed to setup session") - else: - st.error("โŒ Failed to save credentials") - else: - st.error(f"โŒ {message}") - else: - st.error("Please fill in all credentials") - - return False - -def apply_tweet_generator_styling(): - """Apply modern CSS styling specific to the tweet generator.""" - st.markdown(""" - - """, unsafe_allow_html=True) - -def suggest_hashtags(topic: str, tone: str) -> List[str]: - """Suggest relevant hashtags based on topic and tone.""" - base_hashtags = { - "professional": ["#Business", "#Leadership", "#Innovation", "#Growth", "#Success"], - "casual": ["#Life", "#Fun", "#Trending", "#Daily", "#Mood"], - "informative": ["#Learn", "#Tips", "#HowTo", "#Knowledge", "#Education"], - "humorous": ["#Funny", "#LOL", "#Humor", "#Comedy", "#Memes"], - "inspirational": ["#Motivation", "#Success", "#Growth", "#Inspiration", "#Goals"] - } - - topic_hashtags = { - "tech": ["#Technology", "#TechNews", "#Innovation", "#AI", "#Digital"], - "business": ["#Business", "#Entrepreneurship", "#Startup", "#Marketing", "#Sales"], - "marketing": ["#Marketing", "#DigitalMarketing", "#SocialMedia", "#Content", "#Branding"], - "education": ["#Education", "#Learning", "#Knowledge", "#Skills", "#Training"], - "health": ["#Health", "#Wellness", "#Fitness", "#Lifestyle", "#Mindfulness"] - } - - # Combine and return unique hashtags - suggested = base_hashtags.get(tone.lower(), []) + topic_hashtags.get(topic.lower(), []) - return list(set(suggested))[:5] - -def suggest_emojis(tone: str, count: int = 3) -> List[str]: - """Suggest emojis based on tone.""" - return EMOJI_CATEGORIES.get(tone.capitalize(), EMOJI_CATEGORIES["Casual"])[:count] - -def calculate_character_count(text: str) -> Dict[str, any]: - """Calculate character count and provide styling info.""" - count = len(text) - remaining = MAX_TWEET_LENGTH - count - - if count <= 240: - status = "good" - color_class = "count-good" - elif count <= 270: - status = "warning" - color_class = "count-warning" - else: - status = "danger" - color_class = "count-danger" - - return { - "count": count, - "remaining": remaining, - "status": status, - "color_class": color_class, - "percentage": (count / MAX_TWEET_LENGTH) * 100 - } - -def render_tweet_preview(tweet_text: str, hashtags: List[str], engagement_score: int): - """Render a modern tweet preview.""" - char_info = calculate_character_count(tweet_text) - - # Format hashtags - hashtag_str = " ".join(hashtags) if hashtags else "" - full_text = f"{tweet_text} {hashtag_str}".strip() - - st.markdown(f""" -
-
{full_text}
-
- -
- {char_info['count']}/{MAX_TWEET_LENGTH} -
-
-
- """, unsafe_allow_html=True) - - return char_info - -def render_optimization_tips(): - """Render tweet optimization tips.""" - st.markdown(""" -
-

๐Ÿ’ก Tweet Optimization Tips

-
- โ€ข - Keep tweets between 70-140 characters for maximum engagement -
-
- โ€ข - Use 1-2 relevant hashtags to increase discoverability -
-
- โ€ข - Include emojis to make your tweets more visually appealing -
-
- โ€ข - Ask questions to encourage engagement and replies -
-
- โ€ข - Post during peak hours (9-10 AM, 7-9 PM) for better reach -
-
- """, unsafe_allow_html=True) - -def generate_ai_tweet_variations( - hook: str, - target_audience: str, - tone: str, - call_to_action: str = "", - keywords: str = "", - length: str = "medium", - num_variations: int = 3 -) -> List[Dict]: - """Generate tweet variations using real AI integration.""" - - # Create a comprehensive prompt for AI generation - system_prompt = """You are an expert social media content creator specializing in Twitter. - Your task is to create engaging, viral-worthy tweets that maximize engagement and reach. - - You understand Twitter's algorithm, trending topics, and what makes content shareable. - You know how to craft tweets that spark conversations, drive engagement, and build communities. - - Always consider: - - Character limits (280 max) - - Optimal hashtag usage (1-2 per tweet) - - Emoji placement for visual appeal - - Call-to-action integration - - Audience-specific language and tone - - Current social media trends - """ - - # Length specifications - length_specs = { - "short": "50-100 characters", - "medium": "100-180 characters", - "long": "180-270 characters" - } - - length_spec = length_specs.get(length, "100-180 characters") - - prompt = f""" - Create {num_variations} unique, engaging tweet variations with these specifications: - - **Content Requirements:** - - Main Topic/Hook: {hook} - - Target Audience: {target_audience} - - Tone: {tone} - - Call to Action: {call_to_action} - - Keywords to include: {keywords} - - Length: {length_spec} - - **Tweet Guidelines:** - 1. Each tweet must be unique and engaging - 2. Include 1-2 relevant hashtags per tweet - 3. Use appropriate emojis for the tone (2-3 per tweet) - 4. Stay within Twitter's 280 character limit - 5. Naturally integrate the call to action - 6. Make them conversation-starters - 7. Optimize for engagement and shareability - - **Output Format:** - Return ONLY a valid JSON array with this exact structure: - [ - {{ - "text": "tweet content without hashtags", - "hashtags": ["#hashtag1", "#hashtag2"], - "emojis": ["emoji1", "emoji2"], - "engagement_score": 85, - "reasoning": "brief explanation of why this tweet will perform well" - }} - ] - - **Important:** - - Do not include hashtags in the "text" field - put them separately in "hashtags" - - Engagement score should be 60-95 based on predicted performance - - Each tweet should have a different approach/angle - - Make them authentic and human-like, not robotic - """ - - try: - # Generate tweets using AI - ai_response = llm_text_gen(prompt, system_prompt) - - # Parse the JSON response - try: - # Clean the response to extract JSON - json_start = ai_response.find('[') - json_end = ai_response.rfind(']') + 1 - - if json_start != -1 and json_end != -1: - json_str = ai_response[json_start:json_end] - tweets = json.loads(json_str) - - # Validate and clean the tweets - validated_tweets = [] - for tweet in tweets: - if validate_ai_tweet(tweet): - validated_tweets.append(tweet) - - if validated_tweets: - return validated_tweets - else: - raise ValueError("No valid tweets generated") - else: - raise ValueError("No valid JSON found in response") - - except json.JSONDecodeError as e: - st.error(f"Failed to parse AI response as JSON: {e}") - # Fallback to template-based generation - return generate_fallback_tweets(hook, tone, call_to_action, keywords, num_variations) - - except Exception as e: - st.error(f"AI generation failed: {e}") - # Fallback to template-based generation - return generate_fallback_tweets(hook, tone, call_to_action, keywords, num_variations) - -def validate_ai_tweet(tweet: Dict) -> bool: - """Validate AI-generated tweet structure and content.""" - required_fields = ['text', 'hashtags', 'emojis', 'engagement_score'] - - # Check required fields - for field in required_fields: - if field not in tweet: - return False - - # Validate text length - full_text = f"{tweet['text']} {' '.join(tweet['hashtags'])}" - if len(full_text) > MAX_TWEET_LENGTH: - return False - - # Validate hashtags format - if not isinstance(tweet['hashtags'], list): - return False - - for hashtag in tweet['hashtags']: - if not hashtag.startswith('#'): - return False - - # Validate engagement score - if not isinstance(tweet['engagement_score'], (int, float)): - return False - - if not (0 <= tweet['engagement_score'] <= 100): - return False - - return True - -def generate_fallback_tweets(hook: str, tone: str, cta: str, keywords: str, num_variations: int) -> List[Dict]: - """Generate fallback tweets using templates when AI fails.""" - templates = [ - "{emoji} {hook} {hashtags} {cta}", - "{hook} {emoji} {hashtags} {cta}", - "{emoji} {hook} - {cta} {hashtags}", - "๐Ÿ’ก {hook} {emoji} {hashtags} {cta}", - "{hook} ๐Ÿš€ {cta} {hashtags} {emoji}" - ] - - tweets = [] - for i in range(num_variations): - template = templates[i % len(templates)] - emoji_list = suggest_emojis(tone, 2) - hashtag_list = suggest_hashtags(keywords, tone) - - emoji_str = " ".join(emoji_list[:2]) - hashtag_str = " ".join(hashtag_list[:2]) - - tweet_text = template.format( - emoji=emoji_str, - hook=hook, - hashtags=hashtag_str, - cta=cta - ).strip() - - # Ensure tweet is within character limit - if len(tweet_text) > MAX_TWEET_LENGTH: - tweet_text = tweet_text[:MAX_TWEET_LENGTH-3] + "..." - - tweets.append({ - "text": hook, - "hashtags": hashtag_list[:2], - "emojis": emoji_list[:2], - "engagement_score": random.randint(65, 85), - "reasoning": "Template-based generation (AI fallback)" - }) - - return tweets - -async def post_tweet_to_twitter(tweet_content: str, hashtags: List[str]) -> Dict[str, any]: - """Post tweet to Twitter using the platform adapter with real error handling.""" - user_id = get_current_user_id() - adapter = get_twitter_adapter(user_id) - - if not adapter: - return { - "success": False, - "error": "Twitter adapter not configured. Please connect your Twitter account." - } - - # Prepare content for posting - full_text = f"{tweet_content} {' '.join(hashtags)}".strip() - - # Validate tweet length - if len(full_text) > MAX_TWEET_LENGTH: - return { - "success": False, - "error": f"Tweet too long ({len(full_text)}/{MAX_TWEET_LENGTH} characters)" - } - - content = { - "text": full_text, - "media": [] - } - - try: - # Post the tweet - result = await adapter.publish_content(content) - - if result.get("success"): - tweet_data = result.get("data", {}) - return { - "success": True, - "tweet_id": tweet_data.get("id"), - "tweet_url": f"https://twitter.com/{st.session_state.twitter_user_info['screen_name']}/status/{tweet_data.get('id')}", - "posted_at": tweet_data.get("created_at"), - "full_text": full_text - } - else: - return { - "success": False, - "error": result.get("error", "Unknown error occurred") - } - - except Exception as e: - error_message = str(e) - - # Handle specific Twitter API errors - if "401" in error_message or "Unauthorized" in error_message: - return { - "success": False, - "error": "Authentication failed. Please reconnect your Twitter account." - } - elif "403" in error_message or "Forbidden" in error_message: - return { - "success": False, - "error": "Access forbidden. Check your API permissions." - } - elif "429" in error_message or "Rate limit" in error_message: - return { - "success": False, - "error": "Rate limit exceeded. Please wait before posting again." - } - elif "duplicate" in error_message.lower(): - return { - "success": False, - "error": "Duplicate tweet detected. Please modify your content." - } - else: - return { - "success": False, - "error": f"Failed to post tweet: {error_message}" - } - -async def get_real_tweet_analytics(tweet_id: str) -> Dict[str, Any]: - """Get real analytics for a posted tweet.""" - user_id = get_current_user_id() - adapter = get_twitter_adapter(user_id) - - if not adapter: - return {"error": "Twitter adapter not available"} - - try: - result = await adapter.get_analytics(tweet_id) - - if result.get("success"): - metrics = result.get("data", {}).get("metrics", {}) - return { - "success": True, - "metrics": { - "likes": metrics.get("favorites", 0), - "retweets": metrics.get("retweets", 0), - "replies": metrics.get("replies", 0), - "impressions": metrics.get("impressions", 0), - "engagement_rate": result.get("data", {}).get("engagement_rate", 0) - } - } - else: - return {"error": result.get("error", "Failed to get analytics")} - - except Exception as e: - return {"error": f"Analytics error: {str(e)}"} - -def smart_tweet_generator(): - """Enhanced Smart Tweet Generator with real AI integration and Twitter posting.""" - # Apply styling - apply_tweet_generator_styling() - - # Header - st.markdown(""" -
-
-

โœจ AI-Powered Tweet Generator

-

Create engaging tweets with real AI and post directly to Twitter

-
-
- """, unsafe_allow_html=True) - - # AI Status - st.markdown(""" -
- ๐Ÿค– AI Integration Active - Using advanced language models for tweet generation -
- """, unsafe_allow_html=True) - - # Twitter Authentication - twitter_connected = render_twitter_authentication() - - # Input Section - with st.container(): - st.markdown('
', unsafe_allow_html=True) - st.markdown('

๐Ÿ“ Tweet Content

', unsafe_allow_html=True) - - # Main content input - hook = st.text_area( - "What's your main message?", - placeholder="Enter your tweet content, idea, or topic...", - help="This will be the core message of your tweet", - height=100 - ) - - # Advanced options in columns - col1, col2 = st.columns(2) - - with col1: - target_audience = st.selectbox( - "๐ŸŽฏ Target Audience", - ["General Public", "Professionals", "Students", "Entrepreneurs", "Creators", "Tech Enthusiasts"], - help="Who is your primary audience?" - ) - - tone = st.selectbox( - "๐ŸŽญ Tone & Style", - ["Professional", "Casual", "Humorous", "Inspirational", "Informative"], - index=1, - help="Choose the tone that matches your brand" - ) - - length = st.select_slider( - "๐Ÿ“ Tweet Length", - options=["Short (< 100 chars)", "Medium (100-200 chars)", "Long (200+ chars)"], - value="Medium (100-200 chars)", - help="Shorter tweets often get more engagement" - ) - - with col2: - call_to_action = st.text_input( - "๐Ÿ“ข Call to Action", - placeholder="e.g., Learn more, Follow for tips, Share your thoughts...", - help="What action do you want your audience to take?" - ) - - keywords = st.text_input( - "๐Ÿ” Keywords/Topics", - placeholder="e.g., AI, marketing, productivity...", - help="Keywords to help generate relevant hashtags" - ) - - num_variations = st.slider( - "๐Ÿ”„ Number of Variations", - min_value=1, - max_value=5, - value=3, - help="How many different tweet versions would you like?" - ) - - st.markdown('
', unsafe_allow_html=True) - - # Optimization Tips - render_optimization_tips() - - # Generate Button - if st.button("๐Ÿš€ Generate AI Tweets", use_container_width=True, type="primary"): - if not hook.strip(): - show_error_message("Please enter your main message or topic!") - return - - with st.spinner("๐Ÿค– AI is crafting your tweets..."): - # Generate tweet variations using AI - tweets = generate_ai_tweet_variations( - hook, target_audience, tone, - call_to_action, keywords, length.split()[0].lower(), - num_variations - ) - - # Store in session state - save_to_session("generated_tweets", tweets) - - show_success_message(f"โœ… Generated {len(tweets)} AI-powered tweet variations!") - - # Display Generated Tweets - generated_tweets = get_from_session("generated_tweets", []) - - if generated_tweets: - st.markdown("### ๐ŸŽฏ AI-Generated Tweet Variations") - - for i, tweet in enumerate(generated_tweets): - with st.container(): - st.markdown(f"#### Variation {i + 1}") - - # Tweet preview - char_info = render_tweet_preview( - tweet["text"], - tweet["hashtags"], - tweet["engagement_score"] - ) - - # Show AI reasoning if available - if "reasoning" in tweet: - st.markdown(f"**AI Insight:** {tweet['reasoning']}") - - # Action buttons - col1, col2, col3, col4 = st.columns(4) - - with col1: - if st.button(f"๐Ÿ“‹ Copy", key=f"copy_{i}"): - full_tweet = f"{tweet['text']} {' '.join(tweet['hashtags'])}" - st.code(full_tweet, language=None) - show_success_message("Tweet copied to clipboard!") - - with col2: - if st.button(f"๐Ÿ’พ Save", key=f"save_{i}"): - saved_tweets = get_from_session("saved_tweets", []) - saved_tweets.append(tweet) - save_to_session("saved_tweets", saved_tweets) - show_success_message("Tweet saved!") - - with col3: - if st.button(f"๐Ÿ”„ Regenerate", key=f"regen_{i}"): - with st.spinner("๐Ÿค– Regenerating tweet..."): - new_tweets = generate_ai_tweet_variations( - hook, target_audience, tone, - call_to_action, keywords, length.split()[0].lower(), - 1 - ) - if new_tweets: - generated_tweets[i] = new_tweets[0] - save_to_session("generated_tweets", generated_tweets) - st.rerun() - - with col4: - if twitter_connected: - if st.button(f"๐Ÿฆ Post to Twitter", key=f"post_{i}"): - with st.spinner("๐Ÿฆ Posting to Twitter..."): - result = asyncio.run(post_tweet_to_twitter( - tweet["text"], - tweet["hashtags"] - )) - - if result["success"]: - show_success_message(f"โœ… Tweet posted successfully!") - - # Update tweet with posted info - tweet["posted"] = True - tweet["tweet_id"] = result["tweet_id"] - tweet["tweet_url"] = result["tweet_url"] - tweet["posted_at"] = result["posted_at"] - save_to_session("generated_tweets", generated_tweets) - - # Show tweet URL - st.markdown(f"[View Tweet on Twitter]({result['tweet_url']})") - else: - show_error_message(f"โŒ {result['error']}") - else: - st.button(f"๐Ÿ”’ Connect Twitter", key=f"connect_{i}", disabled=True, - help="Connect your Twitter account to enable posting") - - # Tweet details with real analytics if posted - with st.expander(f"๐Ÿ“Š Details for Variation {i + 1}"): - detail_col1, detail_col2 = st.columns(2) - - with detail_col1: - st.markdown("**Hashtags:**") - for hashtag in tweet["hashtags"]: - st.markdown(f"- {hashtag}") - - st.markdown("**Emojis:**") - st.markdown(" ".join(tweet["emojis"])) - - if tweet.get("posted"): - st.markdown("**Status:** โœ… Posted to Twitter") - if tweet.get("tweet_url"): - st.markdown(f"**Tweet URL:** [View on Twitter]({tweet['tweet_url']})") - - with detail_col2: - st.markdown("**Character Count:**") - full_text = f"{tweet['text']} {' '.join(tweet['hashtags'])}" - char_info = calculate_character_count(full_text) - st.markdown(f"- Total: {char_info['count']}/{MAX_TWEET_LENGTH}") - st.markdown(f"- Remaining: {char_info['remaining']}") - - # Show real analytics if tweet is posted - if tweet.get("posted") and tweet.get("tweet_id"): - if st.button(f"๐Ÿ“Š Get Real Analytics", key=f"analytics_{i}"): - with st.spinner("Fetching real analytics..."): - analytics = asyncio.run(get_real_tweet_analytics(tweet["tweet_id"])) - - if analytics.get("success"): - metrics = analytics["metrics"] - st.markdown("**Real Twitter Analytics:**") - st.markdown(f"- Likes: {metrics['likes']}") - st.markdown(f"- Retweets: {metrics['retweets']}") - st.markdown(f"- Replies: {metrics['replies']}") - st.markdown(f"- Engagement Rate: {metrics['engagement_rate']:.2f}%") - else: - st.error(f"Failed to get analytics: {analytics.get('error')}") - else: - st.markdown("**AI Engagement Score:**") - st.progress(tweet["engagement_score"] / 100) - st.markdown(f"{tweet['engagement_score']}% predicted engagement") - - st.markdown("---") - - # Saved Tweets Section - saved_tweets = get_from_session("saved_tweets", []) - if saved_tweets: - with st.expander(f"๐Ÿ’พ Saved Tweets ({len(saved_tweets)})"): - for i, tweet in enumerate(saved_tweets): - st.markdown(f"**Saved Tweet {i + 1}:**") - st.markdown(f"{tweet['text']} {' '.join(tweet['hashtags'])}") - - col1, col2 = st.columns(2) - with col1: - if st.button(f"Remove", key=f"remove_saved_{i}"): - saved_tweets.pop(i) - save_to_session("saved_tweets", saved_tweets) - st.rerun() - - with col2: - if twitter_connected and not tweet.get("posted"): - if st.button(f"๐Ÿฆ Post", key=f"post_saved_{i}"): - with st.spinner("๐Ÿฆ Posting to Twitter..."): - result = asyncio.run(post_tweet_to_twitter( - tweet["text"], - tweet["hashtags"] - )) - - if result["success"]: - show_success_message(f"โœ… Tweet posted successfully!") - tweet["posted"] = True - tweet["tweet_id"] = result["tweet_id"] - tweet["tweet_url"] = result["tweet_url"] - save_to_session("saved_tweets", saved_tweets) - st.rerun() - - st.markdown("---") - - # Analytics Section - if generated_tweets: - st.markdown("### ๐Ÿ“Š AI Tweet Analytics Preview") - - # Create engagement comparison chart - tweet_names = [f"Variation {i+1}" for i in range(len(generated_tweets))] - engagement_scores = [tweet["engagement_score"] for tweet in generated_tweets] - - fig = px.bar( - x=tweet_names, - y=engagement_scores, - title="AI-Predicted Engagement Comparison", - labels={"x": "Tweet Variations", "y": "AI Engagement Score (%)"}, - color=engagement_scores, - color_continuous_scale="Blues" - ) - - fig.update_layout( - plot_bgcolor='rgba(0,0,0,0)', - paper_bgcolor='rgba(0,0,0,0)', - showlegend=False - ) - - st.plotly_chart(fig, use_container_width=True) - - # AI insights and best practices - st.markdown("### ๐Ÿค– AI Optimization Insights") - - best_tweet = max(generated_tweets, key=lambda x: x["engagement_score"]) - best_index = generated_tweets.index(best_tweet) - - # Calculate average character count - total_chars = 0 - for t in generated_tweets: - full_text = f"{t['text']} {' '.join(t['hashtags'])}" - total_chars += len(full_text) - avg_chars = total_chars // len(generated_tweets) - - # Calculate average hashtag count - avg_hashtags = sum(len(t['hashtags']) for t in generated_tweets) // len(generated_tweets) - - insights = [ - f"๐Ÿ† Variation {best_index + 1} has the highest AI-predicted engagement ({best_tweet['engagement_score']}%)", - f"๐Ÿ“ Average character count: {avg_chars} characters", - f"#๏ธโƒฃ Using {avg_hashtags} hashtags on average", - "๐ŸŽฏ AI recommends posting during peak hours (9-10 AM, 7-9 PM) for better reach", - "๐Ÿค– All tweets generated using advanced AI for maximum engagement potential" - ] - - for insight in insights: - st.info(insight) - -if __name__ == "__main__": - smart_tweet_generator() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_dashboard.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_dashboard.py deleted file mode 100644 index ae12b229..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_dashboard.py +++ /dev/null @@ -1,729 +0,0 @@ -""" -Enhanced Twitter Dashboard with modern UI components and improved user experience. -""" - -import streamlit as st -from typing import Dict, List, Optional, Any -import json -from datetime import datetime, timedelta -import plotly.express as px -import plotly.graph_objects as go -from plotly.subplots import make_subplots -import pandas as pd -import numpy as np - -from .tweet_generator import smart_tweet_generator -from .twitter_streamlit_ui import ( - TwitterDashboard, - FeatureCard, - TweetCard, - TweetForm, - SettingsForm, - Sidebar, - Header, - Tabs, - Breadcrumbs, - Theme, - save_to_session, - get_from_session, - clear_session, - show_success_message, - show_error_message, - show_info_message, - show_warning_message -) - -def apply_modern_styling(): - """Apply modern CSS styling to the dashboard.""" - st.markdown(""" - - """, unsafe_allow_html=True) - -def render_connection_status(): - """Render Twitter connection status with modern styling.""" - # Simulate connection status (replace with real authentication check) - is_connected = get_from_session("twitter_connected", False) - - if is_connected: - user_info = get_from_session("twitter_user", {"name": "Demo User", "handle": "@demo_user"}) - st.markdown(f""" -
- โœ… -
- Connected as {user_info['name']} -
{user_info['handle']}
-
-
- """, unsafe_allow_html=True) - else: - st.markdown(""" -
- โš ๏ธ -
- Twitter Not Connected -
Connect your account to access all features
-
-
- """, unsafe_allow_html=True) - - if st.button("๐Ÿ”— Connect Twitter Account", key="connect_twitter"): - # Simulate connection (replace with real OAuth flow) - save_to_session("twitter_connected", True) - save_to_session("twitter_user", {"name": "Demo User", "handle": "@demo_user"}) - st.rerun() - -def render_dashboard_header(): - """Render the modern dashboard header.""" - st.markdown(""" -
-

๐Ÿฆ Twitter AI Dashboard

-

Create, analyze, and optimize your Twitter content with AI-powered tools

-
- """, unsafe_allow_html=True) - -def render_quick_actions(): - """Render quick action buttons.""" - st.markdown("### ๐Ÿš€ Quick Actions") - - col1, col2, col3, col4 = st.columns(4) - - with col1: - if st.button("โœ๏ธ Create Tweet", use_container_width=True, key="quick_tweet"): - st.session_state.current_page = "tweet_generator" - st.rerun() - - with col2: - if st.button("๐Ÿ“Š View Analytics", use_container_width=True, key="quick_analytics"): - st.session_state.current_page = "analytics" - st.rerun() - - with col3: - if st.button("๐Ÿ“… Content Calendar", use_container_width=True, key="quick_calendar"): - show_info_message("Content Calendar feature coming soon!") - - with col4: - if st.button("โš™๏ธ Settings", use_container_width=True, key="quick_settings"): - st.session_state.current_page = "settings" - st.rerun() - -def render_metrics_overview(): - """Render key metrics overview.""" - st.markdown("### ๐Ÿ“ˆ Performance Overview") - - # Generate sample metrics (replace with real data) - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.markdown(""" -
-
1,234
-
Total Tweets
-
- """, unsafe_allow_html=True) - - with col2: - st.markdown(""" -
-
45.2K
-
Total Engagement
-
- """, unsafe_allow_html=True) - - with col3: - st.markdown(""" -
-
3.8%
-
Engagement Rate
-
- """, unsafe_allow_html=True) - - with col4: - st.markdown(""" -
-
12.5K
-
Followers
-
- """, unsafe_allow_html=True) - -def render_engagement_chart(): - """Render engagement trends chart.""" - st.markdown("### ๐Ÿ“Š Engagement Trends") - - # Generate sample data (replace with real Twitter data) - dates = pd.date_range(start=datetime.now() - timedelta(days=30), periods=30) - engagement = np.random.normal(100, 20, 30) - engagement = np.maximum(engagement, 0) # Ensure positive values - - df = pd.DataFrame({ - 'Date': dates, - 'Engagement': engagement, - 'Likes': engagement * 0.6, - 'Retweets': engagement * 0.3, - 'Replies': engagement * 0.1 - }) - - # Create interactive chart - fig = make_subplots( - rows=2, cols=1, - subplot_titles=('Total Engagement', 'Engagement Breakdown'), - vertical_spacing=0.1, - row_heights=[0.7, 0.3] - ) - - # Main engagement line - fig.add_trace( - go.Scatter( - x=df['Date'], - y=df['Engagement'], - mode='lines+markers', - name='Total Engagement', - line=dict(color='#1DA1F2', width=3), - marker=dict(size=6) - ), - row=1, col=1 - ) - - # Stacked area chart for breakdown - fig.add_trace( - go.Scatter( - x=df['Date'], - y=df['Likes'], - mode='lines', - name='Likes', - fill='tonexty', - line=dict(color='#E53E3E') - ), - row=2, col=1 - ) - - fig.add_trace( - go.Scatter( - x=df['Date'], - y=df['Retweets'], - mode='lines', - name='Retweets', - fill='tonexty', - line=dict(color='#38A169') - ), - row=2, col=1 - ) - - fig.add_trace( - go.Scatter( - x=df['Date'], - y=df['Replies'], - mode='lines', - name='Replies', - fill='tonexty', - line=dict(color='#D69E2E') - ), - row=2, col=1 - ) - - fig.update_layout( - height=500, - showlegend=True, - hovermode='x unified', - plot_bgcolor='rgba(0,0,0,0)', - paper_bgcolor='rgba(0,0,0,0)' - ) - - fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)') - fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)') - - st.plotly_chart(fig, use_container_width=True) - -def render_feature_grid(): - """Render the feature grid with modern cards.""" - st.markdown("### ๐Ÿ› ๏ธ Available Tools") - - features = [ - { - "title": "Smart Tweet Generator", - "description": "Create engaging tweets with AI assistance, hashtag suggestions, and emoji optimization", - "icon": "โœจ", - "status": "active", - "action": "tweet_generator" - }, - { - "title": "Performance Predictor", - "description": "Predict tweet engagement and find optimal posting times", - "icon": "๐Ÿ”ฎ", - "status": "coming_soon", - "action": None - }, - { - "title": "Content Calendar", - "description": "Plan and schedule your Twitter content strategy", - "icon": "๐Ÿ“…", - "status": "coming_soon", - "action": None - }, - { - "title": "Hashtag Research", - "description": "Discover trending hashtags and analyze their performance", - "icon": "#๏ธโƒฃ", - "status": "coming_soon", - "action": None - }, - { - "title": "Visual Content", - "description": "Create quote cards, infographics, and visual tweets", - "icon": "๐ŸŽจ", - "status": "coming_soon", - "action": None - }, - { - "title": "Analytics Dashboard", - "description": "Deep dive into your Twitter performance metrics", - "icon": "๐Ÿ“Š", - "status": "coming_soon", - "action": None - } - ] - - # Create grid layout - cols = st.columns(3) - - for i, feature in enumerate(features): - with cols[i % 3]: - status_class = "status-active" if feature["status"] == "active" else "status-coming-soon" - - card_html = f""" -
- {feature['icon']} -

{feature['title']}

-

{feature['description']}

- {feature['status'].replace('_', ' ')} -
- """ - - st.markdown(card_html, unsafe_allow_html=True) - - # Add button for active features - if feature["status"] == "active" and feature["action"]: - if st.button(f"Launch {feature['title']}", key=f"launch_{i}", use_container_width=True): - st.session_state.current_page = feature["action"] - st.rerun() - -def render_recent_activity(): - """Render recent activity feed.""" - st.markdown("### ๐Ÿ“ฑ Recent Activity") - - # Sample activity data (replace with real data) - activities = [ - {"time": "2 hours ago", "action": "Generated tweet", "details": "AI-powered content about social media trends"}, - {"time": "5 hours ago", "action": "Analyzed performance", "details": "Tweet received 45 likes and 12 retweets"}, - {"time": "1 day ago", "action": "Scheduled tweet", "details": "Content scheduled for optimal posting time"}, - {"time": "2 days ago", "action": "Updated hashtags", "details": "Added trending hashtags to improve reach"} - ] - - for activity in activities: - st.markdown(f""" -
-
- {activity['action']} -
-
- {activity['details']} -
-
- {activity['time']} -
-
- """, unsafe_allow_html=True) - -def run_dashboard(): - """Main function to run the enhanced Twitter dashboard.""" - # Apply modern styling - apply_modern_styling() - - # Initialize session state - if "current_page" not in st.session_state: - st.session_state.current_page = "dashboard" - - # Handle page navigation - if st.session_state.current_page == "tweet_generator": - if st.button("โ† Back to Dashboard", key="back_to_dashboard"): - st.session_state.current_page = "dashboard" - st.rerun() - smart_tweet_generator() - return - - # Main dashboard container - st.markdown('
', unsafe_allow_html=True) - - # Render dashboard header - render_dashboard_header() - - # Render connection status - render_connection_status() - - # Create main layout - tab1, tab2, tab3 = st.tabs(["๐Ÿ  Overview", "๐Ÿ“Š Analytics", "โš™๏ธ Settings"]) - - with tab1: - # Quick actions - render_quick_actions() - - # Metrics overview - render_metrics_overview() - - # Feature grid - render_feature_grid() - - # Recent activity - col1, col2 = st.columns([2, 1]) - with col1: - render_engagement_chart() - with col2: - render_recent_activity() - - with tab2: - st.markdown("### ๐Ÿ“ˆ Advanced Analytics") - - # Time range selector - col1, col2 = st.columns([1, 3]) - with col1: - time_range = st.selectbox( - "Time Range", - ["Last 7 days", "Last 30 days", "Last 90 days", "Last year"], - index=1 - ) - - # Detailed analytics - render_engagement_chart() - - # Performance insights - st.markdown("### ๐Ÿ’ก Performance Insights") - - insights = [ - "Your tweets perform 23% better when posted between 2-4 PM", - "Tweets with 2-3 hashtags get 15% more engagement", - "Visual content increases engagement by 35%", - "Questions in tweets boost replies by 28%" - ] - - for insight in insights: - st.info(f"๐Ÿ’ก {insight}") - - with tab3: - st.markdown("### โš™๏ธ Dashboard Settings") - - # Twitter API settings - with st.expander("๐Ÿ”‘ Twitter API Configuration", expanded=False): - st.markdown("Configure your Twitter API credentials to enable full functionality.") - - api_key = st.text_input("API Key", type="password", help="Your Twitter API key") - api_secret = st.text_input("API Secret", type="password", help="Your Twitter API secret") - access_token = st.text_input("Access Token", type="password", help="Your Twitter access token") - access_token_secret = st.text_input("Access Token Secret", type="password", help="Your Twitter access token secret") - - if st.button("Save API Configuration"): - # Save configuration (implement secure storage) - show_success_message("API configuration saved successfully!") - - # Dashboard preferences - with st.expander("๐ŸŽจ Dashboard Preferences", expanded=True): - theme = st.selectbox("Theme", ["Light", "Dark", "Auto"], index=0) - default_tone = st.selectbox("Default Tweet Tone", ["Professional", "Casual", "Humorous", "Inspirational"], index=1) - auto_hashtags = st.checkbox("Auto-suggest hashtags", value=True) - - if st.button("Save Preferences"): - show_success_message("Preferences saved successfully!") - - # Account management - with st.expander("๐Ÿ‘ค Account Management", expanded=False): - st.markdown("Manage your connected Twitter accounts and permissions.") - - if get_from_session("twitter_connected", False): - st.success("โœ… Twitter account connected") - if st.button("Disconnect Account"): - save_to_session("twitter_connected", False) - st.rerun() - else: - st.warning("โš ๏ธ No Twitter account connected") - if st.button("Connect Account"): - save_to_session("twitter_connected", True) - st.rerun() - - st.markdown('
', unsafe_allow_html=True) - -# JavaScript for handling feature clicks -st.markdown(""" - -""", unsafe_allow_html=True) - -if __name__ == "__main__": - run_dashboard() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/README.md b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/README.md deleted file mode 100644 index 1f3878d5..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/README.md +++ /dev/null @@ -1,203 +0,0 @@ -# Twitter Streamlit UI Components - -This module provides a unified, reusable UI component library for all Twitter-related features in the AI Writer suite. It implements best practices for Streamlit UI development and ensures consistency across all Twitter tools. - -## Structure - -``` -twitter_streamlit_ui/ -โ”œโ”€โ”€ components/ # Reusable UI components -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ cards.py # Card components (feature cards, tweet cards) -โ”‚ โ”œโ”€โ”€ forms.py # Form components (input forms, settings forms) -โ”‚ โ”œโ”€โ”€ navigation.py # Navigation components (tabs, sidebar) -โ”‚ โ”œโ”€โ”€ feedback.py # Feedback components (loading, errors, success) -โ”‚ โ””โ”€โ”€ layout.py # Layout components (containers, columns) -โ”œโ”€โ”€ styles/ # CSS and styling -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ theme.py # Theme configuration -โ”‚ โ”œโ”€โ”€ components.py # Component-specific styles -โ”‚ โ””โ”€โ”€ animations.py # Animation styles -โ”œโ”€โ”€ utils/ # UI utilities -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ state.py # State management -โ”‚ โ”œโ”€โ”€ validation.py # Input validation -โ”‚ โ””โ”€โ”€ performance.py # Performance optimizations -โ””โ”€โ”€ README.md # This file -``` - -## Key Improvements - -### 1. Consistent UI Components - -- **Card Components** - - Feature cards with consistent styling - - Tweet cards with standardized layout - - Status badges with unified design - -- **Form Components** - - Standardized input forms - - Consistent validation feedback - - Unified error handling - -- **Navigation Components** - - Consistent tab styling - - Standardized sidebar navigation - - Breadcrumb navigation - -### 2. Enhanced User Experience - -- **Loading States** - - Progress indicators for long operations - - Skeleton loading for content - - Smooth transitions between states - -- **Feedback Mechanisms** - - Toast notifications for actions - - Error messages with recovery options - - Success confirmations - -- **Responsive Design** - - Mobile-friendly layouts - - Adaptive column systems - - Flexible containers - -### 3. Performance Optimizations - -- **State Management** - - Centralized state handling - - Efficient data persistence - - Optimized re-rendering - -- **Resource Loading** - - Lazy loading of components - - Optimized image loading - - Cached computations - -### 4. Accessibility Features - -- **Keyboard Navigation** - - Focus management - - Keyboard shortcuts - - ARIA labels - -- **Visual Accessibility** - - High contrast themes - - Screen reader support - - Color blind friendly - -### 5. Error Handling - -- **Graceful Degradation** - - Fallback UI components - - Error boundaries - - Recovery options - -- **User Feedback** - - Clear error messages - - Actionable suggestions - - Help documentation - -## Usage - -### Basic Component Usage - -```python -from twitter_streamlit_ui.components.cards import FeatureCard -from twitter_streamlit_ui.components.forms import TweetForm -from twitter_streamlit_ui.styles.theme import apply_theme - -# Apply theme -apply_theme() - -# Use components -feature_card = FeatureCard( - title="Tweet Generator", - description="Create engaging tweets with AI", - icon="๐Ÿฆ" -) -feature_card.render() - -tweet_form = TweetForm() -tweet_form.render() -``` - -### State Management - -```python -from twitter_streamlit_ui.utils.state import StateManager - -# Initialize state -state = StateManager() -state.initialize() - -# Update state -state.update("current_tweet", tweet_data) -``` - -### Error Handling - -```python -from twitter_streamlit_ui.components.feedback import ErrorBoundary - -with ErrorBoundary(): - # Your code here - pass -``` - -## Best Practices - -1. **Component Reusability** - - Use existing components when possible - - Create new components only when necessary - - Follow the established patterns - -2. **State Management** - - Use the StateManager for all state - - Avoid direct session state manipulation - - Keep state updates atomic - -3. **Performance** - - Use lazy loading for heavy components - - Implement caching where appropriate - - Monitor render performance - -4. **Accessibility** - - Include ARIA labels - - Ensure keyboard navigation - - Test with screen readers - -5. **Error Handling** - - Use ErrorBoundary components - - Provide clear error messages - - Include recovery options - -## Future Improvements - -1. **Component Library** - - Add more specialized components - - Enhance existing components - - Create component documentation - -2. **Theme System** - - Add more theme options - - Implement theme switching - - Create custom theme builder - -3. **Performance** - - Implement virtual scrolling - - Add performance monitoring - - Optimize resource loading - -4. **Testing** - - Add component tests - - Implement E2E tests - - Create test documentation - -## Contributing - -1. Follow the established patterns -2. Add tests for new components -3. Update documentation -4. Ensure accessibility -5. Optimize performance \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py deleted file mode 100644 index 30496c43..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Twitter Streamlit UI package. -Provides a modern and user-friendly interface for Twitter tools. -""" - -from .dashboard import TwitterDashboard -from .components.cards import FeatureCard, TweetCard -from .components.forms import TweetForm, SettingsForm -from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs -from .styles.theme import Theme -from .utils.helpers import ( - save_to_session, - get_from_session, - clear_session, - save_to_file, - load_from_file, - format_datetime, - parse_datetime, - validate_tweet_content, - validate_hashtags, - validate_emojis, - calculate_engagement_score, - generate_tweet_metrics, - copy_to_clipboard, - show_success_message, - show_error_message, - show_info_message, - show_warning_message, - create_download_button, - create_upload_button -) - -__version__ = "1.0.0" -__author__ = "AI Writer Team" - -__all__ = [ - "TwitterDashboard", - "FeatureCard", - "TweetCard", - "TweetForm", - "SettingsForm", - "Sidebar", - "Header", - "Tabs", - "Breadcrumbs", - "Theme", - "save_to_session", - "get_from_session", - "clear_session", - "save_to_file", - "load_from_file", - "format_datetime", - "parse_datetime", - "validate_tweet_content", - "validate_hashtags", - "validate_emojis", - "calculate_engagement_score", - "generate_tweet_metrics", - "copy_to_clipboard", - "show_success_message", - "show_error_message", - "show_info_message", - "show_warning_message", - "create_download_button", - "create_upload_button" -] \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py deleted file mode 100644 index be93901b..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/cards.py +++ /dev/null @@ -1,634 +0,0 @@ -""" -Enhanced UI Cards with modern styling and improved functionality. -""" - -import streamlit as st -from typing import Dict, List, Optional, Callable -import plotly.express as px -import plotly.graph_objects as go -from datetime import datetime - -def apply_cards_styling(): - """Apply modern CSS styling for cards.""" - st.markdown(""" - - """, unsafe_allow_html=True) - -class FeatureCard: - """Modern feature card component.""" - - def __init__( - self, - title: str, - description: str, - icon: str = "๐Ÿ”ง", - stats: Optional[Dict[str, any]] = None, - actions: Optional[List[Dict]] = None, - on_click: Optional[Callable] = None - ): - self.title = title - self.description = description - self.icon = icon - self.stats = stats or {} - self.actions = actions or [] - self.on_click = on_click - - def render(self): - """Render the feature card.""" - apply_cards_styling() - - # Create stats HTML - stats_html = "" - if self.stats: - stats_items = [] - for label, value in self.stats.items(): - stats_items.append(f""" -
- {value} - {label} -
- """) - stats_html = f""" -
- {''.join(stats_items)} -
- """ - - # Create actions HTML - actions_html = "" - if self.actions: - action_buttons = [] - for action in self.actions: - button_class = "action-button" - if action.get("primary", False): - button_class += " primary" - - action_buttons.append(f""" - - """) - actions_html = f""" -
- {''.join(action_buttons)} -
- """ - - # Render the card - card_html = f""" -
-
-
{self.icon}
-
-

{self.title}

-
-
-

{self.description}

- {stats_html} - {actions_html} -
- """ - - st.markdown(card_html, unsafe_allow_html=True) - -class TweetCard: - """Modern tweet card component.""" - - def __init__( - self, - content: str, - engagement_score: int = 0, - hashtags: List[str] = None, - emojis: List[str] = None, - metrics: Optional[Dict] = None, - timestamp: Optional[str] = None, - on_copy: Optional[Callable] = None, - on_save: Optional[Callable] = None, - on_edit: Optional[Callable] = None, - on_post: Optional[Callable] = None - ): - self.content = content - self.engagement_score = engagement_score - self.hashtags = hashtags or [] - self.emojis = emojis or [] - self.metrics = metrics or {} - self.timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M") - self.on_copy = on_copy - self.on_save = on_save - self.on_edit = on_edit - self.on_post = on_post - - def _get_character_info(self): - """Get character count information.""" - full_text = f"{self.content} {' '.join(self.hashtags)}" - count = len(full_text) - remaining = 280 - count - - if count <= 240: - status_class = "char-good" - elif count <= 270: - status_class = "char-warning" - else: - status_class = "char-danger" - - return { - "count": count, - "remaining": remaining, - "status_class": status_class - } - - def render(self): - """Render the tweet card.""" - apply_cards_styling() - - char_info = self._get_character_info() - full_content = f"{self.content} {' '.join(self.hashtags)}" - - # Create metrics HTML - metrics_html = "" - if self.metrics: - metric_items = [] - for label, value in self.metrics.items(): - metric_items.append(f""" -
- {value} - {label} -
- """) - metrics_html = f""" -
- {''.join(metric_items)} -
- """ - - # Create actions - actions = [] - if self.on_copy: - actions.append('') - if self.on_save: - actions.append('') - if self.on_edit: - actions.append('') - if self.on_post: - actions.append('') - - actions_html = f'
{"".join(actions)}
' if actions else "" - - # Render the card - card_html = f""" -
-
{full_content}
- {metrics_html} - - {actions_html} -
- """ - - st.markdown(card_html, unsafe_allow_html=True) - -class MetricsCard: - """Modern metrics display card.""" - - def __init__( - self, - title: str, - metrics: Dict[str, any], - chart_data: Optional[Dict] = None, - trend: Optional[str] = None - ): - self.title = title - self.metrics = metrics - self.chart_data = chart_data - self.trend = trend - - def render(self): - """Render the metrics card.""" - apply_cards_styling() - - # Create metrics grid - metric_items = [] - for label, value in self.metrics.items(): - metric_items.append(f""" -
- {value} - {label} -
- """) - - metrics_grid = f""" -
- {''.join(metric_items)} -
- """ - - # Add trend indicator - trend_html = "" - if self.trend: - trend_color = "#52C41A" if "up" in self.trend.lower() else "#F5222D" - trend_icon = "๐Ÿ“ˆ" if "up" in self.trend.lower() else "๐Ÿ“‰" - trend_html = f""" -
- {trend_icon} {self.trend} -
- """ - - # Render the card - card_html = f""" -
-

{self.title}

- {metrics_grid} - {trend_html} -
- """ - - st.markdown(card_html, unsafe_allow_html=True) - - # Add chart if provided - if self.chart_data: - self._render_chart() - - def _render_chart(self): - """Render chart for metrics.""" - if self.chart_data.get("type") == "line": - fig = px.line( - x=self.chart_data.get("x", []), - y=self.chart_data.get("y", []), - title=self.chart_data.get("title", ""), - labels=self.chart_data.get("labels", {}) - ) - elif self.chart_data.get("type") == "bar": - fig = px.bar( - x=self.chart_data.get("x", []), - y=self.chart_data.get("y", []), - title=self.chart_data.get("title", ""), - labels=self.chart_data.get("labels", {}) - ) - else: - return - - fig.update_layout( - plot_bgcolor='rgba(0,0,0,0)', - paper_bgcolor='rgba(0,0,0,0)', - showlegend=False, - height=300 - ) - - st.plotly_chart(fig, use_container_width=True) - -class StatusCard: - """Status indicator card.""" - - def __init__( - self, - title: str, - status: str, - message: str, - icon: str = "โ„น๏ธ", - actions: Optional[List[Dict]] = None - ): - self.title = title - self.status = status # success, warning, error, info - self.message = message - self.icon = icon - self.actions = actions or [] - - def render(self): - """Render the status card.""" - apply_cards_styling() - - # Status colors - status_colors = { - "success": "#52C41A", - "warning": "#FA8C16", - "error": "#F5222D", - "info": "#1890FF" - } - - color = status_colors.get(self.status, "#1890FF") - - # Create actions - actions_html = "" - if self.actions: - action_buttons = [] - for action in self.actions: - action_buttons.append(f""" - - """) - actions_html = f""" -
- {''.join(action_buttons)} -
- """ - - # Render the card - card_html = f""" -
-
- {self.icon} -
-

{self.title}

- - {self.status} - -
-
-

{self.message}

- {actions_html} -
- """ - - st.markdown(card_html, unsafe_allow_html=True) - -# Utility functions for creating common cards -def create_feature_card(title: str, description: str, icon: str = "๐Ÿ”ง", **kwargs): - """Create and render a feature card.""" - card = FeatureCard(title, description, icon, **kwargs) - card.render() - -def create_tweet_card(content: str, **kwargs): - """Create and render a tweet card.""" - card = TweetCard(content, **kwargs) - card.render() - -def create_metrics_card(title: str, metrics: Dict, **kwargs): - """Create and render a metrics card.""" - card = MetricsCard(title, metrics, **kwargs) - card.render() - -def create_status_card(title: str, status: str, message: str, **kwargs): - """Create and render a status card.""" - card = StatusCard(title, status, message, **kwargs) - card.render() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py deleted file mode 100644 index fa7281ce..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/forms.py +++ /dev/null @@ -1,1041 +0,0 @@ -""" -Enhanced Form Components for Twitter UI with modern styling and improved functionality. -""" - -import streamlit as st -from typing import Dict, List, Optional, Callable, Any, Tuple -import re -from datetime import datetime, timedelta -from ..styles.theme import Theme - -def apply_forms_styling(): - """Apply modern CSS styling for form components.""" - st.markdown(""" - - """, unsafe_allow_html=True) - -class TweetForm: - """Enhanced tweet composition form with AI integration.""" - - def __init__(self, theme: Optional[Theme] = None): - self.theme = theme or Theme() - self.max_length = 280 - - def render( - self, - title: str = "โœจ Create Tweet", - subtitle: str = "Compose your tweet with AI assistance", - show_preview: bool = True, - show_ai_options: bool = True, - on_submit: Optional[Callable] = None, - on_ai_generate: Optional[Callable] = None - ) -> Dict[str, Any]: - """Render the enhanced tweet form.""" - apply_forms_styling() - - st.markdown('
', unsafe_allow_html=True) - - # Header - st.markdown(f''' -
-
{title}
-

{subtitle}

-
- ''', unsafe_allow_html=True) - - # Form data - form_data = {} - - # Main content section - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿ“ Tweet Content
', unsafe_allow_html=True) - - # Tweet text input - tweet_text = st.text_area( - "What's happening?", - placeholder="Share your thoughts, ideas, or updates...", - height=120, - help="Write your tweet content here. Use emojis and hashtags to make it engaging!", - key="tweet_text_input" - ) - - # Character counter - char_count = len(tweet_text) - remaining = self.max_length - char_count - - if char_count <= 240: - counter_class = "counter-good" - elif char_count <= 270: - counter_class = "counter-warning" - else: - counter_class = "counter-danger" - - st.markdown(f''' -
- Characters: {char_count}/{self.max_length} - Remaining: {remaining} -
- ''', unsafe_allow_html=True) - - form_data['text'] = tweet_text - form_data['char_count'] = char_count - form_data['remaining'] = remaining - - st.markdown('
', unsafe_allow_html=True) - - # AI Options Section - if show_ai_options: - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿค– AI Enhancement Options
', unsafe_allow_html=True) - - col1, col2 = st.columns(2) - - with col1: - tone = st.selectbox( - "๐ŸŽญ Tone & Style", - ["Professional", "Casual", "Humorous", "Inspirational", "Informative"], - index=1, - help="Choose the tone that matches your brand" - ) - - target_audience = st.selectbox( - "๐ŸŽฏ Target Audience", - ["General Public", "Professionals", "Students", "Entrepreneurs", "Creators", "Tech Enthusiasts"], - help="Who is your primary audience?" - ) - - with col2: - call_to_action = st.text_input( - "๐Ÿ“ข Call to Action", - placeholder="e.g., Learn more, Follow for tips, Share your thoughts...", - help="What action do you want your audience to take?" - ) - - keywords = st.text_input( - "๐Ÿ” Keywords/Topics", - placeholder="e.g., AI, marketing, productivity...", - help="Keywords to help generate relevant hashtags" - ) - - form_data.update({ - 'tone': tone, - 'target_audience': target_audience, - 'call_to_action': call_to_action, - 'keywords': keywords - }) - - st.markdown('
', unsafe_allow_html=True) - - # Preview Section - if show_preview and tweet_text: - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿ‘€ Tweet Preview
', unsafe_allow_html=True) - - st.markdown(f''' -
-
๐Ÿฆ How your tweet will look:
-
{tweet_text}
-
- ''', unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - - # Validation - validation_messages = self._validate_tweet(tweet_text) - for message in validation_messages: - st.markdown(f''' -
- {message['icon']} {message['text']} -
- ''', unsafe_allow_html=True) - - # Action buttons - st.markdown('
', unsafe_allow_html=True) - - col1, col2, col3 = st.columns(3) - - with col1: - if st.button("๐Ÿค– AI Generate", use_container_width=True, type="secondary"): - if on_ai_generate: - on_ai_generate(form_data) - return {'action': 'ai_generate', 'data': form_data} - - with col2: - if st.button("๐Ÿ’พ Save Draft", use_container_width=True, type="secondary"): - self._save_draft(form_data) - st.success("Draft saved!") - return {'action': 'save_draft', 'data': form_data} - - with col3: - tweet_valid = len(validation_messages) == 0 and tweet_text.strip() - if st.button("๐Ÿฆ Post Tweet", use_container_width=True, type="primary", disabled=not tweet_valid): - if on_submit: - on_submit(form_data) - return {'action': 'post_tweet', 'data': form_data} - - st.markdown('
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - return {'action': None, 'data': form_data} - - def _validate_tweet(self, text: str) -> List[Dict[str, str]]: - """Validate tweet content and return validation messages.""" - messages = [] - - if not text.strip(): - messages.append({ - 'type': 'error', - 'icon': 'โŒ', - 'text': 'Tweet cannot be empty' - }) - - if len(text) > self.max_length: - messages.append({ - 'type': 'error', - 'icon': 'โŒ', - 'text': f'Tweet exceeds {self.max_length} character limit' - }) - elif len(text) > 240: - messages.append({ - 'type': 'warning', - 'icon': 'โš ๏ธ', - 'text': 'Tweet is getting long - consider shortening for better engagement' - }) - - # Check for good practices - if len(text) < 50: - messages.append({ - 'type': 'warning', - 'icon': '๐Ÿ’ก', - 'text': 'Very short tweets may get less engagement' - }) - - if not re.search(r'[.!?]$', text.strip()): - messages.append({ - 'type': 'warning', - 'icon': '๐Ÿ’ก', - 'text': 'Consider ending with punctuation for better readability' - }) - - hashtag_count = len(re.findall(r'#\w+', text)) - if hashtag_count > 2: - messages.append({ - 'type': 'warning', - 'icon': 'โš ๏ธ', - 'text': 'Too many hashtags may reduce engagement - consider using 1-2' - }) - - return messages - - def _save_draft(self, form_data: Dict[str, Any]): - """Save tweet as draft.""" - drafts = st.session_state.get('tweet_drafts', []) - draft = { - 'text': form_data['text'], - 'created_at': datetime.now().isoformat(), - 'tone': form_data.get('tone'), - 'target_audience': form_data.get('target_audience'), - 'call_to_action': form_data.get('call_to_action'), - 'keywords': form_data.get('keywords') - } - drafts.append(draft) - st.session_state.tweet_drafts = drafts - -class TwitterConfigForm: - """Form for configuring Twitter API credentials.""" - - def __init__(self, theme: Optional[Theme] = None): - self.theme = theme or Theme() - - def render(self, title: str = "๐Ÿ”ง Twitter API Configuration") -> Dict[str, Any]: - """Render Twitter configuration form.""" - apply_forms_styling() - - st.markdown('
', unsafe_allow_html=True) - - # Header - st.markdown(f''' -
-
{title}
-

Configure your Twitter API credentials to enable posting

-
- ''', unsafe_allow_html=True) - - # Instructions - st.markdown(''' -
- โ„น๏ธ You need Twitter API credentials to post tweets directly. Get them from the Twitter Developer Portal. -
- ''', unsafe_allow_html=True) - - # Form fields - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿ”‘ API Credentials
', unsafe_allow_html=True) - - # Get existing config - existing_config = st.session_state.get('twitter_config', {}) - - api_key = st.text_input( - "API Key", - value=existing_config.get('api_key', ''), - type="password", - help="Your Twitter API Key from the Developer Portal" - ) - - api_secret = st.text_input( - "API Secret", - value=existing_config.get('api_secret', ''), - type="password", - help="Your Twitter API Secret Key" - ) - - access_token = st.text_input( - "Access Token", - value=existing_config.get('access_token', ''), - type="password", - help="Your Twitter Access Token" - ) - - access_token_secret = st.text_input( - "Access Token Secret", - value=existing_config.get('access_token_secret', ''), - type="password", - help="Your Twitter Access Token Secret" - ) - - st.markdown('
', unsafe_allow_html=True) - - # Validation - all_filled = all([api_key, api_secret, access_token, access_token_secret]) - - if not all_filled: - st.markdown(''' -
- โŒ Please fill in all API credentials -
- ''', unsafe_allow_html=True) - - # Action buttons - st.markdown('
', unsafe_allow_html=True) - - col1, col2, col3 = st.columns(3) - - with col1: - if st.button("๐Ÿงช Test Connection", use_container_width=True, type="secondary", disabled=not all_filled): - # Test the connection - config = { - 'api_key': api_key, - 'api_secret': api_secret, - 'access_token': access_token, - 'access_token_secret': access_token_secret - } - - if self._test_twitter_connection(config): - st.success("โœ… Twitter connection successful!") - else: - st.error("โŒ Twitter connection failed. Please check your credentials.") - - with col2: - if st.button("๐Ÿ’พ Save Configuration", use_container_width=True, type="primary", disabled=not all_filled): - config = { - 'api_key': api_key, - 'api_secret': api_secret, - 'access_token': access_token, - 'access_token_secret': access_token_secret - } - - st.session_state.twitter_config = config - st.success("โœ… Twitter configuration saved!") - return {'action': 'save_config', 'data': config} - - with col3: - if st.button("๐Ÿ—‘๏ธ Clear Configuration", use_container_width=True, type="secondary"): - if 'twitter_config' in st.session_state: - del st.session_state.twitter_config - st.success("Configuration cleared!") - st.rerun() - - st.markdown('
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - return {'action': None, 'data': None} - - def _test_twitter_connection(self, config: Dict[str, str]) -> bool: - """Test Twitter API connection.""" - try: - from ....integrations.platform_adapters.twitter import TwitterAdapter - adapter = TwitterAdapter(config) - # Try to get user info as a test - return True # Simplified for now - except Exception as e: - st.error(f"Connection test failed: {str(e)}") - return False - -class ScheduleForm: - """Form for scheduling tweets.""" - - def __init__(self, theme: Optional[Theme] = None): - self.theme = theme or Theme() - - def render(self, tweet_text: str = "") -> Dict[str, Any]: - """Render tweet scheduling form.""" - apply_forms_styling() - - st.markdown('
', unsafe_allow_html=True) - - # Header - st.markdown(''' -
-
๐Ÿ“… Schedule Tweet
-

Choose when to post your tweet for maximum engagement

-
- ''', unsafe_allow_html=True) - - # Tweet preview - if tweet_text: - st.markdown(f''' -
-
๐Ÿฆ Tweet to be scheduled:
-
{tweet_text}
-
- ''', unsafe_allow_html=True) - - # Scheduling options - st.markdown('
', unsafe_allow_html=True) - st.markdown('
โฐ Scheduling Options
', unsafe_allow_html=True) - - schedule_type = st.radio( - "When to post:", - ["Post now", "Schedule for later", "Best time (AI recommended)"], - help="Choose when you want this tweet to be posted" - ) - - schedule_data = {'type': schedule_type} - - if schedule_type == "Schedule for later": - col1, col2 = st.columns(2) - - with col1: - schedule_date = st.date_input( - "๐Ÿ“… Date", - min_value=datetime.now().date(), - value=datetime.now().date() - ) - - with col2: - schedule_time = st.time_input( - "๐Ÿ• Time", - value=datetime.now().time() - ) - - schedule_data.update({ - 'date': schedule_date, - 'time': schedule_time, - 'datetime': datetime.combine(schedule_date, schedule_time) - }) - - elif schedule_type == "Best time (AI recommended)": - st.info("๐Ÿค– AI will determine the optimal posting time based on your audience engagement patterns") - - # Show recommended times - recommended_times = [ - "Today at 9:00 AM (High engagement expected)", - "Today at 7:30 PM (Peak activity time)", - "Tomorrow at 10:15 AM (Optimal for your audience)" - ] - - selected_time = st.selectbox( - "๐ŸŽฏ AI Recommended Times", - recommended_times, - help="These times are optimized for your audience" - ) - - schedule_data['recommended_time'] = selected_time - - st.markdown('
', unsafe_allow_html=True) - - # Additional options - st.markdown('
', unsafe_allow_html=True) - st.markdown('
โš™๏ธ Additional Options
', unsafe_allow_html=True) - - auto_delete = st.checkbox( - "๐Ÿ—‘๏ธ Auto-delete after 24 hours", - help="Automatically delete this tweet after 24 hours" - ) - - track_performance = st.checkbox( - "๐Ÿ“Š Track performance", - value=True, - help="Monitor engagement and analytics for this tweet" - ) - - schedule_data.update({ - 'auto_delete': auto_delete, - 'track_performance': track_performance - }) - - st.markdown('
', unsafe_allow_html=True) - - # Action buttons - st.markdown('
', unsafe_allow_html=True) - - col1, col2 = st.columns(2) - - with col1: - if st.button("๐Ÿ“… Schedule Tweet", use_container_width=True, type="primary"): - return {'action': 'schedule', 'data': schedule_data} - - with col2: - if st.button("โŒ Cancel", use_container_width=True, type="secondary"): - return {'action': 'cancel', 'data': None} - - st.markdown('
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - return {'action': None, 'data': schedule_data} - -class SettingsForm: - """Settings form component for Twitter configuration and preferences.""" - - def __init__(self, theme: Optional[Theme] = None, on_submit: Optional[Callable] = None): - """Initialize the settings form.""" - self.theme = theme or Theme() - self.on_submit = on_submit - - def render(self, title: str = "โš™๏ธ Settings") -> Dict[str, Any]: - """Render the settings form.""" - apply_forms_styling() - - # Form container - with st.container(): - st.markdown('
', unsafe_allow_html=True) - - # Form header - st.markdown(f''' -
-

{title}

-

Configure your Twitter integration and preferences

-
- ''', unsafe_allow_html=True) - - # Initialize session state - if "api_key" not in st.session_state: - st.session_state["api_key"] = "" - if "theme" not in st.session_state: - st.session_state["theme"] = "Light" - if "notifications" not in st.session_state: - st.session_state["notifications"] = True - if "auto_save" not in st.session_state: - st.session_state["auto_save"] = True - if "language" not in st.session_state: - st.session_state["language"] = "English" - - # API Configuration Section - st.markdown(''' -
-

๐Ÿ”‘ API Configuration

-
- ''', unsafe_allow_html=True) - - api_key = st.text_input( - "Twitter API Key", - value=st.session_state["api_key"], - type="password", - help="Enter your Twitter API key for posting tweets", - key="api_key" - ) - - api_secret = st.text_input( - "Twitter API Secret", - type="password", - help="Enter your Twitter API secret", - key="api_secret" - ) - - access_token = st.text_input( - "Access Token", - type="password", - help="Enter your Twitter access token", - key="access_token" - ) - - access_token_secret = st.text_input( - "Access Token Secret", - type="password", - help="Enter your Twitter access token secret", - key="access_token_secret" - ) - - # Test API Connection - if st.button("๐Ÿ” Test API Connection", key="test_api"): - if api_key and api_secret and access_token and access_token_secret: - with st.spinner("Testing connection..."): - # Simulate API test (replace with actual Twitter API test) - import time - time.sleep(2) - st.success("โœ… API connection successful!") - else: - st.error("โŒ Please fill in all API credentials") - - # Preferences Section - st.markdown(''' -
-

๐ŸŽจ Preferences

-
- ''', unsafe_allow_html=True) - - theme = st.selectbox( - "Theme", - options=["Light", "Dark", "Auto"], - index=["Light", "Dark", "Auto"].index(st.session_state["theme"]), - help="Choose your preferred theme", - key="theme" - ) - - language = st.selectbox( - "Language", - options=["English", "Spanish", "French", "German", "Italian", "Portuguese"], - index=["English", "Spanish", "French", "German", "Italian", "Portuguese"].index(st.session_state["language"]), - help="Choose your preferred language", - key="language" - ) - - # Notifications Section - st.markdown(''' -
-

๐Ÿ”” Notifications

-
- ''', unsafe_allow_html=True) - - notifications = st.checkbox( - "Enable Notifications", - value=st.session_state["notifications"], - help="Receive notifications for tweet performance and updates", - key="notifications" - ) - - auto_save = st.checkbox( - "Auto-save Drafts", - value=st.session_state["auto_save"], - help="Automatically save tweet drafts as you type", - key="auto_save" - ) - - # Advanced Settings Section - st.markdown(''' -
-

โšก Advanced Settings

-
- ''', unsafe_allow_html=True) - - max_tweets_per_day = st.number_input( - "Max Tweets per Day", - min_value=1, - max_value=100, - value=10, - help="Maximum number of tweets to post per day", - key="max_tweets_per_day" - ) - - default_hashtags = st.text_input( - "Default Hashtags", - placeholder="#AI #Twitter #Content", - help="Default hashtags to include in tweets (comma-separated)", - key="default_hashtags" - ) - - # Form Actions - st.markdown('
', unsafe_allow_html=True) - - col1, col2, col3 = st.columns([1, 1, 1]) - - with col1: - if st.button("๐Ÿ’พ Save Settings", key="save_settings", type="primary"): - if self.on_submit: - self.on_submit() - else: - st.success("Settings saved successfully!") - - with col2: - if st.button("๐Ÿ”„ Reset to Defaults", key="reset_settings"): - # Reset to default values - st.session_state["api_key"] = "" - st.session_state["theme"] = "Light" - st.session_state["notifications"] = True - st.session_state["auto_save"] = True - st.session_state["language"] = "English" - st.rerun() - - with col3: - if st.button("๐Ÿ“ค Export Settings", key="export_settings"): - settings_data = { - "theme": st.session_state["theme"], - "notifications": st.session_state["notifications"], - "auto_save": st.session_state["auto_save"], - "language": st.session_state["language"], - "max_tweets_per_day": st.session_state.get("max_tweets_per_day", 10), - "default_hashtags": st.session_state.get("default_hashtags", "") - } - st.download_button( - "Download Settings", - data=str(settings_data), - file_name="twitter_settings.json", - mime="application/json" - ) - - st.markdown('
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - - # Return form data - return { - "api_key": api_key, - "api_secret": st.session_state.get("api_secret", ""), - "access_token": st.session_state.get("access_token", ""), - "access_token_secret": st.session_state.get("access_token_secret", ""), - "theme": theme, - "language": language, - "notifications": notifications, - "auto_save": auto_save, - "max_tweets_per_day": st.session_state.get("max_tweets_per_day", 10), - "default_hashtags": st.session_state.get("default_hashtags", "") - } - -def render_draft_manager(): - """Render draft management interface.""" - drafts = st.session_state.get('tweet_drafts', []) - - if not drafts: - st.info("๐Ÿ“ No saved drafts yet. Create a tweet and save it as a draft!") - return - - st.markdown("### ๐Ÿ“ Saved Drafts") - - for i, draft in enumerate(drafts): - with st.expander(f"Draft {i + 1} - {draft['created_at'][:10]}"): - st.markdown(f"**Text:** {draft['text']}") - - if draft.get('tone'): - st.markdown(f"**Tone:** {draft['tone']}") - - if draft.get('target_audience'): - st.markdown(f"**Audience:** {draft['target_audience']}") - - col1, col2, col3 = st.columns(3) - - with col1: - if st.button(f"โœ๏ธ Edit", key=f"edit_draft_{i}"): - # Load draft into form - st.session_state.load_draft = draft - st.rerun() - - with col2: - if st.button(f"๐Ÿฆ Post", key=f"post_draft_{i}"): - # Post the draft - st.success("Draft posted!") - - with col3: - if st.button(f"๐Ÿ—‘๏ธ Delete", key=f"delete_draft_{i}"): - drafts.pop(i) - st.session_state.tweet_drafts = drafts - st.rerun() - -def create_tweet_form() -> TweetForm: - """Create and return a tweet form instance.""" - return TweetForm() - -def create_config_form() -> TwitterConfigForm: - """Create and return a Twitter config form instance.""" - return TwitterConfigForm() - -def create_schedule_form() -> ScheduleForm: - """Create and return a schedule form instance.""" - return ScheduleForm() - -def create_settings_form() -> SettingsForm: - """Create a settings form instance.""" - return SettingsForm() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py deleted file mode 100644 index ff751dad..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/components/navigation.py +++ /dev/null @@ -1,554 +0,0 @@ -""" -Enhanced Navigation Component for Twitter UI with modern styling and improved functionality. -""" - -import streamlit as st -from typing import Dict, List, Optional, Callable, Any -from ..styles.theme import Theme -import os - -def apply_navigation_styling(): - """Apply modern CSS styling for navigation components.""" - st.markdown(""" - - """, unsafe_allow_html=True) - -class TwitterNavigation: - """Enhanced navigation component for Twitter dashboard.""" - - def __init__(self, theme: Optional[Theme] = None): - self.theme = theme or Theme() - self.current_page = st.session_state.get('current_page', 'dashboard') - - def render_header(self, title: str = "Twitter AI Assistant", show_status: bool = True): - """Render the navigation header with title and status.""" - apply_navigation_styling() - - st.markdown('', unsafe_allow_html=True) - - return st.session_state.get('current_page', menu_items[0].get('key')) - - def render_breadcrumb(self, items: List[Dict]): - """Render breadcrumb navigation.""" - st.markdown('', unsafe_allow_html=True) - - def render_actions(self, actions: List[Dict]): - """Render action buttons in navigation.""" - st.markdown('', unsafe_allow_html=True) - - def render_sidebar_menu(self, menu_items: List[Dict]): - """Render sidebar navigation menu.""" - with st.sidebar: - st.markdown("### ๐Ÿฆ Twitter Tools") - - for item in menu_items: - icon = item.get('icon', '') - label = item.get('label', '') - key = item.get('key', '') - - if st.button(f"{icon} {label}", key=f"sidebar_{key}", use_container_width=True): - st.session_state.current_page = key - if item.get('callback'): - item['callback']() - st.rerun() - - # Twitter connection status in sidebar - st.markdown("---") - twitter_connected = self._check_twitter_connection() - - if twitter_connected: - st.success("๐Ÿฆ Twitter Connected") - else: - st.warning("โš ๏ธ Twitter Not Connected") - if st.button("๐Ÿ”ง Configure Twitter", use_container_width=True): - st.session_state.show_twitter_config = True - st.rerun() - - def _check_twitter_connection(self) -> bool: - """Check if Twitter is connected.""" - twitter_config = st.session_state.get('twitter_config', {}) - return bool(twitter_config and all([ - twitter_config.get('api_key'), - twitter_config.get('api_secret'), - twitter_config.get('access_token'), - twitter_config.get('access_token_secret') - ])) - -class Sidebar: - """Sidebar navigation component.""" - - def __init__(self, title: str = "Navigation", logo: Optional[str] = None): - """Initialize the sidebar.""" - self.title = title - self.logo = logo - self.menu_items = [] - - def add_menu_item(self, label: str, icon: str, key: str, callback: Optional[Callable] = None): - """Add a menu item to the sidebar.""" - self.menu_items.append({ - 'label': label, - 'icon': icon, - 'key': key, - 'callback': callback - }) - - def render(self) -> str: - """Render the sidebar and return the selected page.""" - with st.sidebar: - # Logo and title - if self.logo and os.path.exists(self.logo): - st.image(self.logo, width=100) - st.title(self.title) - st.markdown("---") - - # Menu items - selected_page = None - for item in self.menu_items: - if st.button( - f"{item['icon']} {item['label']}", - key=f"sidebar_{item['key']}", - use_container_width=True - ): - selected_page = item['key'] - if item.get('callback'): - item['callback']() - - return selected_page or st.session_state.get('current_page', 'dashboard') - - -class Header: - """Header component with title and actions.""" - - def __init__(self, title: str = "Dashboard", subtitle: str = ""): - """Initialize the header.""" - self.title = title - self.subtitle = subtitle - self.actions = [] - - def add_action(self, label: str, icon: str, callback: Callable, help_text: str = ""): - """Add an action button to the header.""" - self.actions.append({ - 'label': label, - 'icon': icon, - 'callback': callback, - 'help': help_text - }) - - def render(self): - """Render the header.""" - col1, col2 = st.columns([3, 1]) - - with col1: - st.title(f"{self.title}") - if self.subtitle: - st.markdown(f"*{self.subtitle}*") - - with col2: - if self.actions: - for i, action in enumerate(self.actions): - if st.button( - f"{action['icon']} {action['label']}", - key=f"header_action_{i}", - help=action.get('help', ''), - use_container_width=True - ): - action['callback']() - - -class Tabs: - """Tab navigation component.""" - - def __init__(self): - """Initialize the tabs.""" - self.tabs = [] - - def add_tab(self, label: str, icon: str, content_func: Callable): - """Add a tab.""" - self.tabs.append({ - 'label': label, - 'icon': icon, - 'content_func': content_func - }) - - def render(self): - """Render the tabs.""" - if not self.tabs: - return - - tab_labels = [f"{tab['icon']} {tab['label']}" for tab in self.tabs] - selected_tabs = st.tabs(tab_labels) - - for i, tab in enumerate(self.tabs): - with selected_tabs[i]: - tab['content_func']() - - -class Breadcrumbs: - """Breadcrumb navigation component.""" - - def __init__(self): - """Initialize breadcrumbs.""" - self.items = [] - - def add_item(self, label: str, key: str = None, callback: Callable = None): - """Add a breadcrumb item.""" - self.items.append({ - 'label': label, - 'key': key, - 'callback': callback - }) - - def render(self): - """Render the breadcrumbs.""" - if not self.items: - return - - breadcrumb_html = '' - st.markdown(breadcrumb_html, unsafe_allow_html=True) - - -def create_main_navigation() -> TwitterNavigation: - """Create and return the main navigation instance.""" - return TwitterNavigation() - -def render_page_header(title: str, subtitle: str = "", icon: str = ""): - """Render a consistent page header.""" - st.markdown(f""" -
-

{icon} {title}

- {f'

{subtitle}

' if subtitle else ''} -
- """, unsafe_allow_html=True) - -def render_quick_actions(actions: List[Dict]): - """Render quick action buttons.""" - st.markdown("### โšก Quick Actions") - - cols = st.columns(len(actions)) - - for i, action in enumerate(actions): - with cols[i]: - if st.button( - f"{action.get('icon', '')} {action.get('label', '')}", - key=f"quick_action_{i}", - use_container_width=True, - help=action.get('help', '') - ): - if action.get('callback'): - action['callback']() - -# Default menu items for Twitter dashboard -DEFAULT_MENU_ITEMS = [ - { - 'key': 'dashboard', - 'label': 'Dashboard', - 'icon': '๐Ÿ ', - 'help': 'Main dashboard overview' - }, - { - 'key': 'generator', - 'label': 'Tweet Generator', - 'icon': 'โœจ', - 'help': 'AI-powered tweet generation' - }, - { - 'key': 'analytics', - 'label': 'Analytics', - 'icon': '๐Ÿ“Š', - 'help': 'Tweet performance analytics' - }, - { - 'key': 'scheduler', - 'label': 'Scheduler', - 'icon': '๐Ÿ“…', - 'help': 'Schedule tweets for later' - }, - { - 'key': 'settings', - 'label': 'Settings', - 'icon': 'โš™๏ธ', - 'help': 'Twitter account and API settings' - } -] - -DEFAULT_QUICK_ACTIONS = [ - { - 'key': 'new_tweet', - 'label': 'New Tweet', - 'icon': 'โœ๏ธ', - 'help': 'Create a new tweet' - }, - { - 'key': 'ai_generate', - 'label': 'AI Generate', - 'icon': '๐Ÿค–', - 'help': 'Generate tweets with AI' - }, - { - 'key': 'view_analytics', - 'label': 'View Analytics', - 'icon': '๐Ÿ“ˆ', - 'help': 'Check tweet performance' - } -] \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py deleted file mode 100644 index 91614a9a..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/dashboard.py +++ /dev/null @@ -1,278 +0,0 @@ -""" -Main dashboard for Twitter UI. -Combines all UI components into a cohesive interface. -""" - -import streamlit as st -from typing import Dict, Any, Optional -from .components.cards import FeatureCard, TweetCard -from .components.forms import TweetForm, SettingsForm -from .components.navigation import Sidebar, Header, Tabs, Breadcrumbs -from .styles.theme import Theme -import os - -class TwitterDashboard: - """Main dashboard class for Twitter UI.""" - - def __init__(self): - """Initialize the Twitter dashboard.""" - self.setup_theme() - self.setup_navigation() - self.setup_state() - - def get_logo_path(self) -> str: - """Get the best available logo path with fallbacks.""" - # List of potential logo paths in order of preference - logo_paths = [ - "lib/workspace/alwrity_logo.png", - "lib/workspace/AskAlwrity-min.ico", - "lib/workspace/alwrity_ai_writer.png" - ] - - for path in logo_paths: - if os.path.exists(path): - return path - - # If no logo files are found, return None - return None - - def setup_theme(self) -> None: - """Setup theme and styling.""" - Theme.apply() - - def setup_navigation(self) -> None: - """Setup navigation components.""" - # Sidebar - self.sidebar = Sidebar( - title="Twitter Tools", - logo=self.get_logo_path() - ) - - # Add menu items - self.sidebar.add_menu_item("Dashboard", "๐Ÿ“Š", "dashboard") - self.sidebar.add_menu_item("Tweet Generator", "โœ๏ธ", "tweet_generator") - self.sidebar.add_menu_item("Analytics", "๐Ÿ“ˆ", "analytics") - self.sidebar.add_menu_item("Settings", "โš™๏ธ", "settings") - - # Header - self.header = Header( - title="Twitter Dashboard", - subtitle="Create and manage your Twitter content" - ) - - # Add header actions - self.header.add_action( - "New Tweet", - "โœ๏ธ", - self.create_new_tweet, - "Create a new tweet" - ) - self.header.add_action( - "Refresh", - "๐Ÿ”„", - self.refresh_dashboard, - "Refresh dashboard data" - ) - - # Tabs - self.tabs = Tabs() - - # Add tabs - self.tabs.add_tab("Overview", "๐Ÿ“Š", self.render_overview) - self.tabs.add_tab("Recent Tweets", "๐Ÿฆ", self.render_recent_tweets) - self.tabs.add_tab("Analytics", "๐Ÿ“ˆ", self.render_analytics) - - # Breadcrumbs - self.breadcrumbs = Breadcrumbs() - - def setup_state(self) -> None: - """Initialize session state variables.""" - if "current_page" not in st.session_state: - st.session_state["current_page"] = "dashboard" - if "current_tab" not in st.session_state: - st.session_state["current_tab"] = "Overview" - if "tweets" not in st.session_state: - st.session_state["tweets"] = [] - - def create_new_tweet(self) -> None: - """Handle new tweet creation.""" - st.session_state["current_page"] = "tweet_generator" - - def refresh_dashboard(self) -> None: - """Refresh dashboard data.""" - st.rerun() - - def render_overview(self) -> None: - """Render the overview tab content.""" - # Feature cards - col1, col2, col3 = st.columns(3) - - with col1: - FeatureCard( - title="Tweet Generator", - description="Create engaging tweets with AI assistance", - icon="โœ๏ธ", - features=[ - { - "name": "AI-Powered", - "description": "Generate tweets using advanced AI" - }, - { - "name": "Customizable", - "description": "Adjust tone, length, and style" - } - ], - on_click=self.create_new_tweet - ).render() - - with col2: - FeatureCard( - title="Analytics", - description="Track your tweet performance", - icon="๐Ÿ“ˆ", - features=[ - { - "name": "Engagement", - "description": "Monitor likes, retweets, and replies" - }, - { - "name": "Growth", - "description": "Track follower growth over time" - } - ] - ).render() - - with col3: - FeatureCard( - title="Settings", - description="Customize your experience", - icon="โš™๏ธ", - features=[ - { - "name": "Preferences", - "description": "Set your default options" - }, - { - "name": "API", - "description": "Configure Twitter API settings" - } - ] - ).render() - - def render_recent_tweets(self) -> None: - """Render the recent tweets tab content.""" - # Tweet form - tweet_form = TweetForm( - on_submit=self.handle_tweet_submit - ) - tweet_form.render() - - # Recent tweets - st.markdown("### Recent Tweets") - - for tweet in st.session_state["tweets"]: - TweetCard( - content=tweet["content"], - engagement_score=tweet["engagement_score"], - hashtags=tweet["hashtags"], - emojis=tweet["emojis"], - metrics=tweet["metrics"], - on_copy=lambda: self.copy_tweet(tweet), - on_save=lambda: self.save_tweet(tweet) - ).render() - - def render_analytics(self) -> None: - """Render the analytics tab content.""" - # Analytics content - st.markdown("### Tweet Analytics") - - # Placeholder for analytics charts - st.info("Analytics features coming soon!") - - def handle_tweet_submit(self) -> None: - """Handle tweet form submission.""" - # Get form data - content = st.session_state["tweet_content"] - tone = st.session_state["tone"] - length = st.session_state["length"] - hashtags = st.session_state["hashtags"] - emojis = st.session_state["emojis"] - engagement_boost = st.session_state["engagement_boost"] - - # Create tweet object - tweet = { - "content": content, - "tone": tone, - "length": length, - "hashtags": hashtags, - "emojis": emojis, - "engagement_score": engagement_boost, - "metrics": { - "Engagement": engagement_boost, - "Reach": engagement_boost * 0.8, - "Growth": engagement_boost * 0.6 - } - } - - # Add to tweets list - st.session_state["tweets"].append(tweet) - - # Show success message - st.success("Tweet created successfully!") - - def copy_tweet(self, tweet: Dict[str, Any]) -> None: - """Copy tweet to clipboard.""" - st.write("Tweet copied to clipboard!") - - def save_tweet(self, tweet: Dict[str, Any]) -> None: - """Save tweet for later.""" - st.write("Tweet saved!") - - def render(self) -> None: - """Render the complete dashboard.""" - # Render navigation - self.sidebar.render() - self.header.render() - self.breadcrumbs.render() - - # Render content based on current page - if st.session_state["current_page"] == "dashboard": - self.tabs.render() - elif st.session_state["current_page"] == "tweet_generator": - self.render_recent_tweets() - elif st.session_state["current_page"] == "analytics": - self.render_analytics() - elif st.session_state["current_page"] == "settings": - settings_form = SettingsForm( - on_submit=self.handle_settings_submit - ) - settings_form.render() - - def handle_settings_submit(self) -> None: - """Handle settings form submission.""" - # Get form data - api_key = st.session_state["api_key"] - theme = st.session_state["theme"] - notifications = st.session_state["notifications"] - auto_save = st.session_state["auto_save"] - language = st.session_state["language"] - - # Save settings - st.session_state["settings"] = { - "api_key": api_key, - "theme": theme, - "notifications": notifications, - "auto_save": auto_save, - "language": language - } - - # Show success message - st.success("Settings saved successfully!") - -def main(): - """Main entry point for the dashboard.""" - dashboard = TwitterDashboard() - dashboard.render() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py deleted file mode 100644 index b96cfdf4..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/styles/theme.py +++ /dev/null @@ -1,173 +0,0 @@ -""" -Theme configuration for Twitter UI components. -Provides consistent styling across all Twitter-related features. -""" - -import streamlit as st -from typing import Dict, Any - -class Theme: - """Theme configuration for Twitter UI components.""" - - # Color palette - COLORS = { - "primary": "#1DA1F2", # Twitter blue - "secondary": "#14171A", # Dark blue - "background": "#15202B", # Dark background - "text": "#FFFFFF", # White text - "text_secondary": "#8899A6", # Gray text - "success": "#17BF63", # Green - "warning": "#FFAD1F", # Yellow - "error": "#E0245E", # Red - "border": "rgba(255, 255, 255, 0.1)", # Subtle border - } - - # Typography - TYPOGRAPHY = { - "font_family": "'Helvetica Neue', sans-serif", - "font_sizes": { - "h1": "2.5rem", - "h2": "2rem", - "h3": "1.5rem", - "body": "1rem", - "small": "0.875rem", - }, - "font_weights": { - "regular": 400, - "medium": 500, - "bold": 700, - }, - } - - # Spacing - SPACING = { - "xs": "0.25rem", - "sm": "0.5rem", - "md": "1rem", - "lg": "1.5rem", - "xl": "2rem", - } - - # Border radius - BORDER_RADIUS = { - "sm": "4px", - "md": "8px", - "lg": "12px", - "xl": "16px", - "full": "9999px", - } - - # Shadows - SHADOWS = { - "sm": "0 1px 2px rgba(0, 0, 0, 0.05)", - "md": "0 4px 6px rgba(0, 0, 0, 0.1)", - "lg": "0 10px 15px rgba(0, 0, 0, 0.1)", - "xl": "0 20px 25px rgba(0, 0, 0, 0.15)", - } - - # Transitions - TRANSITIONS = { - "fast": "0.15s ease", - "normal": "0.3s ease", - "slow": "0.5s ease", - } - - @classmethod - def get_css(cls) -> str: - """Get the complete CSS for the theme.""" - return f""" - /* Base styles */ - .stApp {{ - background-color: {cls.COLORS['background']}; - color: {cls.COLORS['text']}; - font-family: {cls.TYPOGRAPHY['font_family']}; - }} - - /* Typography */ - h1, h2, h3, h4, h5, h6 {{ - color: {cls.COLORS['text']}; - font-family: {cls.TYPOGRAPHY['font_family']}; - font-weight: {cls.TYPOGRAPHY['font_weights']['bold']}; - }} - - /* Buttons */ - .stButton > button {{ - background: linear-gradient(45deg, {cls.COLORS['primary']}, #0C85D0); - color: {cls.COLORS['text']}; - border: none; - padding: {cls.SPACING['md']} {cls.SPACING['lg']}; - border-radius: {cls.BORDER_RADIUS['full']}; - font-weight: {cls.TYPOGRAPHY['font_weights']['medium']}; - transition: all {cls.TRANSITIONS['normal']}; - box-shadow: {cls.SHADOWS['md']}; - }} - - .stButton > button:hover {{ - transform: translateY(-2px); - box-shadow: {cls.SHADOWS['lg']}; - }} - - /* Cards */ - .card {{ - background: rgba(255, 255, 255, 0.05); - border: 1px solid {cls.COLORS['border']}; - border-radius: {cls.BORDER_RADIUS['lg']}; - padding: {cls.SPACING['lg']}; - margin-bottom: {cls.SPACING['md']}; - backdrop-filter: blur(10px); - transition: transform {cls.TRANSITIONS['normal']}; - }} - - .card:hover {{ - transform: translateY(-4px); - }} - - /* Forms */ - .stTextInput > div > div > input {{ - background-color: rgba(255, 255, 255, 0.05); - border: 1px solid {cls.COLORS['border']}; - border-radius: {cls.BORDER_RADIUS['md']}; - color: {cls.COLORS['text']}; - padding: {cls.SPACING['md']}; - }} - - /* Tabs */ - .stTabs [data-baseweb="tab-list"] {{ - gap: {cls.SPACING['sm']}; - background-color: rgba(0, 0, 0, 0.2); - padding: {cls.SPACING['md']}; - border-radius: {cls.BORDER_RADIUS['lg']}; - }} - - .stTabs [data-baseweb="tab"] {{ - background-color: transparent; - color: {cls.COLORS['text']}; - border: 1px solid {cls.COLORS['border']}; - border-radius: {cls.BORDER_RADIUS['md']}; - padding: {cls.SPACING['sm']} {cls.SPACING['md']}; - }} - - /* Status badges */ - .status-badge {{ - display: inline-block; - padding: {cls.SPACING['xs']} {cls.SPACING['md']}; - border-radius: {cls.BORDER_RADIUS['full']}; - font-size: {cls.TYPOGRAPHY['font_sizes']['small']}; - font-weight: {cls.TYPOGRAPHY['font_weights']['medium']}; - }} - - .status-active {{ - background: linear-gradient(45deg, {cls.COLORS['success']}, #69F0AE); - color: {cls.COLORS['secondary']}; - }} - - .status-coming-soon {{ - background: linear-gradient(45deg, {cls.COLORS['warning']}, #FFA000); - color: {cls.COLORS['secondary']}; - }} - """ - - @classmethod - def apply(cls) -> None: - """Apply the theme to the Streamlit app.""" - st.markdown(f"", unsafe_allow_html=True) \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/twitter_dashboard.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/twitter_dashboard.py deleted file mode 100644 index 936e9ff0..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/twitter_dashboard.py +++ /dev/null @@ -1,503 +0,0 @@ -""" -Enhanced Twitter Dashboard with real authentication and posting capabilities. -""" - -import streamlit as st -import asyncio -from datetime import datetime, timedelta -import json -from typing import Dict, Any, List, Optional - -# Import our enhanced components -from .components.navigation import TwitterNavigation, create_main_navigation -from .components.cards import TwitterCard, create_analytics_card, create_tweet_card -from .components.forms import TweetForm, TwitterConfigForm -from ..tweet_generator.smart_tweet_generator import ( - smart_tweet_generator, - post_tweet_to_twitter, - get_real_tweet_analytics, - render_twitter_authentication -) -from ....integrations.twitter_auth_bridge import ( - TwitterAuthBridge, - save_twitter_credentials, - load_twitter_credentials, - is_twitter_authenticated, - setup_twitter_session, - clear_twitter_session -) - -# Initialize authentication bridge -auth_bridge = TwitterAuthBridge() - -def initialize_dashboard(): - """Initialize the Twitter dashboard with proper styling and state management.""" - - # Apply custom CSS - st.markdown(""" - - """, unsafe_allow_html=True) - - # Initialize session state - if 'twitter_dashboard_initialized' not in st.session_state: - st.session_state.twitter_dashboard_initialized = True - st.session_state.current_page = 'dashboard' - st.session_state.tweet_drafts = [] - st.session_state.posted_tweets = [] - st.session_state.analytics_data = {} - -def render_dashboard_header(): - """Render the main dashboard header with connection status.""" - - st.markdown('
', unsafe_allow_html=True) - - col1, col2, col3 = st.columns([1, 2, 1]) - - with col2: - st.markdown('

๐Ÿฆ Twitter AI Dashboard

', unsafe_allow_html=True) - st.markdown('

AI-Powered Tweet Generation & Analytics

', unsafe_allow_html=True) - - # Connection status - is_connected = is_twitter_authenticated() - - if is_connected: - user_info = st.session_state.get('twitter_user', {}) - username = user_info.get('screen_name', 'Unknown') - st.markdown(f''' -
- โœ… Connected as @{username} -
- ''', unsafe_allow_html=True) - else: - st.markdown(''' -
- โŒ Not Connected to Twitter -
- ''', unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - -def render_quick_actions(): - """Render quick action buttons.""" - - st.markdown("### ๐Ÿš€ Quick Actions") - - col1, col2, col3, col4 = st.columns(4) - - with col1: - if st.button("๐Ÿ“ Generate Tweet", key="quick_generate", help="Create AI-powered tweets"): - st.session_state.current_page = 'generate' - st.rerun() - - with col2: - if st.button("๐Ÿ“Š View Analytics", key="quick_analytics", help="View tweet performance"): - st.session_state.current_page = 'analytics' - st.rerun() - - with col3: - if st.button("โš™๏ธ Settings", key="quick_settings", help="Configure Twitter connection"): - st.session_state.current_page = 'settings' - st.rerun() - - with col4: - if st.button("๐Ÿ“‹ Drafts", key="quick_drafts", help="Manage tweet drafts"): - st.session_state.current_page = 'drafts' - st.rerun() - -def render_dashboard_overview(): - """Render the main dashboard overview with metrics.""" - - if not is_twitter_authenticated(): - st.warning("โš ๏ธ Please connect your Twitter account to view dashboard metrics.") - if st.button("Connect Twitter Account", type="primary"): - st.session_state.current_page = 'settings' - st.rerun() - return - - # Get user metrics - user_info = st.session_state.get('twitter_user', {}) - - # Display metrics - st.markdown("### ๐Ÿ“ˆ Account Overview") - - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.markdown(f''' -
-
{user_info.get('followers_count', 0):,}
-
Followers
-
- ''', unsafe_allow_html=True) - - with col2: - st.markdown(f''' -
-
{user_info.get('friends_count', 0):,}
-
Following
-
- ''', unsafe_allow_html=True) - - with col3: - posted_count = len(st.session_state.get('posted_tweets', [])) - st.markdown(f''' -
-
{posted_count}
-
Posted Today
-
- ''', unsafe_allow_html=True) - - with col4: - draft_count = len(st.session_state.get('tweet_drafts', [])) - st.markdown(f''' -
-
{draft_count}
-
Drafts
-
- ''', unsafe_allow_html=True) - - # Recent activity - st.markdown("### ๐Ÿ“ Recent Activity") - - recent_tweets = st.session_state.get('posted_tweets', [])[-5:] # Last 5 tweets - - if recent_tweets: - for tweet in reversed(recent_tweets): - with st.expander(f"Tweet: {tweet.get('text', '')[:50]}..."): - col1, col2 = st.columns([2, 1]) - - with col1: - st.write(f"**Text:** {tweet.get('text', '')}") - st.write(f"**Posted:** {tweet.get('created_at', '')}") - - if tweet.get('metrics'): - metrics = tweet['metrics'] - st.write(f"**Engagement:** {metrics.get('favorite_count', 0)} likes, " - f"{metrics.get('retweet_count', 0)} retweets") - - with col2: - if st.button(f"View Analytics", key=f"analytics_{tweet.get('id')}"): - st.session_state.selected_tweet_id = tweet.get('id') - st.session_state.current_page = 'analytics' - st.rerun() - else: - st.info("No recent tweets found. Start by generating and posting some content!") - -def render_settings_page(): - """Render the settings page for Twitter configuration.""" - - st.markdown("### โš™๏ธ Twitter Configuration") - - # Twitter Authentication Section - with st.expander("๐Ÿ” Twitter API Configuration", expanded=not is_twitter_authenticated()): - render_twitter_authentication() - - # Account Information - if is_twitter_authenticated(): - st.markdown("### ๐Ÿ‘ค Account Information") - - user_info = st.session_state.get('twitter_user', {}) - - col1, col2 = st.columns(2) - - with col1: - st.write(f"**Username:** @{user_info.get('screen_name', 'N/A')}") - st.write(f"**Display Name:** {user_info.get('name', 'N/A')}") - st.write(f"**Followers:** {user_info.get('followers_count', 0):,}") - - with col2: - st.write(f"**Following:** {user_info.get('friends_count', 0):,}") - st.write(f"**Tweets:** {user_info.get('statuses_count', 0):,}") - st.write(f"**Account Created:** {user_info.get('created_at', 'N/A')}") - - # Disconnect option - st.markdown("---") - if st.button("๐Ÿ”“ Disconnect Twitter Account", type="secondary"): - clear_twitter_session() - st.success("Twitter account disconnected successfully!") - st.rerun() - -def render_analytics_page(): - """Render the analytics page with real Twitter metrics.""" - - st.markdown("### ๐Ÿ“Š Tweet Analytics") - - if not is_twitter_authenticated(): - st.warning("Please connect your Twitter account to view analytics.") - return - - # Tweet selection - posted_tweets = st.session_state.get('posted_tweets', []) - - if not posted_tweets: - st.info("No tweets found. Generate and post some tweets to see analytics!") - return - - # Select tweet for analysis - tweet_options = { - f"{tweet.get('text', '')[:50]}... ({tweet.get('created_at', '')})": tweet.get('id') - for tweet in posted_tweets - } - - selected_tweet_text = st.selectbox( - "Select a tweet to analyze:", - options=list(tweet_options.keys()) - ) - - if selected_tweet_text: - tweet_id = tweet_options[selected_tweet_text] - - # Get analytics - with st.spinner("Loading analytics..."): - analytics_result = asyncio.run(get_real_tweet_analytics(tweet_id)) - - if analytics_result.get('success'): - analytics_data = analytics_result['data'] - - # Display metrics - st.markdown("#### ๐Ÿ“ˆ Performance Metrics") - - col1, col2, col3, col4 = st.columns(4) - - metrics = analytics_data.get('metrics', {}) - - with col1: - st.metric("Likes", metrics.get('likes', 0)) - - with col2: - st.metric("Retweets", metrics.get('retweets', 0)) - - with col3: - st.metric("Replies", metrics.get('replies', 0)) - - with col4: - engagement = analytics_data.get('engagement', {}) - st.metric("Engagement Rate", f"{engagement.get('engagement_rate', 0):.2f}%") - - # Detailed analytics - st.markdown("#### ๐Ÿ” Detailed Analysis") - - col1, col2 = st.columns(2) - - with col1: - st.markdown("**Engagement Breakdown:**") - total_engagement = metrics.get('total_engagement', 0) - st.write(f"โ€ข Total Engagement: {total_engagement}") - st.write(f"โ€ข Likes Rate: {engagement.get('likes_rate', 0):.2f}%") - st.write(f"โ€ข Retweets Rate: {engagement.get('retweets_rate', 0):.2f}%") - - with col2: - st.markdown("**Content Analysis:**") - content_analysis = analytics_data.get('content_analysis', {}) - st.write(f"โ€ข Character Count: {content_analysis.get('character_count', 0)}") - st.write(f"โ€ข Hashtags: {content_analysis.get('hashtag_count', 0)}") - st.write(f"โ€ข Mentions: {content_analysis.get('mention_count', 0)}") - - # Timing analysis - timing = analytics_data.get('timing', {}) - if timing: - st.markdown("#### โฐ Timing Analysis") - st.write(f"โ€ข Posted: {timing.get('posted_at', 'N/A')}") - st.write(f"โ€ข Age: {timing.get('age_hours', 0):.1f} hours") - st.write(f"โ€ข Peak Period: {timing.get('peak_engagement_period', 'N/A')}") - st.write(f"โ€ข Engagement Velocity: {timing.get('engagement_velocity', 0):.2f} per hour") - - else: - st.error(f"Failed to load analytics: {analytics_result.get('error', 'Unknown error')}") - -def render_drafts_page(): - """Render the drafts management page.""" - - st.markdown("### ๐Ÿ“‹ Tweet Drafts") - - drafts = st.session_state.get('tweet_drafts', []) - - if not drafts: - st.info("No drafts found. Create some tweets in the generator to save as drafts!") - return - - for i, draft in enumerate(drafts): - with st.expander(f"Draft {i+1}: {draft.get('text', '')[:50]}..."): - col1, col2 = st.columns([3, 1]) - - with col1: - st.write(f"**Text:** {draft.get('text', '')}") - st.write(f"**Created:** {draft.get('created_at', '')}") - if draft.get('hashtags'): - st.write(f"**Hashtags:** {', '.join(draft['hashtags'])}") - - with col2: - if st.button(f"Post Now", key=f"post_draft_{i}"): - if is_twitter_authenticated(): - with st.spinner("Posting tweet..."): - result = asyncio.run(post_tweet_to_twitter(draft)) - - if result.get('success'): - st.success("Tweet posted successfully!") - # Move from drafts to posted - st.session_state.posted_tweets.append(result['data']) - st.session_state.tweet_drafts.pop(i) - st.rerun() - else: - st.error(f"Failed to post: {result.get('error')}") - else: - st.error("Please connect your Twitter account first!") - - if st.button(f"Delete", key=f"delete_draft_{i}"): - st.session_state.tweet_drafts.pop(i) - st.rerun() - -def main_twitter_dashboard(): - """Main Twitter dashboard function.""" - - # Initialize dashboard - initialize_dashboard() - - # Create navigation - nav = TwitterNavigation() - current_page = nav.render_main_navigation() - - # Update session state if page changed - if current_page != st.session_state.get('current_page'): - st.session_state.current_page = current_page - - # Render dashboard header - render_dashboard_header() - - # Route to appropriate page - page = st.session_state.get('current_page', 'dashboard') - - if page == 'dashboard': - render_quick_actions() - render_dashboard_overview() - - elif page == 'generate': - st.markdown("### ๐Ÿค– AI Tweet Generator") - smart_tweet_generator() - - elif page == 'analytics': - render_analytics_page() - - elif page == 'settings': - render_settings_page() - - elif page == 'drafts': - render_drafts_page() - - else: - # Default to dashboard - render_quick_actions() - render_dashboard_overview() - -if __name__ == "__main__": - main_twitter_dashboard() \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py b/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py deleted file mode 100644 index 2c36ab09..00000000 --- a/ToBeMigrated/ai_writers/twitter_writers/twitter_streamlit_ui/utils/helpers.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -Utility functions for Twitter UI. -Provides helper functions for common operations. -""" - -import streamlit as st -from typing import Dict, Any, List, Optional -import json -import os -from datetime import datetime - -def save_to_session(key: str, value: Any) -> None: - """Save a value to the session state.""" - st.session_state[key] = value - -def get_from_session(key: str, default: Any = None) -> Any: - """Get a value from the session state.""" - return st.session_state.get(key, default) - -def clear_session() -> None: - """Clear all session state variables.""" - for key in list(st.session_state.keys()): - del st.session_state[key] - -def save_to_file(data: Dict[str, Any], filename: str) -> None: - """Save data to a JSON file.""" - try: - with open(filename, 'w') as f: - json.dump(data, f, indent=4) - except Exception as e: - st.error(f"Error saving data: {str(e)}") - -def load_from_file(filename: str) -> Optional[Dict[str, Any]]: - """Load data from a JSON file.""" - try: - if os.path.exists(filename): - with open(filename, 'r') as f: - return json.load(f) - except Exception as e: - st.error(f"Error loading data: {str(e)}") - return None - -def format_datetime(dt: datetime) -> str: - """Format a datetime object for display.""" - return dt.strftime("%Y-%m-%d %H:%M:%S") - -def parse_datetime(dt_str: str) -> Optional[datetime]: - """Parse a datetime string.""" - try: - return datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S") - except ValueError: - return None - -def validate_tweet_content(content: str) -> bool: - """Validate tweet content.""" - if not content: - st.error("Tweet content cannot be empty") - return False - if len(content) > 280: - st.error("Tweet content cannot exceed 280 characters") - return False - return True - -def validate_hashtags(hashtags: List[str]) -> bool: - """Validate hashtags.""" - for tag in hashtags: - if not tag.startswith('#'): - st.error(f"Hashtag {tag} must start with #") - return False - if len(tag) > 30: - st.error(f"Hashtag {tag} cannot exceed 30 characters") - return False - return True - -def validate_emojis(emojis: List[str]) -> bool: - """Validate emojis.""" - for emoji in emojis: - if len(emoji) != 1: - st.error(f"Invalid emoji: {emoji}") - return False - return True - -def calculate_engagement_score( - content: str, - hashtags: List[str], - emojis: List[str], - tone: str -) -> float: - """Calculate engagement score for a tweet.""" - score = 0.0 - - # Content length score (optimal length is 100-150 characters) - content_length = len(content) - if 100 <= content_length <= 150: - score += 30 - elif 50 <= content_length <= 200: - score += 20 - else: - score += 10 - - # Hashtag score (optimal number is 2-3 hashtags) - hashtag_count = len(hashtags) - if 2 <= hashtag_count <= 3: - score += 20 - elif 1 <= hashtag_count <= 4: - score += 15 - else: - score += 5 - - # Emoji score (optimal number is 1-2 emojis) - emoji_count = len(emojis) - if 1 <= emoji_count <= 2: - score += 20 - elif 0 <= emoji_count <= 3: - score += 15 - else: - score += 5 - - # Tone score - tone_scores = { - "professional": 15, - "casual": 20, - "humorous": 25, - "informative": 15, - "inspirational": 20 - } - score += tone_scores.get(tone, 10) - - return min(score, 100) - -def generate_tweet_metrics(engagement_score: float) -> Dict[str, float]: - """Generate metrics for a tweet based on engagement score.""" - return { - "Engagement": engagement_score, - "Reach": engagement_score * 0.8, - "Growth": engagement_score * 0.6 - } - -def copy_to_clipboard(text: str) -> None: - """Copy text to clipboard.""" - try: - st.write(f'', unsafe_allow_html=True) - except Exception as e: - st.error(f"Error copying to clipboard: {str(e)}") - -def show_success_message(message: str) -> None: - """Show a success message.""" - st.success(message) - -def show_error_message(message: str) -> None: - """Show an error message.""" - st.error(message) - -def show_info_message(message: str) -> None: - """Show an info message.""" - st.info(message) - -def show_warning_message(message: str) -> None: - """Show a warning message.""" - st.warning(message) - -def create_download_button( - data: Dict[str, Any], - filename: str, - button_text: str = "Download" -) -> None: - """Create a download button for data.""" - try: - json_str = json.dumps(data, indent=4) - st.download_button( - label=button_text, - data=json_str, - file_name=filename, - mime="application/json" - ) - except Exception as e: - st.error(f"Error creating download button: {str(e)}") - -def create_upload_button( - on_upload: callable, - button_text: str = "Upload", - file_types: List[str] = ["json"] -) -> None: - """Create an upload button for data.""" - try: - uploaded_file = st.file_uploader( - button_text, - type=file_types - ) - if uploaded_file is not None: - data = json.load(uploaded_file) - on_upload(data) - except Exception as e: - st.error(f"Error handling upload: {str(e)}") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/web_url_ai_writer.py b/ToBeMigrated/ai_writers/web_url_ai_writer.py deleted file mode 100644 index 86c5e846..00000000 --- a/ToBeMigrated/ai_writers/web_url_ai_writer.py +++ /dev/null @@ -1,121 +0,0 @@ -import sys -import os - -from textwrap import dedent -import json -from pathlib import Path -from datetime import datetime -import streamlit as st - -from dotenv import load_dotenv -load_dotenv(Path('../../.env')) -from loguru import logger -logger.remove() -logger.add(sys.stdout, - colorize=True, - format="{level}|{file}:{line}:{function}| {message}" - ) - -from ..ai_web_researcher.firecrawl_web_crawler import scrape_url -from ..blog_metadata.get_blog_metadata import blog_metadata, run_async -from ..blog_postprocessing.save_blog_to_file import save_blog_to_file -from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image -from ..gpt_providers.text_generation.main_text_generation import llm_text_gen - - -def blog_from_url(weburl): - """ - This function will take a blog Topic to first generate sections for it - and then generate content for each section. - """ - # Use to store the blog in a string, to save in a *.md file. - blog_markdown_str = None - tavily_search_result = None - # Initializing the variables - blog_title = None - blog_meta_desc = None - blog_tags = None - blog_categories = None - - logger.info(f"Researching and Writing Blog on: {weburl}") - with st.status("Started Writing..", expanded=True) as status: - st.empty() - status.update(label=f"Researching and Writing Blog on: {weburl}") - try: - scraped_text = scrape_url(weburl) - #logger.info(scraped_text) - except Exception as err: - st.error(f"Failed to scrape web page from url-{weburl} - Error: {err}") - logger.error(f"Failed in web research: {err}") - st.stop() - status.update(label=f"Successfully Scraped/Fetched url: {weburl}", expanded=False, state="complete") - - with st.status(f"Started Writing blog from {weburl}..", expanded=True) as status: - # Do Tavily AI research to augument the above blog. - try: - blog_markdown_str = write_blog_from_weburl(scraped_text) - status.update(label="Finished Writing Blog From: {weburl}") - except Exception as err: - logger.error(f"Failed to write blog from: {weburl}") - st.error(f"Failed to write blog from: {weburl}") - st.stop() - - try: - status.update(label="๐Ÿ™Ž Generating - Title, Meta Description, Tags, Categories for the content.") - blog_title, blog_meta_desc, blog_tags, blog_categories = run_async(blog_metadata(blog_markdown_str)) - except Exception as err: - st.error(f"Failed to get blog metadata: {err}") - - try: - status.update(label="๐Ÿ™Ž Generating Image for the new blog.") - generated_image_filepath = generate_image(f"{blog_title} + ' ' + {blog_meta_desc}") - except Exception as err: - st.warning(f"Failed in Image generation: {err}") - - saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc, - blog_tags, blog_categories, generated_image_filepath) - status.update(label=f"Saved the content in this file: {saved_blog_to_file}") - - logger.info(f"\n\n --------- Finished writing Blog for : {weburl} -------------- \n") - if generated_image_filepath: - st.image(generated_image_filepath) - - st.markdown(f"{blog_markdown_str}") - status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}", state="complete") - - -def write_blog_from_weburl(scraped_website): - """Combine the given online research and GPT blog content""" - try: - config_path = Path(os.environ["ALWRITY_CONFIG"]) - with open(config_path, 'r', encoding='utf-8') as file: - config = json.load(file) - except Exception as err: - logger.error(f"Error: Failed to read values from config: {err}") - exit(1) - - blog_characteristics = config['Blog Content Characteristics'] - - prompt = f""" - As expert Creative Content writer, I will provide you with scraped website content. - I want you to write a detailed {blog_characteristics['Blog Type']} blog post including 5 FAQs. - - Below are the guidelines to follow: - 1). You must respond in {blog_characteristics['Blog Language']} language. - 2). Tone and Brand Alignment: Adjust your tone, voice, personality for {blog_characteristics['Blog Tone']} audience. - 3). Make sure your response content length is of {blog_characteristics['Blog Length']} words. - 4). Include FAQs from 'People also Ask' section of provided context 'google search result'. - - I want the post to offer unique insights, relatable examples, and a fresh perspective on the topic. - \n\n - Website Content: - '''{scraped_website}''' - """ - logger.info("Generating blog and FAQs from Google web search results.") - - try: - response = llm_text_gen(prompt) - return response - except Exception as err: - logger.error(f"Exit: Failed to get response from LLM: {err}") - exit(1) diff --git a/ToBeMigrated/ai_writers/youtube_writers/README b/ToBeMigrated/ai_writers/youtube_writers/README deleted file mode 100644 index 27708919..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/README +++ /dev/null @@ -1,225 +0,0 @@ -YouTube Description Generator with SEO optimization features. Here's a summary of the improvements I've made: -1. Added SEO Optimization Features -Primary and Secondary Keywords: -Renamed the original keywords field to "Primary Keywords" for clarity -Added a new field for "Secondary Keywords" in the SEO Optimization tab -Updated the prompt generation to include both primary and secondary keywords - -Keyword Density Checker: -Added a new calculate_keyword_density function that: -Counts occurrences of each keyword in the text -Calculates the density as a percentage of total words -Returns a formatted string with density for each keyword -Character Counter and SEO Score: -Added a character counter that displays the total length of the description -Created a comprehensive calculate_seo_score function that evaluates: -Text length (optimal is between 200-5000 characters) -Keyword placement in the first 100 characters -Keyword density (optimal is between 0.5-2.5%) -Presence of call-to-action phrases -Inclusion of hashtags -Presence of links -Returns a percentage score based on these factors - - Improved User Interface -Tabbed Interface: -Organized the interface into three tabs: "Basic Info", "SEO Optimization", and "Advanced Options" -This makes the interface cleaner and more focused -Enhanced Input Fields: -Added more descriptive help text for each field -Improved field organization and grouping -Preview Options: -Added tabs for different views of the generated description: -"Formatted" - Shows the description with proper formatting -"Plain Text" - Shows the raw text for copying -"SEO Analysis" - Shows the SEO metrics and score -Download Option: -Added a download button to save the description as a text file - - Improved Prompt Generation -Dynamic Prompt Building: -Restructured the prompt generation to be more dynamic -Only includes sections that are relevant based on user input -Provides more specific instructions when additional information is available -Template Support: -Added support for different description templates -Includes a custom template option for advanced users -These enhancements make the YouTube Description Generator much more useful for content creators by providing: -Better SEO optimization -More detailed analysis of the generated content -A more organized and user-friendly interface -More customization options -The tool now helps creators not only generate descriptions but also evaluate and optimize them for better performance on YouTube. - -YouTube Title Generator with the following features: -Character Counter: -Tracks the length of each generated title -Indicates if the title is within the optimal length range (50-60 characters) -Provides visual feedback with success/warning messages -Clickbait Detector: -Contains a comprehensive list of clickbait phrases -Calculates a clickbait score based on the presence of these phrases -Provides clear visual feedback about clickbait detection -SEO Score: -Calculates a score out of 10 based on various SEO elements -Considers title length, numbers, question marks, colons, and brackets -Provides visual feedback about the SEO score - -User Interface Improvements: -Displays each title in an expandable section -Shows detailed analysis for each title -Includes a copy button for easy title copying -Provides visual indicators (โœ…, โš ๏ธ, โŒ) for quick assessment - -Script Structure Templates -I've expanded the script structure options from just 3 to 14 different formats: -Problem-Solution: Identifies a problem and presents your solution -Before-After-Bridge: Shows the problem, solution, and transformation -Hook-Problem-Solution-Call to Action: Attention-grabbing format with clear problem, solution, and call to action -Compare and Contrast: Compares different options or approaches -Step-by-Step Tutorial: Detailed instructions broken down into clear steps -Case Study: Examines a specific example or scenario in detail -Interview Format: Structured as an interview with questions and answers -Review Format: Evaluates a product, service, or topic with pros and cons -Vlog Format: Personal, conversational style documenting experiences -Educational Format: Focused on teaching a specific concept or skill -Entertainment Format: Engaging, fun-focused content with humor or excitement -Additional Improvements -Structure Descriptions: Added helpful descriptions for each script structure to help users understand which format best suits their content. -Advanced Options: Added an expandable section with customizable options: -Attention-grabbing hooks -Call-to-action elements -Viewer engagement prompts -Suggested timestamps -Visual cues/transitions with different style options - -Enhanced Script Generation: -Structure-specific instructions for each template -Visual cue instructions for better video production -Improved prompt engineering for more natural, conversational scripts -Better User Experience: -Progress bar during generation -Tabbed preview with formatted and plain text views -Download button for saving scripts -Improved error handling -More Use Cases: Added additional use cases like News Coverage, How-To Guides, Product Demonstrations, Travel Videos, Cooking/Recipe Videos, Gaming Content, and Tech Reviews. -These enhancements make the YouTube Script Generator much more powerful and flexible, allowing content creators to generate scripts tailored to their specific needs and content types. The structure-specific instructions ensure that each script follows best practices for its format, resulting in more professional and engaging content. - -1. Enhanced Engagement Hooks -I've added a variety of engagement hook options that users can select to include in their scripts: -Question Hook: Start with a thought-provoking question -Story Hook: Begin with a brief, relevant story or anecdote -Statistic Hook: Open with an interesting statistic or fact -Controversy Hook: Present a controversial statement to spark interest -Promise Hook: Make a promise about what viewers will learn -Scenario Hook: Describe a relatable scenario -Mystery Hook: Create a sense of mystery or intrigue -Quote Hook: Start with a relevant quote from an expert - - -2. Community Interaction Points -I've added several options for community interaction that can be included in the script: -Comment Prompt: Ask viewers to share experiences in comments -Poll Suggestion: Suggest creating a poll for viewers -Question for Comments: Pose a specific question for comments -Challenge: Challenge viewers to try something and report back -Tag Friends: Encourage tagging friends who might benefit -Share Request: Ask viewers to share the video -Community Post: Mention creating a community post -Live Stream Teaser: Tease an upcoming live stream - -3. Script Export Options -I've implemented a comprehensive export system with multiple format options: -Text (.txt): Simple text format -Markdown (.md): For platforms that support markdown -HTML (.html): Web-friendly format -JSON (.json): Structured data format -Subtitles (SRT): Basic subtitle format for video editing -Additional export features include: -Custom filename option -Copy to clipboard functionality -Formatted and plain text views of the script -Download button with the selected format - -UI Improvements -Added a new "Engagement & Export" tab to organize the new features -Improved script display with tabs for formatted and plain text views -Added a subheader for export options -Included additional export options that can be expanded -These enhancements make the YouTube Script Generator more powerful and user-friendly, providing creators with more tools to engage their audience and export their content in various formats. - -1. YouTube Thumbnail Generator -Added a dedicated tab with a "Coming Soon" notice -Included a comprehensive description of the tool's features: -Thumbnail concept generation based on video content -Color scheme suggestions aligned with brand -Layout recommendations for maximum click-through rate -Best practices for thumbnail design -Text placement suggestions for readability -Added a placeholder image to visually represent the upcoming feature - -2. YouTube Tags Generator - -Created a tab with a "Coming Soon" notice -Provided a detailed description of the tool's capabilities: -Relevant tag generation based on video content -Trending tag suggestions to increase visibility -Tag combination recommendations -Tag research tools for finding popular keywords -Recommendations for tag placement and usage -Added a placeholder image for visual appeal - -3. YouTube End Screen Generator - -Added a tab with a "Coming Soon" notice -Included a description of the tool's features: -End screen template generation based on video type -Strategic CTA placement recommendations -Video playlist promotion suggestions -Best practices for end screen design -Cross-promotion opportunity recommendations -Added a placeholder image to represent the upcoming feature - -4. YouTube Playlist Description Generator - -Created a tab with a "Coming Soon" notice -Provided a description of the tool's capabilities: -Engaging playlist description generation -SEO optimization recommendations for playlists -Playlist organization suggestions -Best practices for playlist metadata -Recommendations for playlist thumbnails and titles -Added a placeholder image for visual appeal - - -5. Additional "More Tools" Tab - -Added an extra tab for future tools -Included a list of potential future features: -YouTube Analytics Insights -Channel Trailer Generator -Video Series Planner -YouTube Shorts Script Generator -Community Post Generator -Added a call for user suggestions for new tools -Included a placeholder image for visual appeal - - -Each tool tab follows a consistent format with: - -A clear title with an emoji for visual identification -A "Coming Soon" notice using Streamlit's info component -A detailed description of the tool's features -A placeholder image to represent the upcoming feature - -This implementation provides users with a clear roadmap of upcoming features while maintaining the existing functionality of the YouTube AI Writer. The "coming soon" state allows you to gauge user interest in these features before fully implementing them. - - - -TBD: -Allow alwrity end users to connect their youtube accounts to fetch their youtube data for analytics and then generate YT related content based on their data and needs: - -1). https://developers.google.com/youtube/reporting/v1/code_samples/python -2). https://github.com/youtube/api-samples/blob/master/python/yt_analytics_report.py -3). https://developers.google.com/youtube/reporting/guides/authorization/server-side-web-apps#python - diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md b/ToBeMigrated/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md deleted file mode 100644 index 9e97aac5..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md +++ /dev/null @@ -1,96 +0,0 @@ -# YouTube Thumbnail Generator - -A powerful AI-powered tool for creating engaging, click-worthy thumbnails for your YouTube videos. - -## Overview - -The YouTube Thumbnail Generator is a specialized module within the AI Writer suite that helps content creators design eye-catching thumbnails optimized for YouTube. Using advanced AI image generation technology, this tool creates custom thumbnails based on your video content, target audience, and style preferences. - -## Features - -### 1. AI-Powered Thumbnail Generation -- **Concept Generation**: Automatically generates multiple thumbnail concept ideas based on your video title, description, and target audience -- **Visual Design**: Creates high-quality thumbnail images using state-of-the-art AI image generation -- **Style Customization**: Choose from various style preferences including bold, clean, colorful, dark, professional, playful, retro, and modern - -### 2. Advanced Customization Options -- **Aspect Ratio Selection**: Choose from standard YouTube ratios (16:9, 1:1, 4:3, 9:16) -- **Text Overlay Options**: Add and customize text overlays with different styles -- **Image Style Selection**: Choose from photorealistic, artistic, cartoon/anime, sketch/drawing, digital art, or 3D render -- **Focus Selection**: For photorealistic images, specify focus areas like portraits, objects, motion, or wide-angle - -### 3. Thumbnail Editing -- **AI-Powered Editing**: Make changes to your generated thumbnails using natural language instructions -- **Iterative Refinement**: Continue editing until you're satisfied with the result -- **Preserve Original**: Keep both original and edited versions of your thumbnails - -### 4. Thumbnail Analysis -- **AI Analysis**: Get feedback on your thumbnail's effectiveness -- **Improvement Suggestions**: Receive specific recommendations to enhance your thumbnail's impact -- **Best Practices**: Learn about visual hierarchy, text readability, emotional impact, and click-worthiness - -### 5. User-Friendly Interface -- **Tabbed Interface**: Organize your workflow with intuitive tabs for basic info and style settings -- **Concept Tabs**: View and select from multiple thumbnail concepts -- **Real-time Preview**: See your generated thumbnails immediately -- **Download Options**: Easily download your thumbnails in high resolution - -## How to Use - -### Step 1: Enter Basic Information -- Provide your video title and description -- Specify your target audience -- Select your content type (tutorial, vlog, review, etc.) - -### Step 2: Customize Style Preferences -- Choose your preferred thumbnail style -- Select the number of concepts to generate -- Pick your desired aspect ratio -- Configure text overlay options - -### Step 3: Generate Thumbnail Concepts -- Click "Generate Thumbnail Concepts" to create multiple thumbnail ideas -- Review each concept in the provided tabs -- Select the concept you'd like to develop further - -### Step 4: Generate and Customize Your Thumbnail -- Click "Generate Image" for your selected concept -- Use the editing tools to refine your thumbnail -- Apply changes using natural language instructions -- Download your final thumbnail when satisfied - -### Step 5: Analyze Your Thumbnail -- Use the "Analyze Thumbnail" feature to get AI feedback -- Review suggestions for improvement -- Make additional edits based on the analysis - -## Technical Details - -The Thumbnail Generator uses: -- **Gemini AI**: For high-quality image generation and editing -- **Advanced Prompt Engineering**: To ensure consistent and relevant results -- **Retry Mechanism**: Handles service overloads with exponential backoff -- **Session State Management**: Preserves your work across page refreshes - -## Tips for Best Results - -1. **Be Specific**: Provide detailed video descriptions to help the AI understand your content -2. **Target Your Audience**: Specify your audience demographics and interests -3. **Choose Appropriate Style**: Select a style that matches your channel's branding -4. **Use Keywords**: Add relevant keywords to enhance the AI's understanding -5. **Iterate**: Don't hesitate to generate multiple concepts and make edits -6. **Analyze**: Use the analysis feature to get objective feedback on your thumbnails - -## Requirements - -- Internet connection for AI services -- Modern web browser -- No additional software installation required - -## Support - -For technical issues or feature requests, please contact our support team or submit an issue on our GitHub repository. - ---- - -*The YouTube Thumbnail Generator is part of the AI Writer suite, designed to help content creators streamline their workflow and produce high-quality content.* \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/README_endScreen.md b/ToBeMigrated/ai_writers/youtube_writers/modules/README_endScreen.md deleted file mode 100644 index 81f15c33..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/README_endScreen.md +++ /dev/null @@ -1,108 +0,0 @@ -End Screen Generator feature for YouTube videos. - -## Step 1: Understanding End Screens - -YouTube end screens are the final elements shown at the end of a video that encourage viewers to take action, such as subscribing, watching another video, or visiting a website. They typically include: - -1. Call-to-action elements (subscribe button, playlists, other videos) -2. Visual elements (background image, branding) -3. Text overlays (promotional messages, channel name) -4. Layout options (different templates for different purposes) - -## Step 2: Required User Inputs - -Based on the thumbnail generator and YouTube end screen requirements, we'll need these inputs: - -1. **Basic Video Information**: - - Video title - - Video description - - Target audience - - Content type (tutorial, vlog, review, etc.) - -2. **End Screen Purpose**: - - Primary goal (drive subscriptions, promote playlist, promote next video, etc.) - - Secondary goal (if applicable) - -3. **Visual Style Preferences**: - - Color scheme - - Style (minimal, bold, branded, etc.) - - Brand elements to include (logo, channel name, etc.) - -4. **Content Elements**: - - Number of elements to include (1-4) - - Types of elements (subscribe button, playlist, video, website) - - Text for each element - -5. **Advanced Settings**: - - Background style (solid color, gradient, image, etc.) - - Animation preferences - - Custom branding elements - -## Step 3: Implementation Plan - -Let's create a new module called `end_screen_generator.py` in the same directory as the thumbnail generator. Here's how we'll structure it: - -1. **Functions**: - - `generate_end_screen_concepts`: Generate end screen design concepts - - `generate_end_screen_design`: Create visual end screen designs - - `analyze_end_screen`: Provide feedback on end screen effectiveness - - `write_yt_end_screen`: Main UI function - -2. **User Interface**: - - Tabs for different sections (Basic Info, Style & Elements, Preview) - - Input fields for all required information - - Preview section to show generated end screens - - Download options for the end screen designs - - -### End Screen Generator Features - -1. **Comprehensive User Inputs**: - - Basic video information (title, description, target audience) - - End screen purpose (subscribe, next video, playlist, website, social media) - - Visual style preferences (modern, minimalist, bold, playful, elegant) - - Content elements (text, CTAs, visual elements) - - Advanced settings (image style, focus, keywords) - -2. **AI-Powered Generation**: - - Concept generation with detailed descriptions - - Image generation with style customization - - Thumbnail analysis for effectiveness - - Image editing capabilities - -3. **User Interface**: - - Tabbed interface for multiple end screen concepts - - Visual preview of generated end screens - - Download options for all generated images - - Edit functionality for refining designs - -4. **Integration with Existing Tools**: - - Reuses the image generation and editing functions from the thumbnail generator - - Consistent UI/UX with other YouTube tools - - Proper error handling and logging - -### How to Use the End Screen Generator - -1. **Access the Tool**: - - Select "End Screen Generator" from the YouTube tools menu - - The tool is now active and ready to use - -2. **Generate End Screens**: - - Enter your video details (title, description, target audience) - - Select the primary purpose of your end screen - - Choose your preferred visual style - - Select content elements to include - - Optionally customize advanced settings - - Click "Generate End Screen Concepts" - -3. **Review and Customize**: - - Browse through the generated concepts in tabs - - Generate images for concepts you like - - Edit the generated images with specific instructions - - Download your final end screen designs - -4. **Analyze Effectiveness**: - - Get AI-powered analysis of your end screen designs - - Receive feedback on visual hierarchy, text readability, and more - -The End Screen Generator is now fully integrated into the YouTube AI Writer and ready to use. Would you like me to make any adjustments or enhancements to the implementation? diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/README_yt_shorts_scripts.md b/ToBeMigrated/ai_writers/youtube_writers/modules/README_yt_shorts_scripts.md deleted file mode 100644 index f05b3b44..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/README_yt_shorts_scripts.md +++ /dev/null @@ -1,273 +0,0 @@ -# YouTube Shorts Script Generator ๐Ÿ“ฑ - -Welcome to the ultimate YouTube Shorts Script Generator! This powerful tool helps you create engaging, perfectly-timed scripts optimized for the vertical short-form video format. Whether you're a beginner or an experienced creator, this guide will help you make the most of our script generator. - -## ๐ŸŽฏ Why Use This Tool? - -- Create attention-grabbing scripts in seconds -- Optimize for vertical viewing (9:16 aspect ratio) -- Get perfect timing for 15-60 second videos -- Include strategic hooks that stop the scroll -- Generate scripts that work even on mute -- Receive instant script analysis and optimization tips - -## ๐Ÿ“‹ Features Overview - -### 1. Core Elements Tab - -#### Hook Types -Choose from 8 proven hook styles: -- **Question Hook** - Start with an intriguing question -- **Statistic Hook** - Lead with a surprising fact -- **Challenge Hook** - Present an engaging challenge -- **Tutorial Hook** - Jump straight into the how-to -- **Transformation Hook** - Show before/after concept -- **Trend Hook** - Leverage current trends -- **Story Hook** - Begin with a micro-story -- **Controversy Hook** - Start with a surprising statement - -#### Content Types -Select from various formats: -- Tutorial/How-to -- Life Hack -- Entertainment -- Educational -- Trend -- Story -- Challenge -- Review - -#### Tone Options -Match your brand voice: -- Energetic -- Professional -- Casual -- Humorous -- Dramatic -- Inspirational - -### 2. Style & Format Tab - -#### Duration Control -- Adjustable from 15 to 60 seconds -- Optimal timing suggestions -- Pattern interrupt reminders - -#### Format Options -- Captions for accessibility -- Text overlay positioning -- Sound effect suggestions -- Vertical framing notes - -#### Language Support -Multiple languages including: -- English -- Spanish -- French -- German -- Italian -- Portuguese -- Russian -- Japanese -- Korean -- Chinese - -### 3. Preview & Export Tab - -#### Script Analysis -Get instant feedback on: -- Estimated duration -- Pattern interrupt count -- Text overlay optimization -- Overall engagement score -- Script optimization metrics - -#### Export Options -Download your script in various formats: -- Text format -- Markdown -- Shot List -- Storyboard - -## ๐ŸŽฌ How to Create the Perfect Shorts Script - -### Step 1: Plan Your Content -1. **Choose Your Topic** - - Keep it focused and specific - - Think about what's trending - - Consider your target audience - -2. **Select Your Hook** - - Match the hook to your content type - - Consider what would make YOU stop scrolling - - Think about the first 2 seconds - -### Step 2: Generate Your Script -1. Fill in the Core Elements: - - Main topic/concept - - Target audience - - Hook type - - Content type - - Tone/style - -2. Customize Style & Format: - - Set your desired duration - - Choose language - - Select formatting options - - Enable/disable features as needed - -### Step 3: Optimize Your Script -Use the Analysis tab to: -- Check estimated duration -- Review pattern interrupts -- Verify text overlay count -- Aim for an optimization score above 80% - -## ๐Ÿ“ˆ Best Practices for Shorts Scripts - -### Timing & Structure -- **First 2 seconds**: Hook viewer attention -- **3-50 seconds**: Main content with pattern interrupts -- **Last 10 seconds**: Clear call-to-action -- Add pattern interrupts every 3-5 seconds - -### Text & Visuals -- Center text in middle 50% of vertical frame -- Keep text concise and readable -- Use contrasting colors for text -- Include visual transitions -- Consider viewing without sound - -### Engagement Tips -- Start with your strongest point -- Use pattern interrupts to maintain interest -- End with a clear call-to-action -- Include viewer prompts when relevant - -## ๐ŸŽฏ Script Structure Template - -``` -1. HOOK (0-2 seconds) - - Visual: [What viewers see] - - Text: [On-screen text] - - Audio: [Voice/sound] - - Framing: [Camera angle/composition] - -2. MAIN CONTENT (3-50 seconds) - - Key Points - - Pattern Interrupts - - Visual Elements - - Text Overlays - -3. CALL TO ACTION (last 10 seconds) - - Clear instruction - - Engagement prompt - - Next steps -``` - -## ๐Ÿš€ Pro Tips - -1. **Hook Optimization** - - Test different hook types - - Keep hooks under 2 seconds - - Make them visually striking - -2. **Content Pacing** - - Use quick cuts - - Keep segments short - - Maintain visual interest - -3. **Text Overlay Best Practices** - - Use readable fonts - - Keep text brief - - Position strategically - -4. **Sound Strategy** - - Design for silent viewing - - Add captions when needed - - Use sound effects strategically - -## ๐Ÿ” Script Analysis Guide - -Understanding your script analysis: - -- **Duration Score** - - Green: Perfect length - - Orange: Slightly long/short - - Red: Needs significant timing adjustment - -- **Pattern Interrupts** - - Aim for 1 every 5 seconds - - Include visual transitions - - Mix up shot types - -- **Text Overlay Score** - - Minimum 3 overlays recommended - - Space them throughout video - - Keep them readable - -- **Overall Optimization** - - 90-100%: Excellent - - 80-89%: Good - - Below 80%: Needs improvement - -## ๐ŸŽจ Export Options Explained - -1. **Text Format** - - Clean, simple script - - Easy to copy/paste - - Basic formatting - -2. **Markdown** - - Formatted sections - - Easy to read - - Good for documentation - -3. **Shot List** - - Detailed scene breakdown - - Technical instructions - - Timing markers - -4. **Storyboard** - - Scene-by-scene format - - Visual instructions - - Technical notes - -## ๐Ÿ†˜ Troubleshooting - -Common issues and solutions: - -1. **Script Too Long** - - Reduce main points - - Shorten sentences - - Speed up pacing - -2. **Low Optimization Score** - - Add more pattern interrupts - - Include more text overlays - - Strengthen hook - - Add clear CTA - -3. **Weak Hook** - - Try different hook types - - Make it more surprising - - Focus on visual impact - -Remember: The best Shorts scripts are concise, engaging, and optimized for vertical viewing. Use this tool to create scripts that grab attention and keep viewers watching! - -## ๐Ÿ”„ Regular Updates - -We regularly update our tool with: -- New hook types -- Trending formats -- Additional languages -- Enhanced analysis features -- New export options - -Stay tuned for more features and improvements! - ---- - -Happy Creating! ๐ŸŽฅ โœจ - -For more YouTube content creation tools, check out our other AI-powered generators in the YouTube AI Writer suite. \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/__init__.py b/ToBeMigrated/ai_writers/youtube_writers/modules/__init__.py deleted file mode 100644 index e2009af3..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -YouTube AI Writer Modules - -This package contains modular components for the YouTube AI Writer functionality. -""" \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/channel_trailer_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/channel_trailer_generator.py deleted file mode 100644 index 15f53326..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/channel_trailer_generator.py +++ /dev/null @@ -1,1079 +0,0 @@ -""" -YouTube Channel Trailer Generator - -This module generates professional channel trailers for YouTube channels using AI. -""" - -import streamlit as st -import json -from pathlib import Path -from typing import Dict, List, Optional, Tuple, Any -import sys -import os -from gtts import gTTS -import tempfile -import base64 -from io import BytesIO -from datetime import datetime -import logging - -# Add the project root to the Python path -project_root = str(Path(__file__).parent.parent.parent.parent.parent) -if project_root not in sys.path: - sys.path.append(project_root) - -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image -from lib.utils.save_to_file import save_to_file, save_audio, save_json, save_text - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -class TrailerGeneratorError(Exception): - """Custom exception for trailer generator errors.""" - pass - -def validate_channel_info(channel_info: Dict) -> Tuple[bool, str]: - """Validate channel information before processing.""" - required_fields = ['channel_name', 'channel_niche', 'target_audience', 'key_topics', 'unique_points'] - - # Check for missing required fields - for field in required_fields: - if not channel_info.get(field): - return False, f"Missing required field: {field}" - - # Validate field lengths - if len(channel_info['channel_name']) < 3: - return False, "Channel name must be at least 3 characters long" - - if len(channel_info['target_audience']) < 10: - return False, "Target audience description must be at least 10 characters long" - - if len(channel_info['key_topics']) < 10: - return False, "Key topics must be at least 10 characters long" - - if len(channel_info['unique_points']) < 10: - return False, "Unique selling points must be at least 10 characters long" - - return True, "" - -def handle_voice_over_error(error: Exception) -> None: - """Handle voice-over generation errors gracefully.""" - error_messages = { - "ConnectionError": "Unable to connect to the text-to-speech service. Please check your internet connection.", - "ValueError": "Invalid text input for voice-over generation.", - "Exception": f"An error occurred: {str(error)}" - } - error_type = type(error).__name__ - error_message = error_messages.get(error_type, error_messages["Exception"]) - logger.error(f"Voice-over generation error: {error_message}") - st.error(error_message) - -def validate_script(script: Dict) -> Tuple[bool, str]: - """Validate the generated script.""" - required_sections = ['hook', 'introduction', 'showcase', 'value_proposition', 'call_to_action'] - - # Check for missing sections - for section in required_sections: - if section not in script: - return False, f"Missing required section: {section}" - - # Validate section content - for section, content in script.items(): - if not content.get('text'): - return False, f"Missing text in section: {section}" - if not content.get('duration'): - return False, f"Missing duration in section: {section}" - - # Validate total duration - total_duration = sum(float(content['duration'].split()[0]) for content in script.values()) - if total_duration > 90: # 90 seconds max - return False, f"Total duration ({total_duration}s) exceeds maximum allowed (90s)" - - return True, "" - -def validate_narration(narration: Dict) -> Tuple[bool, str]: - """Validate the generated narration script.""" - if not narration.get('narration'): - return False, "Missing narration content" - - required_sections = ['hook', 'introduction', 'showcase', 'value_proposition', 'call_to_action'] - for section in required_sections: - if section not in narration['narration']: - return False, f"Missing narration for section: {section}" - - section_content = narration['narration'][section] - required_fields = ['text', 'voice_style', 'emotion', 'pauses', 'emphasis'] - for field in required_fields: - if field not in section_content: - return False, f"Missing {field} in {section} narration" - - return True, "" - -# Voice-over configuration -VOICE_OPTIONS = { - "languages": { - "en": { - "name": "English", - "voices": ["en-US", "en-GB", "en-AU", "en-IN"], - "default_voice": "en-US" - }, - "es": { - "name": "Spanish", - "voices": ["es-ES", "es-MX", "es-AR"], - "default_voice": "es-ES" - }, - "fr": { - "name": "French", - "voices": ["fr-FR", "fr-CA"], - "default_voice": "fr-FR" - }, - "de": { - "name": "German", - "voices": ["de-DE", "de-AT"], - "default_voice": "de-DE" - }, - "ja": { - "name": "Japanese", - "voices": ["ja-JP"], - "default_voice": "ja-JP" - } - }, - "voice_styles": { - "professional": { - "name": "Professional", - "description": "Clear, confident, and authoritative tone", - "pace": "moderate", - "pitch": "medium" - }, - "casual": { - "name": "Casual", - "description": "Friendly and conversational tone", - "pace": "slightly_fast", - "pitch": "medium_high" - }, - "enthusiastic": { - "name": "Enthusiastic", - "description": "Energetic and engaging tone", - "pace": "fast", - "pitch": "high" - }, - "calm": { - "name": "Calm", - "description": "Relaxed and soothing tone", - "pace": "slow", - "pitch": "low" - }, - "energetic": { - "name": "Energetic", - "description": "Dynamic and vibrant tone", - "pace": "fast", - "pitch": "medium_high" - } - }, - "pacing_options": { - "very_slow": 0.5, - "slow": 0.75, - "moderate": 1.0, - "slightly_fast": 1.25, - "fast": 1.5 - } -} - -def get_voice_over_options() -> Dict: - """Get available voice-over options.""" - return VOICE_OPTIONS - -def get_voice_style_settings(style: str) -> Dict: - """Get settings for a specific voice style.""" - return VOICE_OPTIONS["voice_styles"].get(style, VOICE_OPTIONS["voice_styles"]["professional"]) - -def adjust_text_for_voice_style(text: str, style: str) -> str: - """Adjust text to better match the selected voice style.""" - style_settings = get_voice_style_settings(style) - - # Add pauses and emphasis based on style - if style == "professional": - # Add strategic pauses for clarity - text = text.replace(".", ". [pause]") - text = text.replace("!", "! [pause]") - elif style == "casual": - # Add conversational markers - text = text.replace(".", "...") - elif style == "enthusiastic": - # Add emphasis markers - text = text.replace("!", "! [emphasis]") - elif style == "calm": - # Add longer pauses - text = text.replace(".", ". [long_pause]") - elif style == "energetic": - # Add dynamic emphasis - text = text.replace("!", "! [dynamic_emphasis]") - - return text - -@st.cache_data(ttl=3600) # Cache for 1 hour -def generate_voice_over( - text: str, - language: str = 'en', - voice_style: str = 'professional', - slow: bool = False -) -> bytes: - """Generate voice-over audio using gTTS with enhanced options.""" - try: - # Get language settings - lang_settings = VOICE_OPTIONS["languages"].get(language, VOICE_OPTIONS["languages"]["en"]) - voice = lang_settings["default_voice"] - - # Adjust text based on voice style - adjusted_text = adjust_text_for_voice_style(text, voice_style) - - # Get style settings - style_settings = get_voice_style_settings(voice_style) - - # Generate voice-over - tts = gTTS( - text=adjusted_text, - lang=voice, - slow=slow - ) - - audio_file = BytesIO() - tts.write_to_fp(audio_file) - audio_file.seek(0) - return audio_file.getvalue() - except Exception as e: - handle_voice_over_error(e) - return None - -def display_voice_over_options() -> Dict: - """Display voice-over options in the UI and return selected options.""" - st.markdown("### ๐ŸŽค Voice-over Settings") - - # Language selection - language = st.selectbox( - "Language", - options=list(VOICE_OPTIONS["languages"].keys()), - format_func=lambda x: VOICE_OPTIONS["languages"][x]["name"], - help="Select the language for your voice-over" - ) - - # Voice style selection - voice_style = st.selectbox( - "Voice Style", - options=list(VOICE_OPTIONS["voice_styles"].keys()), - format_func=lambda x: VOICE_OPTIONS["voice_styles"][x]["name"], - help="Select the style of voice-over" - ) - - # Display style description - style_info = VOICE_OPTIONS["voice_styles"][voice_style] - st.markdown(f"**Style Description:** {style_info['description']}") - - # Pace selection - pace = st.select_slider( - "Speaking Pace", - options=list(VOICE_OPTIONS["pacing_options"].keys()), - value=style_info["pace"], - help="Adjust the speaking pace of the voice-over" - ) - - return { - "language": language, - "voice_style": voice_style, - "pace": pace - } - -@st.cache_data(ttl=3600) # Cache for 1 hour -def generate_trailer_script( - channel_name: str, - channel_niche: str, - target_audience: str, - key_topics: str, - unique_points: str, - trailer_length: str -) -> Dict: - """Generate the trailer script using GPT with caching.""" - try: - prompt = f"""Create a professional and engaging YouTube channel trailer script for a {channel_niche} channel named '{channel_name}'. - - Channel Details: - - Target Audience: {target_audience} - - Key Topics: {key_topics} - - Unique Selling Points: {unique_points} - - Desired Length: {trailer_length} - - The script should follow this detailed structure: - - 1. Hook (5-10 seconds): - - Start with a powerful question, statement, or visual hook - - Address the viewer's pain points or interests - - Create immediate curiosity - - Use dynamic language and emotional triggers - Visual Requirements: - - Dynamic opening shot or animation - - Text overlay with key hook phrase - - Background elements that match the channel theme - - Smooth camera movement or transition effects - - 2. Channel Introduction (10-15 seconds): - - Clearly state the channel name - - Explain what makes this channel unique - - Establish credibility and expertise - - Use confident, engaging language - Visual Requirements: - - Channel logo reveal animation - - Brand colors and typography - - Professional backdrop or setting - - Subtle motion graphics - - 3. Content Showcase (10-20 seconds): - - Highlight 2-3 key content types - - Show the value and benefits of watching - - Include specific examples of content - - Use dynamic transitions between topics - Visual Requirements: - - Split-screen or grid layout for content previews - - Thumbnail-style frames for each content type - - Dynamic transitions between content examples - - Overlay graphics showing key statistics or achievements - - 4. Value Proposition (5-10 seconds): - - Clearly state what viewers will gain - - Emphasize unique benefits - - Address viewer's needs and desires - - Use compelling language - Visual Requirements: - - Benefit-focused graphics or icons - - Animated text highlights - - Background elements that reinforce the value - - Professional color scheme - - 5. Call to Action (5-10 seconds): - - Clear subscription prompt - - Mention notification bell - - Create urgency or FOMO - - End with channel branding - Visual Requirements: - - Subscription button animation - - Notification bell icon - - Channel branding elements - - Final logo reveal - - Additional Requirements: - - Keep language conversational and engaging - - Use active voice and present tense - - Include specific numbers and examples - - Maintain consistent tone throughout - - Ensure smooth transitions between sections - - Optimize for the selected duration ({trailer_length}) - - Format the response as a JSON with the following structure: - {{ - "hook": {{ - "text": "the hook text", - "duration": "estimated duration in seconds", - "visual_suggestions": {{ - "main_visual": "primary visual element description", - "text_overlay": "text overlay style and content", - "background": "background elements and effects", - "transitions": ["transition effects"], - "color_scheme": "color palette suggestions" - }} - }}, - "introduction": {{ - "text": "the introduction text", - "duration": "estimated duration in seconds", - "visual_suggestions": {{ - "logo_animation": "logo reveal animation style", - "typography": "text style and animation", - "background": "background elements", - "motion_graphics": ["motion graphic elements"], - "color_scheme": "color palette suggestions" - }} - }}, - "showcase": {{ - "text": "the showcase text", - "duration": "estimated duration in seconds", - "visual_suggestions": {{ - "layout": "content showcase layout style", - "thumbnails": ["thumbnail style descriptions"], - "transitions": ["transition effects between content"], - "overlay_graphics": ["overlay elements"], - "color_scheme": "color palette suggestions" - }} - }}, - "value_proposition": {{ - "text": "the value proposition text", - "duration": "estimated duration in seconds", - "visual_suggestions": {{ - "benefit_graphics": ["benefit-focused visual elements"], - "text_animation": "text animation style", - "background": "background elements", - "icons": ["icon suggestions"], - "color_scheme": "color palette suggestions" - }} - }}, - "call_to_action": {{ - "text": "the call to action text", - "duration": "estimated duration in seconds", - "visual_suggestions": {{ - "cta_animation": "call-to-action animation style", - "button_design": "subscription button design", - "notification_icon": "notification bell design", - "branding": "final branding elements", - "color_scheme": "color palette suggestions" - }} - }}, - "total_duration": "total estimated duration in seconds", - "notes": {{ - "tone": "suggested tone and style", - "music_suggestions": ["suggested music types or moods"], - "transitions": ["suggested transition effects"], - "special_effects": ["suggested special effects"], - "production_tips": ["specific production recommendations"], - "visual_consistency": "guidelines for maintaining visual consistency", - "brand_guidelines": "specific brand implementation guidelines" - }} - }} - - Ensure the script is optimized for the {trailer_length} format and maintains high engagement throughout. - """ - - response = get_gpt_response(prompt) - script = json.loads(response) - - # Validate the generated script - is_valid, error_message = validate_script(script) - if not is_valid: - raise TrailerGeneratorError(f"Invalid script generated: {error_message}") - - return script - except json.JSONDecodeError: - logger.error("Error parsing GPT response as JSON") - raise TrailerGeneratorError("Error generating script. Please try again.") - except Exception as e: - logger.error(f"Error generating script: {str(e)}") - raise TrailerGeneratorError(f"An error occurred: {str(e)}") - -@st.cache_data(ttl=3600) # Cache for 1 hour -def generate_narration_script(script: Dict) -> Dict: - """Generate a natural-sounding narration script from the trailer script with caching.""" - try: - prompt = f"""Convert this YouTube channel trailer script into a natural-sounding narration script. - The script should be engaging, conversational, and optimized for voice-over delivery. - - Original Script: - {json.dumps(script, indent=2)} - - Requirements: - 1. Maintain the same structure and timing - 2. Add natural pauses and emphasis - 3. Include voice modulation suggestions - 4. Add emotional cues - 5. Make it sound conversational - 6. Include pronunciation guides for any technical terms - 7. Add emphasis markers for important points - - Format the response as a JSON with the following structure: - {{ - "narration": {{ - "hook": {{ - "text": "narration text with emphasis and pauses", - "voice_style": "suggested voice style", - "emotion": "suggested emotion", - "pauses": ["pause points"], - "emphasis": ["words to emphasize"] - }}, - "introduction": {{ - "text": "narration text with emphasis and pauses", - "voice_style": "suggested voice style", - "emotion": "suggested emotion", - "pauses": ["pause points"], - "emphasis": ["words to emphasize"] - }}, - "showcase": {{ - "text": "narration text with emphasis and pauses", - "voice_style": "suggested voice style", - "emotion": "suggested emotion", - "pauses": ["pause points"], - "emphasis": ["words to emphasize"] - }}, - "value_proposition": {{ - "text": "narration text with emphasis and pauses", - "voice_style": "suggested voice style", - "emotion": "suggested emotion", - "pauses": ["pause points"], - "emphasis": ["words to emphasize"] - }}, - "call_to_action": {{ - "text": "narration text with emphasis and pauses", - "voice_style": "suggested voice style", - "emotion": "suggested emotion", - "pauses": ["pause points"], - "emphasis": ["words to emphasize"] - }} - }}, - "voice_guidelines": {{ - "overall_tone": "suggested overall tone", - "pace": "suggested speaking pace", - "energy_level": "suggested energy level", - "pronunciation_guide": {{ - "term": "pronunciation" - }}, - "special_instructions": ["special voice-over instructions"] - }} - }} - """ - - response = get_gpt_response(prompt) - narration = json.loads(response) - - # Validate the generated narration - is_valid, error_message = validate_narration(narration) - if not is_valid: - raise TrailerGeneratorError(f"Invalid narration generated: {error_message}") - - return narration - except json.JSONDecodeError: - logger.error("Error parsing GPT response as JSON") - raise TrailerGeneratorError("Error generating narration script. Please try again.") - except Exception as e: - logger.error(f"Error generating narration script: {str(e)}") - raise TrailerGeneratorError(f"An error occurred: {str(e)}") - -def update_session_state(key: str, value: Any) -> None: - """Update session state with feedback.""" - st.session_state[key] = value - st.success(f"{key.replace('_', ' ').title()} updated successfully!") - -def write_yt_channel_trailer(): - """Generate a YouTube channel trailer script and visual elements.""" - - st.title("๐ŸŽฅ YouTube Channel Trailer Generator") - st.markdown("Create an engaging channel trailer that converts visitors into subscribers.") - - # Initialize session state for workflow - if 'current_step' not in st.session_state: - st.session_state.current_step = 1 - if 'channel_info' not in st.session_state: - st.session_state.channel_info = {} - if 'script' not in st.session_state: - st.session_state.script = None - if 'visuals' not in st.session_state: - st.session_state.visuals = None - if 'editing_section' not in st.session_state: - st.session_state.editing_section = None - if 'narration' not in st.session_state: - st.session_state.narration = None - if 'voice_overs' not in st.session_state: - st.session_state.voice_overs = {} - if 'errors' not in st.session_state: - st.session_state.errors = [] - - # Progress bar - progress_text = { - 1: "Channel Information", - 2: "Script Generation", - 3: "Visual Elements", - 4: "Review & Edit", - 5: "Final Output" - } - st.progress((st.session_state.current_step - 1) / 4) - st.markdown(f"**Step {st.session_state.current_step}/5: {progress_text[st.session_state.current_step]}**") - - # Display any errors - if st.session_state.errors: - for error in st.session_state.errors: - st.error(error) - st.session_state.errors = [] - - # Step 1: Channel Information - if st.session_state.current_step == 1: - with st.expander("Channel Information", expanded=True): - st.markdown(""" - ### ๐Ÿ“ Basic Information - Let's start by gathering some basic information about your channel. - This will help us create a trailer that perfectly represents your brand. - """) - - channel_name = st.text_input("Channel Name", - value=st.session_state.channel_info.get('channel_name', ''), - help="Enter your YouTube channel name") - - channel_niche = st.text_input("Channel Niche/Category", - value=st.session_state.channel_info.get('channel_niche', ''), - help="e.g., Tech Reviews, Cooking, Gaming, etc.") - - st.markdown("### ๐Ÿ‘ฅ Target Audience") - st.markdown("Describe who your content is for. Be specific about demographics, interests, and needs.") - target_audience = st.text_area("Target Audience", - value=st.session_state.channel_info.get('target_audience', ''), - help="Describe your target audience in detail") - - st.markdown("### ๐Ÿ“š Content Types") - st.markdown("What kind of content do you create? List your main content types and topics.") - key_topics = st.text_area("Key Topics/Content Types", - value=st.session_state.channel_info.get('key_topics', ''), - help="List the main types of content you create") - - st.markdown("### โœจ Unique Selling Points") - st.markdown("What makes your channel different? Highlight your unique features and value.") - unique_points = st.text_area("Unique Selling Points", - value=st.session_state.channel_info.get('unique_points', ''), - help="What makes your channel different?") - - col1, col2 = st.columns(2) - with col1: - trailer_length = st.selectbox( - "Trailer Length", - ["30 seconds", "60 seconds", "90 seconds"], - index=["30 seconds", "60 seconds", "90 seconds"].index( - st.session_state.channel_info.get('trailer_length', "60 seconds") - ), - help="Choose the desired length of your channel trailer" - ) - with col2: - brand_colors = st.color_picker( - "Brand Color", - value=st.session_state.channel_info.get('brand_colors', "#FF0000"), - help="Select your brand's primary color" - ) - - # Save channel info - st.session_state.channel_info = { - 'channel_name': channel_name, - 'channel_niche': channel_niche, - 'target_audience': target_audience, - 'key_topics': key_topics, - 'unique_points': unique_points, - 'trailer_length': trailer_length, - 'brand_colors': brand_colors - } - - if st.button("Next: Generate Script"): - # Validate channel information - is_valid, error_message = validate_channel_info(st.session_state.channel_info) - if not is_valid: - st.session_state.errors.append(error_message) - st.rerun() - else: - st.session_state.current_step = 2 - st.rerun() - - # Step 2: Script Generation - elif st.session_state.current_step == 2: - st.markdown("### ๐Ÿ“ Script Generation") - st.markdown(""" - We'll now generate a script for your channel trailer. - The script will be divided into key sections, each with specific timing and visual suggestions. - """) - - if st.button("Generate Script"): - try: - with st.spinner("Generating your channel trailer script..."): - script = generate_trailer_script( - channel_name=st.session_state.channel_info['channel_name'], - channel_niche=st.session_state.channel_info['channel_niche'], - target_audience=st.session_state.channel_info['target_audience'], - key_topics=st.session_state.channel_info['key_topics'], - unique_points=st.session_state.channel_info['unique_points'], - trailer_length=st.session_state.channel_info['trailer_length'] - ) - update_session_state('script', script) - - # Generate narration script - with st.spinner("Generating narration script..."): - narration = generate_narration_script(script) - update_session_state('narration', narration) - except TrailerGeneratorError as e: - st.session_state.errors.append(str(e)) - st.rerun() - - if st.session_state.script and st.session_state.narration: - # Display script sections with edit buttons and voice-over options - for section in ["hook", "introduction", "showcase", "value_proposition", "call_to_action"]: - with st.expander(f"{section.replace('_', ' ').title()}", expanded=True): - st.markdown(f"**Duration:** {st.session_state.script[section]['duration']}") - st.markdown(f"**Text:** {st.session_state.script[section]['text']}") - - # Display narration details - st.markdown("### ๐ŸŽค Narration") - narration_section = st.session_state.narration['narration'][section] - st.markdown(f"**Voice Style:** {narration_section['voice_style']}") - st.markdown(f"**Emotion:** {narration_section['emotion']}") - st.markdown("**Emphasis Points:**") - for point in narration_section['emphasis']: - st.markdown(f"- {point}") - - # Voice-over generation with enhanced options - st.markdown("### ๐ŸŽ™๏ธ Generate Voice-over") - voice_options = display_voice_over_options() - - if st.button(f"Generate Voice-over for {section.replace('_', ' ').title()}", - key=f"voice_{section}"): - with st.spinner(f"Generating voice-over for {section}..."): - audio_bytes = generate_voice_over( - text=narration_section['text'], - language=voice_options['language'], - voice_style=voice_options['voice_style'], - slow=voice_options['pace'] in ['very_slow', 'slow'] - ) - if audio_bytes: - st.session_state.voice_overs[section] = { - 'audio': audio_bytes, - 'options': voice_options - } - st.markdown(get_audio_player_html(audio_bytes), unsafe_allow_html=True) - - # Display existing voice-over if available - if section in st.session_state.voice_overs: - st.markdown("### ๐Ÿ”Š Current Voice-over") - st.markdown(get_audio_player_html(st.session_state.voice_overs[section]['audio']), - unsafe_allow_html=True) - - # Show current voice-over settings - current_options = st.session_state.voice_overs[section]['options'] - st.markdown("**Current Settings:**") - st.markdown(f"- Language: {VOICE_OPTIONS['languages'][current_options['language']]['name']}") - st.markdown(f"- Voice Style: {VOICE_OPTIONS['voice_styles'][current_options['voice_style']]['name']}") - st.markdown(f"- Pace: {current_options['pace'].replace('_', ' ').title()}") - - if st.button(f"Edit {section.replace('_', ' ').title()}", key=f"edit_{section}"): - st.session_state.editing_section = section - st.session_state.current_step = 4 - st.rerun() - - col1, col2 = st.columns(2) - with col1: - if st.button("Back to Channel Info"): - st.session_state.current_step = 1 - st.rerun() - with col2: - if st.button("Next: Generate Visuals"): - st.session_state.current_step = 3 - st.rerun() - - # Step 3: Visual Elements - elif st.session_state.current_step == 3: - st.markdown("### ๐ŸŽจ Visual Elements") - st.markdown(""" - Let's create the visual elements for your channel trailer. - We'll generate a logo, background, and other visual assets that match your brand. - """) - - if st.button("Generate Visuals"): - with st.spinner("Generating visual elements..."): - visuals = generate_trailer_visuals( - channel_name=st.session_state.channel_info['channel_name'], - channel_niche=st.session_state.channel_info['channel_niche'], - brand_color=st.session_state.channel_info['brand_colors'] - ) - st.session_state.visuals = visuals - st.success("Visual elements generated successfully!") - - if st.session_state.visuals: - for visual in st.session_state.visuals: - with st.expander(f"{visual['type'].replace('_', ' ').title()}", expanded=True): - st.markdown(f"**Usage:** {visual['usage']}") - st.image(visual["image"], use_column_width=True) - if st.button(f"Regenerate {visual['type'].replace('_', ' ').title()}", - key=f"regen_{visual['type']}"): - with st.spinner(f"Regenerating {visual['type']}..."): - new_visual = generate_trailer_visuals( - channel_name=st.session_state.channel_info['channel_name'], - channel_niche=st.session_state.channel_info['channel_niche'], - brand_color=st.session_state.channel_info['brand_colors'] - ) - st.session_state.visuals = new_visual - st.rerun() - - col1, col2 = st.columns(2) - with col1: - if st.button("Back to Script"): - st.session_state.current_step = 2 - st.rerun() - with col2: - if st.button("Next: Review & Edit"): - st.session_state.current_step = 4 - st.rerun() - - # Step 4: Review & Edit - elif st.session_state.current_step == 4: - st.markdown("### โœ๏ธ Review & Edit") - - if st.session_state.editing_section: - st.markdown(f"### Editing {st.session_state.editing_section.replace('_', ' ').title()}") - section = st.session_state.script[st.session_state.editing_section] - narration_section = st.session_state.narration['narration'][st.session_state.editing_section] - - edited_text = st.text_area("Edit Text", value=section['text']) - edited_duration = st.text_input("Edit Duration", value=section['duration']) - - st.markdown("### ๐ŸŽค Narration Settings") - edited_narration = st.text_area("Edit Narration", value=narration_section['text']) - edited_voice_style = st.text_input("Voice Style", value=narration_section['voice_style']) - edited_emotion = st.text_input("Emotion", value=narration_section['emotion']) - - if st.button("Save Changes"): - st.session_state.script[st.session_state.editing_section]['text'] = edited_text - st.session_state.script[st.session_state.editing_section]['duration'] = edited_duration - st.session_state.narration['narration'][st.session_state.editing_section]['text'] = edited_narration - st.session_state.narration['narration'][st.session_state.editing_section]['voice_style'] = edited_voice_style - st.session_state.narration['narration'][st.session_state.editing_section]['emotion'] = edited_emotion - - # Regenerate voice-over for the edited section - if st.session_state.editing_section in st.session_state.voice_overs: - del st.session_state.voice_overs[st.session_state.editing_section] - - st.session_state.editing_section = None - st.success("Changes saved successfully!") - st.rerun() - - if st.button("Cancel Editing"): - st.session_state.editing_section = None - st.rerun() - else: - st.markdown(""" - Review your channel trailer content. You can: - - Edit any section of the script - - Regenerate visual elements - - Download the final content - """) - - # Display final preview - display_trailer_content(st.session_state.script, st.session_state.visuals) - - col1, col2 = st.columns(2) - with col1: - if st.button("Back to Visuals"): - st.session_state.current_step = 3 - st.rerun() - with col2: - if st.button("Finalize & Download"): - st.session_state.current_step = 5 - st.rerun() - - # Step 5: Final Output - elif st.session_state.current_step == 5: - st.markdown("### ๐ŸŽ‰ Final Output") - st.success("Your channel trailer content is ready!") - - # Display final content - display_trailer_content(st.session_state.script, st.session_state.visuals) - - # Display voice-over guidelines - if st.session_state.narration: - st.markdown("### ๐ŸŽค Voice-over Guidelines") - guidelines = st.session_state.narration['voice_guidelines'] - st.markdown(f"**Overall Tone:** {guidelines['overall_tone']}") - st.markdown(f"**Pace:** {guidelines['pace']}") - st.markdown(f"**Energy Level:** {guidelines['energy_level']}") - - st.markdown("**Pronunciation Guide:**") - for term, pronunciation in guidelines['pronunciation_guide'].items(): - st.markdown(f"- {term}: {pronunciation}") - - st.markdown("**Special Instructions:**") - for instruction in guidelines['special_instructions']: - st.markdown(f"- {instruction}") - - # Download options - st.markdown("### ๐Ÿ’พ Download Options") - col1, col2, col3, col4 = st.columns(4) - with col1: - if st.button("Download Script"): - save_to_file(json.dumps(st.session_state.script, indent=2), "channel_trailer_script.json") - with col2: - if st.button("Download Narration"): - save_to_file(json.dumps(st.session_state.narration, indent=2), "channel_trailer_narration.json") - with col3: - if st.button("Download Voice-overs"): - # Save voice-overs logic here - pass - with col4: - if st.button("Start New Trailer"): - # Reset session state - for key in ['current_step', 'channel_info', 'script', 'visuals', 'editing_section', - 'narration', 'voice_overs']: - if key in st.session_state: - del st.session_state[key] - st.rerun() - -def generate_trailer_visuals( - channel_name: str, - channel_niche: str, - brand_color: str -) -> List[Dict]: - """Generate visual elements for the trailer.""" - - # Generate channel logo concept - logo_prompt = f"""Create a professional logo for a {channel_niche} YouTube channel named '{channel_name}'. - Requirements: - - Simple and memorable design - - Works well in small sizes - - Incorporates brand color: {brand_color} - - Modern and clean style - - Suitable for animation - - Includes both icon and text elements - - Maintains readability at different sizes - """ - logo_image = generate_image(logo_prompt) - - # Generate background visuals - background_prompt = f"""Create a dynamic background for a {channel_niche} YouTube channel trailer. - Requirements: - - Matches the channel's theme and niche - - Incorporates brand color: {brand_color} - - Includes subtle motion elements - - Professional and modern design - - Suitable for text overlay - - Maintains visual hierarchy - - Works well with channel branding - """ - background_image = generate_image(background_prompt) - - # Generate thumbnail style previews - thumbnail_prompt = f"""Create a professional thumbnail style for a {channel_niche} YouTube channel. - Requirements: - - Eye-catching design - - Incorporates brand color: {brand_color} - - Includes space for text - - High contrast for visibility - - Modern and clean style - - Suitable for content preview - """ - thumbnail_image = generate_image(thumbnail_prompt) - - # Generate motion graphic elements - motion_prompt = f"""Create a set of motion graphic elements for a {channel_niche} YouTube channel. - Requirements: - - Modern and dynamic design - - Incorporates brand color: {brand_color} - - Suitable for transitions - - Clean and professional style - - Works well with channel branding - """ - motion_image = generate_image(motion_prompt) - - return [ - { - "type": "logo", - "prompt": logo_prompt, - "image": logo_image, - "usage": "Channel branding and identification" - }, - { - "type": "background", - "prompt": background_prompt, - "image": background_image, - "usage": "Main background and scene setting" - }, - { - "type": "thumbnail", - "prompt": thumbnail_prompt, - "image": thumbnail_image, - "usage": "Content preview and showcase" - }, - { - "type": "motion_graphics", - "prompt": motion_prompt, - "image": motion_image, - "usage": "Transitions and visual effects" - } - ] - -def display_trailer_content(script: Dict, visuals: List[Dict]): - """Display the generated trailer content.""" - - # Display script - st.markdown("### ๐Ÿ“ Trailer Script") - - # Create tabs for different sections - script_tab, visuals_tab, production_tab = st.tabs(["Script", "Visuals", "Production Notes"]) - - with script_tab: - for section in ["hook", "introduction", "showcase", "value_proposition", "call_to_action"]: - if section in script: - st.markdown(f"#### {section.replace('_', ' ').title()}") - st.markdown(f"**Duration:** {script[section]['duration']}") - st.markdown(f"**Text:** {script[section]['text']}") - - # Display visual suggestions in an organized way - st.markdown("**Visual Elements:**") - visual_suggestions = script[section]['visual_suggestions'] - for key, value in visual_suggestions.items(): - if isinstance(value, list): - st.markdown(f"**{key.replace('_', ' ').title()}:**") - for item in value: - st.markdown(f"- {item}") - else: - st.markdown(f"**{key.replace('_', ' ').title()}:** {value}") - st.markdown("---") - - with visuals_tab: - st.markdown("### ๐ŸŽจ Visual Elements") - for visual in visuals: - st.markdown(f"#### {visual['type'].replace('_', ' ').title()}") - st.markdown(f"**Usage:** {visual['usage']}") - st.image(visual["image"], use_column_width=True) - st.markdown("---") - - with production_tab: - if "notes" in script: - st.markdown("### ๐Ÿ“‹ Production Notes") - notes = script["notes"] - - st.markdown("#### ๐ŸŽญ Tone and Style") - st.write(notes["tone"]) - - st.markdown("#### ๐ŸŽต Music Suggestions") - for music in notes["music_suggestions"]: - st.markdown(f"- {music}") - - st.markdown("#### ๐Ÿ”„ Transitions") - for transition in notes["transitions"]: - st.markdown(f"- {transition}") - - st.markdown("#### โœจ Special Effects") - for effect in notes["special_effects"]: - st.markdown(f"- {effect}") - - st.markdown("#### ๐Ÿ’ก Production Tips") - for tip in notes["production_tips"]: - st.markdown(f"- {tip}") - - st.markdown("#### ๐ŸŽจ Visual Consistency") - st.write(notes["visual_consistency"]) - - st.markdown("#### ๐Ÿ“ Brand Guidelines") - st.write(notes["brand_guidelines"]) - - # Add download options - st.markdown("### ๐Ÿ’พ Download Options") - col1, col2 = st.columns(2) - with col1: - if st.button("Download Script"): - save_to_file(json.dumps(script, indent=2), "channel_trailer_script.json") - with col2: - if st.button("Download Visuals"): - # Save visuals logic here - pass - -def get_audio_player_html(audio_bytes: bytes) -> str: - """Generate HTML for audio player with the given audio bytes.""" - b64 = base64.b64encode(audio_bytes).decode() - return f""" - - """ - -if __name__ == "__main__": - try: - write_yt_channel_trailer() - except Exception as e: - logger.error(f"Unexpected error in trailer generator: {str(e)}") - st.error("An unexpected error occurred. Please try again or contact support.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/community_post_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/community_post_generator.py deleted file mode 100644 index 681665ac..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/community_post_generator.py +++ /dev/null @@ -1,591 +0,0 @@ -""" -YouTube Community Post Generator Module - -This module provides sophisticated functionality for generating engaging community posts -with AI-powered content suggestions, engagement analysis, and timing optimization. -""" - -import streamlit as st -import time -import logging -import random -from datetime import datetime, timedelta -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -import re -from textblob import TextBlob - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('youtube_community_post_generator') - -def generate_community_post(post_type, main_topic, target_audience, tone_style, - content_purpose, channel_niche, include_emoji=True, - include_hashtags=True, include_poll=False, - include_image_prompt=False, include_timing_suggestion=True, - max_length=None, language="English"): - """Generate an AI-optimized community post with engagement features.""" - - # Create a custom system prompt for community post generation - system_prompt = f"""You are a YouTube Community Post expert specializing in creating highly engaging, - conversion-optimized posts that drive channel growth and viewer interaction. - Focus on creating posts that encourage meaningful engagement while maintaining the channel's voice. - Write the entire post in {language}. - Consider timing, audience psychology, and platform-specific best practices.""" - - # Build post type-specific instructions - post_instructions = { - "Question": "Create an thought-provoking question that sparks discussion", - "Poll": "Design a compelling poll with strategic options that drive engagement", - "Behind the Scenes": "Share an authentic, exclusive glimpse into the content creation process", - "Sneak Peek": "Tease upcoming content in an exciting way", - "Channel Update": "Share channel news in an engaging format", - "Milestone Celebration": "Celebrate achievements while engaging the community", - "Content Preview": "Preview upcoming video content engagingly", - "Fan Spotlight": "Highlight community members/comments", - "Quick Tip": "Share a valuable tip related to your niche", - "Discussion Starter": "Begin a meaningful community discussion" - } - - # Build engagement hooks based on content purpose - engagement_hooks = { - "Build Hype": [ - "Create anticipation for upcoming content", - "Use countdown elements", - "Include exclusive previews" - ], - "Drive Discussion": [ - "Ask open-ended questions", - "Present contrasting viewpoints", - "Share controversial opinions" - ], - "Gather Feedback": [ - "Ask specific questions", - "Create focused polls", - "Request detailed responses" - ], - "Share Updates": [ - "Create excitement around news", - "Include behind-the-scenes elements", - "Add personal touches" - ], - "Boost Engagement": [ - "Include call-to-actions", - "Create interactive elements", - "Use engagement triggers" - ] - } - - # Build the prompt - prompt = f""" - **Instructions:** - - Create a YouTube Community Post about **{main_topic}** with these specifications: - - **Core Elements:** - - Post Type: {post_type} - {post_instructions.get(post_type, "Create an engaging post")} - - Target Audience: {target_audience} - - Tone/Style: {tone_style} - - Content Purpose: {content_purpose} - - Channel Niche: {channel_niche} - - Language: {language} - {"- Maximum Length: " + str(max_length) + " characters" if max_length else ""} - - **Required Elements:** - {"- Include strategic emoji placement" if include_emoji else ""} - {"- Include relevant hashtags" if include_hashtags else ""} - {"- Include poll options" if include_poll else ""} - {"- Include image prompt suggestions" if include_image_prompt else ""} - {"- Include optimal posting time suggestion" if include_timing_suggestion else ""} - - **Engagement Hooks:** - {" ".join(engagement_hooks.get(content_purpose, ["Create engaging content"]))} - - **Format the post with:** - 1. Main Content - 2. Engagement Elements - 3. Call-to-Action - 4. Additional Components (hashtags, etc.) - - **Remember:** - - Keep the tone consistent with channel voice - - Use psychology triggers for engagement - - Include clear call-to-actions - - Make it easy to respond to - - Create shareable content - """ - - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response - except Exception as err: - st.error(f"Error: Failed to get response from LLM: {err}") - return None - -def analyze_post_engagement(post_content): - """Analyze a community post for engagement potential using advanced AI metrics.""" - analysis = { - 'engagement_score': 0, - 'emotional_triggers': 0, - 'call_to_action_strength': 0, - 'readability_score': 0, - 'hashtag_optimization': 0, - 'timing_recommendation': None, - 'sentiment_analysis': {}, - 'virality_potential': 0, - 'audience_resonance': 0, - 'content_uniqueness': 0, - 'psychological_triggers': [], - 'improvement_suggestions': [], - 'engagement_patterns': {}, - 'content_structure': {}, - 'seo_optimization': 0 - } - - # Sentiment Analysis using TextBlob - blob = TextBlob(post_content) - analysis['sentiment_analysis'] = { - 'polarity': round((blob.sentiment.polarity + 1) * 50, 2), # Convert to 0-100 scale - 'subjectivity': round(blob.sentiment.subjectivity * 100, 2), - 'tone': 'Positive' if blob.sentiment.polarity > 0 else 'Negative' if blob.sentiment.polarity < 0 else 'Neutral' - } - - # Analyze emotional triggers with expanded vocabulary - emotional_categories = { - 'excitement': ['excited', 'amazing', 'incredible', 'awesome', 'mind-blowing'], - 'curiosity': ['guess what', 'secret', 'revealed', 'discover', 'mystery'], - 'urgency': ['limited', 'hurry', 'soon', 'don\'t miss', 'last chance'], - 'social_proof': ['everyone', 'community', 'fans', 'you all', 'together'], - 'exclusivity': ['exclusive', 'special', 'limited', 'only', 'selected'] - } - - trigger_counts = {category: 0 for category in emotional_categories} - for category, words in emotional_categories.items(): - trigger_counts[category] = sum(post_content.lower().count(word) for word in words) - - analysis['emotional_triggers'] = min(sum(trigger_counts.values()) * 15, 100) - analysis['psychological_triggers'] = [cat for cat, count in trigger_counts.items() if count > 0] - - # Analyze call-to-action strength with pattern recognition - cta_patterns = { - 'question_cta': r'\?', - 'direct_command': r'(?i)(comment|share|like|subscribe|follow)', - 'engagement_request': r'(?i)(let (me|us) know|tell (me|us)|what do you think)', - 'time_sensitive': r'(?i)(today|now|limited time|hurry)', - 'value_proposition': r'(?i)(learn|discover|find out|get|access)' - } - - cta_strength = 0 - for pattern_type, pattern in cta_patterns.items(): - matches = len(re.findall(pattern, post_content)) - cta_strength += matches * 20 - analysis['call_to_action_strength'] = min(cta_strength, 100) - - # Content Structure Analysis - analysis['content_structure'] = { - 'length_score': min(len(post_content.split()) / 5, 100), # Optimal length analysis - 'paragraph_breaks': min(post_content.count('\n\n') * 20, 100), # Readability through structure - 'emoji_balance': min(len(re.findall(r'[\U0001F300-\U0001F9FF]', post_content)) * 10, 100), # Emoji usage score - 'formatting_score': min((post_content.count('*') + post_content.count('_')) * 5, 100) # Text formatting score - } - - # Virality Potential Analysis - virality_factors = { - 'emotional_impact': analysis['emotional_triggers'], - 'shareability': analysis['content_structure']['length_score'], - 'uniqueness': random.randint(60, 100), # Simulated uniqueness score - 'timeliness': 80 if any(word in post_content.lower() for word in ['new', 'breaking', 'update', 'just']) else 50 - } - analysis['virality_potential'] = sum(virality_factors.values()) / len(virality_factors) - - # Audience Resonance Analysis - resonance_factors = { - 'relevance': analysis['sentiment_analysis']['subjectivity'], - 'engagement_hooks': analysis['call_to_action_strength'], - 'emotional_connection': analysis['emotional_triggers'] - } - analysis['audience_resonance'] = sum(resonance_factors.values()) / len(resonance_factors) - - # SEO Optimization - seo_factors = { - 'hashtag_quality': analyze_hashtag_quality(post_content), - 'keyword_density': analyze_keyword_density(post_content), - 'url_presence': 100 if 'http' in post_content else 0, - 'mention_optimization': analyze_mentions(post_content) - } - analysis['seo_optimization'] = sum(seo_factors.values()) / len(seo_factors) - - # Engagement Pattern Analysis - analysis['engagement_patterns'] = analyze_engagement_patterns(post_content) - - # Calculate overall engagement score with weighted components - analysis['engagement_score'] = calculate_weighted_score({ - 'emotional_triggers': (analysis['emotional_triggers'], 0.2), - 'call_to_action_strength': (analysis['call_to_action_strength'], 0.2), - 'virality_potential': (analysis['virality_potential'], 0.15), - 'audience_resonance': (analysis['audience_resonance'], 0.15), - 'seo_optimization': (analysis['seo_optimization'], 0.1), - 'sentiment_balance': (analysis['sentiment_analysis']['polarity'], 0.1), - 'content_structure': (sum(analysis['content_structure'].values()) / len(analysis['content_structure']), 0.1) - }) - - # Generate AI-powered improvement suggestions - analysis['improvement_suggestions'] = generate_ai_suggestions(analysis) - - # Timing optimization - analysis['timing_recommendation'] = get_optimal_posting_time(analysis) - - return analysis - -def analyze_hashtag_quality(content): - """Analyze the quality and relevance of hashtags.""" - hashtags = re.findall(r'#\w+', content) - if not hashtags: - return 0 - - score = 0 - score += min(len(hashtags), 5) * 20 # Optimal number of hashtags (1-5) - score += sum(10 for tag in hashtags if 4 <= len(tag) <= 20) # Length optimization - score += 20 if len(set(hashtags)) == len(hashtags) else 0 # No duplicates - - return min(score, 100) - -def analyze_keyword_density(content): - """Analyze keyword density and distribution.""" - words = content.lower().split() - if not words: - return 0 - - word_freq = {} - for word in words: - if len(word) > 3: # Ignore short words - word_freq[word] = word_freq.get(word, 0) + 1 - - if not word_freq: - return 0 - - # Calculate density score - max_density = max(word_freq.values()) / len(words) - return 100 if 0.01 <= max_density <= 0.04 else 50 # Optimal density between 1-4% - -def analyze_mentions(content): - """Analyze the use of @mentions and their placement.""" - mentions = re.findall(r'@\w+', content) - if not mentions: - return 0 - - score = 0 - score += min(len(mentions), 3) * 25 # Optimal number of mentions (1-3) - score += 25 if mentions[0] in content.split()[:len(content.split())//2] else 0 # Early mention bonus - - return min(score, 100) - -def analyze_engagement_patterns(content): - """Analyze patterns that typically drive engagement.""" - patterns = { - 'question_hooks': len(re.findall(r'\?', content)), - 'emotional_words': len(re.findall(r'\b(love|hate|amazing|awesome|incredible|excited)\b', content.lower())), - 'community_references': len(re.findall(r'\b(we|our|community|together|everyone)\b', content.lower())), - 'action_words': len(re.findall(r'\b(get|do|make|try|click|watch|share)\b', content.lower())), - 'urgency_triggers': len(re.findall(r'\b(now|today|limited|soon|hurry)\b', content.lower())) - } - - return {k: min(v * 20, 100) for k, v in patterns.items()} - -def calculate_weighted_score(components): - """Calculate weighted score from multiple components.""" - return sum(score * weight for (score, weight) in components.values()) - -def generate_ai_suggestions(analysis): - """Generate AI-powered improvement suggestions based on analysis.""" - suggestions = [] - - if analysis['emotional_triggers'] < 70: - suggestions.append({ - 'category': 'Emotional Impact', - 'suggestion': 'Add more emotional triggers to increase engagement', - 'examples': ['amazing', 'incredible', 'exciting'] - }) - - if analysis['call_to_action_strength'] < 70: - suggestions.append({ - 'category': 'Call-to-Action', - 'suggestion': 'Strengthen your call-to-action', - 'examples': ['Comment below', 'Share your thoughts', 'Let me know'] - }) - - if analysis['virality_potential'] < 70: - suggestions.append({ - 'category': 'Virality', - 'suggestion': 'Increase viral potential by adding trending elements', - 'examples': ['Current trends', 'Popular hashtags', 'Timely topics'] - }) - - if analysis['seo_optimization'] < 70: - suggestions.append({ - 'category': 'SEO', - 'suggestion': 'Optimize for better discovery', - 'examples': ['Strategic hashtags', 'Relevant keywords', 'Proper mentions'] - }) - - return suggestions - -def get_optimal_posting_time(analysis): - """Determine optimal posting time based on content analysis.""" - current_hour = datetime.now().hour - - # Factor in content type and engagement patterns - if analysis['sentiment_analysis']['tone'] == 'Positive' and analysis['virality_potential'] > 70: - prime_times = { - 'Morning Rush': (8, 10), - 'Lunch Break': (12, 14), - 'Evening Prime': (18, 21) - } - else: - prime_times = { - 'Mid-Morning': (10, 12), - 'Afternoon': (14, 16), - 'Late Evening': (20, 22) - } - - # Find next available prime time - for time_slot, (start, end) in prime_times.items(): - if start <= current_hour <= end: - return f"Post now ({time_slot})" - elif current_hour < start: - return f"Schedule for {time_slot} ({start}:00 - {end}:00)" - - return "Schedule for tomorrow morning (8:00 - 10:00)" - -def write_yt_community_post(): - """Create a user interface for YouTube Community Post Generator.""" - st.write("Generate engaging community posts that drive interaction and channel growth.") - - # Initialize session state - if "generated_post" not in st.session_state: - st.session_state.generated_post = None - if "post_history" not in st.session_state: - st.session_state.post_history = [] - - # Create tabs for different sections - tab1, tab2, tab3 = st.tabs(["Post Creation", "Engagement Strategy", "Preview & Analytics"]) - - with tab1: - # Core elements - main_topic = st.text_area("Main Topic/Message", - placeholder="e.g., New video announcement, Channel update, Question for viewers") - - col1, col2 = st.columns(2) - with col1: - post_type = st.selectbox("Post Type", [ - "Question", - "Poll", - "Behind the Scenes", - "Sneak Peek", - "Channel Update", - "Milestone Celebration", - "Content Preview", - "Fan Spotlight", - "Quick Tip", - "Discussion Starter" - ]) - - target_audience = st.text_input("Target Audience", - placeholder="e.g., Tech enthusiasts, Gamers, DIY lovers") - - with col2: - content_purpose = st.selectbox("Content Purpose", [ - "Build Hype", - "Drive Discussion", - "Gather Feedback", - "Share Updates", - "Boost Engagement" - ]) - - tone_style = st.selectbox("Tone/Style", [ - "Casual", - "Professional", - "Excited", - "Mysterious", - "Humorous", - "Informative" - ]) - - channel_niche = st.text_input("Channel Niche", - placeholder="e.g., Tech Reviews, Gaming, Education") - - with tab2: - # Engagement options - st.subheader("Engagement Elements") - col1, col2 = st.columns(2) - - with col1: - include_emoji = st.checkbox("Include Emojis", value=True) - include_hashtags = st.checkbox("Include Hashtags", value=True) - max_length = st.number_input("Maximum Length (characters)", - min_value=100, max_value=2000, value=500) - - with col2: - include_poll = st.checkbox("Include Poll", value=False) - include_image_prompt = st.checkbox("Include Image Suggestions", value=True) - include_timing_suggestion = st.checkbox("Include Timing Suggestion", value=True) - - # Advanced options - st.subheader("Advanced Options") - language = st.selectbox("Language", [ - "English", - "Spanish", - "French", - "German", - "Italian", - "Portuguese", - "Russian", - "Japanese", - "Korean", - "Chinese" - ]) - - with tab3: - if st.session_state.generated_post: - # Display the generated post - st.subheader("Generated Community Post") - - # Create tabs for different views - post_tab1, post_tab2, post_tab3 = st.tabs(["Preview", "Analytics", "History"]) - - with post_tab1: - st.markdown(st.session_state.generated_post) - - # Quick actions - col1, col2 = st.columns(2) - with col1: - if st.button("Copy to Clipboard"): - st.code(st.session_state.generated_post) - st.success("Post copied to clipboard!") - - with col2: - if st.button("Save to History"): - st.session_state.post_history.append({ - 'post': st.session_state.generated_post, - 'timestamp': datetime.now(), - 'type': post_type - }) - st.success("Post saved to history!") - - with post_tab2: - # Analyze the post - analysis = analyze_post_engagement(st.session_state.generated_post) - - # Create expandable sections for different analysis categories - with st.expander("๐Ÿ“Š Overall Performance Metrics", expanded=True): - cols = st.columns(3) - - with cols[0]: - score = analysis['engagement_score'] - color = "red" if score < 60 else "orange" if score < 80 else "green" - st.markdown(f"### Overall Score: {score:.1f}%", - unsafe_allow_html=True) - - # Sentiment Analysis - st.markdown("#### Sentiment Analysis") - st.metric("Polarity", f"{analysis['sentiment_analysis']['polarity']}%") - st.metric("Subjectivity", f"{analysis['sentiment_analysis']['subjectivity']}%") - st.info(f"Tone: {analysis['sentiment_analysis']['tone']}") - - with cols[1]: - st.markdown("#### Engagement Metrics") - st.metric("Emotional Impact", f"{analysis['emotional_triggers']}%") - st.metric("CTA Strength", f"{analysis['call_to_action_strength']}%") - st.metric("Virality Potential", f"{analysis['virality_potential']:.1f}%") - - with cols[2]: - st.markdown("#### Content Quality") - st.metric("Audience Resonance", f"{analysis['audience_resonance']:.1f}%") - st.metric("SEO Score", f"{analysis['seo_optimization']:.1f}%") - if analysis['timing_recommendation']: - st.success(f"๐Ÿ“… {analysis['timing_recommendation']}") - - with st.expander("๐ŸŽฏ Psychological Triggers & Patterns"): - col1, col2 = st.columns(2) - - with col1: - st.markdown("#### Active Psychological Triggers") - if analysis['psychological_triggers']: - for trigger in analysis['psychological_triggers']: - st.markdown(f"โœ“ {trigger.title()}") - else: - st.info("No strong psychological triggers detected") - - with col2: - st.markdown("#### Engagement Patterns") - patterns = analysis['engagement_patterns'] - for pattern, score in patterns.items(): - st.metric(pattern.replace('_', ' ').title(), f"{score}%") - - with st.expander("๐Ÿ“ Content Structure Analysis"): - col1, col2 = st.columns(2) - - with col1: - structure = analysis['content_structure'] - st.markdown("#### Structure Metrics") - for metric, score in structure.items(): - st.metric( - metric.replace('_', ' ').title(), - f"{score:.1f}%" - ) - - with col2: - st.markdown("#### SEO Analysis") - st.metric("Hashtag Quality", f"{analyze_hashtag_quality(st.session_state.generated_post)}%") - st.metric("Keyword Density", f"{analyze_keyword_density(st.session_state.generated_post)}%") - st.metric("Mention Optimization", f"{analyze_mentions(st.session_state.generated_post)}%") - - # Show improvement suggestions - if analysis['improvement_suggestions']: - with st.expander("๐Ÿ’ก AI-Powered Suggestions", expanded=True): - for suggestion in analysis['improvement_suggestions']: - with st.container(): - st.markdown(f"#### {suggestion['category']}") - st.info(suggestion['suggestion']) - if suggestion['examples']: - st.markdown("**Examples:**") - for example in suggestion['examples']: - st.markdown(f"- {example}") - - # Add a refresh button for analysis - if st.button("๐Ÿ”„ Refresh Analysis"): - st.rerun() - - with post_tab3: - if st.session_state.post_history: - st.subheader("Previous Posts") - for i, post in enumerate(reversed(st.session_state.post_history)): - with st.expander(f"Post {len(st.session_state.post_history)-i}: " - f"{post['type']} - " - f"{post['timestamp'].strftime('%Y-%m-%d %H:%M')}"): - st.write(post['post']) - else: - st.info("No post history yet. Save posts to see them here!") - - # Generate button - if st.button("Generate Community Post"): - if not main_topic: - st.error("Please enter a main topic/message.") - return - - with st.spinner("Generating community post..."): - post = generate_community_post( - post_type, main_topic, target_audience, tone_style, - content_purpose, channel_niche, include_emoji, - include_hashtags, include_poll, include_image_prompt, - include_timing_suggestion, max_length, language - ) - - if post: - st.session_state.generated_post = post - st.success("โœจ Post generated successfully! Check the 'Preview & Analytics' tab to view, analyze, and save your post.") - st.rerun() - else: - st.error("Failed to generate post. Please try again.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/description_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/description_generator.py deleted file mode 100644 index 66661ae1..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/description_generator.py +++ /dev/null @@ -1,404 +0,0 @@ -""" -YouTube Description Generator Module - -This module provides functionality for generating YouTube video descriptions. -""" - -import streamlit as st -import time -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - - -def calculate_keyword_density(text, keywords): - """Calculate the density of keywords in the text.""" - if not text or not keywords: - return 0 - - text = text.lower() - keywords = [k.lower() for k in keywords] - - total_words = len(text.split()) - keyword_count = sum(text.count(k) for k in keywords) - - return (keyword_count / total_words) * 100 if total_words > 0 else 0 - - -def calculate_seo_score(text, keywords): - """Calculate the SEO score of the description.""" - score = 0 - - # Text length (optimal: 250-300 words) - word_count = len(text.split()) - if 250 <= word_count <= 300: - score += 3 - elif 200 <= word_count <= 350: - score += 2 - elif 150 <= word_count <= 400: - score += 1 - - # Keyword presence - text_lower = text.lower() - keywords_lower = [k.lower() for k in keywords] - keyword_count = sum(text_lower.count(k) for k in keywords_lower) - if keyword_count >= 3: - score += 3 - elif keyword_count >= 2: - score += 2 - elif keyword_count >= 1: - score += 1 - - # Call to action phrases - cta_phrases = ["subscribe", "like", "comment", "share", "follow", "check out", "visit", "learn more"] - cta_count = sum(text_lower.count(phrase) for phrase in cta_phrases) - if cta_count >= 2: - score += 2 - elif cta_count >= 1: - score += 1 - - # Hashtags - hashtag_count = text.count("#") - if 3 <= hashtag_count <= 5: - score += 2 - elif 1 <= hashtag_count <= 8: - score += 1 - - # Links - link_count = text.count("http") - if 1 <= link_count <= 3: - score += 2 - elif link_count > 3: - score += 1 - - return min(score, 10) # Cap at 10 - - -def generate_youtube_description(target_audience, main_points, tone_style, use_case, primary_keywords, - secondary_keywords, language, seo_goals, include_timestamps=False, - include_hashtags=False, include_social_handles=False): - """Generate a YouTube description based on the provided parameters.""" - - # Create a custom system prompt for YouTube description generation - system_prompt = """You are a YouTube description expert specializing in creating engaging, SEO-optimized video descriptions. - Your task is to generate YouTube video descriptions based on the provided information. - Focus ONLY on creating descriptions that are optimized for YouTube, with proper formatting, keywords, and calls to action. - Return ONLY the description text, without any additional commentary or explanations.""" - - # Build the prompt - prompt = f""" - **Instructions:** - - Please generate a YouTube description for a video about **{main_points}** based on the following information: - - **Target Audience:** {target_audience} - **Tone and Style:** {tone_style} - **Use Case:** {use_case} - **Language:** {language} - **Primary Keywords:** {primary_keywords} - **Secondary Keywords:** {secondary_keywords} - **SEO Goals:** {seo_goals} - - **Additional Elements:** - {"- Include timestamps for key sections." if include_timestamps else ""} - {"- Include relevant hashtags." if include_hashtags else ""} - {"- Include social media handles." if include_social_handles else ""} - - **Specific Instructions:** - * Keep the description informative and engaging. - * Use a conversational tone that matches the target audience. - * Include relevant keywords naturally. - * Add a call to action. - * Keep the length between 250-300 words for optimal SEO. - """ - - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response - except Exception as err: - st.error(f"Error: Failed to get response from LLM: {err}") - return None - - -def write_yt_description(): - """Create a user interface for YouTube Description Generator.""" - st.write("Generate SEO-optimized YouTube video descriptions that drive engagement.") - - # Initialize session state for generated description if it doesn't exist - if "generated_description" not in st.session_state: - st.session_state.generated_description = None - - # Create tabs for different sections - tab1, tab2, tab3 = st.tabs(["Basic Info", "SEO Optimization", "Advanced Options"]) - - with tab1: - # Basic information inputs - main_points = st.text_area("Main Points/Keywords (comma-separated)", - placeholder="e.g., cooking tips, healthy recipes, quick meals") - - # Create columns for the other inputs - col1, col2, col3, col4 = st.columns(4) - - with col1: - tone_style = st.selectbox("Tone/Style", - ["Professional", "Casual", "Humorous", "Educational", "Entertaining", "Inspirational"]) - - with col2: - target_audience = st.text_input("Target Audience", - placeholder="e.g., beginners, professionals, parents") - - with col3: - use_case = st.selectbox("Use Case", - ["How-to/Tutorial", "Vlog", "Review", "Educational", "Entertainment", "News"]) - - with col4: - language = st.selectbox("Language", ["English", "Spanish", "French", "German", "Italian", "Portuguese"]) - - with tab2: - # SEO optimization inputs - primary_keywords = st.text_input("Primary Keywords (comma-separated)", - placeholder="e.g., cooking, recipes, healthy food") - secondary_keywords = st.text_input("Secondary Keywords (comma-separated)", - placeholder="e.g., quick meals, budget cooking") - seo_goals = st.multiselect("SEO Goals", - ["Increase Views", "Drive Engagement", "Build Subscribers", "Promote Products/Services"]) - - with tab3: - # Advanced options - st.subheader("Additional Elements") - include_timestamps = st.checkbox("Include Timestamps", value=True) - include_hashtags = st.checkbox("Include Hashtags", value=True) - include_social_handles = st.checkbox("Include Social Media Handles", value=True) - - if st.button("Generate Description"): - if not main_points: - st.error("Please enter main points/keywords.") - return - - with st.spinner("Generating description..."): - description = generate_youtube_description( - target_audience, main_points, tone_style, use_case, primary_keywords, - secondary_keywords, language, seo_goals, include_timestamps, - include_hashtags, include_social_handles - ) - - if description: - # Store the description in session state - st.session_state.generated_description = description - - # Store other parameters in session state for regeneration - st.session_state.description_params = { - "target_audience": target_audience, - "main_points": main_points, - "tone_style": tone_style, - "use_case": use_case, - "primary_keywords": primary_keywords, - "secondary_keywords": secondary_keywords, - "language": language, - "seo_goals": seo_goals, - "include_timestamps": include_timestamps, - "include_hashtags": include_hashtags, - "include_social_handles": include_social_handles - } - - st.subheader("Generated Description") - - # Display description with analysis - st.text_area("Description", description, height=200) - - # Calculate and display metrics - all_keywords = primary_keywords.split(",") + secondary_keywords.split(",") - keyword_density = calculate_keyword_density(description, all_keywords) - seo_score = calculate_seo_score(description, all_keywords) - - col1, col2 = st.columns(2) - with col1: - st.metric("Keyword Density", f"{keyword_density:.1f}%") - with col2: - st.metric("SEO Score", f"{seo_score}/10") - - # Create columns for the buttons - btn_col1, btn_col2 = st.columns(2) - - with btn_col1: - # Download button - st.download_button( - label="Download Description", - data=description, - file_name="youtube_description.txt", - mime="text/plain" - ) - - with btn_col2: - # Regenerate button - if st.button("Regenerate"): - st.session_state.show_regenerate_popover = True - - # Regenerate popover - if st.session_state.get("show_regenerate_popover", False): - with st.form("regenerate_form"): - st.subheader("Regenerate Description") - st.write("Specify changes you'd like to make to the description:") - changes = st.text_area("Changes to make", - placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits") - - submitted = st.form_submit_button("Regenerate with Changes") - - if submitted and changes: - with st.spinner("Regenerating description..."): - # Get the stored parameters - params = st.session_state.description_params - - # Add the changes to the prompt - params["changes"] = changes - - # Generate a new description with the changes - new_description = generate_youtube_description_with_changes( - params["target_audience"], - params["main_points"], - params["tone_style"], - params["use_case"], - params["primary_keywords"], - params["secondary_keywords"], - params["language"], - params["seo_goals"], - params["include_timestamps"], - params["include_hashtags"], - params["include_social_handles"], - changes - ) - - if new_description: - # Update the stored description - st.session_state.generated_description = new_description - st.session_state.show_regenerate_popover = False - st.rerun() - else: - st.error("Failed to regenerate description. Please try again.") - else: - st.error("Failed to generate description. Please try again.") - - # Display previously generated description if it exists in session state - elif st.session_state.generated_description: - description = st.session_state.generated_description - params = st.session_state.description_params - - st.subheader("Generated Description") - - # Display description with analysis - st.text_area("Description", description, height=200) - - # Calculate and display metrics - all_keywords = params["primary_keywords"].split(",") + params["secondary_keywords"].split(",") - keyword_density = calculate_keyword_density(description, all_keywords) - seo_score = calculate_seo_score(description, all_keywords) - - col1, col2 = st.columns(2) - with col1: - st.metric("Keyword Density", f"{keyword_density:.1f}%") - with col2: - st.metric("SEO Score", f"{seo_score}/10") - - # Create columns for the buttons - btn_col1, btn_col2 = st.columns(2) - - with btn_col1: - # Download button - st.download_button( - label="Download Description", - data=description, - file_name="youtube_description.txt", - mime="text/plain" - ) - - with btn_col2: - # Regenerate button - if st.button("Regenerate"): - st.session_state.show_regenerate_popover = True - - # Regenerate popover - if st.session_state.get("show_regenerate_popover", False): - with st.form("regenerate_form"): - st.subheader("Regenerate Description") - st.write("Specify changes you'd like to make to the description:") - changes = st.text_area("Changes to make", - placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits") - - submitted = st.form_submit_button("Regenerate with Changes") - - if submitted and changes: - with st.spinner("Regenerating description..."): - # Add the changes to the prompt - params["changes"] = changes - - # Generate a new description with the changes - new_description = generate_youtube_description_with_changes( - params["target_audience"], - params["main_points"], - params["tone_style"], - params["use_case"], - params["primary_keywords"], - params["secondary_keywords"], - params["language"], - params["seo_goals"], - params["include_timestamps"], - params["include_hashtags"], - params["include_social_handles"], - changes - ) - - if new_description: - # Update the stored description - st.session_state.generated_description = new_description - st.session_state.show_regenerate_popover = False - st.rerun() - else: - st.error("Failed to regenerate description. Please try again.") - - -def generate_youtube_description_with_changes(target_audience, main_points, tone_style, use_case, primary_keywords, - secondary_keywords, language, seo_goals, include_timestamps=False, - include_hashtags=False, include_social_handles=False, changes=""): - """Generate a YouTube description based on the provided parameters and requested changes.""" - - # Create a custom system prompt for YouTube description generation - system_prompt = """You are a YouTube description expert specializing in creating engaging, SEO-optimized video descriptions. - Your task is to generate YouTube video descriptions based on the provided information. - Focus ONLY on creating descriptions that are optimized for YouTube, with proper formatting, keywords, and calls to action. - Return ONLY the description text, without any additional commentary or explanations.""" - - # Build the prompt - prompt = f""" - **Instructions:** - - Please generate a YouTube description for a video about **{main_points}** based on the following information: - - **Target Audience:** {target_audience} - **Tone and Style:** {tone_style} - **Use Case:** {use_case} - **Language:** {language} - **Primary Keywords:** {primary_keywords} - **Secondary Keywords:** {secondary_keywords} - **SEO Goals:** {seo_goals} - - **Additional Elements:** - {"- Include timestamps for key sections." if include_timestamps else ""} - {"- Include relevant hashtags." if include_hashtags else ""} - {"- Include social media handles." if include_social_handles else ""} - - **Requested Changes:** - {changes} - - **Specific Instructions:** - * Keep the description informative and engaging. - * Use a conversational tone that matches the target audience. - * Include relevant keywords naturally. - * Add a call to action. - * Keep the length between 250-300 words for optimal SEO. - * Incorporate the requested changes into the description. - """ - - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response - except Exception as err: - st.error(f"Error: Failed to get response from LLM: {err}") - return None \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/end_screen_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/end_screen_generator.py deleted file mode 100644 index 861821e8..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/end_screen_generator.py +++ /dev/null @@ -1,740 +0,0 @@ -""" -YouTube End Screen Generator Module - -This module provides functionality for generating YouTube video end screens. -""" - -import streamlit as st -import time -import logging -import traceback -from PIL import Image -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from lib.gpt_providers.text_to_image_generation.gen_gemini_images import generate_gemini_image, edit_image - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('youtube_end_screen_generator') - - -def generate_end_screen_concepts(video_title, video_description, target_audience, content_type, - primary_goal, secondary_goal=None, num_concepts=3): - """Generate end screen concept ideas based on video content.""" - logger.info(f"Generating end screen concepts for: '{video_title}'") - logger.info(f"Parameters: target_audience={target_audience}, content_type={content_type}, " - f"primary_goal={primary_goal}, secondary_goal={secondary_goal}, num_concepts={num_concepts}") - - # Create a system prompt for end screen concept generation - system_prompt = """You are a YouTube end screen expert specializing in creating engaging, action-driving end screen concepts. - Your task is to generate end screen concept ideas based on the provided video information. - Focus ONLY on creating end screens that are optimized for YouTube, with proper visual hierarchy, element placement, and call-to-action triggers. - Return ONLY the concept descriptions, without any additional commentary or explanations. - Each concept should include: - 1. A main visual element or background - 2. Element placement and content (subscribe button, playlist, video, website) - 3. Color scheme suggestions - 4. Text content for each element - 5. Brief explanation of why this concept would be effective for the specified goals - - IMPORTANT: Format each concept with a clear numbered heading like "1. [Concept Name]" to ensure proper parsing.""" - - # Build the prompt - prompt = f""" - **Instructions:** - - Please generate {num_concepts} end screen concept ideas for a YouTube video with the following information: - - **Video Title:** {video_title} - **Video Description:** {video_description} - **Target Audience:** {target_audience} - **Content Type:** {content_type} - **Primary Goal:** {primary_goal} - **Secondary Goal:** {secondary_goal if secondary_goal else "None specified"} - - **Specific Instructions:** - * Each concept should be clearly separated and numbered with a heading like "1. [Concept Name]". - * Focus on creating end screens that drive the specified goals. - * Consider the target audience's interests and preferences. - * Include specific details about visual elements, element placement, and color schemes. - * Explain why each concept would be effective for this specific video and goals. - * Include text suggestions for each element (subscribe button, playlist, video, website). - """ - - try: - logger.info("Sending request to LLM for end screen concepts") - response = llm_text_gen(prompt, system_prompt=system_prompt) - logger.info(f"Received response from LLM: {len(response)} characters") - return response - except Exception as err: - logger.error(f"Error generating end screen concepts: {err}") - logger.error(traceback.format_exc()) - st.error(f"Error: Failed to generate end screen concepts: {err}") - return None - - -def generate_end_screen_design(concept_description, style_preference, element_count=2, - element_types=None, element_texts=None, aspect_ratio="16:9", - keywords=None, style=None, focus=None): - """Generate an end screen image based on the concept description.""" - logger.info(f"Generating end screen design for concept: '{concept_description[:50]}...'") - logger.info(f"Parameters: style_preference={style_preference}, element_count={element_count}, " - f"element_types={element_types}, element_texts={element_texts}, aspect_ratio={aspect_ratio}") - - # Extract key elements from the concept description - # This helps focus the prompt on the most important aspects - concept_lines = concept_description.split('\n') - main_visual = "" - element_placement = "" - color_scheme = "" - text_content = "" - - for line in concept_lines: - if "visual" in line.lower() or "background" in line.lower(): - main_visual = line - elif "placement" in line.lower() or "layout" in line.lower(): - element_placement = line - elif "color" in line.lower() or "scheme" in line.lower(): - color_scheme = line - elif "text" in line.lower() or "content" in line.lower(): - text_content = line - - # Create a more focused prompt for the image generation - image_prompt = f""" - Create a YouTube end screen image with the following specifications: - - MAIN VISUAL: {main_visual if main_visual else "Not specified"} - ELEMENT PLACEMENT: {element_placement if element_placement else "Not specified"} - COLOR SCHEME: {color_scheme if color_scheme else "Not specified"} - TEXT CONTENT: {text_content if text_content else "Not specified"} - - STYLE: {style_preference} - ASPECT RATIO: {aspect_ratio} - NUMBER OF ELEMENTS: {element_count} - - ELEMENT TYPES: {', '.join(element_types) if element_types else 'Not specified'} - ELEMENT TEXTS: {', '.join(element_texts) if element_texts else 'Not specified'} - - IMPORTANT REQUIREMENTS: - 1. This must be a VISUAL IMAGE of a YouTube end screen, not just a text description - 2. The image should be high contrast and visually striking - 3. All text should be large and readable - 4. Elements should be properly placed for optimal viewer engagement - 5. The design should follow the specified color scheme - 6. The image should be optimized for the specified aspect ratio - - PLEASE GENERATE AN ACTUAL IMAGE, NOT JUST A TEXT DESCRIPTION. - """ - - try: - logger.info("Sending request to Gemini for end screen image") - # Generate the image using Gemini with enhanced prompt - img_path = generate_gemini_image( - image_prompt, - keywords=keywords, - style=style, - focus=focus, - enhance_prompt=True - ) - logger.info(f"Received image from Gemini: {img_path}") - return img_path - except Exception as err: - logger.error(f"Error generating end screen image: {err}") - logger.error(traceback.format_exc()) - st.error(f"Error: Failed to generate end screen image: {err}") - return None - - -def edit_end_screen_image(img_path, edit_instructions): - """Edit an end screen image based on user instructions.""" - logger.info(f"Editing end screen image: '{img_path}'") - logger.info(f"Edit instructions: '{edit_instructions}'") - - try: - logger.info("Sending request to Gemini for image editing") - # Edit the image using Gemini - edited_img_path = edit_image(img_path, f"Edit this image according to these instructions: {edit_instructions}. IMPORTANT: Please generate an actual edited image, not just a text description. I need a visual representation of the edited end screen.") - logger.info(f"Image editing completed. Edited image path: {edited_img_path}") - - # Return the path to the edited image - return edited_img_path - except Exception as err: - logger.error(f"Error editing end screen image: {err}") - logger.error(traceback.format_exc()) - st.error(f"Error: Failed to edit end screen image: {err}") - return None - - -def analyze_end_screen(end_screen_path): - """Analyze an end screen for effectiveness.""" - logger.info(f"Analyzing end screen: '{end_screen_path}'") - - # This would typically involve image analysis, but for now we'll use AI to provide feedback - system_prompt = """You are a YouTube end screen expert specializing in analyzing and providing feedback on end screen designs. - Your task is to analyze the end screen and provide constructive feedback on its effectiveness. - Focus on aspects like visual hierarchy, element placement, call-to-action clarity, and overall effectiveness.""" - - # For now, we'll just return a placeholder analysis - # In a real implementation, we would analyze the actual image - logger.info("Generating end screen analysis") - return """ - **End Screen Analysis:** - - - **Visual Hierarchy:** The main elements are well-positioned and stand out against the background. - - **Element Placement:** The call-to-action elements are strategically placed for optimal viewer engagement. - - **Call-to-Action Clarity:** The text and visual cues clearly communicate the desired actions. - - **Overall Effectiveness:** The design is likely to drive the specified goals due to its visual appeal and clear value proposition. - - **Suggestions for Improvement:** - - Consider adding a subtle animation hint to draw attention to the most important element. - - The text could be slightly larger for better readability on mobile devices. - - Adding a small icon or logo could help with brand recognition. - """ - - -def parse_concepts(concepts_text): - """Parse the concepts text into a list of individual concepts.""" - logger.info("Parsing concepts text into individual concepts") - - # Split the concepts text by main concept headers - concepts = [] - current_concept = "" - - # Look for patterns like numbered headings (e.g., "1.", "2.", "3.") or "Concept 1:", "Concept 2:", etc. - concept_patterns = ["1.", "2.", "3.", "4.", "5.", "Concept 1:", "Concept 2:", "Concept 3:", "Concept 4:", "Concept 5:"] - - for line in concepts_text.split('\n'): - # Check if line starts with a concept pattern - is_new_concept = False - for pattern in concept_patterns: - if line.strip().startswith(pattern): - # If we have a previous concept, add it to the list - if current_concept: - concepts.append(current_concept.strip()) - # Start a new concept - current_concept = line - is_new_concept = True - break - - if not is_new_concept: - # Add the line to the current concept - current_concept += "\n" + line - - # Add the last concept - if current_concept: - concepts.append(current_concept.strip()) - - logger.info(f"Parsed {len(concepts)} concepts from the response") - return concepts - - -def write_yt_end_screen(): - """Create a user interface for YouTube End Screen Generator.""" - logger.info("Initializing YouTube End Screen Generator UI") - st.title("YouTube End Screen Generator") - st.write("Create engaging, action-driving end screens for your YouTube videos.") - - # Initialize session state for generated end screens if it doesn't exist - if "generated_end_screens" not in st.session_state: - st.session_state.generated_end_screens = [] - if "end_screen_concepts" not in st.session_state: - st.session_state.end_screen_concepts = None - if "current_end_screen_path" not in st.session_state: - st.session_state.current_end_screen_path = None - if "concept_list" not in st.session_state: - st.session_state.concept_list = [] - if "editing_end_screen" not in st.session_state: - st.session_state.editing_end_screen = False - if "edit_instructions" not in st.session_state: - st.session_state.edit_instructions = "" - if "edited_end_screen_path" not in st.session_state: - st.session_state.edited_end_screen_path = None - if "show_edit_form" not in st.session_state: - st.session_state.show_edit_form = False - - # Create tabs for different sections - tab1, tab2 = st.tabs(["Basic Info", "Style & Elements"]) - - with tab1: - # Basic information inputs - video_title = st.text_input("Video Title", - placeholder="e.g., 10 Tips for Better Photography") - video_description = st.text_area("Video Description", - placeholder="Brief description of your video content") - target_audience = st.text_input("Target Audience", - placeholder="e.g., photography enthusiasts, beginners") - - # Content type selection - content_type = st.selectbox("Content Type", [ - "Tutorial/How-to", - "Vlog", - "Review", - "Educational", - "Entertainment", - "News/Update", - "Product Showcase", - "Challenge", - "Reaction", - "Comparison" - ]) - - # End screen goals - st.subheader("End Screen Goals") - primary_goal = st.selectbox("Primary Goal", [ - "Drive Subscriptions", - "Promote Playlist", - "Promote Next Video", - "Promote Website", - "Promote Social Media", - "Promote Product/Service", - "Encourage Comments", - "Mixed Goals" - ]) - - secondary_goal = st.selectbox("Secondary Goal (Optional)", [ - "None", - "Drive Subscriptions", - "Promote Playlist", - "Promote Next Video", - "Promote Website", - "Promote Social Media", - "Promote Product/Service", - "Encourage Comments" - ]) - - if secondary_goal == "None": - secondary_goal = None - - with tab2: - # Style preferences - st.subheader("Style Preferences") - - # Create columns for style options - col1, col2 = st.columns(2) - - with col1: - style_preference = st.selectbox("End Screen Style", [ - "Bold and Dramatic", - "Clean and Minimal", - "Colorful and Vibrant", - "Dark and Moody", - "Professional and Corporate", - "Playful and Fun", - "Retro/Vintage", - "Modern and Sleek" - ]) - - num_concepts = st.slider("Number of Concepts", 1, 5, 3) - - with col2: - aspect_ratio = st.selectbox("Aspect Ratio", [ - "16:9 (Standard)", - "1:1 (Square)", - "4:3 (Classic)", - "9:16 (Vertical)" - ]) - - include_branding = st.checkbox("Include Branding Elements", value=True) - if include_branding: - branding_elements = st.multiselect("Branding Elements", [ - "Channel Logo", - "Channel Name", - "Channel Tagline", - "Brand Colors", - "Watermark" - ]) - - # Element configuration - st.subheader("End Screen Elements") - - # Number of elements - element_count = st.slider("Number of Elements", 1, 4, 2) - - # Element types - element_types = [] - element_texts = [] - - for i in range(element_count): - st.write(f"Element {i+1}") - col1, col2 = st.columns(2) - - with col1: - element_type = st.selectbox( - f"Type", - ["Subscribe Button", "Playlist", "Video", "Website", "Social Media"], - key=f"element_type_{i}" - ) - element_types.append(element_type) - - with col2: - element_text = st.text_input( - f"Text", - placeholder=f"Text for {element_type}", - key=f"element_text_{i}" - ) - element_texts.append(element_text) - - # Advanced AI Prompt Settings - st.subheader("Advanced AI Prompt Settings") - - # Create columns for advanced settings - col3, col4 = st.columns(2) - - with col3: - # Image style selection - image_style = st.selectbox("Image Style", [ - "Auto (AI will choose best style)", - "Photorealistic", - "Artistic", - "Cartoon/Anime", - "Sketch/Drawing", - "Digital Art", - "3D Render" - ]) - - # Extract style for the generate_gemini_image function - style = None - if image_style == "Photorealistic": - style = "photorealistic" - elif image_style == "Artistic": - style = "artistic" - elif image_style == "Cartoon/Anime": - style = "cartoon" - elif image_style == "Sketch/Drawing": - style = "sketch" - elif image_style == "Digital Art": - style = "digital_art" - elif image_style == "3D Render": - style = "3d_render" - - with col4: - # Focus selection for photorealistic images - focus = None - if style == "photorealistic": - focus = st.selectbox("Image Focus", [ - "Auto (AI will choose best focus)", - "Portraits", - "Objects", - "Motion", - "Wide-angle" - ]) - - # Extract focus for the generate_gemini_image function - if focus == "Portraits": - focus = "portraits" - elif focus == "Objects": - focus = "objects" - elif focus == "Motion": - focus = "motion" - elif focus == "Wide-angle": - focus = "wide-angle" - elif focus == "Auto (AI will choose best focus)": - focus = None - - # Keywords for enhanced prompt generation - st.subheader("Keywords for Enhanced Prompt") - st.write("Add keywords to enhance the AI prompt generation. These will help create more detailed and accurate end screens.") - - # Create a text area for keywords - keywords_input = st.text_area( - "Keywords (comma-separated)", - placeholder="e.g., vibrant, energetic, bold, eye-catching, professional" - ) - - # Process keywords - keywords = None - if keywords_input: - keywords = [k.strip() for k in keywords_input.split(",") if k.strip()] - logger.info(f"User provided keywords: {keywords}") - - # Generate button - placed outside of tabs for better visibility - st.markdown("---") - st.subheader("Generate End Screen Concepts") - st.write("Click the button below to generate end screen concepts based on your inputs.") - - if st.button("Generate End Screen Concepts", type="primary"): - if not video_title: - st.error("Please enter a video title.") - return - - with st.spinner("Generating end screen concepts..."): - logger.info("User clicked Generate End Screen Concepts button") - concepts = generate_end_screen_concepts( - video_title, - video_description, - target_audience, - content_type, - primary_goal, - secondary_goal, - num_concepts - ) - - if concepts: - # Store the concepts in session state - st.session_state.end_screen_concepts = concepts - # Parse the concepts and store in session state - st.session_state.concept_list = parse_concepts(concepts) - logger.info("Stored end screen concepts in session state") - - # Display the concepts in tabs - st.subheader("End Screen Concepts") - - # Create tabs for each concept - concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))]) - - for i, tab in enumerate(concept_tabs): - with tab: - st.markdown(st.session_state.concept_list[i]) - - # Add a button to generate image for this concept - if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_{i}"): - with st.spinner(f"Generating end screen image for concept {i+1}..."): - logger.info(f"User selected concept {i+1} for image generation") - # Get the selected concept - selected_concept = st.session_state.concept_list[i] - - # Generate the end screen image with enhanced prompt - img_path = generate_end_screen_design( - selected_concept, - style_preference, - element_count, - element_types, - element_texts, - aspect_ratio.split()[0], # Extract just the ratio part - keywords=keywords, - style=style, - focus=focus - ) - - if img_path: - # Store the current end screen path in session state - st.session_state.current_end_screen_path = img_path - logger.info(f"Stored current end screen path in session state: {img_path}") - - # Display the generated image - st.subheader("Generated End Screen") - st.image(img_path, use_container_width=True) - - # Add download button - with open(img_path, "rb") as file: - st.download_button( - label="Download End Screen", - data=file, - file_name=f"youtube_end_screen_{int(time.time())}.png", - mime="image/png" - ) - - # Add image editing section - st.subheader("Edit End Screen") - st.write("Make changes to your end screen by providing instructions below:") - - # Create a text area for edit instructions - edit_instructions = st.text_area( - "Edit Instructions", - placeholder="e.g., Make the background darker, Add a red border, Change the text color to white", - key=f"edit_instructions_{i}" - ) - - # Store edit instructions in session state - st.session_state.edit_instructions = edit_instructions - - # Add a button to apply edits - if st.button("Apply Edits", key=f"apply_edits_{i}"): - if not edit_instructions: - st.warning("Please provide edit instructions.") - else: - # Set editing flag - st.session_state.editing_end_screen = True - st.session_state.show_edit_form = True - - # Rerun to update the UI - st.rerun() - - # Add analysis button - if st.button("Analyze End Screen", key=f"analyze_{i}"): - logger.info("User clicked Analyze End Screen button") - analysis = analyze_end_screen(img_path) - st.subheader("End Screen Analysis") - st.markdown(analysis) - else: - st.error("Failed to generate end screen concepts. Please try again.") - - # Display previously generated concepts if they exist in session state - elif st.session_state.end_screen_concepts and st.session_state.concept_list: - logger.info("Displaying previously generated concepts from session state") - st.subheader("End Screen Concepts") - - # Create tabs for each concept - concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))]) - - for i, tab in enumerate(concept_tabs): - with tab: - st.markdown(st.session_state.concept_list[i]) - - # Add a button to generate image for this concept - if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_existing_{i}"): - with st.spinner(f"Generating end screen image for concept {i+1}..."): - logger.info(f"User selected concept {i+1} for image generation") - # Get the selected concept - selected_concept = st.session_state.concept_list[i] - - # Generate the end screen image with enhanced prompt - img_path = generate_end_screen_design( - selected_concept, - style_preference, - element_count, - element_types, - element_texts, - aspect_ratio.split()[0], # Extract just the ratio part - keywords=keywords, - style=style, - focus=focus - ) - - if img_path: - # Store the current end screen path in session state - st.session_state.current_end_screen_path = img_path - logger.info(f"Stored current end screen path in session state: {img_path}") - - # Display the generated image - st.subheader("Generated End Screen") - st.image(img_path, use_container_width=True) - - # Add download button - with open(img_path, "rb") as file: - st.download_button( - label="Download End Screen", - data=file, - file_name=f"youtube_end_screen_{int(time.time())}.png", - mime="image/png" - ) - - # Add image editing section - st.subheader("Edit End Screen") - st.write("Make changes to your end screen by providing instructions below:") - - # Create a text area for edit instructions - edit_instructions = st.text_area( - "Edit Instructions", - placeholder="e.g., Make the background darker, Add a red border, Change the text color to white", - key=f"edit_instructions_existing_{i}" - ) - - # Store edit instructions in session state - st.session_state.edit_instructions = edit_instructions - - # Add a button to apply edits - if st.button("Apply Edits", key=f"apply_edits_existing_{i}"): - if not edit_instructions: - st.warning("Please provide edit instructions.") - else: - # Set editing flag - st.session_state.editing_end_screen = True - st.session_state.show_edit_form = True - - # Rerun to update the UI - st.rerun() - - # Add analysis button - if st.button("Analyze End Screen", key=f"analyze_existing_{i}"): - logger.info("User clicked Analyze End Screen button") - analysis = analyze_end_screen(img_path) - st.subheader("End Screen Analysis") - st.markdown(analysis) - - # Display current end screen if it exists in session state - elif st.session_state.current_end_screen_path: - logger.info(f"Displaying current end screen from session state: {st.session_state.current_end_screen_path}") - st.subheader("Current End Screen") - st.image(st.session_state.current_end_screen_path, use_container_width=True) - - # Add download button - with open(st.session_state.current_end_screen_path, "rb") as file: - st.download_button( - label="Download End Screen", - data=file, - file_name=f"youtube_end_screen_{int(time.time())}.png", - mime="image/png" - ) - - # Add image editing section - st.subheader("Edit End Screen") - st.write("Make changes to your end screen by providing instructions below:") - - # Create a text area for edit instructions - edit_instructions = st.text_area( - "Edit Instructions", - placeholder="e.g., Make the background darker, Add a new element, Change the text color to white", - key="edit_instructions_current", - value=st.session_state.edit_instructions if st.session_state.edit_instructions else "" - ) - - # Store edit instructions in session state - st.session_state.edit_instructions = edit_instructions - - # Add a button to apply edits - if st.button("Apply Edits", key="apply_edits_current"): - if not edit_instructions: - st.warning("Please provide edit instructions.") - else: - # Set editing flag - st.session_state.editing_end_screen = True - st.session_state.show_edit_form = True - - # Rerun to update the UI - st.rerun() - - # Add analysis button - if st.button("Analyze End Screen", key="analyze_current"): - logger.info("User clicked Analyze End Screen button") - analysis = analyze_end_screen(st.session_state.current_end_screen_path) - st.subheader("End Screen Analysis") - st.markdown(analysis) - - # Handle the editing process - if st.session_state.editing_end_screen and st.session_state.show_edit_form: - st.subheader("Editing End Screen") - - # Show a spinner while editing - with st.spinner("Editing end screen..."): - logger.info(f"User provided edit instructions: '{st.session_state.edit_instructions}'") - # Edit the end screen image - edited_img_path = edit_end_screen_image(st.session_state.current_end_screen_path, st.session_state.edit_instructions) - - if edited_img_path: - # Update the current end screen path in session state - st.session_state.edited_end_screen_path = edited_img_path - logger.info(f"Updated current end screen path in session state: {edited_img_path}") - - # Reset editing flags - st.session_state.editing_end_screen = False - st.session_state.show_edit_form = False - - # Display the edited image - st.subheader("Edited End Screen") - st.image(edited_img_path, use_container_width=True) - - # Add download button for the edited image - with open(edited_img_path, "rb") as file: - st.download_button( - label="Download Edited End Screen", - data=file, - file_name=f"youtube_end_screen_edited_{int(time.time())}.png", - mime="image/png" - ) - - # Update the current end screen path to the edited one - st.session_state.current_end_screen_path = edited_img_path - - # Add a button to continue editing - if st.button("Continue Editing"): - st.session_state.show_edit_form = True - st.rerun() - else: - # Reset editing flags - st.session_state.editing_end_screen = False - st.session_state.show_edit_form = False - - st.error("Failed to edit the end screen. Please try again with different instructions.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/script_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/script_generator.py deleted file mode 100644 index d6769b91..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/script_generator.py +++ /dev/null @@ -1,556 +0,0 @@ -""" -YouTube Script Generator Module - -This module provides functionality for generating YouTube video scripts. -""" - -import streamlit as st -import time -import json -import os -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - - -def generate_youtube_script(target_audience, main_points, tone_style, use_case, script_structure, - include_hook=False, include_cta=False, include_engagement=False, - include_timestamps=False, include_visual_cues=False, engagement_hooks=None, - community_interactions=None, language="English"): - """Generate a YouTube script based on the provided parameters.""" - - # Create a custom system prompt for YouTube script generation - system_prompt = f"""You are a YouTube script expert specializing in creating engaging, well-structured video scripts in {language}. - Your task is to generate YouTube video scripts based on the provided information. - Focus ONLY on creating scripts that are optimized for YouTube, with proper structure, engagement hooks, and calls to action. - Return ONLY the script text, without any additional commentary or explanations. - Format the script with clear sections, speaker notes, and visual cues where appropriate. - Write the entire script in {language}.""" - - # Build structure-specific instructions - structure_instructions = { - "Problem-Solution": "Structure the script to first present a problem, then provide a solution.", - "Before-After-Bridge": "Structure the script to show the before state, the transformation process, and the after state.", - "Hook-Problem-Solution-Call to Action": "Start with a hook, present the problem, provide the solution, and end with a call to action.", - "Compare and Contrast": "Structure the script to compare and contrast different options or approaches.", - "Step-by-Step Tutorial": "Break down the content into clear, sequential steps.", - "Case Study": "Present a real-world example or case study to illustrate the main points.", - "Interview Format": "Structure the script as an interview with questions and answers.", - "Review Format": "Structure the script as a review with pros, cons, and a final verdict.", - "Vlog Format": "Structure the script as a personal video blog with a conversational tone.", - "Educational Format": "Structure the script to teach a concept with examples and explanations.", - "Entertainment Format": "Structure the script to entertain while delivering the main message." - } - - # Build the prompt - prompt = f""" - **Instructions:** - - Please generate a YouTube script in {language} for a video about **{main_points}** based on the following information: - - **Target Audience:** {target_audience} - **Tone and Style:** {tone_style} - **Use Case:** {use_case} - **Script Structure:** {script_structure} - **Language:** {language} - - **Structure Instructions:** - {structure_instructions.get(script_structure, "Follow a logical flow to present the content.")} - - **Additional Elements:** - {"- Include a hook at the beginning to grab attention." if include_hook else ""} - {"- End with a strong call to action." if include_cta else ""} - {"- Include prompts for viewer engagement (e.g., questions, polls)." if include_engagement else ""} - {"- Include suggested timestamps for key sections." if include_timestamps else ""} - {"- Include visual cues and transitions." if include_visual_cues else ""} - """ - - # Add engagement hooks if provided - if engagement_hooks: - prompt += "\n**Engagement Hooks:**\n" - for hook in engagement_hooks: - prompt += f"- {hook}\n" - - # Add community interaction points if provided - if community_interactions: - prompt += "\n**Community Interaction Points:**\n" - for interaction in community_interactions: - prompt += f"- {interaction}\n" - - prompt += """ - **Specific Instructions:** - * Keep the language clear and engaging. - * Use a conversational tone that matches the target audience. - * Include relevant examples and explanations. - * Ensure the script flows naturally and maintains viewer interest. - """ - - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response - except Exception as err: - st.error(f"Error: Failed to get response from LLM: {err}") - return None - - -def generate_youtube_script_with_changes(target_audience, main_points, tone_style, use_case, script_structure, - include_hook=False, include_cta=False, include_engagement=False, - include_timestamps=False, include_visual_cues=False, engagement_hooks=None, - community_interactions=None, changes="", language="English"): - """Generate a YouTube script based on the provided parameters and requested changes.""" - - # Create a custom system prompt for YouTube script generation - system_prompt = f"""You are a YouTube script expert specializing in creating engaging, well-structured video scripts in {language}. - Your task is to generate YouTube video scripts based on the provided information. - Focus ONLY on creating scripts that are optimized for YouTube, with proper structure, engagement hooks, and calls to action. - Return ONLY the script text, without any additional commentary or explanations. - Format the script with clear sections, speaker notes, and visual cues where appropriate. - Write the entire script in {language}.""" - - # Build structure-specific instructions - structure_instructions = { - "Problem-Solution": "Structure the script to first present a problem, then provide a solution.", - "Before-After-Bridge": "Structure the script to show the before state, the transformation process, and the after state.", - "Hook-Problem-Solution-Call to Action": "Start with a hook, present the problem, provide the solution, and end with a call to action.", - "Compare and Contrast": "Structure the script to compare and contrast different options or approaches.", - "Step-by-Step Tutorial": "Break down the content into clear, sequential steps.", - "Case Study": "Present a real-world example or case study to illustrate the main points.", - "Interview Format": "Structure the script as an interview with questions and answers.", - "Review Format": "Structure the script as a review with pros, cons, and a final verdict.", - "Vlog Format": "Structure the script as a personal video blog with a conversational tone.", - "Educational Format": "Structure the script to teach a concept with examples and explanations.", - "Entertainment Format": "Structure the script to entertain while delivering the main message." - } - - # Build the prompt - prompt = f""" - **Instructions:** - - Please generate a YouTube script in {language} for a video about **{main_points}** based on the following information: - - **Target Audience:** {target_audience} - **Tone and Style:** {tone_style} - **Use Case:** {use_case} - **Script Structure:** {script_structure} - **Language:** {language} - - **Structure Instructions:** - {structure_instructions.get(script_structure, "Follow a logical flow to present the content.")} - - **Additional Elements:** - {"- Include a hook at the beginning to grab attention." if include_hook else ""} - {"- End with a strong call to action." if include_cta else ""} - {"- Include prompts for viewer engagement (e.g., questions, polls)." if include_engagement else ""} - {"- Include suggested timestamps for key sections." if include_timestamps else ""} - {"- Include visual cues and transitions." if include_visual_cues else ""} - """ - - # Add engagement hooks if provided - if engagement_hooks: - prompt += "\n**Engagement Hooks:**\n" - for hook in engagement_hooks: - prompt += f"- {hook}\n" - - # Add community interaction points if provided - if community_interactions: - prompt += "\n**Community Interaction Points:**\n" - for interaction in community_interactions: - prompt += f"- {interaction}\n" - - # Add requested changes - prompt += f""" - **Requested Changes:** - {changes} - - **Specific Instructions:** - * Keep the language clear and engaging. - * Use a conversational tone that matches the target audience. - * Include relevant examples and explanations. - * Ensure the script flows naturally and maintains viewer interest. - * Incorporate the requested changes into the script. - """ - - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response - except Exception as err: - st.error(f"Error: Failed to get response from LLM: {err}") - return None - - -def export_script(script, format_type, filename=None): - """Export the script in various formats.""" - if not filename: - filename = "youtube_script" - - if format_type == "Text": - return script, f"{filename}.txt", "text/plain" - elif format_type == "Markdown": - return script, f"{filename}.md", "text/markdown" - elif format_type == "HTML": - html_content = f"
{script}
" - return html_content, f"{filename}.html", "text/html" - elif format_type == "JSON": - json_content = json.dumps({"script": script}, indent=2) - return json_content, f"{filename}.json", "application/json" - elif format_type == "Subtitles (SRT)": - # Convert script to basic SRT format - lines = script.split('\n') - srt_content = "" - for i, line in enumerate(lines): - if line.strip(): - start_time = f"00:00:{i*5:02d},000" - end_time = f"00:00:{(i+1)*5:02d},000" - srt_content += f"{i+1}\n{start_time} --> {end_time}\n{line}\n\n" - return srt_content, f"{filename}.srt", "text/plain" - else: - return script, f"{filename}.txt", "text/plain" - - -def write_yt_script(): - """Create a user interface for YouTube Script Generator.""" - st.write("Generate professional YouTube video scripts with optimized structures for engagement.") - - # Initialize session state for generated script if it doesn't exist - if "generated_script" not in st.session_state: - st.session_state.generated_script = None - - # Create tabs for different sections - tab1, tab2, tab3 = st.tabs(["Basic Info", "Advanced Options", "Engagement & Export"]) - - with tab1: - # Basic information inputs - main_points = st.text_area("Main Points/Keywords (comma-separated)", - placeholder="e.g., cooking tips, healthy recipes, quick meals") - target_audience = st.text_input("Target Audience", - placeholder="e.g., beginners, professionals, parents") - - # Create columns for tone, use case, structure, and language - col1, col2, col3, col4 = st.columns(4) - - with col1: - tone_style = st.selectbox("Tone/Style", - ["Professional", "Casual", "Humorous", "Educational", "Entertaining", "Inspirational"]) - - with col2: - use_case = st.selectbox("Use Case", - ["How-to/Tutorial", "Vlog", "Review", "Educational", "Entertainment", "News"]) - - with col3: - script_structure = st.selectbox("Script Structure", [ - "Problem-Solution", - "Before-After-Bridge", - "Hook-Problem-Solution-Call to Action", - "Compare and Contrast", - "Step-by-Step Tutorial", - "Case Study", - "Interview Format", - "Review Format", - "Vlog Format", - "Educational Format", - "Entertainment Format" - ]) - - with col4: - language = st.selectbox("Language", [ - "English", - "Spanish", - "French", - "German", - "Italian", - "Portuguese", - "Russian", - "Japanese", - "Korean", - "Chinese", - "Hindi", - "Arabic" - ]) - - with tab2: - # Advanced options - st.subheader("Additional Elements") - include_hook = st.checkbox("Include Hook", value=True) - include_cta = st.checkbox("Include Call to Action", value=True) - include_engagement = st.checkbox("Include Viewer Engagement Prompts", value=True) - include_timestamps = st.checkbox("Include Suggested Timestamps", value=True) - include_visual_cues = st.checkbox("Include Visual Cues/Transitions", value=True) - - with tab3: - # Engagement hooks - st.subheader("Engagement Hooks") - st.write("Select engagement hooks to include in your script:") - - engagement_hooks = [] - if st.checkbox("Question Hook", value=False): - engagement_hooks.append("Start with a thought-provoking question to engage viewers immediately") - if st.checkbox("Story Hook", value=False): - engagement_hooks.append("Begin with a brief, relevant story or anecdote") - if st.checkbox("Statistic Hook", value=False): - engagement_hooks.append("Open with an interesting statistic or fact") - if st.checkbox("Controversy Hook", value=False): - engagement_hooks.append("Present a controversial statement or opinion to spark interest") - if st.checkbox("Promise Hook", value=False): - engagement_hooks.append("Make a promise about what viewers will learn or gain") - if st.checkbox("Scenario Hook", value=False): - engagement_hooks.append("Describe a scenario or situation viewers can relate to") - if st.checkbox("Mystery Hook", value=False): - engagement_hooks.append("Create a sense of mystery or intrigue") - if st.checkbox("Quote Hook", value=False): - engagement_hooks.append("Start with a relevant quote from an expert or notable figure") - - # Community interaction points - st.subheader("Community Interaction Points") - st.write("Select community interaction points to include in your script:") - - community_interactions = [] - if st.checkbox("Comment Prompt", value=False): - community_interactions.append("Ask viewers to share their experiences or opinions in the comments") - if st.checkbox("Poll Suggestion", value=False): - community_interactions.append("Suggest creating a poll for viewers to vote on") - if st.checkbox("Question for Comments", value=False): - community_interactions.append("Pose a specific question for viewers to answer in the comments") - if st.checkbox("Challenge", value=False): - community_interactions.append("Challenge viewers to try something and report back") - if st.checkbox("Tag Friends", value=False): - community_interactions.append("Encourage viewers to tag friends who might benefit from the content") - if st.checkbox("Share Request", value=False): - community_interactions.append("Ask viewers to share the video with others who might find it helpful") - if st.checkbox("Community Post", value=False): - community_interactions.append("Mention creating a community post to continue the discussion") - if st.checkbox("Live Stream Teaser", value=False): - community_interactions.append("Tease an upcoming live stream on the same topic") - - # Export options - st.subheader("Export Options") - export_format = st.selectbox("Export Format", [ - "Text", - "Markdown", - "HTML", - "JSON", - "Subtitles (SRT)" - ]) - - custom_filename = st.text_input("Custom Filename (optional)", - placeholder="Leave blank for default filename") - - if st.button("Generate Script"): - if not main_points: - st.error("Please enter main points/keywords.") - return - - with st.spinner("Generating script..."): - script = generate_youtube_script( - target_audience, main_points, tone_style, use_case, script_structure, - include_hook, include_cta, include_engagement, include_timestamps, include_visual_cues, - engagement_hooks if engagement_hooks else None, - community_interactions if community_interactions else None, - language - ) - - if script: - # Store the script in session state - st.session_state.generated_script = script - - # Store other parameters in session state for regeneration - st.session_state.script_params = { - "target_audience": target_audience, - "main_points": main_points, - "tone_style": tone_style, - "use_case": use_case, - "script_structure": script_structure, - "include_hook": include_hook, - "include_cta": include_cta, - "include_engagement": include_engagement, - "include_timestamps": include_timestamps, - "include_visual_cues": include_visual_cues, - "engagement_hooks": engagement_hooks if engagement_hooks else None, - "community_interactions": community_interactions if community_interactions else None, - "language": language - } - - st.subheader("Generated Script") - - # Display script with tabs for different views - script_tab1, script_tab2 = st.tabs(["Formatted View", "Plain Text"]) - - with script_tab1: - st.markdown(script) - - with script_tab2: - st.code(script) - - # Export options - st.subheader("Export Script") - - # Get export data - export_data, export_filename, mime_type = export_script( - script, - export_format, - custom_filename if custom_filename else None - ) - - # Create columns for the buttons - btn_col1, btn_col2 = st.columns(2) - - with btn_col1: - # Download button - st.download_button( - label=f"Download as {export_format}", - data=export_data, - file_name=export_filename, - mime=mime_type - ) - - with btn_col2: - # Regenerate button - if st.button("Regenerate"): - st.session_state.show_regenerate_popover = True - - # Regenerate popover - if st.session_state.get("show_regenerate_popover", False): - with st.form("regenerate_form"): - st.subheader("Regenerate Script") - st.write("Specify changes you'd like to make to the script:") - changes = st.text_area("Changes to make", - placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits") - - submitted = st.form_submit_button("Regenerate with Changes") - - if submitted and changes: - with st.spinner("Regenerating script..."): - # Get the stored parameters - params = st.session_state.script_params - - # Generate a new script with the changes - new_script = generate_youtube_script_with_changes( - params["target_audience"], - params["main_points"], - params["tone_style"], - params["use_case"], - params["script_structure"], - params["include_hook"], - params["include_cta"], - params["include_engagement"], - params["include_timestamps"], - params["include_visual_cues"], - params["engagement_hooks"], - params["community_interactions"], - changes, - params["language"] - ) - - if new_script: - # Update the stored script - st.session_state.generated_script = new_script - st.session_state.show_regenerate_popover = False - st.rerun() - else: - st.error("Failed to regenerate script. Please try again.") - - # Additional export options - if st.checkbox("Show additional export options"): - col1, col2 = st.columns(2) - with col1: - if st.button("Copy to Clipboard"): - st.code(script) - st.success("Script copied to clipboard!") - - with col2: - if st.button("Save to Local File"): - # This is a placeholder - actual file saving would require additional backend functionality - st.info("This feature would save the file locally on your device.") - else: - st.error("Failed to generate script. Please try again.") - - # Display previously generated script if it exists in session state - elif st.session_state.generated_script: - script = st.session_state.generated_script - params = st.session_state.script_params - - st.subheader("Generated Script") - - # Display script with tabs for different views - script_tab1, script_tab2 = st.tabs(["Formatted View", "Plain Text"]) - - with script_tab1: - st.markdown(script) - - with script_tab2: - st.code(script) - - # Export options - st.subheader("Export Script") - - # Get export data - export_data, export_filename, mime_type = export_script( - script, - export_format, - custom_filename if custom_filename else None - ) - - # Create columns for the buttons - btn_col1, btn_col2 = st.columns(2) - - with btn_col1: - # Download button - st.download_button( - label=f"Download as {export_format}", - data=export_data, - file_name=export_filename, - mime=mime_type - ) - - with btn_col2: - # Regenerate button - if st.button("Regenerate"): - st.session_state.show_regenerate_popover = True - - # Regenerate popover - if st.session_state.get("show_regenerate_popover", False): - with st.form("regenerate_form"): - st.subheader("Regenerate Script") - st.write("Specify changes you'd like to make to the script:") - changes = st.text_area("Changes to make", - placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits") - - submitted = st.form_submit_button("Regenerate with Changes") - - if submitted and changes: - with st.spinner("Regenerating script..."): - # Generate a new script with the changes - new_script = generate_youtube_script_with_changes( - params["target_audience"], - params["main_points"], - params["tone_style"], - params["use_case"], - params["script_structure"], - params["include_hook"], - params["include_cta"], - params["include_engagement"], - params["include_timestamps"], - params["include_visual_cues"], - params["engagement_hooks"], - params["community_interactions"], - changes, - params["language"] - ) - - if new_script: - # Update the stored script - st.session_state.generated_script = new_script - st.session_state.show_regenerate_popover = False - st.rerun() - else: - st.error("Failed to regenerate script. Please try again.") - - # Additional export options - if st.checkbox("Show additional export options"): - col1, col2 = st.columns(2) - with col1: - if st.button("Copy to Clipboard"): - st.code(script) - st.success("Script copied to clipboard!") - - with col2: - if st.button("Save to Local File"): - # This is a placeholder - actual file saving would require additional backend functionality - st.info("This feature would save the file locally on your device.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/shorts_script_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/shorts_script_generator.py deleted file mode 100644 index b33c64e9..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/shorts_script_generator.py +++ /dev/null @@ -1,314 +0,0 @@ -""" -YouTube Shorts Script Generator Module - -This module provides functionality for generating optimized scripts for YouTube Shorts. -""" - -import streamlit as st -import time -import logging -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('youtube_shorts_generator') - -def generate_shorts_script(hook_type, main_topic, target_audience, tone_style, - content_type, duration_seconds=60, include_captions=True, - include_text_overlay=True, include_sound_effects=False, - vertical_framing_notes=True, language="English"): - """Generate a YouTube Shorts script optimized for vertical format and short duration.""" - - # Create a custom system prompt for Shorts script generation - system_prompt = f"""You are a YouTube Shorts expert specializing in creating viral, engaging scripts for vertical short-form videos. - Your task is to generate scripts that are perfectly timed for {duration_seconds} seconds or less. - Focus on hooks that grab attention in the first 1-2 seconds. - Format the script with clear sections for visuals, audio, and text overlays. - Write the entire script in {language}. - Remember that Shorts are viewed vertically (9:16 aspect ratio) and need to work without sound.""" - - # Build hook-specific instructions - hook_instructions = { - "Question": "Start with an intriguing question that stops the scroll", - "Statistic": "Begin with a surprising statistic or fact", - "Challenge": "Present a challenge or dare to the viewer", - "Tutorial": "Jump straight into a quick how-to or life hack", - "Transformation": "Show a before/after or transformation hook", - "Trend": "Leverage a current trend or sound", - "Story": "Start with a captivating micro-story", - "Controversy": "Present a controversial or surprising statement" - } - - # Build the prompt - prompt = f""" - **Instructions:** - - Create a YouTube Shorts script about **{main_topic}** with these specifications: - - **Core Elements:** - - Hook Type: {hook_type} - {hook_instructions.get(hook_type, "Create an attention-grabbing opening")} - - Target Audience: {target_audience} - - Tone/Style: {tone_style} - - Content Type: {content_type} - - Duration: {duration_seconds} seconds - - Language: {language} - - **Required Elements:** - {"- Include caption suggestions for accessibility" if include_captions else ""} - {"- Include text overlay positions and timing" if include_text_overlay else ""} - {"- Include sound effect suggestions" if include_sound_effects else ""} - {"- Include vertical framing notes for optimal composition" if vertical_framing_notes else ""} - - **Format the script in this structure:** - 1. HOOK (0-2 seconds) - 2. MAIN CONTENT (3-50 seconds) - 3. CALL TO ACTION (last 10 seconds) - - **For each section, specify:** - - Visual Instructions (what to show) - - Text Overlays (what text appears and where) - - Audio/Voiceover - - Timing (in seconds) - - Camera Angles/Framing Notes - - **Remember:** - - Scripts must work without sound (many viewers watch on mute) - - Text should be centered in the middle 50% of the vertical frame - - Keep text concise and readable - - Include pattern interrupts every 3-5 seconds - - End with a clear call-to-action - """ - - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response - except Exception as err: - st.error(f"Error: Failed to get response from LLM: {err}") - return None - -def analyze_shorts_script(script): - """Analyze a Shorts script for optimal engagement metrics.""" - analysis = { - 'duration_estimate': 0, - 'hook_strength': 0, - 'pattern_interrupts': 0, - 'text_overlay_count': 0, - 'readability_score': 0, - 'optimization_score': 0 - } - - # Basic analysis (can be enhanced with more sophisticated metrics) - lines = script.split('\n') - word_count = len(script.split()) - - # Estimate duration (rough approximation) - analysis['duration_estimate'] = word_count * 0.4 # Average speaking speed - - # Count pattern interrupts - analysis['pattern_interrupts'] = script.lower().count('cut to') + script.lower().count('transition') - - # Count text overlays - analysis['text_overlay_count'] = script.lower().count('text:') + script.lower().count('overlay:') - - # Calculate optimization score - score = 100 - - # Penalize if estimated duration is too long - if analysis['duration_estimate'] > 60: - score -= (analysis['duration_estimate'] - 60) * 2 - - # Check for hook presence - if not any(hook in script.lower() for hook in ['hook:', 'opening:', '0-2 seconds:']): - score -= 20 - - # Check for pattern interrupts (ideal is 1 every 5 seconds) - ideal_interrupts = analysis['duration_estimate'] / 5 - if analysis['pattern_interrupts'] < ideal_interrupts: - score -= 10 - - # Check for text overlay usage - if analysis['text_overlay_count'] < 3: - score -= 10 - - # Check for call-to-action - if not any(cta in script.lower() for cta in ['call to action', 'cta:', 'subscribe', 'follow']): - score -= 15 - - analysis['optimization_score'] = max(0, score) - return analysis - -def generate_shorts_narration(shorts_script, language="English"): - system_prompt = f"""You are an expert at converting YouTube Shorts scripts into natural, engaging narration.\nYour task is to read the provided Shorts script and output only the narration lines, as they would be spoken in the video.\nOmit all visual instructions, timing, text overlays, and technical cues. Write the narration in {language}.""" - prompt = f"""Shorts Script:\n{shorts_script}\n\nInstructions:\nExtract and rewrite only the narration lines, as they would be spoken in the video. Do not include any section headers, cues, or formatting. Output only the narration text.""" - try: - response = llm_text_gen(prompt, system_prompt=system_prompt) - return response.strip() - except Exception as err: - st.error(f"Error: Failed to get narration from LLM: {err}") - return "" - -def write_yt_shorts(): - """Create a user interface for YouTube Shorts Script Generator.""" - st.write("Generate optimized scripts for YouTube Shorts that grab attention and drive engagement.") - - # Initialize session state for generated script and active tab if they don't exist - if "generated_shorts_script" not in st.session_state: - st.session_state.generated_shorts_script = None - if "active_tab" not in st.session_state: - st.session_state.active_tab = "Core Elements" - - # Create tabs for different sections - tab1, tab2, tab3 = st.tabs(["Core Elements", "Style & Format", "Preview & Export"]) - - # Set the active tab based on session state - if st.session_state.active_tab == "Core Elements": - tab1.active = True - elif st.session_state.active_tab == "Style & Format": - tab2.active = True - elif st.session_state.active_tab == "Preview & Export": - tab3.active = True - - with tab1: - # Core elements - main_topic = st.text_area("Main Topic/Concept", - placeholder="e.g., Quick cooking hack, Life-changing productivity tip") - - col1, col2 = st.columns(2) - with col1: - hook_type = st.selectbox("Hook Type", [ - "Question", - "Statistic", - "Challenge", - "Tutorial", - "Transformation", - "Trend", - "Story", - "Controversy" - ]) - - target_audience = st.text_input("Target Audience", - placeholder="e.g., Gen Z, busy professionals") - - with col2: - content_type = st.selectbox("Content Type", [ - "Tutorial/How-to", - "Life Hack", - "Entertainment", - "Educational", - "Trend", - "Story", - "Challenge", - "Review" - ]) - - tone_style = st.selectbox("Tone/Style", [ - "Energetic", - "Professional", - "Casual", - "Humorous", - "Dramatic", - "Inspirational" - ]) - - with tab2: - # Style and format options - col1, col2 = st.columns(2) - - with col1: - duration_seconds = st.slider("Duration (seconds)", 15, 60, 60) - language = st.selectbox("Language", [ - "English", - "Spanish", - "French", - "German", - "Italian", - "Portuguese", - "Russian", - "Japanese", - "Korean", - "Chinese" - ]) - - with col2: - include_captions = st.checkbox("Include Captions", value=True) - include_text_overlay = st.checkbox("Include Text Overlay Positions", value=True) - include_sound_effects = st.checkbox("Include Sound Effects", value=False) - vertical_framing_notes = st.checkbox("Include Vertical Framing Notes", value=True) - - with tab3: - if st.session_state.generated_shorts_script: - # Display the generated script - st.subheader("Generated Shorts Script") - - # Create tabs for different views - script_tab1, script_tab2, script_tab3 = st.tabs(["Formatted", "Analysis", "Export"]) - - with script_tab1: - st.markdown(st.session_state.generated_shorts_script) - - with script_tab2: - # Analyze the script - analysis = analyze_shorts_script(st.session_state.generated_shorts_script) - - # Display analysis results - col1, col2 = st.columns(2) - - with col1: - st.metric("Estimated Duration", f"{analysis['duration_estimate']:.1f}s") - st.metric("Pattern Interrupts", analysis['pattern_interrupts']) - st.metric("Text Overlays", analysis['text_overlay_count']) - - with col2: - # Display optimization score with color - score = analysis['optimization_score'] - color = "red" if score < 60 else "orange" if score < 80 else "green" - st.markdown(f"### Optimization Score: {score}%", - unsafe_allow_html=True) - - with script_tab3: - # Export options - export_format = st.selectbox("Export Format", [ - "Text", - "Markdown", - "Shot List", - "Storyboard" - ]) - - if st.button("Export Script"): - # Implement export functionality based on selected format - st.success(f"Script exported in {export_format} format!") - st.download_button( - "Download Script", - st.session_state.generated_shorts_script, - file_name=f"shorts_script.{export_format.lower()}", - mime="text/plain" - ) - - # Generate button - if st.button("Generate Shorts Script"): - if not main_topic: - st.error("Please enter a main topic/concept.") - return - - with st.spinner("Generating Shorts script..."): - script = generate_shorts_script( - hook_type, main_topic, target_audience, tone_style, content_type, - duration_seconds, include_captions, include_text_overlay, - include_sound_effects, vertical_framing_notes, language - ) - - if script: - st.session_state.generated_shorts_script = script - # Set active tab to Preview & Export - st.session_state.active_tab = "Preview & Export" - st.success("โœจ Script generated successfully! Check the 'Preview & Export' tab to view, analyze, and download your script.") - st.rerun() - else: - st.error("Failed to generate script. Please try again.") - - # Add a message about preview and export if script exists but we're not on the Preview tab - if st.session_state.generated_shorts_script and st.session_state.active_tab != "Preview & Export": - st.info("๐Ÿ’ก Your generated script is ready! Go to the 'Preview & Export' tab to view, analyze, and download it.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/shorts_video_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/shorts_video_generator.py deleted file mode 100644 index aea5906e..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/shorts_video_generator.py +++ /dev/null @@ -1,972 +0,0 @@ -""" -YouTube Shorts Video Generator - -This module provides functionality to generate YouTube Shorts videos using AI. -It adapts the story video generator for the vertical format and shorter duration of Shorts. -""" - -import os -import re -import time -import json -import uuid -import tempfile -import logging -import traceback -from pathlib import Path -from typing import List, Dict, Any, Tuple, Optional, Union, Callable -from functools import wraps -from datetime import datetime -import random -import functools - -import streamlit as st -import numpy as np -from PIL import Image, ImageDraw, ImageFont -import requests - -# Try importing moviepy with proper error handling -try: - from moviepy.editor import ( - ImageSequenceClip, - TextClip, - CompositeVideoClip, - AudioFileClip, - AudioClip, - CompositeAudioClip, - ) - MOVIEPY_AVAILABLE = True -except ImportError as e: - st.error( - "MoviePy is not properly installed. Please install it using:\n" - "pip install moviepy imageio imageio-ffmpeg" - ) - MOVIEPY_AVAILABLE = False - -# Try importing gTTS with proper error handling -try: - from gtts import gTTS - GTTS_AVAILABLE = True -except ImportError: - st.error( - "gTTS is not installed. Please install it using:\n" - "pip install gTTS" - ) - GTTS_AVAILABLE = False - -# Import LLM text generation and image generation -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image -from .shorts_script_generator import generate_shorts_script, generate_shorts_narration -from lib.ai_writers.ai_story_video_generator.story_video_generator import StoryVideoGenerator - -# Configure logging -log_dir = Path("logs") -log_dir.mkdir(exist_ok=True) -log_file = log_dir / f"shorts_generator_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" - -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[ - logging.FileHandler(log_file), - logging.StreamHandler() - ] -) -logger = logging.getLogger(__name__) - -# Constants -DEFAULT_FPS = 30 # Higher FPS for smoother Shorts -DEFAULT_DURATION = 2 # seconds per scene (shorter for Shorts) -DEFAULT_TRANSITION_DURATION = 0.5 # seconds for transition -DEFAULT_FONT_SIZE = 32 # Larger font for vertical format -DEFAULT_FONT_COLOR = "white" -DEFAULT_MUSIC_URL = "https://freepd.com/music/Upbeat%20Uplifting%20Corporate.mp3" # Example free music URL -DEFAULT_IMAGE_WIDTH = 1080 # Standard Shorts width -DEFAULT_IMAGE_HEIGHT = 1920 # Standard Shorts height (9:16 aspect ratio) -TEXT_AREA_HEIGHT_RATIO = 1/4 # Smaller text area for vertical format -TEXT_PADDING = 30 -TEXT_OVERLAY_ALPHA = 180 # More opaque overlay for better readability - -# Shorts-specific constants -MAX_SHORTS_DURATION = 60 # Maximum duration for YouTube Shorts -MIN_SHORTS_DURATION = 15 # Minimum duration for YouTube Shorts -DEFAULT_SHORTS_DURATION = 30 # Default duration for Shorts -MAX_SCENES = 15 # Maximum number of scenes to generate -MIN_SCENES = 5 # Minimum number of scenes -WORDS_PER_SECOND = 2.5 # Average speaking rate for narration - -# Video resolutions for Shorts (vertical format) -VIDEO_RESOLUTIONS = { - "1080p": (1080, 1920), # Standard Shorts resolution - "720p": (720, 1280), # Lower resolution option -} - -# Transition styles optimized for Shorts -TRANSITION_STYLES = { - "None": None, - "Fade": "fade", - "Slide Up": "slide_up", - "Slide Down": "slide_down", - "Zoom": "zoom", - "Wipe": "wipe" -} - -# Content styles for Shorts -CONTENT_STYLES = { - "Tutorial": { - "style": "tutorial", - "description": "Step-by-step instructional content" - }, - "Story": { - "style": "story", - "description": "Narrative-driven content" - }, - "Tips": { - "style": "tips", - "description": "Quick tips and tricks" - }, - "Review": { - "style": "review", - "description": "Product or service reviews" - }, - "Behind the Scenes": { - "style": "behind_scenes", - "description": "Behind-the-scenes content" - } -} - -# Narration languages -NARRATION_LANGUAGES = { - "English (US)": "en-us", - "English (UK)": "en-gb", - "Spanish": "es", - "French": "fr", - "German": "de", - "Italian": "it", - "Japanese": "ja", - "Korean": "ko", - "Chinese": "zh-cn", - "Hindi": "hi" -} - -# Retry configuration -MAX_RETRIES = 3 -INITIAL_RETRY_DELAY = 1 # Initial delay in seconds -MAX_RETRY_DELAY = 30 # Maximum delay in seconds -RETRYABLE_ERRORS = ( - ConnectionError, - TimeoutError, - requests.exceptions.RequestException, - OSError, # For file system operations - IOError, # For file system operations -) - -def retry_on_error(max_retries: int = MAX_RETRIES, initial_delay: int = INITIAL_RETRY_DELAY, max_delay: int = MAX_RETRY_DELAY): - """ - Decorator for retrying functions on specific errors with exponential backoff. - - # ... existing code ... -""" - -def extract_narration_from_shorts_script(script: str) -> str: - """ - Extract and optimize narration from the script for Shorts format. - Ensures narration is concise, valuable, and properly timed. - """ - scenes = re.split(r'\n\n+', script) - narration_lines = [] - total_words = 0 - max_words = 75 # Target for 30-second video (2.5 words per second) - - # Extract all potential narration lines first - potential_lines = [] - for scene in scenes: - match = re.search(r'Audio/Voiceover:\s*(.*)', scene) - if match: - narration = match.group(1).strip() - narration = re.split(r'\n[A-Z][^:]+:', narration)[0].strip() - if narration: - potential_lines.append(narration) - - # Process lines to create engaging narration - if potential_lines: - # Start with a hook - first_line = potential_lines[0] - if not any(word in first_line.lower() for word in ['discover', 'learn', 'find out', 'see how', 'watch']): - first_line = f"Discover how to {first_line.lower()}" - narration_lines.append(first_line) - total_words += len(first_line.split()) - - # Process middle lines - for line in potential_lines[1:-1]: - # Add value-focused phrases - if not any(word in line.lower() for word in ['because', 'why', 'how', 'what', 'when', 'where']): - line = f"Here's why: {line}" - - # Check word count - words = line.split() - if total_words + len(words) <= max_words: - narration_lines.append(line) - total_words += len(words) - else: - break - - # Add a strong closing - if len(potential_lines) > 1: - last_line = potential_lines[-1] - if not any(phrase in last_line.lower() for phrase in ['try it', 'get started', 'follow for more']): - last_line = f"Ready to try it? {last_line}" - if total_words + len(last_line.split()) <= max_words: - narration_lines.append(last_line) - - # If we have too few words, add a call to action - if total_words < 50 and narration_lines: - cta = "Follow for more tips like this!" - if total_words + len(cta.split()) <= max_words: - narration_lines.append(cta) - - # Join with proper pacing and emphasis - final_narration = ' '.join(narration_lines) - - # Add emphasis to key points - final_narration = re.sub(r'([.!?])\s+', r'\1\n\n', final_narration) # Add pauses - - return final_narration - -def generate_shorts_narration(script: str, language: str = "en-us", target_duration: int = 30) -> str: - """ - Generate a clean, natural-sounding narration script for YouTube Shorts. - Focuses only on what the listener needs to hear, without technical details. - """ - # Calculate target word count based on duration and user-defined speaking rate - words_per_second = getattr(st.session_state, 'svgen_words_per_second', WORDS_PER_SECOND) - narration_padding = getattr(st.session_state, 'svgen_narration_padding', 0.5) - target_words = int((target_duration - narration_padding) * words_per_second) - - # Extract key information from the script - scenes = re.split(r'\n\n+', script) - audio_lines = [] - - for scene in scenes: - # Extract only the audio/voiceover content - audio_match = re.search(r'Audio/Voiceover:\s*(.*?)(?=\n|$)', scene) - if audio_match: - audio_lines.append(audio_match.group(1).strip()) - - # Create a specialized prompt for clean narration generation - narration_prompt = f""" - Create a natural, conversational narration script for a YouTube Shorts video. - Focus ONLY on what the listener needs to hear - no technical details, scene descriptions, or timing markers. - - Content Context: - {script} - - Requirements: - 1. Length: {target_duration} seconds (approximately {target_words} words) - 2. Style: Natural, conversational, and engaging - 3. Structure: - - Start with a hook - - Present key points - - End with a call to action - 4. Tone: {st.session_state.svgen_content_style.lower()} - - Important Guidelines: - - Write ONLY the spoken words - no descriptions, timing, or technical details - - Use natural language that sounds good when spoken - - Keep sentences short and clear - - Add natural pauses with ellipsis (...) - - No scene numbers, timing markers, or technical instructions - - No sound effect descriptions or music cues - - No formatting markers or special characters - - Target word count: {target_words} words (ยฑ10%) - - Speaking rate: {words_per_second} words per second - - Example of good narration: - "Writer's block got you down? Meet your new secret weapon: an AI content writer! This tool helps you write ten times faster. No more blank page terror! Blog posts, social media, even killer emails - all generated in seconds. Ready to unleash your content creation superpowers? Try it free today!" - - Format the narration as a single, flowing script with natural pauses. - """ - - try: - # Generate narration using LLM - narration = llm_text_gen(narration_prompt) - if narration: - # Clean up the narration - narration = re.sub(r'\s+', ' ', narration) # Remove extra spaces - narration = re.sub(r'[^\w\s.,!?โ€ฆ-]', '', narration) # Keep only essential punctuation - narration = re.sub(r'([.!?])\s+', r'\1\n\n', narration) # Add natural pauses - narration = re.sub(r'\*\*.*?\*\*', '', narration) # Remove any markdown - narration = re.sub(r'\(.*?\)', '', narration) # Remove any parenthetical notes - narration = re.sub(r'\n\s*\n', '\n\n', narration) # Clean up extra line breaks - - # Verify word count - word_count = len(narration.split()) - if word_count < target_words * 0.9 or word_count > target_words * 1.1: - print(f'[WARNING] Generated narration word count ({word_count}) is outside target range ({target_words}ยฑ10%)') - - return narration.strip() - except Exception as e: - print(f'[ERROR] Failed to generate narration: {e}') - return None - -def write_yt_shorts_video(): - """ - Main function to generate a YouTube Shorts video. - This function provides a Streamlit interface for users to generate Shorts videos. - """ - st.markdown(""" - - """, unsafe_allow_html=True) - - # Stepper logic - if 'shorts_stage' not in st.session_state: - st.session_state.shorts_stage = 1 - if 'generated_script' not in st.session_state: - st.session_state.generated_script = None - if 'script_approved' not in st.session_state: - st.session_state.script_approved = False - - # Stepper UI - st.markdown(f''' -
-
1. Input Details
-
2. Script Review
-
3. Video Generation
-
- ''', unsafe_allow_html=True) - - # --- Stage 1: Input Details --- - if st.session_state.shorts_stage == 1: - print('[DEBUG] Stage 1: Input Details loaded') - st.markdown('---') - st.markdown('### 1๏ธโƒฃ Input Video Details') - st.info("Fill in all details below, then click **Generate Script** to continue.") - with st.container(): - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿ“ Video Content
', unsafe_allow_html=True) - video_topic = st.text_input( - "What's your video about?", - placeholder="Enter the main topic or theme of your Shorts video", - help="Be specific about what you want to create" - ) - style_col, duration_col = st.columns(2) - with style_col: - content_style = st.selectbox( - "Content Style", - list(CONTENT_STYLES.keys()), - help="Select the style that best fits your content" - ) - with duration_col: - video_duration = st.slider( - "Duration (seconds)", - MIN_SHORTS_DURATION, - MAX_SHORTS_DURATION, - DEFAULT_SHORTS_DURATION, - help=f"Shorts must be between {MIN_SHORTS_DURATION} and {MAX_SHORTS_DURATION} seconds" - ) - - # Calculate and display scene count based on duration - scene_duration = DEFAULT_DURATION # seconds per scene - max_possible_scenes = min(MAX_SCENES, int(video_duration / scene_duration)) - min_possible_scenes = max(MIN_SCENES, int(video_duration / (scene_duration * 2))) - - scene_count = st.slider( - "Number of Scenes", - min_possible_scenes, - max_possible_scenes, - min(max_possible_scenes, 10), # Default to 10 or max possible - help=f"Based on {scene_duration}s per scene, you can have {min_possible_scenes}-{max_possible_scenes} scenes" - ) - st.markdown('
', unsafe_allow_html=True) - - with st.container(): - settings_col = st.columns(1)[0] - with settings_col: - with st.expander("โš™๏ธ Video Settings", expanded=True): - res_col, trans_col = st.columns(2) - with res_col: - resolution = st.selectbox( - "Resolution", - list(VIDEO_RESOLUTIONS.keys()), - help="Higher resolution = better quality but longer processing time" - ) - with trans_col: - transition_style = st.selectbox( - "Transition Style", - list(TRANSITION_STYLES.keys()), - help="How scenes transition between each other" - ) - - # Add timing controls - st.markdown("---") - st.markdown("#### โฑ๏ธ Timing Settings") - - # Scene timing controls - timing_col1, timing_col2 = st.columns(2) - with timing_col1: - scene_duration = st.slider( - "Seconds per Scene", - min_value=1.0, - max_value=5.0, - value=DEFAULT_DURATION, - step=0.5, - help="How long each scene should be displayed" - ) - st.session_state.svgen_scene_duration = scene_duration - - with timing_col2: - transition_duration = st.slider( - "Transition Duration (seconds)", - min_value=0.1, - max_value=1.0, - value=DEFAULT_TRANSITION_DURATION, - step=0.1, - help="Duration of transitions between scenes" - ) - st.session_state.svgen_transition_duration = transition_duration - - # Narration timing controls - narr_timing_col1, narr_timing_col2 = st.columns(2) - with narr_timing_col1: - words_per_second = st.slider( - "Speaking Rate (words/second)", - min_value=1.5, - max_value=3.5, - value=WORDS_PER_SECOND, - step=0.1, - help="Adjust narration speed (default: 2.5 words/second)" - ) - st.session_state.svgen_words_per_second = words_per_second - - with narr_timing_col2: - narration_padding = st.slider( - "Narration Padding (seconds)", - min_value=0.0, - max_value=2.0, - value=0.5, - step=0.1, - help="Extra time to add to narration duration" - ) - st.session_state.svgen_narration_padding = narration_padding - - # Calculate and display timing information - total_scene_time = scene_duration * scene_count - total_transition_time = transition_duration * (scene_count - 1) - total_video_time = total_scene_time + total_transition_time - - st.info(f""" - **Timing Summary:** - - Total Scene Time: {total_scene_time:.1f}s - - Total Transition Time: {total_transition_time:.1f}s - - Estimated Video Duration: {total_video_time:.1f}s - - Target Narration Length: {int(total_video_time * words_per_second)} words - """) - with st.expander("๐ŸŽ™๏ธ Narration Settings", expanded=True): - narr_col1, narr_col2 = st.columns(2) - with narr_col1: - narration_language = st.selectbox( - "Language", - list(NARRATION_LANGUAGES.keys()), - help="Select the language for narration" - ) - with narr_col2: - include_music = st.checkbox( - "Include Background Music", - value=True, - help="Add background music to enhance the video" - ) - st.markdown('---') - can_generate_script = bool(video_topic and content_style and video_duration and resolution and narration_language) - if st.button("๐Ÿ“ Generate Script", key="generate_script_btn", help="Generate a script for your Shorts video", use_container_width=True, disabled=not can_generate_script): - print(f'[DEBUG] Generate Script button clicked. Topic: {video_topic}, Style: {content_style}, Duration: {video_duration}, Resolution: {resolution}, Language: {narration_language}') - try: - with st.spinner("Generating script..."): - script = generate_shorts_script( - hook_type="Question", - main_topic=video_topic, - target_audience="general", - tone_style=content_style, - content_type=CONTENT_STYLES[content_style]["style"], - duration_seconds=video_duration, - include_captions=True, - include_text_overlay=True, - include_sound_effects=True, - vertical_framing_notes=True, - language=narration_language - ) - print(f'[DEBUG] Script generated: {bool(script)}') - if script: - st.session_state.generated_script = script - st.session_state.script_approved = False - st.session_state.shorts_stage = 2 - st.session_state.svgen_resolution = resolution - st.session_state.svgen_transition_style = transition_style - st.session_state.svgen_narration_language = narration_language - st.session_state.svgen_include_music = include_music - st.session_state.svgen_content_style = content_style - st.session_state.svgen_video_duration = video_duration - st.session_state.svgen_video_topic = video_topic - print('[DEBUG] Script saved to session state and moving to Stage 2') - st.success("Script generated! Review and edit below.") - else: - print('[ERROR] Script generation failed') - st.error("Failed to generate script. Please try again.") - except Exception as e: - print(f'[ERROR] Exception during script generation: {e}') - st.error(f"An error occurred while generating the script: {str(e)}") - logger.error(f"Error in script generation: {str(e)}") - logger.error(traceback.format_exc()) - if not can_generate_script: - st.warning("Please fill in all required fields above to enable script generation.") - st.markdown('---') - st.info("Next: Review and edit your script.") - - # --- Stage 2: Script Review & Edit --- - if st.session_state.shorts_stage == 2: - print('[DEBUG] Stage 2: Script Review & Edit loaded') - st.markdown('---') - st.markdown('### 2๏ธโƒฃ Script Review & Edit') - st.info("Review your generated script. Use the Edit tab to make changes. Approve to continue.") - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿ“„ Script Preview & Edit
', unsafe_allow_html=True) - preview_tab, edit_tab = st.tabs(["Preview", "Edit"]) - with preview_tab: - st.markdown(st.session_state.generated_script) - if not st.session_state.script_approved: - if st.button("โœ… Approve Script", key="approve_script_btn", use_container_width=True): - st.session_state.script_approved = True - print('[DEBUG] Script approved by user') - st.success("Script approved! You can now generate your video.") - with edit_tab: - edited_script = st.text_area( - "Edit Script", - value=st.session_state.generated_script, - height=400, - help="Make any necessary changes to the script. The format should be maintained." - ) - if edited_script != st.session_state.generated_script: - print('[DEBUG] Script edited by user') - st.session_state.generated_script = edited_script - st.session_state.script_approved = False - st.info("Script updated. Please review and approve the changes.") - st.markdown('
', unsafe_allow_html=True) - st.markdown('---') - st.button("โฌ…๏ธ Back to Details", key="back_to_details_btn", use_container_width=True, on_click=lambda: st.session_state.update({'shorts_stage': 1})) - if st.session_state.script_approved: - st.success("Script approved! You can now generate your video.") - st.button("๐ŸŽฌ Proceed to Video Generation", key="proceed_to_video_btn", use_container_width=True, on_click=lambda: st.session_state.update({'shorts_stage': 3})) - else: - st.warning("Please approve your script before proceeding.") - st.markdown('---') - st.info("Next: Review and edit narration, then generate your video.") - - # --- Stage 3: Video Generation --- - if st.session_state.shorts_stage == 3: - print('[DEBUG] Stage 3: Narration & Video Generation loaded') - st.markdown('---') - st.markdown('### 3๏ธโƒฃ Narration & Video Generation') - st.info("Edit or generate narration, preview audio, then click **Generate Video**.") - st.markdown('
', unsafe_allow_html=True) - st.markdown('
๐Ÿ—ฃ๏ธ Narration for Review & Edit
', unsafe_allow_html=True) - narr_col1, narr_col2 = st.columns([4, 1]) - with narr_col1: - if 'editable_narration' not in st.session_state: - st.session_state.editable_narration = generate_shorts_narration( - st.session_state.generated_script, - language=st.session_state.svgen_narration_language, - target_duration=st.session_state.svgen_video_duration - ) - print('[DEBUG] Initial narration generated') - - edited_narration = st.text_area( - "Edit narration to be used for TTS:", - value=st.session_state.editable_narration, - height=120, - key="editable_narration_area", - help="Edit the narration to sound natural when spoken. No technical details needed." - ) - st.session_state.editable_narration = edited_narration - - # Calculate and display timing information - narration_word_count = len(edited_narration.split()) - words_per_second = 2.5 # Standard speaking rate - estimated_duration = narration_word_count / words_per_second - - narration_stats = ( - f"Words: {narration_word_count} | " - f"Est. duration: {estimated_duration:.1f}s | " - f"Target: {st.session_state.svgen_video_duration}s" - ) - st.caption(narration_stats) - - # Display timing warnings - if estimated_duration < 20: - st.warning("โš ๏ธ Narration is too short for a 30-second video. Consider generating a new narration.") - elif estimated_duration > 35: - st.warning("โš ๏ธ Narration is too long for a 30-second video. Consider generating a new narration.") - - # Narration Tips in an expander - with st.expander("๐Ÿ’ก Narration Tips", expanded=False): - st.markdown(""" - ### Tips for Natural Narration - - - Write only what should be spoken - - Keep it conversational and clear - - Use natural pauses (...) - - Focus on the message, not the technical details - - End with a clear call to action - """) - - tts_col1, tts_col2 = st.columns(2) - with tts_col1: - tts_gender = st.selectbox("Voice Gender (affects some TTS engines)", ["Default", "Female", "Male"], key="tts_gender_select") - with tts_col2: - tts_speed = st.selectbox("Speech Speed", ["Normal", "Slow"], key="tts_speed_select") - if st.button("๐Ÿ”Š Preview Narration Audio", key="preview_tts_btn"): - print('[DEBUG] TTS preview button clicked') - try: - tts_kwargs = {"lang": NARRATION_LANGUAGES[st.session_state.svgen_narration_language]} - tts_kwargs["slow"] = tts_speed == "Slow" - tts = gTTS(text=edited_narration, **tts_kwargs) - preview_audio_path = os.path.join(tempfile.gettempdir(), f"tts_preview_{os.getpid()}.mp3") - tts.save(preview_audio_path) - with open(preview_audio_path, "rb") as audio_file: - audio_bytes = audio_file.read() - st.audio(audio_bytes, format="audio/mp3") - print('[DEBUG] TTS preview audio generated and played') - except Exception as tts_err: - print(f'[ERROR] Failed to generate TTS preview: {tts_err}') - st.error(f"Failed to generate TTS preview: {tts_err}") - if narration_word_count < 10: - st.warning("Narration is very short. Consider adding more detail.") - elif narration_word_count > 120: - st.warning("Narration is quite long. Consider shortening for Shorts.") - with narr_col2: - if st.button("๐Ÿ”„ Generate New Narration", key="generate_narration_btn"): - with st.spinner("Generating engaging narration..."): - new_narration = generate_shorts_narration( - st.session_state.generated_script, - language=st.session_state.svgen_narration_language, - target_duration=st.session_state.svgen_video_duration - ) - if new_narration: - st.session_state.editable_narration = new_narration - print('[DEBUG] New narration generated') - st.success("New narration generated successfully!") - st.rerun() - else: - st.error("Failed to generate narration. Please try again.") - - if st.button("๐Ÿค– Generate AI Narration", key="ai_narration_btn"): - with st.spinner("Generating AI-optimized narration..."): - ai_narr = generate_shorts_narration( - st.session_state.generated_script, - language=st.session_state.svgen_narration_language, - target_duration=st.session_state.svgen_video_duration - ) - if ai_narr: - st.session_state.editable_narration = ai_narr - print('[DEBUG] AI-generated narration updated') - st.success("AI-generated narration updated.") - st.rerun() - else: - st.error("Failed to generate AI narration. Please try again.") - st.markdown('
', unsafe_allow_html=True) - st.markdown('---') - st.markdown('### 3๏ธโƒฃ Video Generation') - st.info("Click **Generate Video** to start the final process. This may take a few minutes.") - st.markdown('
', unsafe_allow_html=True) - st.markdown('
Video Generation
', unsafe_allow_html=True) - - # Video Information in an expander - with st.expander("๐Ÿ“‹ Video Information", expanded=True): - st.markdown(""" - ### Video Details - | Setting | Value | - |---------|--------| - | Video Topic | {} | - | Content Style | {} | - | Duration | {} seconds | - | Resolution | {} | - | Narration Language | {} | - | Background Music | {} | - """.format( - st.session_state.svgen_video_topic, - st.session_state.svgen_content_style, - st.session_state.svgen_video_duration, - st.session_state.svgen_resolution, - st.session_state.svgen_narration_language, - "Yes" if st.session_state.svgen_include_music else "No" - )) - - st.markdown('
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - st.button("โฌ…๏ธ Back to Script Review", key="back_to_script_btn", use_container_width=True, on_click=lambda: st.session_state.update({'shorts_stage': 2})) - if st.button("๐Ÿš€ Generate Video", key="generate_video_btn", use_container_width=True): - print('[DEBUG] Generate Video button clicked') - try: - with st.spinner("Generating your Shorts video..."): - st.info("Step 1/3: Generating images...") - image_paths = [] - temp_dir = Path(tempfile.mkdtemp()) - # Filter out empty scenes and limit to MAX_SCENES - scenes = [s.strip() for s in st.session_state.generated_script.split("\n\n") if s.strip()][:MAX_SCENES] - resolution = st.session_state.svgen_resolution - narration_language = st.session_state.svgen_narration_language - scene_count = 0 - num_scenes_total = len(scenes) - progress_bar = st.progress(0.0) - status_text = st.empty() - - # Initialize or load image cache - if 'generated_image_paths' not in st.session_state: - st.session_state.generated_image_paths = {} - generated_image_paths = st.session_state.generated_image_paths - - # Clear any invalid cache entries - generated_image_paths = {k: v for k, v in generated_image_paths.items() - if os.path.exists(v) and k < num_scenes_total} - st.session_state.generated_image_paths = generated_image_paths - - preview_container = st.container() - preview_thumbnails = [] - - def retry_on_error(max_retries=3, initial_delay=1, max_delay=10): - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - delay = initial_delay - for attempt in range(max_retries): - try: - return func(*args, **kwargs) - except Exception as e: - if attempt == max_retries - 1: - raise - print(f'[WARN] Retry {attempt+1}/{max_retries} for image generation: {e}') - time.sleep(delay) - delay = min(delay * 2, max_delay) - return None - return wrapper - return decorator - - @retry_on_error(max_retries=3, initial_delay=2, max_delay=10) - def safe_generate_image(prompt): - return generate_image(prompt) - - for i, scene in enumerate(scenes): - print(f'[DEBUG] Processing scene {i+1}/{num_scenes_total}') - status_text.text(f"Generating image for scene {i+1}/{num_scenes_total}...") - - # Check cache first - if i in generated_image_paths: - image_paths.append(generated_image_paths[i]) - preview_thumbnails.append((generated_image_paths[i], i+1)) - print(f'[DEBUG] Using cached image for scene {i+1}') - scene_count += 1 - progress_bar.progress(scene_count / num_scenes_total) - continue - - # Extract details for a more specific prompt - visual_desc = scene.split("Visual Instructions:")[1].split("\n")[0] if "Visual Instructions:" in scene else scene - narration_match = re.search(r'Audio/Voiceover:\s*(.*)', scene) - narration_line = narration_match.group(1).strip() if narration_match else "" - - # Enhanced prompt with more specific details and style guidance - prompt = ( - f"Create a vertical (9:16) image for YouTube Shorts video.\n" - f"Scene {i+1} of {num_scenes_total}:\n" - f"Visual Description: {visual_desc}\n" - f"Context: {narration_line}\n" - f"Style Requirements:\n" - f"- High contrast and vibrant colors for better mobile viewing\n" - f"- Clear focal point in the center for vertical format\n" - f"- Professional quality, cinematic lighting\n" - f"- Text-safe areas on top and bottom\n" - f"- Visually distinct from other scenes\n" - f"- Modern, engaging composition\n" - f"- Suitable for {st.session_state.svgen_content_style} style content\n" - f"Technical Requirements:\n" - f"- Vertical 9:16 aspect ratio\n" - f"- High resolution, sharp details\n" - f"- No text or watermarks\n" - f"- No blurry or low-quality elements" - ) - - try: - image_path = safe_generate_image(prompt) - if image_path: - img = Image.open(image_path) - target_size = VIDEO_RESOLUTIONS[resolution] - img = img.resize(target_size, Image.LANCZOS) - resized_path = temp_dir / f"scene_{i}.png" - img.save(resized_path) - image_paths.append(str(resized_path)) - generated_image_paths[i] = str(resized_path) - st.session_state.generated_image_paths = generated_image_paths - preview_thumbnails.append((str(resized_path), i+1)) - print(f'[DEBUG] Generated and cached new image for scene {i+1}') - else: - print(f'[ERROR] Image generation failed for scene {i+1}') - st.warning(f"Image generation failed for scene {i+1}. Skipping.") - except Exception as img_err: - print(f'[ERROR] Exception during image generation for scene {i+1}: {img_err}') - st.warning(f"Error generating image for scene {i+1}: {img_err}") - - scene_count += 1 - progress_bar.progress(scene_count / num_scenes_total) - - # Update preview after each image - with preview_container: - preview_container.empty() # Clear previous preview - if preview_thumbnails: - # Create a grid layout with 5 columns - cols = st.columns(5) - - # Display thumbnails in a grid - for idx, (img_path, sc_num) in enumerate(preview_thumbnails): - with cols[idx % 5]: - # Create a smaller thumbnail - img = Image.open(img_path) - # Calculate aspect ratio to maintain 9:16 - target_width = 100 # Smaller width - target_height = int(target_width * (16/9)) - img = img.resize((target_width, target_height), Image.LANCZOS) - - # Display with a compact caption - st.image( - img, - caption=f"Scene {sc_num}", - use_column_width=True, - key=f"preview_{sc_num}" # Add unique key for each image - ) - - # Add a small progress indicator - if idx == len(preview_thumbnails) - 1: - st.caption(f"Generating scene {scene_count + 1}...") - - # Add a clear divider between preview and next steps - st.markdown("---") - status_text.text("Image generation complete!") - print(f'[DEBUG] Image generation complete. Total images: {len(image_paths)}') - if not image_paths: - print('[ERROR] No images generated') - st.error("Failed to generate images. Please try again.") - return - st.info("Step 2/3: Generating narration...") - narration_path = temp_dir / "narration.mp3" - narration_text = st.session_state.editable_narration - try: - tts = gTTS(text=narration_text, lang=NARRATION_LANGUAGES[narration_language]) - tts.save(str(narration_path)) - print('[DEBUG] Narration audio generated and saved') - - # Verify the audio file was created and is valid - if not os.path.exists(str(narration_path)): - raise Exception("Narration audio file was not created") - - # Test the audio file by loading it - test_audio = AudioFileClip(str(narration_path)) - if test_audio.duration <= 0: - raise Exception("Generated audio file is invalid or empty") - test_audio.close() - - except Exception as tts_err: - print(f'[ERROR] Failed to generate narration: {tts_err}') - st.error(f"Failed to generate narration: {tts_err}") - return - - st.info("Step 3/3: Creating video...") - video_generator = StoryVideoGenerator() - try: - # Verify audio file exists before video creation - if not os.path.exists(str(narration_path)): - raise Exception("Narration audio file not found") - - video_path = video_generator.create_video( - image_paths=image_paths, - audio_path=str(narration_path), - fps=DEFAULT_FPS, - duration_per_image=getattr(st.session_state, 'svgen_scene_duration', DEFAULT_DURATION) - ) - if video_path and os.path.exists(video_path): - print(f'[DEBUG] Video generated at {video_path}') - st.success("โœจ Video generated successfully! Preview below and download your video.") - st.video(video_path) - safe_topic = re.sub(r'[^\w\-]+', '_', st.session_state.svgen_video_topic) - download_filename = f"{safe_topic}_shorts_video.mp4" - with open(video_path, "rb") as f: - video_bytes = f.read() - st.download_button( - label="โฌ‡๏ธ Download Video", - data=video_bytes, - file_name=download_filename, - mime="video/mp4" - ) - else: - print('[ERROR] Video file not found after generation') - st.error("Failed to create video. Please try again.") - except Exception as vid_err: - print(f'[ERROR] Exception during video creation: {vid_err}') - st.error(f"An error occurred while creating the video: {vid_err}") - logger.error(f"Error in video generation: {vid_err}") - logger.error(traceback.format_exc()) - except Exception as e: - print(f'[ERROR] Exception during full video generation: {e}') - st.error(f"An error occurred while generating the video: {str(e)}") - logger.error(f"Error in video generation: {str(e)}") - logger.error(traceback.format_exc()) - st.markdown('
', unsafe_allow_html=True) - st.markdown('---') - st.info("All done! You can download your video above or go back to make changes.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/tags_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/tags_generator.py deleted file mode 100644 index 53acabe1..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/tags_generator.py +++ /dev/null @@ -1,406 +0,0 @@ -""" -YouTube Tags Generator Module - -This module provides functionality for generating and optimizing YouTube video tags. -""" - -import streamlit as st -import time -import logging -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from pytrends.request import TrendReq -import pandas as pd - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('youtube_tags_generator') - -def get_pytrends_data(keyword): - """Get trending data using PyTrends with simplified, reliable approach.""" - logger.info(f"Getting PyTrends data for: '{keyword}'") - - # Initialize empty results - results = { - 'topics': [], - 'queries': [], - 'trending': [] - } - - try: - # Initialize PyTrends with minimal configuration - pytrends = TrendReq(hl='en-US', tz=360) - time.sleep(1) # Basic rate limiting - - # 1. Get suggestions (most reliable method) - try: - suggestions = pytrends.suggestions(keyword) - if suggestions: - results['trending'] = [sugg['title'] for sugg in suggestions if sugg['title']][:3] - except Exception as e: - logger.warning(f"Error getting suggestions: {str(e)}") - - # 2. Get trending searches as backup - if not results['trending']: - try: - trending = pytrends.trending_searches(pn='united_states') - if not trending.empty: - results['trending'] = trending.head(3).values.tolist() - except Exception as e: - logger.warning(f"Error getting trending searches: {str(e)}") - - # 3. Use keyword variations as fallback - if not any(results.values()): - results['trending'] = [keyword] - results['queries'] = [keyword.lower(), keyword.title()] - results['topics'] = [keyword.capitalize()] - - return results - - except Exception as e: - logger.error(f"Error in PyTrends: {str(e)}") - # Return basic keyword variations as fallback - return { - 'topics': [keyword.capitalize()], - 'queries': [keyword.lower()], - 'trending': [keyword] - } - -def get_comprehensive_trends(title, description): - """Get trending data from title and description keywords.""" - logger.info(f"Getting comprehensive trends for title: '{title}'") - - # Extract main keywords (only words longer than 3 chars) - words = [w for w in title.split() if len(w) > 3] - if description: - desc_words = [w for w in description.split() if len(w) > 3] - words.extend(desc_words) - - # Remove duplicates and limit to 2 keywords to prevent rate limiting - keywords = list(dict.fromkeys(words))[:2] - - # Get trending data for main keywords - all_trends = { - 'topics': [], - 'queries': [], - 'trending': [] - } - - for keyword in keywords: - try: - trends = get_pytrends_data(keyword) - for key in all_trends: - if trends[key]: - all_trends[key].extend(trends[key]) - time.sleep(1) # Rate limiting between keywords - except Exception as e: - logger.warning(f"Error getting trends for keyword '{keyword}': {str(e)}") - continue - - # Remove duplicates while preserving order - for key in all_trends: - seen = set() - all_trends[key] = [x for x in all_trends[key] if x and not (x.lower() in seen or seen.add(x.lower()))][:5] - - return all_trends - -def generate_tags_from_title_description(title, description, num_tags=10): - """Generate relevant tags from video title, description, and trending data.""" - logger.info(f"Generating tags for title: '{title}'") - - # Get comprehensive trending data - trends = get_comprehensive_trends(title, description) - - # Create a comprehensive context for GPT - trend_context = f""" - Related Topics: {', '.join(trends['topics'][:10])} - Related Queries: {', '.join(trends['queries'][:10])} - Trending Suggestions: {', '.join(trends['trending'][:10])} - """ - - system_prompt = """You are a YouTube SEO expert specializing in tag optimization. - Generate relevant, searchable tags based on the video title, description, and trending data provided. - Focus on a mix of specific and broad tags that will help with video discovery. - Consider the trending topics and queries provided to maximize searchability. - Return only the tags, separated by commas.""" - - user_prompt = f"""Generate {num_tags} relevant YouTube tags for a video with: - Title: {title} - Description: {description} - - Consider this trending data: - {trend_context} - - Include a mix of: - - Exact match phrases from title and description - - Related trending topics and queries - - Broader category tags - - Specific niche tags - - Popular search variations - - Format: Return only the tags, separated by commas.""" - - try: - tags = llm_text_gen(user_prompt, system_prompt=system_prompt) - generated_tags = [tag.strip() for tag in tags.split(',')] - - # Add some trending tags directly - trending_tags = ( - trends['topics'][:3] + # Top 3 related topics - trends['queries'][:3] + # Top 3 related queries - trends['trending'][:3] # Top 3 trending suggestions - ) - - # Combine and remove duplicates - all_tags = generated_tags + trending_tags - seen = set() - final_tags = [tag for tag in all_tags if not (tag.lower() in seen or seen.add(tag.lower()))] - - return final_tags - except Exception as e: - logger.error(f"Error generating tags: {str(e)}") - return [] - -def analyze_tags(tags): - """Analyze tags for optimization opportunities.""" - analysis = { - 'total_tags': len(tags), - 'total_characters': sum(len(tag) for tag in tags), - 'avg_tag_length': sum(len(tag) for tag in tags) / len(tags) if tags else 0, - 'duplicate_tags': len(tags) - len(set(tags)), - 'tags_too_long': [tag for tag in tags if len(tag) > 30], - 'single_word_tags': [tag for tag in tags if len(tag.split()) == 1], - 'optimization_score': 0 - } - - # Calculate optimization score (0-100) - score = 100 - if analysis['total_tags'] < 5: - score -= 30 - if analysis['total_characters'] > 500: - score -= 20 - if analysis['duplicate_tags'] > 0: - score -= 10 * analysis['duplicate_tags'] - if len(analysis['tags_too_long']) > 0: - score -= 5 * len(analysis['tags_too_long']) - if len(analysis['single_word_tags']) > len(tags) * 0.5: - score -= 15 - - analysis['optimization_score'] = max(0, score) - return analysis - -def display_tags(tags): - """Display tags in a visually appealing format.""" - if not tags: - return - - # Create a container for all tags - st.markdown(""" - -
- """, unsafe_allow_html=True) - - # Display tags - for tag in tags: - st.markdown(f'
{tag}
', unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - - # Display tag count and character count - tags_text = ", ".join(tags) - char_count = len(tags_text) - col1, col2 = st.columns(2) - with col1: - st.caption(f"Total tags: {len(tags)}") - with col2: - st.caption(f"Characters: {char_count}/500") - -def write_yt_tags(): - """Create a user interface for YouTube Tags Generator.""" - logger.info("Initializing YouTube Tags Generator UI") - st.write("Generate optimized tags for your videos with trending tag suggestions to improve discoverability.") - - # Initialize session state - if "generated_tags" not in st.session_state: - st.session_state.generated_tags = None - if "tag_analysis" not in st.session_state: - st.session_state.tag_analysis = None - - # Create tabs for different sections - tab1, tab2, tab3 = st.tabs(["Quick Generate", "Advanced Options", "Analysis"]) - - with tab1: - # Basic information inputs - title = st.text_input("Video Title", - placeholder="Enter your video title") - description = st.text_area("Video Description", - placeholder="Enter your video description") - - col1, col2 = st.columns(2) - - with col1: - num_tags = st.number_input("Number of Tags", - min_value=5, - max_value=30, - value=15) - - with col2: - include_trending = st.checkbox("Include Trending Suggestions", value=True) - - if st.button("Generate Tags"): - if not title: - st.error("Please enter a video title.") - return - - with st.spinner("Generating tags..."): - # Generate tags using the comprehensive method - tags = generate_tags_from_title_description(title, description, num_tags) - - if tags: - # Analyze tags - st.session_state.tag_analysis = analyze_tags(tags) - st.session_state.generated_tags = tags - - # Display tags in the new format - st.subheader("Generated Tags") - display_tags(tags) - - # Add copy button for all tags - tags_text = ", ".join(tags) - st.text_area("Tags (copy to use)", value=tags_text, height=100) - - # Display character count - char_count = len(tags_text) - st.info(f"Total characters: {char_count}/500 ({500 - char_count} remaining)") - - # Quick analysis summary - col1, col2, col3 = st.columns(3) - with col1: - st.metric("Number of Tags", len(tags)) - with col2: - st.metric("Optimization Score", f"{st.session_state.tag_analysis['optimization_score']}%") - with col3: - st.metric("Avg Tag Length", f"{st.session_state.tag_analysis['avg_tag_length']:.1f}") - - # Display trending data summary if enabled - if include_trending: - st.subheader("Trending Data Used") - trends = get_comprehensive_trends(title, description) - - # Create columns for different trend types - tcol1, tcol2, tcol3 = st.columns(3) - - with tcol1: - st.markdown("##### Related Topics") - if trends['topics']: - for topic in trends['topics'][:5]: - st.markdown(f"โ€ข {topic}") - else: - st.markdown("*No related topics found*") - - with tcol2: - st.markdown("##### Related Queries") - if trends['queries']: - for query in trends['queries'][:5]: - st.markdown(f"โ€ข {query}") - else: - st.markdown("*No related queries found*") - - with tcol3: - st.markdown("##### Trending Suggestions") - if trends['trending']: - for trend in trends['trending'][:5]: - st.markdown(f"โ€ข {trend}") - else: - st.markdown("*No trending suggestions found*") - else: - st.error("Failed to generate tags. Please try again.") - - with tab2: - st.info("Advanced tag generation options coming soon!") - st.markdown(""" - Future features will include: - - Competitor tag analysis - - Tag performance tracking - - Category-specific tag suggestions - - Multi-language tag generation - - Tag sets management - """) - - with tab3: - if st.session_state.tag_analysis: - st.subheader("Tag Analysis") - - # Create metrics - col1, col2 = st.columns(2) - - with col1: - st.metric("Total Tags", st.session_state.tag_analysis['total_tags']) - st.metric("Total Characters", st.session_state.tag_analysis['total_characters']) - st.metric("Average Tag Length", f"{st.session_state.tag_analysis['avg_tag_length']:.1f}") - - with col2: - st.metric("Duplicate Tags", st.session_state.tag_analysis['duplicate_tags']) - st.metric("Single Word Tags", len(st.session_state.tag_analysis['single_word_tags'])) - st.metric("Tags Too Long", len(st.session_state.tag_analysis['tags_too_long'])) - - # Optimization score with color - score = st.session_state.tag_analysis['optimization_score'] - score_color = 'red' if score < 50 else 'orange' if score < 80 else 'green' - st.markdown(f""" -
-

Optimization Score: {score}%

-
- """, unsafe_allow_html=True) - - # Optimization suggestions - st.subheader("Optimization Suggestions") - suggestions = [] - - if st.session_state.tag_analysis['total_tags'] < 5: - suggestions.append("โŒ Add more tags (aim for at least 15)") - if st.session_state.tag_analysis['total_characters'] > 500: - suggestions.append("โŒ Total character count exceeds limit (max 500)") - if st.session_state.tag_analysis['duplicate_tags'] > 0: - suggestions.append("โŒ Remove duplicate tags") - if len(st.session_state.tag_analysis['tags_too_long']) > 0: - suggestions.append("โŒ Some tags are too long (max 30 characters)") - if len(st.session_state.tag_analysis['single_word_tags']) > st.session_state.tag_analysis['total_tags'] * 0.5: - suggestions.append("โŒ Too many single-word tags (use more specific phrases)") - - if not suggestions: - st.success("โœ… Your tags are well-optimized!") - else: - for suggestion in suggestions: - st.warning(suggestion) - else: - st.info("Generate tags first to see analysis") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/thumbnail_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/thumbnail_generator.py deleted file mode 100644 index 44465681..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/thumbnail_generator.py +++ /dev/null @@ -1,622 +0,0 @@ -""" -YouTube Thumbnail Generator Module - -This module provides functionality for generating YouTube video thumbnails. -""" - -import streamlit as st -import time -import logging -import os -import traceback -from PIL import Image -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen -from lib.gpt_providers.text_to_image_generation.gen_gemini_images import generate_gemini_image, edit_image - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('youtube_thumbnail_generator') - - -def generate_thumbnail_concepts(video_title, video_description, target_audience, content_type, style_preference, num_concepts=3): - """Generate thumbnail concept ideas based on video content.""" - logger.info(f"Generating thumbnail concepts for: '{video_title}'") - logger.info(f"Parameters: target_audience={target_audience}, content_type={content_type}, style_preference={style_preference}, num_concepts={num_concepts}") - - # Create a system prompt for thumbnail concept generation - system_prompt = """You are a YouTube thumbnail expert specializing in creating engaging, click-worthy thumbnail concepts. - Your task is to generate thumbnail concept ideas based on the provided video information. - Focus ONLY on creating concepts that are optimized for YouTube, with proper visual hierarchy, text placement, and emotional triggers. - Return ONLY the concept descriptions, without any additional commentary or explanations. - Each concept should include: - 1. A main visual element or scene - 2. Text placement and content - 3. Color scheme suggestions - 4. Emotional trigger or hook - 5. Brief explanation of why this concept would be effective""" - - # Build the prompt - prompt = f""" - **Instructions:** - - Please generate {num_concepts} thumbnail concept ideas for a YouTube video with the following information: - - **Video Title:** {video_title} - **Video Description:** {video_description} - **Target Audience:** {target_audience} - **Content Type:** {content_type} - **Style Preference:** {style_preference} - - **Specific Instructions:** - * Each concept should be clearly separated and numbered. - * Focus on creating thumbnails that stand out in search results and recommendations. - * Consider the target audience's interests and preferences. - * Include specific details about visual elements, text placement, and color schemes. - * Explain why each concept would be effective for this specific video. - """ - - try: - logger.info("Sending request to LLM for thumbnail concepts") - response = llm_text_gen(prompt, system_prompt=system_prompt) - logger.info(f"Received response from LLM: {len(response)} characters") - return response - except Exception as err: - logger.error(f"Error generating thumbnail concepts: {err}") - logger.error(traceback.format_exc()) - st.error(f"Error: Failed to generate thumbnail concepts: {err}") - return None - - -def generate_thumbnail_design(concept_description, style_preference, aspect_ratio="16:9", keywords=None, style=None, focus=None): - """Generate a thumbnail image based on the concept description.""" - logger.info(f"Generating thumbnail design for concept: '{concept_description[:50]}...'") - logger.info(f"Parameters: style_preference={style_preference}, aspect_ratio={aspect_ratio}, keywords={keywords}, style={style}, focus={focus}") - - # Create a prompt for the image generation - image_prompt = f""" - Create a YouTube thumbnail image with the following specifications: - - Concept: {concept_description} - Style: {style_preference} - Aspect Ratio: {aspect_ratio} - - The image should be: - - High contrast and visually striking - - Suitable for a YouTube thumbnail - - Include the specified visual elements and text - - Follow the color scheme described - - Optimized for small display sizes - - Make sure the text is large and readable, and the main subject is centered and prominent. - """ - - try: - logger.info("Sending request to Gemini for thumbnail image") - # Generate the image using Gemini with enhanced prompt - img_path = generate_gemini_image( - image_prompt, - keywords=keywords, - style=style, - focus=focus, - enhance_prompt=True - ) - logger.info(f"Received image from Gemini: {img_path}") - return img_path - except Exception as err: - logger.error(f"Error generating thumbnail image: {err}") - logger.error(traceback.format_exc()) - st.error(f"Error: Failed to generate thumbnail image: {err}") - return None - - -def edit_thumbnail_image(img_path, edit_instructions): - """Edit a thumbnail image based on user instructions.""" - logger.info(f"Editing thumbnail image: '{img_path}'") - logger.info(f"Edit instructions: '{edit_instructions}'") - - try: - logger.info("Sending request to Gemini for image editing") - # Edit the image using Gemini - edited_img_path = edit_image(img_path, edit_instructions) - logger.info(f"Image editing completed. Edited image path: {edited_img_path}") - - # Return the path to the edited image - return edited_img_path - except Exception as err: - logger.error(f"Error editing thumbnail image: {err}") - logger.error(traceback.format_exc()) - st.error(f"Error: Failed to edit thumbnail image: {err}") - return None - - -def analyze_thumbnail(thumbnail_path): - """Analyze a thumbnail for effectiveness.""" - logger.info(f"Analyzing thumbnail: '{thumbnail_path}'") - - # This would typically involve image analysis, but for now we'll use AI to provide feedback - system_prompt = """You are a YouTube thumbnail expert specializing in analyzing and providing feedback on thumbnail designs. - Your task is to analyze the thumbnail and provide constructive feedback on its effectiveness. - Focus on aspects like visual hierarchy, text readability, emotional impact, and click-worthiness.""" - - # For now, we'll just return a placeholder analysis - # In a real implementation, we would analyze the actual image - logger.info("Generating thumbnail analysis") - return """ - **Thumbnail Analysis:** - - - **Visual Hierarchy:** The main subject is well-positioned and stands out against the background. - - **Text Readability:** The text is clear and readable, with good contrast against the background. - - **Emotional Impact:** The thumbnail creates curiosity and emotional connection with the target audience. - - **Click-worthiness:** The design is likely to attract clicks due to its visual appeal and clear value proposition. - - **Suggestions for Improvement:** - - Consider adding a subtle border to make the thumbnail stand out more in search results. - - The text could be slightly larger for better readability on mobile devices. - - Adding a small icon or logo could help with brand recognition. - """ - - -def parse_concepts(concepts_text): - """Parse the concepts text into a list of individual concepts.""" - logger.info("Parsing concepts text into individual concepts") - - concept_list = [] - current_concept = "" - - for line in concepts_text.split('\n'): - if line.strip().startswith(('1.', '2.', '3.', '4.', '5.')): - if current_concept: - concept_list.append(current_concept.strip()) - current_concept = line - else: - current_concept += "\n" + line - - if current_concept: - concept_list.append(current_concept.strip()) - - logger.info(f"Parsed {len(concept_list)} concepts from the response") - return concept_list - - -def write_yt_thumbnail(): - """Create a user interface for YouTube Thumbnail Generator.""" - logger.info("Initializing YouTube Thumbnail Generator UI") - st.title("YouTube Thumbnail Generator") - st.write("Create engaging, click-worthy thumbnails for your YouTube videos.") - - # Initialize session state for generated thumbnails if it doesn't exist - if "generated_thumbnails" not in st.session_state: - st.session_state.generated_thumbnails = [] - if "thumbnail_concepts" not in st.session_state: - st.session_state.thumbnail_concepts = None - if "current_thumbnail_path" not in st.session_state: - st.session_state.current_thumbnail_path = None - if "concept_list" not in st.session_state: - st.session_state.concept_list = [] - if "editing_thumbnail" not in st.session_state: - st.session_state.editing_thumbnail = False - if "edit_instructions" not in st.session_state: - st.session_state.edit_instructions = "" - if "edited_thumbnail_path" not in st.session_state: - st.session_state.edited_thumbnail_path = None - if "show_edit_form" not in st.session_state: - st.session_state.show_edit_form = False - - # Create tabs for different sections - tab1, tab2 = st.tabs(["Basic Info", "Style & Generation"]) - - with tab1: - # Basic information inputs - video_title = st.text_input("Video Title", - placeholder="e.g., 10 Tips for Better Photography") - video_description = st.text_area("Video Description", - placeholder="Brief description of your video content") - target_audience = st.text_input("Target Audience", - placeholder="e.g., photography enthusiasts, beginners") - - # Content type selection - content_type = st.selectbox("Content Type", [ - "Tutorial/How-to", - "Vlog", - "Review", - "Educational", - "Entertainment", - "News/Update", - "Product Showcase", - "Challenge", - "Reaction", - "Comparison" - ]) - - with tab2: - # Style preferences - st.subheader("Style Preferences") - - # Create columns for style options - col1, col2 = st.columns(2) - - with col1: - style_preference = st.selectbox("Thumbnail Style", [ - "Bold and Dramatic", - "Clean and Minimal", - "Colorful and Vibrant", - "Dark and Moody", - "Professional and Corporate", - "Playful and Fun", - "Retro/Vintage", - "Modern and Sleek" - ]) - - num_concepts = st.slider("Number of Concepts", 1, 5, 3) - - with col2: - aspect_ratio = st.selectbox("Aspect Ratio", [ - "16:9 (Standard)", - "1:1 (Square)", - "4:3 (Classic)", - "9:16 (Vertical)" - ]) - - include_text = st.checkbox("Include Text Overlay", value=True) - if include_text: - text_style = st.selectbox("Text Style", [ - "Bold and Impactful", - "Clean and Readable", - "Stylized and Thematic", - "Minimal and Subtle" - ]) - - # Advanced AI Prompt Settings - st.subheader("Advanced AI Prompt Settings") - - # Create columns for advanced settings - col3, col4 = st.columns(2) - - with col3: - # Image style selection - image_style = st.selectbox("Image Style", [ - "Auto (AI will choose best style)", - "Photorealistic", - "Artistic", - "Cartoon/Anime", - "Sketch/Drawing", - "Digital Art", - "3D Render" - ]) - - # Extract style for the generate_gemini_image function - style = None - if image_style == "Photorealistic": - style = "photorealistic" - elif image_style == "Artistic": - style = "artistic" - elif image_style == "Cartoon/Anime": - style = "cartoon" - elif image_style == "Sketch/Drawing": - style = "sketch" - elif image_style == "Digital Art": - style = "digital_art" - elif image_style == "3D Render": - style = "3d_render" - - with col4: - # Focus selection for photorealistic images - focus = None - if style == "photorealistic": - focus = st.selectbox("Image Focus", [ - "Auto (AI will choose best focus)", - "Portraits", - "Objects", - "Motion", - "Wide-angle" - ]) - - # Extract focus for the generate_gemini_image function - if focus == "Portraits": - focus = "portraits" - elif focus == "Objects": - focus = "objects" - elif focus == "Motion": - focus = "motion" - elif focus == "Wide-angle": - focus = "wide-angle" - elif focus == "Auto (AI will choose best focus)": - focus = None - - # Keywords for enhanced prompt generation - st.subheader("Keywords for Enhanced Prompt") - st.write("Add keywords to enhance the AI prompt generation. These will help create more detailed and accurate thumbnails.") - - # Create a text area for keywords - keywords_input = st.text_area( - "Keywords (comma-separated)", - placeholder="e.g., vibrant, energetic, bold, eye-catching, professional" - ) - - # Process keywords - keywords = None - if keywords_input: - keywords = [k.strip() for k in keywords_input.split(",") if k.strip()] - logger.info(f"User provided keywords: {keywords}") - - # Generate button - if st.button("Generate Thumbnail Concepts"): - if not video_title: - st.error("Please enter a video title.") - return - - with st.spinner("Generating thumbnail concepts..."): - logger.info("User clicked Generate Thumbnail Concepts button") - concepts = generate_thumbnail_concepts( - video_title, - video_description, - target_audience, - content_type, - style_preference, - num_concepts - ) - - if concepts: - # Store the concepts in session state - st.session_state.thumbnail_concepts = concepts - # Parse the concepts and store in session state - st.session_state.concept_list = parse_concepts(concepts) - logger.info("Stored thumbnail concepts in session state") - - # Display the concepts in tabs - st.subheader("Thumbnail Concepts") - - # Create tabs for each concept - concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))]) - - for i, tab in enumerate(concept_tabs): - with tab: - st.markdown(st.session_state.concept_list[i]) - - # Add a button to generate image for this concept - if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_{i}"): - with st.spinner(f"Generating thumbnail image for concept {i+1}..."): - logger.info(f"User selected concept {i+1} for image generation") - # Get the selected concept - selected_concept = st.session_state.concept_list[i] - - # Generate the thumbnail image with enhanced prompt - img_path = generate_thumbnail_design( - selected_concept, - style_preference, - aspect_ratio.split()[0], # Extract just the ratio part - keywords=keywords, - style=style, - focus=focus - ) - - if img_path: - # Store the current thumbnail path in session state - st.session_state.current_thumbnail_path = img_path - logger.info(f"Stored current thumbnail path in session state: {img_path}") - - # Display the generated image - st.subheader("Generated Thumbnail") - st.image(img_path, use_container_width=True) - - # Add download button - with open(img_path, "rb") as file: - st.download_button( - label="Download Thumbnail", - data=file, - file_name=f"youtube_thumbnail_{int(time.time())}.png", - mime="image/png" - ) - - # Add image editing section - st.subheader("Edit Thumbnail") - st.write("Make changes to your thumbnail by providing instructions below:") - - # Create a text area for edit instructions - edit_instructions = st.text_area( - "Edit Instructions", - placeholder="e.g., Make the background darker, Add a red border, Change the text color to white", - key=f"edit_instructions_{i}" - ) - - # Store edit instructions in session state - st.session_state.edit_instructions = edit_instructions - - # Add a button to apply edits - if st.button("Apply Edits", key=f"apply_edits_{i}"): - if not edit_instructions: - st.warning("Please provide edit instructions.") - else: - # Set editing flag - st.session_state.editing_thumbnail = True - st.session_state.show_edit_form = True - - # Rerun to update the UI - st.rerun() - - # Add analysis button - if st.button("Analyze Thumbnail", key=f"analyze_{i}"): - logger.info("User clicked Analyze Thumbnail button") - analysis = analyze_thumbnail(img_path) - st.subheader("Thumbnail Analysis") - st.markdown(analysis) - else: - st.error("Failed to generate thumbnail concepts. Please try again.") - - # Display previously generated concepts if they exist in session state - elif st.session_state.thumbnail_concepts and st.session_state.concept_list: - logger.info("Displaying previously generated concepts from session state") - st.subheader("Thumbnail Concepts") - - # Create tabs for each concept - concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))]) - - for i, tab in enumerate(concept_tabs): - with tab: - st.markdown(st.session_state.concept_list[i]) - - # Add a button to generate image for this concept - if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_existing_{i}"): - with st.spinner(f"Generating thumbnail image for concept {i+1}..."): - logger.info(f"User selected concept {i+1} for image generation") - # Get the selected concept - selected_concept = st.session_state.concept_list[i] - - # Generate the thumbnail image with enhanced prompt - img_path = generate_thumbnail_design( - selected_concept, - style_preference, - aspect_ratio.split()[0], # Extract just the ratio part - keywords=keywords, - style=style, - focus=focus - ) - - if img_path: - # Store the current thumbnail path in session state - st.session_state.current_thumbnail_path = img_path - logger.info(f"Stored current thumbnail path in session state: {img_path}") - - # Display the generated image - st.subheader("Generated Thumbnail") - st.image(img_path, use_container_width=True) - - # Add download button - with open(img_path, "rb") as file: - st.download_button( - label="Download Thumbnail", - data=file, - file_name=f"youtube_thumbnail_{int(time.time())}.png", - mime="image/png" - ) - - # Add image editing section - st.subheader("Edit Thumbnail") - st.write("Make changes to your thumbnail by providing instructions below:") - - # Create a text area for edit instructions - edit_instructions = st.text_area( - "Edit Instructions", - placeholder="e.g., Make the background darker, Add a red border, Change the text color to white", - key=f"edit_instructions_existing_{i}" - ) - - # Store edit instructions in session state - st.session_state.edit_instructions = edit_instructions - - # Add a button to apply edits - if st.button("Apply Edits", key=f"apply_edits_existing_{i}"): - if not edit_instructions: - st.warning("Please provide edit instructions.") - else: - # Set editing flag - st.session_state.editing_thumbnail = True - st.session_state.show_edit_form = True - - # Rerun to update the UI - st.rerun() - - # Add analysis button - if st.button("Analyze Thumbnail", key=f"analyze_existing_{i}"): - logger.info("User clicked Analyze Thumbnail button") - analysis = analyze_thumbnail(img_path) - st.subheader("Thumbnail Analysis") - st.markdown(analysis) - - # Display current thumbnail if it exists in session state - elif st.session_state.current_thumbnail_path: - logger.info(f"Displaying current thumbnail from session state: {st.session_state.current_thumbnail_path}") - st.subheader("Current Thumbnail") - st.image(st.session_state.current_thumbnail_path, use_container_width=True) - - # Add download button - with open(st.session_state.current_thumbnail_path, "rb") as file: - st.download_button( - label="Download Thumbnail", - data=file, - file_name=f"youtube_thumbnail_{int(time.time())}.png", - mime="image/png" - ) - - # Add image editing section - st.subheader("Edit Thumbnail") - st.write("Make changes to your thumbnail by providing instructions below:") - - # Create a text area for edit instructions - edit_instructions = st.text_area( - "Edit Instructions", - placeholder="e.g., Make the background darker, Add a red border, Change the text color to white", - key="edit_instructions_current", - value=st.session_state.edit_instructions if st.session_state.edit_instructions else "" - ) - - # Store edit instructions in session state - st.session_state.edit_instructions = edit_instructions - - # Add a button to apply edits - if st.button("Apply Edits", key="apply_edits_current"): - if not edit_instructions: - st.warning("Please provide edit instructions.") - else: - # Set editing flag - st.session_state.editing_thumbnail = True - st.session_state.show_edit_form = True - - # Rerun to update the UI - st.rerun() - - # Add analysis button - if st.button("Analyze Thumbnail", key="analyze_current"): - logger.info("User clicked Analyze Thumbnail button") - analysis = analyze_thumbnail(st.session_state.current_thumbnail_path) - st.subheader("Thumbnail Analysis") - st.markdown(analysis) - - # Handle the editing process - if st.session_state.editing_thumbnail and st.session_state.show_edit_form: - st.subheader("Editing Thumbnail") - - # Show a spinner while editing - with st.spinner("Editing thumbnail..."): - logger.info(f"User provided edit instructions: '{st.session_state.edit_instructions}'") - # Edit the thumbnail image - edited_img_path = edit_thumbnail_image(st.session_state.current_thumbnail_path, st.session_state.edit_instructions) - - if edited_img_path: - # Update the current thumbnail path in session state - st.session_state.edited_thumbnail_path = edited_img_path - logger.info(f"Updated current thumbnail path in session state: {edited_img_path}") - - # Reset editing flags - st.session_state.editing_thumbnail = False - st.session_state.show_edit_form = False - - # Display the edited image - st.subheader("Edited Thumbnail") - st.image(edited_img_path, use_container_width=True) - - # Add download button for the edited image - with open(edited_img_path, "rb") as file: - st.download_button( - label="Download Edited Thumbnail", - data=file, - file_name=f"youtube_thumbnail_edited_{int(time.time())}.png", - mime="image/png" - ) - - # Update the current thumbnail path to the edited one - st.session_state.current_thumbnail_path = edited_img_path - - # Add a button to continue editing - if st.button("Continue Editing"): - st.session_state.show_edit_form = True - st.rerun() - else: - # Reset editing flags - st.session_state.editing_thumbnail = False - st.session_state.show_edit_form = False - - st.error("Failed to edit the thumbnail. Please try again with different instructions.") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/modules/title_generator.py b/ToBeMigrated/ai_writers/youtube_writers/modules/title_generator.py deleted file mode 100644 index 7f56f18d..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/modules/title_generator.py +++ /dev/null @@ -1,452 +0,0 @@ -""" -YouTube Title Generator Module - -This module provides functionality for generating YouTube video titles. -""" - -import streamlit as st -import time -import logging -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger('youtube_title_generator') - - -def analyze_title(title): - """Analyze a YouTube title for SEO and clickbait.""" - logger.info(f"Analyzing title: '{title}'") - - # Character count - char_count = len(title) - optimal_length = 50 <= char_count <= 60 - logger.info(f"Character count: {char_count}, Optimal length: {optimal_length}") - - # Clickbait detection. TBD: Use AI to detect clickbait. - clickbait_phrases = [ - "shocking", "you won't believe", "gone wrong", "gone sexual", - "free v-bucks", "free robux", "100%", "gone viral", "viral", - "you need to see this", "wait till the end", "at 3am", "3am", - "don't watch this", "watch till the end", "gone too far", - "insane", "unbelievable", "mind-blowing", "life-changing", - "secret", "hidden", "revealed", "exposed", "leaked", - "never before seen", "first time ever", "world's first", - "no one knows", "experts hate this", "doctors hate this", - "this will change your life", "this will blow your mind", - "you've been doing it wrong", "the truth about", "the real reason", - "what they don't want you to know", "what they're hiding", - "what they don't tell you", "what you need to know", - "what you should know", "what you must know", "what you must see", - "what you must watch", "what you must do", "what you must have", - "what you must buy", "what you must try", "what you must avoid", - "what you must stop doing", "what you must start doing", - "what you must change", "what you must learn", "what you must understand", - "what you must realize", "what you must accept", "what you must believe", - "what you must know about", "what you must see about", "what you must watch about", - "what you must do about", "what you must have about", "what you must buy about", - "what you must try about", "what you must avoid about", "what you must stop doing about", - "what you must start doing about", "what you must change about", "what you must learn about", - "what you must understand about", "what you must realize about", "what you must accept about", - "what you must believe about", "what you must know about", "what you must see about", - "what you must watch about", "what you must do about", "what you must have about", - "what you must buy about", "what you must try about", "what you must avoid about", - "what you must stop doing about", "what you must start doing about", "what you must change about", - "what you must learn about", "what you must understand about", "what you must realize about", - "what you must accept about", "what you must believe about" - ] - - clickbait_score = 0 - detected_phrases = [] - for phrase in clickbait_phrases: - if phrase.lower() in title.lower(): - clickbait_score += 1 - detected_phrases.append(phrase) - - is_clickbait = clickbait_score > 0 - logger.info(f"Clickbait detection: score={clickbait_score}, is_clickbait={is_clickbait}") - if detected_phrases: - logger.info(f"Detected clickbait phrases: {', '.join(detected_phrases)}") - - # SEO elements - has_number = any(char.isdigit() for char in title) - has_question = "?" in title - has_colon = ":" in title - has_brackets = "[" in title or "]" in title or "(" in title or ")" in title - - logger.info(f"SEO elements: has_number={has_number}, has_question={has_question}, has_colon={has_colon}, has_brackets={has_brackets}") - - # Calculate SEO score - seo_score = 0 - if optimal_length: - seo_score += 3 - if has_number: - seo_score += 1 - if has_question: - seo_score += 1 - if has_colon: - seo_score += 1 - if has_brackets: - seo_score += 1 - if not is_clickbait: - seo_score += 2 - - logger.info(f"Final SEO score: {seo_score}/10") - - return { - "char_count": char_count, - "optimal_length": optimal_length, - "is_clickbait": is_clickbait, - "clickbait_score": clickbait_score, - "seo_score": seo_score, - "has_number": has_number, - "has_question": has_question, - "has_colon": has_colon, - "has_brackets": has_brackets - } - - -def generate_youtube_title(target_audience, main_points, tone_style, use_case, num_titles=5, progress_bar=None): - """ Generate youtube title generator """ - logger.info(f"Starting title generation with parameters: target_audience='{target_audience}', main_points='{main_points}', tone_style='{tone_style}', use_case='{use_case}', num_titles={num_titles}") - - # Create a custom system prompt that doesn't include blog-specific instructions - system_prompt = """You are a YouTube title expert specializing in creating engaging, clickable video titles. - Your task is to generate YouTube video titles based on the provided information. - Focus ONLY on creating titles that are optimized for YouTube. - Return ONLY the titles, one per line, without any numbering or additional text.""" - - prompt = f""" - **Instructions:** - - Please generate {num_titles} YouTube title options for a video about **{main_points}** based on the following information: - - - **Target Audience:** {target_audience} - - **Tone and Style:** {tone_style} - - **Use Case:** {use_case} - - **Specific Instructions:** - - * Make the titles catchy and attention-grabbing. - * Use relevant keywords to improve SEO. - * Tailor the language and tone to the target audience. - * Ensure the title reflects the content and use case of the video. - * Return ONLY the titles, one per line, without any numbering or additional text. - """ - - logger.info("Generated prompt for title generation") - logger.debug(f"Prompt: {prompt}") - logger.debug(f"System prompt: {system_prompt}") - - try: - # Update progress bar if provided - if progress_bar: - progress_bar.progress(30) - progress_bar.text("Analyzing your content and target audience...") - logger.info("Progress bar updated: 30% - Analyzing content and target audience") - - # Simulate some processing time to show progress - time.sleep(1) - - if progress_bar: - progress_bar.progress(60) - progress_bar.text("Generating creative title options...") - logger.info("Progress bar updated: 60% - Generating creative title options") - - # Get the response from the language model with custom system prompt - logger.info("Calling LLM for title generation with custom system prompt") - start_time = time.time() - response = llm_text_gen(prompt, system_prompt=system_prompt) - end_time = time.time() - logger.info(f"LLM response received in {end_time - start_time:.2f} seconds") - logger.debug(f"Raw LLM response: {response}") - - if progress_bar: - progress_bar.progress(90) - progress_bar.text("Processing and formatting titles...") - logger.info("Progress bar updated: 90% - Processing and formatting titles") - - # Split the response into individual titles - titles = [title.strip() for title in response.split('\n') if title.strip()] - logger.info(f"Generated {len(titles)} titles") - for i, title in enumerate(titles, 1): - logger.info(f"Title {i}: '{title}'") - - if progress_bar: - progress_bar.progress(100) - progress_bar.text("Titles generated successfully!") - logger.info("Progress bar updated: 100% - Titles generated successfully") - - return titles - except Exception as err: - logger.error(f"Error generating titles: {err}", exc_info=True) - if progress_bar: - progress_bar.progress(100) - progress_bar.text("Error generating titles. Please try again.") - logger.info("Progress bar updated: 100% - Error generating titles") - st.error(f"Error: Failed to get response from LLM: {err}") - return None - - -def write_yt_title(): - """Create a user interface for YouTube Title Generator.""" - logger.info("Initializing YouTube Title Generator UI") - st.write("Generate engaging YouTube video titles that drive clicks and views.") - - # Initialize session state for generated titles if it doesn't exist - if "generated_titles" not in st.session_state: - st.session_state.generated_titles = None - - # Main points input (full width) - main_points = st.text_area("Main Points/Keywords (comma-separated)", - placeholder="e.g., cooking tips, healthy recipes, quick meals") - - # Create columns for the other inputs - col1, col2, col3, col4 = st.columns(4) - - with col1: - tone_style = st.selectbox("Tone/Style", - ["Professional", "Casual", "Humorous", "Educational", "Entertaining", "Inspirational"]) - - with col2: - target_audience = st.text_input("Target Audience", - placeholder="e.g., beginners, professionals, parents") - - with col3: - use_case = st.selectbox("Use Case", - ["How-to/Tutorial", "Vlog", "Review", "Educational", "Entertainment", "News"]) - - with col4: - num_titles = st.number_input("Number of Titles", - min_value=1, - max_value=20, - value=5, - step=1) - - if st.button("Generate Titles"): - logger.info("Generate Titles button clicked") - logger.info(f"User inputs: main_points='{main_points}', tone_style='{tone_style}', target_audience='{target_audience}', use_case='{use_case}', num_titles={num_titles}") - - if not main_points: - logger.warning("No main points provided") - st.error("Please enter main points/keywords.") - return - - # Create a progress bar - progress_bar = st.progress(0) - progress_bar.text("Initializing title generation...") - logger.info("Created progress bar for title generation") - - # Generate titles with progress updates - logger.info("Calling generate_youtube_title function") - titles = generate_youtube_title(main_points, tone_style, target_audience, use_case, num_titles, progress_bar) - - # Clear the progress bar after a short delay - time.sleep(1) - progress_bar.empty() - logger.info("Cleared progress bar") - - if titles: - logger.info(f"Successfully generated {len(titles)} titles") - - # Store titles in session state for persistence - st.session_state.generated_titles = titles - - # Display titles section - st.markdown(""" -
-

Generated YouTube Titles

-

Click on a title to see detailed analysis and copy options

-
- """, unsafe_allow_html=True) - - # Display titles with analysis - for i, title in enumerate(titles, 1): - logger.info(f"Analyzing title {i}: '{title}'") - - # Create a more visually appealing expander - with st.expander(f"Title {i}: {title}", expanded=False): - # Add a divider for better visual separation - st.markdown("---") - - # Title display with better formatting - st.markdown(f""" -
-

{title}

-
- """, unsafe_allow_html=True) - - # Analysis section - st.markdown("### Analysis") - analysis = analyze_title(title) - - # Create columns for analysis metrics - col1, col2 = st.columns(2) - - with col1: - # Character count - st.markdown("#### Character Count") - st.write(f"**{analysis['char_count']}** characters") - if analysis['optimal_length']: - st.success("โœ… Optimal length (50-60 characters)") - else: - st.warning("โš ๏ธ Not optimal length (should be 50-60 characters)") - - # Clickbait detection - st.markdown("#### Clickbait Detection") - if analysis['is_clickbait']: - st.error(f"โš ๏ธ Possible clickbait detected (score: {analysis['clickbait_score']})") - else: - st.success("โœ… No clickbait detected") - - with col2: - # SEO score - st.markdown("#### SEO Score") - score_color = "#28a745" if analysis['seo_score'] >= 7 else "#ffc107" if analysis['seo_score'] >= 5 else "#dc3545" - st.markdown(f"

{analysis['seo_score']}/10

", unsafe_allow_html=True) - if analysis['seo_score'] >= 7: - st.success("โœ… Good SEO score") - elif analysis['seo_score'] >= 5: - st.warning("โš ๏ธ Moderate SEO score") - else: - st.error("โŒ Low SEO score") - - # SEO elements - st.markdown("#### SEO Elements") - elements = [] - if analysis['has_number']: - elements.append("โœ… Contains numbers") - if analysis['has_question']: - elements.append("โœ… Contains question mark") - if analysis['has_colon']: - elements.append("โœ… Contains colon") - if analysis['has_brackets']: - elements.append("โœ… Contains brackets/parentheses") - - for element in elements: - st.write(element) - - # Copy functionality using session state - st.markdown("### Copy Title") - st.code(title, language="text") - - # Use a different approach for copy functionality - copy_key = f"copy_{i}" - if st.button(f"Copy Title {i}", key=copy_key): - # Use JavaScript to copy to clipboard - escaped_title = title.replace('"', '\\"') - st.markdown( - f""" - - """, - unsafe_allow_html=True - ) - st.success(f"โœ… Title {i} copied to clipboard!") - else: - logger.error("Failed to generate titles") - st.error("Failed to generate titles. Please try again.") - - # Display previously generated titles if they exist in session state - elif st.session_state.generated_titles: - titles = st.session_state.generated_titles - - # Display titles section - st.markdown(""" -
-

Generated YouTube Titles

-

Click on a title to see detailed analysis and copy options

-
- """, unsafe_allow_html=True) - - # Display titles with analysis - for i, title in enumerate(titles, 1): - logger.info(f"Analyzing title {i}: '{title}'") - - # Create a more visually appealing expander - with st.expander(f"Title {i}: {title}", expanded=False): - # Add a divider for better visual separation - st.markdown("---") - - # Title display with better formatting - st.markdown(f""" -
-

{title}

-
- """, unsafe_allow_html=True) - - # Analysis section - st.markdown("### Analysis") - analysis = analyze_title(title) - - # Create columns for analysis metrics - col1, col2 = st.columns(2) - - with col1: - # Character count - st.markdown("#### Character Count") - st.write(f"**{analysis['char_count']}** characters") - if analysis['optimal_length']: - st.success("โœ… Optimal length (50-60 characters)") - else: - st.warning("โš ๏ธ Not optimal length (should be 50-60 characters)") - - # Clickbait detection - st.markdown("#### Clickbait Detection") - if analysis['is_clickbait']: - st.error(f"โš ๏ธ Possible clickbait detected (score: {analysis['clickbait_score']})") - else: - st.success("โœ… No clickbait detected") - - with col2: - # SEO score - st.markdown("#### SEO Score") - score_color = "#28a745" if analysis['seo_score'] >= 7 else "#ffc107" if analysis['seo_score'] >= 5 else "#dc3545" - st.markdown(f"

{analysis['seo_score']}/10

", unsafe_allow_html=True) - if analysis['seo_score'] >= 7: - st.success("โœ… Good SEO score") - elif analysis['seo_score'] >= 5: - st.warning("โš ๏ธ Moderate SEO score") - else: - st.error("โŒ Low SEO score") - - # SEO elements - st.markdown("#### SEO Elements") - elements = [] - if analysis['has_number']: - elements.append("โœ… Contains numbers") - if analysis['has_question']: - elements.append("โœ… Contains question mark") - if analysis['has_colon']: - elements.append("โœ… Contains colon") - if analysis['has_brackets']: - elements.append("โœ… Contains brackets/parentheses") - - for element in elements: - st.write(element) - - # Copy functionality using session state - st.markdown("### Copy Title") - st.code(title, language="text") - - # Use a different approach for copy functionality - copy_key = f"copy_{i}" - if st.button(f"Copy Title {i}", key=copy_key): - # Use JavaScript to copy to clipboard - escaped_title = title.replace('"', '\\"') - st.markdown( - f""" - - """, - unsafe_allow_html=True - ) - st.success(f"โœ… Title {i} copied to clipboard!") \ No newline at end of file diff --git a/ToBeMigrated/ai_writers/youtube_writers/youtube_ai_writer.py b/ToBeMigrated/ai_writers/youtube_writers/youtube_ai_writer.py deleted file mode 100644 index 569c4bec..00000000 --- a/ToBeMigrated/ai_writers/youtube_writers/youtube_ai_writer.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -YouTube AI Writer - -This module provides a comprehensive suite of tools for generating YouTube content. -""" - -import streamlit as st -import importlib -import sys -import os -from pathlib import Path -from .modules.title_generator import write_yt_title -from .modules.description_generator import write_yt_description -from .modules.script_generator import write_yt_script -from .modules.thumbnail_generator import write_yt_thumbnail -from .modules.end_screen_generator import write_yt_end_screen -from .modules.tags_generator import write_yt_tags -from .modules.shorts_script_generator import write_yt_shorts -from .modules.community_post_generator import write_yt_community_post -from .modules.shorts_video_generator import write_yt_shorts_video -from .modules.channel_trailer_generator import write_yt_channel_trailer - - -def youtube_main_menu(): - """Main function for the YouTube AI Writer.""" - - # Initialize session state for selected tool if it doesn't exist - if "selected_tool" not in st.session_state: - st.session_state.selected_tool = None - - # Define the YouTube tools with their details - youtube_tools = [ - # Content Creation Tools - { - "name": "YT Title Generator", - "icon": "๐Ÿ“", - "description": "Create engaging YouTube video titles that drive clicks and views.", - "color": "#FF0000", # YouTube red - "category": "Content Creation", - "function": write_yt_title, - "status": "active" - }, - { - "name": "YT Description Generator", - "icon": "๐Ÿ“„", - "description": "Generate SEO-optimized descriptions for your YouTube videos.", - "color": "#FF0000", # YouTube red - "category": "Content Creation", - "function": write_yt_description, - "status": "active" - }, - { - "name": "YT Script Generator", - "icon": "๐ŸŽฌ", - "description": "Create professional YouTube scripts with optimized structures for engagement.", - "color": "#FF0000", # YouTube red - "category": "Content Creation", - "function": write_yt_script, - "status": "active" - }, - { - "name": "YT Shorts Script Generator", - "icon": "๐Ÿ“ฑ", - "description": "Create engaging scripts optimized for YouTube Shorts format with vertical framing and hooks.", - "color": "#FF0000", # YouTube red - "category": "Content Creation", - "function": write_yt_shorts, - "status": "active" - }, - { - "name": "YT Shorts Video Generator", - "icon": "๐ŸŽฅ", - "description": "Generate complete YouTube Shorts videos with AI-generated images, narration, and music.", - "color": "#FF0000", # YouTube red - "category": "Content Creation", - "function": write_yt_shorts_video, - "status": "active" - }, - { - "name": "Channel Trailer Generator", - "icon": "๐ŸŽฅ", - "description": "Create compelling channel trailers that convert visitors into subscribers.", - "color": "#FF0000", # YouTube red - "category": "Content Creation", - "function": write_yt_channel_trailer, - "status": "active" - }, - - # Optimization Tools - { - "name": "Thumbnail Generator", - "icon": "๐ŸŽจ", - "description": "Create engaging thumbnail ideas and descriptions with color scheme suggestions based on your brand.", - "color": "#FF0000", # YouTube red - "category": "Optimization", - "function": write_yt_thumbnail, - "status": "active" - }, - { - "name": "YouTube Tags Generator", - "icon": "๐Ÿท๏ธ", - "description": "Generate optimized tags for your videos with trending tag suggestions to improve discoverability.", - "color": "#FF0000", # YouTube red - "category": "Optimization", - "function": write_yt_tags, - "status": "active" - }, - - # Engagement Tools - { - "name": "End Screen Generator", - "icon": "๐ŸŽฌ", - "description": "Create effective end screen content and CTAs with template suggestions based on video type.", - "color": "#FF0000", # YouTube red - "category": "Engagement", - "function": write_yt_end_screen, - "status": "active" - }, - { - "name": "Community Post Generator", - "icon": "๐Ÿ’ฌ", - "description": "Generate engaging community posts with AI-powered content suggestions and timing optimization.", - "color": "#FF0000", # YouTube red - "category": "Engagement", - "function": write_yt_community_post, - "status": "active" - }, - { - "name": "Playlist Description Generator", - "icon": "๐Ÿ“š", - "description": "Generate SEO-optimized descriptions for your playlists with organization suggestions.", - "color": "#CC0000", # Darker red for coming soon - "category": "Engagement", - "function": None, - "status": "coming_soon" - }, - - # Future Tools - { - "name": "Analytics Insights", - "icon": "๐Ÿ“Š", - "description": "Get AI-powered insights and recommendations based on your channel analytics.", - "color": "#990000", # Even darker red for future - "category": "Future Tools", - "function": None, - "status": "future" - }, - { - "name": "Video Series Planner", - "icon": "๐Ÿ“…", - "description": "Plan and organize your video series with content calendars and topic ideas.", - "color": "#990000", # Even darker red for future - "category": "Future Tools", - "function": None, - "status": "future" - } - ] - - # Create a container for the dashboard - dashboard_container = st.container() - - # Create a container for the tool input section - tool_container = st.container() - - # If a tool is selected, show its input section - if st.session_state.selected_tool is not None: - with tool_container: - # Display the selected tool's input section - st.markdown("---") - st.markdown(f"# {st.session_state.selected_tool['icon']} {st.session_state.selected_tool['name']}") - - # Add a back button - if st.button("โ† Back to Dashboard", key="back_to_dashboard"): - # Clear the selected tool from session state - st.session_state.selected_tool = None - st.rerun() - - # Call the function for the selected tool - if st.session_state.selected_tool["function"]: - # Directly call the function instead of using it as a reference - st.session_state.selected_tool["function"]() - else: - # Display coming soon or future tool information - st.info(f"**{st.session_state.selected_tool['status'].replace('_', ' ').title()}!**") - st.write(st.session_state.selected_tool["description"]) - st.image(f"https://via.placeholder.com/600x300?text={st.session_state.selected_tool['name']}+Coming+Soon", use_column_width=True) - else: - with dashboard_container: - # Display the dashboard - # Header - st.markdown(""" -
-

๐ŸŽฅ YouTube AI Writer

-

Generate professional YouTube content with ALwrity's AI-powered tools

-
- """, unsafe_allow_html=True) - - # Group tools by category - categories = {} - for tool in youtube_tools: - category = tool["category"] - if category not in categories: - categories[category] = [] - categories[category].append(tool) - - # Display tools by category - for category, tools in categories.items(): - st.markdown(f"## {category}") - - # Create a 3-column layout for the tool cards - cols = st.columns(3) - - # Display the tool cards - for i, tool in enumerate(tools): - # Determine which column to use - col = cols[i % 3] - - with col: - # Create a card for each tool - status_badge = "" - if tool["status"] == "coming_soon": - status_badge = "Coming Soon" - elif tool["status"] == "future": - status_badge = "Future" - - st.markdown(f""" -
-

{tool["icon"]} {tool["name"]} {status_badge}

-

{tool["description"]}

-
- """, unsafe_allow_html=True) - - # Add a button to access the tool - if st.button(f"Use {tool['name']}", key=f"btn_{tool['name']}"): - # Store the selected tool in session state - st.session_state.selected_tool = tool - st.rerun() \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/README.md b/ToBeMigrated/alwrity_ui/alwrity_researcher/README.md deleted file mode 100644 index 00e3bf85..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# AI Web Researcher Dashboard for Content Creators - -## Overview - -The AI Web Researcher Dashboard is a comprehensive suite of research tools designed specifically for content creators. This dashboard integrates various web research modules to streamline the content research process, enhance content quality, and improve workflow efficiency. - -## Available Research Modules - -### 1. Search Engine Research Tools - -#### Google SERP Search -- **Functionality**: Retrieves organic search results, People Also Ask questions, and related searches -- **Best for**: Initial topic research, understanding search intent, and identifying content gaps -- **Key features**: Comprehensive search results with structured data extraction - -#### Tavily AI Search -- **Functionality**: Advanced AI-powered search with semantic understanding -- **Best for**: In-depth research requiring contextual understanding -- **Key features**: Provides direct answers to questions and follow-up question suggestions - -### 2. Neural Search Tools - -#### Metaphor Neural Search -- **Functionality**: Semantic search technology for finding related content -- **Best for**: Discovering content based on conceptual similarity rather than keyword matching -- **Key features**: Find similar articles, competitor analysis, and content inspiration - -### 3. Trend Analysis Tools - -#### Google Trends Researcher -- **Functionality**: Analyzes search term popularity over time -- **Best for**: Content planning, seasonal topic identification, and trend forecasting -- **Key features**: Interest over time visualization, regional interest mapping, and related query analysis - -### 4. Web Crawling & Analysis Tools - -#### Async Web Crawler -- **Functionality**: Crawls websites to extract structured content -- **Best for**: Content auditing, competitor analysis, and data extraction -- **Key features**: Extracts titles, descriptions, main content, headings, links, and images - -#### Firecrawl Web Crawler -- **Functionality**: Specialized crawler for single-page or website scraping -- **Best for**: Detailed content extraction from specific URLs -- **Key features**: Configurable depth and page limits for targeted crawling - -#### Website Analyzer -- **Functionality**: Comprehensive website analysis for content and technical aspects -- **Best for**: Content quality assessment, SEO analysis, and technical audits -- **Key features**: Content quality metrics, SEO recommendations, and technical performance insights - -## Optimal Workflows for Content Creators - -### Research Workflow 1: Comprehensive Topic Research - -1. **Initial Exploration** (Google SERP Search) - - Search for your main topic to understand the search landscape - - Analyze People Also Ask questions to identify user intent - -2. **In-depth Research** (Tavily AI Search) - - Use identified subtopics for deeper research - - Collect factual information and expert insights - -3. **Competitive Analysis** (Metaphor Neural Search + Web Crawler) - - Find similar content from competitors - - Analyze content structure and approach - -4. **Trend Validation** (Google Trends Researcher) - - Verify topic popularity and seasonal patterns - - Identify related trending topics - -### Research Workflow 2: Content Gap Analysis - -1. **Competitor Content Mapping** (Async Web Crawler) - - Crawl competitor websites to extract content structure - - Identify their content categories and topics - -2. **Topic Opportunity Discovery** (Google Trends + Metaphor Search) - - Research trending topics in your niche - - Find content areas with high interest but low competition - -3. **Content Quality Benchmarking** (Website Analyzer) - - Analyze top-performing content in your niche - - Identify quality benchmarks and content standards - -### Research Workflow 3: Content Refresh & Update - -1. **Content Performance Analysis** (Website Analyzer) - - Analyze existing content for improvement opportunities - - Identify outdated information and gaps - -2. **Current Trend Integration** (Google Trends Researcher) - - Research current trends related to your content - - Identify new angles and perspectives - -3. **Competitive Edge Research** (Tavily AI + Metaphor Search) - - Research what competitors have updated recently - - Find new research, statistics, and information to incorporate - -## Integration with Content Creation Process - -### Pre-Writing Phase -- Use research modules to gather comprehensive information -- Create content briefs based on research findings -- Identify key points, statistics, and examples to include - -### Writing Phase -- Reference research findings for accurate information -- Use competitor insights to differentiate your content -- Incorporate trending topics and keywords - -### Post-Publishing Phase -- Monitor content performance against researched benchmarks -- Update content based on new research findings -- Plan related content based on research insights - -## Dashboard Usage Examples - -### Example 1: Creating a Comprehensive Blog Post - -1. Start with Google SERP Search for your main topic -2. Use Tavily AI Search to gather in-depth information on subtopics -3. Analyze competitor content with Metaphor Neural Search -4. Check topic seasonality with Google Trends Researcher -5. Create content that addresses all aspects discovered in research - -### Example 2: Developing a Content Strategy - -1. Use Google Trends to identify trending topics in your niche -2. Analyze competitor websites with Async Web Crawler -3. Research content gaps using Metaphor Neural Search -4. Prioritize content topics based on trend data and competition -5. Create a content calendar incorporating research insights - -### Example 3: Updating Existing Content - -1. Analyze current content with Website Analyzer -2. Research new developments with Tavily AI Search -3. Check for changing trends with Google Trends Researcher -4. Identify new angles with Metaphor Neural Search -5. Update content with new information and perspectives - -## Best Practices for Effective Research - -1. **Start Broad, Then Narrow**: Begin with general search tools before using specialized ones -2. **Combine Multiple Research Methods**: Use different modules for comprehensive insights -3. **Focus on User Intent**: Prioritize research that reveals what your audience wants -4. **Validate with Data**: Use trend analysis to validate topic importance -5. **Organize Research Findings**: Create structured notes from your research -6. **Set Research Goals**: Define what you need to learn before starting -7. **Schedule Regular Research**: Keep content updated with periodic research - -## Technical Requirements - -- API keys for various services (Google, Tavily, Metaphor, etc.) -- Proper configuration in the .env file -- Sufficient system resources for web crawling operations - -## Future Enhancements - -- Integration with content planning calendar -- Automated research reports -- Competitive content gap analysis -- AI-powered content brief generation -- Customizable research workflows - ---- - -This dashboard is designed to streamline the research process for content creators, providing powerful tools to enhance content quality, relevance, and performance. By following the suggested workflows and best practices, content creators can significantly improve their research efficiency and content outcomes. \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/README_DASHBOARD.md b/ToBeMigrated/alwrity_ui/alwrity_researcher/README_DASHBOARD.md deleted file mode 100644 index a0a8f09d..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/README_DASHBOARD.md +++ /dev/null @@ -1,98 +0,0 @@ -# AI Web Researcher Dashboard - -## Overview - -The AI Web Researcher Dashboard is a modern, intuitive interface designed specifically for content creators and digital marketing professionals. This dashboard integrates various web research tools to streamline the content research process, enhance content quality, and improve workflow efficiency. - -## Features - -### 1. Intuitive User Interface -- Modern, colorful, and professional design -- Easy navigation through different research tools -- Responsive layout that works on various screen sizes - -### 2. Integrated Research Tools -- **Search Engine Research**: Google SERP Search and Tavily AI Search -- **Neural Search**: Metaphor Neural Search for finding conceptually similar content -- **Trend Analysis**: Google Trends Researcher for analyzing search term popularity -- **Web Crawling & Analysis**: Tools for extracting and analyzing web content - -### 3. Guided Research Workflows -- Comprehensive Topic Research workflow -- Content Gap Analysis workflow -- Content Refresh & Update workflow - -## Installation - -1. Ensure you have Python 3.8+ installed -2. Install the required dependencies: - ``` - pip install -r requirements.txt - ``` -3. Set up the necessary API keys in your environment variables or .env file - -## Usage - -1. Navigate to the dashboard directory: - ``` - cd AI-Writer/lib/alwrity_ui/alwrity_researcher - ``` - -2. Run the dashboard: - ``` - python -m streamlit run main.py - ``` - -3. Access the dashboard in your web browser at http://localhost:8501 - -## Dashboard Sections - -### Dashboard Home -Provides an overview of available research tools and featured workflows. - -### Search Tools -Access to Google SERP Search and Tavily AI Search for comprehensive search capabilities. - -### Neural Search -Use Metaphor Neural Search to find content based on conceptual similarity rather than keyword matching. - -### Trend Analysis -Analyze search term popularity over time using Google Trends Researcher. - -### Web Crawling -Extract structured content from websites using various web crawling tools. - -### Research Workflows -Guided workflows that combine multiple tools for specific research objectives. - -## Current Implementation Status - -This is the initial implementation of the dashboard with placeholder functionality. The UI is fully implemented, but the actual integration with the AI web research modules will be completed in the next phase. - -### What's Implemented -- Complete user interface with all sections and forms -- Mock data visualization for demonstration purposes -- Placeholder functionality for all research tools - -### Next Steps -- Integrate with actual AI web research modules -- Implement data processing and visualization with real data -- Add user authentication and result saving functionality - -## Technical Details - -- Built with Streamlit for rapid UI development -- Modular design for easy extension and maintenance -- Responsive layout that works on desktop and mobile devices - -## File Structure - -- `main.py`: Entry point for the application -- `dashboard.py`: Main dashboard implementation -- `utils.py`: Utility functions for data processing and visualization -- `style.css`: Custom CSS for styling the dashboard -- `requirements.txt`: Required Python packages - -## Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/config.py b/ToBeMigrated/alwrity_ui/alwrity_researcher/config.py deleted file mode 100644 index 09ad327a..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/config.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import streamlit as st -from dotenv import load_dotenv - -# Load environment variables from .env file -load_dotenv() - -def get_api_key(key_name): - """Get API key from environment variables or Streamlit secrets""" - # Try to get from environment variables first - api_key = os.getenv(key_name) - - # If not found in environment, try Streamlit secrets - if not api_key and key_name in st.secrets: - api_key = st.secrets[key_name] - - return api_key - -def check_api_configuration(): - """Check if all required API keys are configured""" - api_status = { - "SERPER_API_KEY": bool(get_api_key("SERPER_API_KEY")), - "TAVILY_API_KEY": bool(get_api_key("TAVILY_API_KEY")), - "METAPHOR_API_KEY": bool(get_api_key("METAPHOR_API_KEY")), - "FIRECRAWL_API_KEY": bool(get_api_key("FIRECRAWL_API_KEY")) - } - - return api_status - -def display_api_configuration_status(): - """Display API configuration status in the sidebar with improved styling""" - api_status = check_api_configuration() - - st.sidebar.markdown("
", unsafe_allow_html=True) - st.sidebar.markdown("
API Configuration Status
", unsafe_allow_html=True) - - # Display API status with improved styling - for api_name, is_configured in api_status.items(): - if is_configured: - st.sidebar.markdown( - f"
โœ… {api_name}
", - unsafe_allow_html=True - ) - else: - st.sidebar.markdown( - f"
โŒ {api_name}
", - unsafe_allow_html=True - ) - - # Display instructions if any API key is missing with improved styling - if not all(api_status.values()): - with st.sidebar.expander("How to configure API keys"): - st.markdown(""" -
-

To configure missing API keys, create a .env file in the project root with the following format:

-
-SERPER_API_KEY=your_serper_api_key
-TAVILY_API_KEY=your_tavily_api_key
-METAPHOR_API_KEY=your_metaphor_api_key
-FIRECRAWL_API_KEY=your_firecrawl_api_key
-                
-

Alternatively, you can set these as environment variables in your system.

-
- """, unsafe_allow_html=True) - - st.sidebar.markdown("
", unsafe_allow_html=True) \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/dashboard.py b/ToBeMigrated/alwrity_ui/alwrity_researcher/dashboard.py deleted file mode 100644 index 5f2907e3..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/dashboard.py +++ /dev/null @@ -1,729 +0,0 @@ -import streamlit as st -import sys -import os -from pathlib import Path - -# Add parent directory to path to import modules -sys.path.append(str(Path(__file__).parent.parent.parent)) - -# Import utils module -from utils import ( - load_css, - display_google_serp_results, - display_tavily_results, - display_metaphor_results, - display_google_trends_results, - display_crawler_results, - display_analyzer_results -) - -# Configure Streamlit page settings -st.set_page_config( - page_title="AI Web Researcher Dashboard", - page_icon="๐Ÿ”", - layout="wide", - initial_sidebar_state="expanded" -) - -# Apply custom CSS immediately -st.markdown(""" - -""", unsafe_allow_html=True) - -# CSS is now loaded at the top of the file - -# Initialize session state variables -def init_session_state(): - if 'current_section' not in st.session_state: - st.session_state['current_section'] = 'Dashboard Home' - -# Initialize session state at startup -init_session_state() - -def main(): - # Initialize session state before accessing it - init_session_state() - - # Main navigation header - st.markdown('', unsafe_allow_html=True) - - # Create tabs for navigation - selected_section = st.tabs([ - "๐Ÿ  Dashboard Home", - "๐Ÿ” Search Tools", - "๐Ÿง  Neural Search", - "๐Ÿ“ˆ Trend Analysis", - "๐Ÿ•ธ๏ธ Web Crawling", - "๐Ÿ“š Academic Research", - "๐Ÿ“‹ Research Workflows" - ]) - - # Display appropriate section based on selected tab - with selected_section[0]: - display_home() - with selected_section[1]: - display_search_tools() - with selected_section[2]: - display_neural_search() - with selected_section[3]: - display_trend_analysis() - with selected_section[4]: - display_web_crawling() - with selected_section[5]: - display_academic_research() - with selected_section[6]: - display_research_workflows() - - # Ensure CSS is consistently applied - load_css() - -def display_home(): - - # Main header with improved styling - st.markdown('
AI Web Researcher Dashboard
', unsafe_allow_html=True) - - # Introduction with better formatting - st.markdown(""" -
-

Welcome to the AI Web Researcher Dashboard, a comprehensive suite of research tools designed - specifically for content creators and digital marketing professionals. This dashboard integrates - various web research modules to streamline your content research process, enhance content quality, - and improve workflow efficiency.

-
- """, unsafe_allow_html=True) - - # Quick access to tool categories with improved header - st.markdown('
Research Tool Categories
', unsafe_allow_html=True) - - # Use 2 columns with equal width for better layout - col1, col2 = st.columns([1, 1]) - - with col1: - st.markdown(""" -
-
๐Ÿ” Search Engine Research
-
Powerful search tools to understand search intent, identify content gaps, and discover high-performing content in your niche
-
Includes: Google SERP Search โ€ข Tavily AI Search
-
Use cases: Keyword research, competitor analysis, content planning, identifying user questions
-
- SERP Analysis - AI-Powered -
-
- -
-
๐Ÿง  Neural Search
-
Advanced semantic search technology that understands concepts rather than just keywords to find truly relevant content
-
Includes: Metaphor Neural Search
-
Use cases: Finding conceptually similar articles, discovering unique content angles, competitive research
-
- Semantic - Deep Learning -
-
- """, unsafe_allow_html=True) - - with col2: - st.markdown(""" -
-
๐Ÿ“ˆ Trend Analysis
-
Comprehensive trend analysis tools that track search term popularity over time to identify seasonal patterns and emerging topics
-
Includes: Google Trends Researcher
-
Use cases: Seasonal content planning, topic validation, identifying rising trends, content calendar optimization
-
- Time Series - Forecasting -
-
- -
-
๐Ÿ•ธ๏ธ Web Crawling & Analysis
-
Powerful web extraction tools that gather and structure content from websites for in-depth analysis and insights
-
Includes: Async Web Crawler โ€ข Firecrawl Web Crawler โ€ข Website Analyzer
-
Use cases: Content auditing, competitor analysis, data extraction, market research, content aggregation
-
- Content Extraction - Data Analysis -
-
- """, unsafe_allow_html=True) - - # Featured workflow with improved header - st.markdown('
Featured Research Workflow
', unsafe_allow_html=True) - - st.markdown(""" -
-
Comprehensive Topic Research Workflow
-

Follow this proven research workflow to develop comprehensive, data-driven content that resonates with your audience and performs well in search.

-
-
1
-
-
Initial Exploration
-
Use Google SERP Search to understand the search landscape, identify user intent, and discover what content currently ranks well. Analyze People Also Ask questions to identify key user concerns.
-
-
-
-
2
-
-
In-depth Research
-
Use Tavily AI Search for deeper research on identified subtopics. The AI-powered search provides more contextual information and helps uncover expert insights that might be missed in traditional searches.
-
-
-
-
3
-
-
Competitive Analysis
-
Use Metaphor Neural Search to find conceptually similar content from competitors. Analyze their approach, identify content gaps, and discover unique angles that differentiate your content.
-
-
-
-
4
-
-
Trend Validation
-
Use Google Trends Researcher to verify topic popularity, identify seasonal patterns, and discover related trending topics. This ensures your content is timely and aligned with current audience interests.
-
-
-
-
5
-
-
Content Extraction
-
Use Web Crawling tools to extract specific content from top-performing pages for detailed analysis. This helps identify content structure, depth, and formatting approaches that resonate with your audience.
-
-
-
- """, unsafe_allow_html=True) - -def display_search_tools(): - - st.markdown('
Search Engine Research Tools
', unsafe_allow_html=True) - - # Google SERP Search - st.markdown('
Google SERP Search
', unsafe_allow_html=True) - - st.markdown(""" -
-
Google SERP Search
-
Retrieves organic search results, People Also Ask questions, and related searches
-
- Best for: Initial topic research, understanding search intent, and identifying content gaps
- Key features: Comprehensive search results with structured data extraction -
-
- """, unsafe_allow_html=True) - - # Input form for Google SERP Search - with st.form("google_serp_search_form"): - search_query = st.text_input("Enter your search query") - col1, col2 = st.columns(2) - with col1: - num_results = st.slider("Number of results", 5, 30, 10) - with col2: - include_paa = st.checkbox("Include People Also Ask", value=True) - - submitted = st.form_submit_button("Search") - if submitted and search_query: - try: - with st.spinner("Searching Google SERP..."): - # Import the actual module - from ai_web_researcher.google_serp_search import google_search - - # Call the actual implementation - results = google_search(search_query) - - # Display the results - if results: - display_google_serp_results(results) - else: - st.error("No results found. Please try a different query.") - except Exception as e: - st.error(f"Error performing Google SERP search: {str(e)}") - st.info("Please check your API configuration in the .env file.") - st.info("Required API: SERPER_API_KEY for Google SERP Search") - display_google_serp_results(None) - - # Tavily AI Search - st.markdown('
Tavily AI Search
', unsafe_allow_html=True) - - st.markdown(""" -
-
Tavily AI Search
-
Advanced AI-powered search with semantic understanding
-
- Best for: In-depth research requiring contextual understanding
- Key features: Provides direct answers to questions and follow-up question suggestions -
-
- """, unsafe_allow_html=True) - - # Input form for Tavily AI Search - with st.form("tavily_ai_search_form"): - search_query = st.text_input("Enter your search query or question") - search_depth = st.select_slider("Search depth", options=["basic", "medium", "deep"], value="medium") - - submitted = st.form_submit_button("Search with Tavily AI") - if submitted and search_query: - try: - with st.spinner("Searching with Tavily AI..."): - # Import the actual module - from ai_web_researcher.tavily_ai_search import do_tavily_ai_search - - # Call the actual implementation - results = do_tavily_ai_search(search_query, search_depth=search_depth) - - # Display the results - if results: - display_tavily_results(results) - else: - st.error("No results found. Please try a different query.") - except Exception as e: - st.error(f"Error performing Tavily AI search: {str(e)}") - st.info("Please check your API configuration in the .env file.") - st.info("Required API: TAVILY_API_KEY for Tavily AI Search") - display_tavily_results(None) - -def display_neural_search(): - - st.markdown('
Neural Search Tools
', unsafe_allow_html=True) - - # Metaphor Neural Search - st.markdown('
Metaphor Neural Search
', unsafe_allow_html=True) - - st.markdown(""" -
-
Metaphor Neural Search
-
Semantic search technology for finding related content
-
- Best for: Discovering content based on conceptual similarity rather than keyword matching
- Key features: Find similar articles, competitor analysis, and content inspiration -
-
- """, unsafe_allow_html=True) - - # Input form for Metaphor Neural Search - with st.form("metaphor_search_form"): - st.markdown("**Search by keyword or find similar content to a URL**") - search_type = st.radio("Search type", ["Keyword Search", "Similar Content Search"]) - - if search_type == "Keyword Search": - search_query = st.text_input("Enter your search query") - url_input = "" - else: - search_query = "" - url_input = st.text_input("Enter a URL to find similar content") - - num_results = st.slider("Number of results", 3, 20, 5) - - submitted = st.form_submit_button("Search with Metaphor") - if submitted and (search_query or url_input): - try: - with st.spinner("Searching with Metaphor Neural Search..."): - # Import the actual module - from ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles, metaphor_find_similar - - # Call the actual implementation - if search_query: - results = metaphor_search_articles(search_query, num_results=num_results) - else: - results = metaphor_find_similar(url_input, num_results=num_results) - - # Display the results - if results: - display_metaphor_results(results) - else: - st.error("No results found. Please try a different query.") - except Exception as e: - st.error(f"Error performing Metaphor Neural search: {str(e)}") - st.info("Please check your API configuration in the .env file.") - st.info("Required API: METAPHOR_API_KEY for Metaphor Neural Search") - display_metaphor_results(None) - -def display_trend_analysis(): - - st.markdown('
Trend Analysis Tools
', unsafe_allow_html=True) - - # Google Trends Researcher - st.markdown('
Google Trends Researcher
', unsafe_allow_html=True) - - st.markdown(""" -
-
Google Trends Researcher
-
Analyze search term popularity and related queries
-
- Best for: Content planning, trend forecasting, and seasonal content optimization
- Key features: Interest over time charts, related queries, and regional interest data -
-
- """, unsafe_allow_html=True) - - # Input form for Google Trends Researcher - with st.form("google_trends_form"): - search_terms = st.text_area("Enter search terms (one per line)") - col1, col2 = st.columns(2) - with col1: - time_frame = st.select_slider("Time range", options=["past_hour", "past_day", "past_week", "past_month", "past_90_days", "past_12_months", "past_5_years"], value="past_12_months") - with col2: - geo = st.text_input("Geographic region (ISO country code, e.g., 'US')", value="US") - - submitted = st.form_submit_button("Analyze Trends") - if submitted and search_terms: - try: - with st.spinner("Analyzing Google Trends..."): - # Import the actual module - from ai_web_researcher.google_trends_researcher import do_google_trends_analysis - - # Call the actual implementation - search_terms_list = [term.strip() for term in search_terms.split('\n') if term.strip()] - results = do_google_trends_analysis(search_terms_list, time_frame=time_frame, geo=geo) - - # Display the results - if results: - display_google_trends_results(results) - else: - st.error("No trend data found. Please try different search terms.") - except Exception as e: - st.error(f"Error analyzing Google Trends: {str(e)}") - st.info("Google Trends analysis doesn't require an API key, but there might be rate limiting or network issues.") - display_google_trends_results(None) - -def display_web_crawling(): - - st.markdown('
Web Crawling & Analysis Tools
', unsafe_allow_html=True) - - # Create tabs for different crawling tools - tab1, tab2, tab3 = st.tabs(["Firecrawl Web Crawler", "Website Analyzer", "Async Web Crawler"]) - - with tab1: - st.markdown(""" -
-
Firecrawl Web Crawler
-
Extract structured content from websites
-
- Best for: Content extraction, competitor analysis, and website auditing
- Key features: Extracts titles, descriptions, headings, and content from web pages -
-
- """, unsafe_allow_html=True) - - # Input form for Firecrawl Web Crawler - with st.form("firecrawl_form"): - website_url = st.text_input("Enter website URL") - depth = st.slider("Crawl depth", 1, 5, 1) - max_pages = st.slider("Maximum pages", 1, 50, 10) - - submitted = st.form_submit_button("Crawl Website") - if submitted and website_url: - try: - with st.spinner("Crawling website..."): - # Import the actual module - from ai_web_researcher.firecrawl_web_crawler import scrape_website - - # Call the actual implementation - results = scrape_website(website_url, depth=depth, max_pages=max_pages) - - # Display the results - if results: - display_crawler_results(results) - else: - st.error("No crawler results found. Please try a different URL.") - except Exception as e: - st.error(f"Error crawling website: {str(e)}") - st.info("Please check your API configuration in the .env file.") - st.info("Required API: FIRECRAWL_API_KEY for Web Crawler") - display_crawler_results(None) - - with tab2: - st.markdown(""" -
-
Website Analyzer
-
Analyze website content and structure
-
- Best for: Content analysis, SEO auditing, and competitor research
- Key features: Content analysis, keyword extraction, and readability metrics -
-
- """, unsafe_allow_html=True) - - # Input form for Website Analyzer - with st.form("website_analyzer_form"): - website_url = st.text_input("Enter website URL") - analyze_type = st.selectbox("Analysis type", ["Basic Analysis", "SEO Analysis", "Content Analysis", "Competitor Analysis"]) - - submitted = st.form_submit_button("Analyze Website") - if submitted and website_url: - st.info("Website Analyzer is coming soon. This feature is under development.") - - with tab3: - st.markdown(""" -
-
Async Web Crawler
-
High-performance asynchronous web crawler
-
- Best for: Large-scale crawling, data extraction, and content aggregation
- Key features: Fast, efficient crawling with customizable extraction rules -
-
- """, unsafe_allow_html=True) - - # Input form for Async Web Crawler - with st.form("async_crawler_form"): - website_url = st.text_input("Enter website URL") - max_urls = st.slider("Maximum URLs to crawl", 10, 100, 30) - - submitted = st.form_submit_button("Start Crawling") - if submitted and website_url: - st.info("Async Web Crawler is coming soon. This feature is under development.") - -def display_academic_research(): - - st.markdown('
Academic Research Tools
', unsafe_allow_html=True) - - # ArXiv Search Section - st.markdown('
ArXiv Scholarly Search
', unsafe_allow_html=True) - - # Search Parameters - search_col1, search_col2 = st.columns([2, 1]) - with search_col1: - search_query = st.text_input("๐Ÿ” Enter research topic or keywords", key="arxiv_search") - with search_col2: - max_results = st.number_input("Maximum Results", min_value=1, max_value=50, value=10) - - # Search Button - if st.button("๐Ÿ”Ž Search ArXiv", key="arxiv_search_button"): - if search_query: - with st.spinner("Searching ArXiv database..."): - try: - # Import arxiv search function - from ai_web_researcher.arxiv_schlorly_research import fetch_arxiv_data, create_dataframe - - # Fetch results - results = fetch_arxiv_data(search_query, max_results) - - if results: - # Create DataFrame - df = create_dataframe(results, ["Title", "Published Date", "ArXiv ID", "Summary", "PDF URL"]) - - # Display results in an expander - with st.expander("๐Ÿ“š Search Results", expanded=True): - # Display each paper with options to view abstract and download - for idx, row in df.iterrows(): - st.markdown(f"### {row['Title']}") - st.markdown(f"*Published: {row['Published Date']}*") - - # Create columns for buttons - btn_col1, btn_col2, btn_col3 = st.columns([1, 1, 1]) - - with btn_col1: - if st.button(f"๐Ÿ“„ View Abstract #{idx}"): - st.markdown(f"**Abstract:**\n{row['Summary']}") - - with btn_col2: - st.markdown(f"[๐Ÿ“ฅ Download PDF]({row['PDF URL']})") - if st.button(f"๐Ÿ“ Summarize #{idx}"): - with st.spinner("Generating summary..."): - try: - from ai_web_researcher.gpt_summarize_web_content import summarize_web_content - summary = summarize_web_content(row['PDF URL']) - if summary: - st.markdown("### GPT Summary") - st.markdown(summary) - # Add export option for the summary - st.download_button( - label="๐Ÿ“ฅ Export Summary", - data=summary, - file_name=f"summary_{row['ArXiv ID']}.txt", - mime="text/plain" - ) - except Exception as e: - st.error(f"Error generating summary: {str(e)}") - - with btn_col3: - if st.button(f"๐Ÿ” Related Web Content #{idx}"): - # Use Google SERP to find related content - from ai_web_researcher.google_serp_search import google_search - web_results = google_search(row['Title']) - if web_results: - st.markdown("### Related Web Content") - for result in web_results['organic'][:3]: - st.markdown(f"- [{result['title']}]({result['link']})\n {result['snippet']}") - - st.markdown("---") - else: - st.warning("No results found. Try modifying your search terms.") - except Exception as e: - st.error(f"An error occurred while searching: {str(e)}") - else: - st.warning("Please enter a search query.") - - # Research Notes Section - st.markdown('
Research Notes
', unsafe_allow_html=True) - - # Initialize session state for notes if not exists - if 'research_notes' not in st.session_state: - st.session_state.research_notes = {} - - notes_col1, notes_col2 = st.columns([2, 1]) - - with notes_col1: - paper_id = st.text_input("ArXiv ID or Paper Title", key="notes_paper_id") - notes_content = st.text_area("Research Notes", height=200, key="notes_content") - - if st.button("Save Notes"): - if paper_id: - st.session_state.research_notes[paper_id] = notes_content - st.success("Notes saved successfully!") - else: - st.warning("Please enter a paper identifier.") - - with notes_col2: - st.markdown("### Saved Notes") - for paper_id, notes in st.session_state.research_notes.items(): - with st.expander(f"๐Ÿ“ {paper_id}"): - st.text_area("Saved Notes", value=notes, height=150, key=f"saved_{paper_id}", disabled=True) - if st.button("Export Notes", key=f"export_{paper_id}"): - notes_export = f"Research Notes for {paper_id}\n\n{notes}" - st.download_button( - label="๐Ÿ“ฅ Download Notes", - data=notes_export, - file_name=f"research_notes_{paper_id}.txt", - mime="text/plain" - ) - - # Citation Management Section - st.markdown('
Citation Management
', unsafe_allow_html=True) - - # BibTeX Export - st.markdown("### Export Citations") - arxiv_id = st.text_input("Enter ArXiv ID for citation export") - if arxiv_id and st.button("Generate BibTeX"): - try: - from ai_web_researcher.arxiv_schlorly_research import arxiv_bibtex - bibtex = arxiv_bibtex(arxiv_id) - if bibtex: - st.code(bibtex, language="bibtex") - st.download_button( - label="๐Ÿ“ฅ Download BibTeX", - data=bibtex, - file_name=f"citation_{arxiv_id}.bib", - mime="text/plain" - ) - except Exception as e: - st.error(f"Error generating citation: {str(e)}") - -def display_research_workflows(): - - st.markdown('
Research Workflows
', unsafe_allow_html=True) - - st.markdown(""" -
- Research workflows combine multiple research tools to provide comprehensive insights for specific content creation tasks. - Select a workflow to get started. -
- """, unsafe_allow_html=True) - - # Create tabs for different workflows - tab1, tab2, tab3 = st.tabs(["Topic Research", "Competitor Analysis", "Trend Discovery"]) - - with tab1: - st.markdown(""" -
-
Comprehensive Topic Research
-
- This workflow helps you thoroughly research a topic for content creation by combining search results, - semantic understanding, and trend analysis. -
-
- """, unsafe_allow_html=True) - - # Input form for Topic Research workflow - with st.form("topic_research_form"): - topic = st.text_input("Enter your topic") - include_trends = st.checkbox("Include trend analysis", value=True) - include_competitors = st.checkbox("Include competitor analysis", value=True) - - submitted = st.form_submit_button("Start Research Workflow") - if submitted and topic: - st.info("Research workflows are coming soon. This feature is under development.") - - with tab2: - st.markdown(""" -
-
Competitor Content Analysis
-
- This workflow analyzes your competitors' content to identify gaps and opportunities for your own content strategy. -
-
- """, unsafe_allow_html=True) - - # Input form for Competitor Analysis workflow - with st.form("competitor_analysis_form"): - competitor_urls = st.text_area("Enter competitor URLs (one per line)") - topic_focus = st.text_input("Topic focus (optional)") - - submitted = st.form_submit_button("Start Competitor Analysis") - if submitted and competitor_urls: - st.info("Research workflows are coming soon. This feature is under development.") - - with tab3: - st.markdown(""" -
-
Trend Discovery & Content Planning
-
- This workflow identifies trending topics in your niche and helps you plan content around them. -
-
- """, unsafe_allow_html=True) - - # Input form for Trend Discovery workflow - with st.form("trend_discovery_form"): - niche = st.text_input("Enter your niche or industry") - time_period = st.select_slider("Time period", options=["past_week", "past_month", "past_90_days", "past_12_months"], value="past_month") - - submitted = st.form_submit_button("Discover Trends") - if submitted and niche: - st.info("Research workflows are coming soon. This feature is under development.") \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/main.py b/ToBeMigrated/alwrity_ui/alwrity_researcher/main.py deleted file mode 100644 index c3fbdd2c..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/main.py +++ /dev/null @@ -1,14 +0,0 @@ -import streamlit as st -import sys -import os -from pathlib import Path - -# Add the current directory to the path -sys.path.append(str(Path(__file__).parent)) - -# Import the dashboard module -from dashboard import main - -# Run the dashboard -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/style.css b/ToBeMigrated/alwrity_ui/alwrity_researcher/style.css deleted file mode 100644 index 8374d190..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/style.css +++ /dev/null @@ -1,517 +0,0 @@ -/* Main Dashboard Styles */ -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; - background-color: #f0f4f8; - color: #1f2937; - background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); -} - -.main-header { - font-size: 2.2rem; - background: linear-gradient(120deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-align: center; - margin: 1.5rem 0; - font-weight: 700; - letter-spacing: -0.025em; - padding-bottom: 0.75rem; - border-bottom: 1px solid rgba(229, 231, 235, 0.5); - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); -} - -.category-header { - font-size: 1.4rem; - background: linear-gradient(90deg, #2563eb 0%, #4f46e5 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - margin-top: 1.5rem; - margin-bottom: 1rem; - font-weight: 600; - padding-left: 0.5rem; - border-left: 4px solid; - border-image: linear-gradient(to bottom, #3b82f6, #4f46e5) 1; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -/* Glassomorphic Tool Card Styles */ -.tool-card { - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 12px; - padding: 1.75rem; - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - border: 1px solid rgba(255, 255, 255, 0.18); - margin-bottom: 1.5rem; - border-left: 4px solid; - border-image: linear-gradient(to bottom, rgba(59, 130, 246, 0.8), rgba(79, 70, 229, 0.8)) 1; - transition: all 0.3s ease; -} - -.tool-card:hover { - transform: translateY(-5px); - box-shadow: 0 15px 30px rgba(31, 38, 135, 0.25); - border-image: linear-gradient(to bottom, rgba(59, 130, 246, 1), rgba(79, 70, 229, 1)) 1; - background: linear-gradient(120deg, rgba(255, 255, 255, 0.9) 0%, rgba(240, 249, 255, 0.8) 100%); -} - -.tool-title { - font-size: 1.3rem; - font-weight: 600; - background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - margin-bottom: 0.75rem; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.tool-description { - color: #374151; - margin-bottom: 1rem; - line-height: 1.6; - font-size: 1rem; -} - -.tool-features { - font-size: 0.95rem; - color: #4b5563; - margin-bottom: 0.75rem; - line-height: 1.6; - background-color: rgba(243, 244, 246, 0.6); - padding: 0.85rem; - border-radius: 8px; - border: 1px solid rgba(229, 231, 235, 0.5); -} - -/* Tool Badge Styles */ -.tool-badge { - display: inline-block; - padding: 0.35rem 0.75rem; - border-radius: 20px; - font-size: 0.75rem; - font-weight: 500; - margin-right: 0.5rem; - background-color: rgba(243, 244, 246, 0.8); - color: #4b5563; - border: 1px solid rgba(209, 213, 219, 0.5); - backdrop-filter: blur(5px); -} - -.tool-badge.ai-powered { - background-color: rgba(79, 70, 229, 0.15); - color: #4338ca; - border-color: rgba(79, 70, 229, 0.3); -} - -.tool-badge.semantic { - background-color: rgba(16, 185, 129, 0.15); - color: #065f46; - border-color: rgba(16, 185, 129, 0.3); -} - -.tool-badge.deep-learning { - background-color: rgba(245, 158, 11, 0.15); - color: #92400e; - border-color: rgba(245, 158, 11, 0.3); -} - -.tool-badge.time-series { - background-color: rgba(6, 182, 212, 0.15); - color: #0e7490; - border-color: rgba(6, 182, 212, 0.3); -} - -.tool-badge.forecasting { - background-color: rgba(168, 85, 247, 0.15); - color: #6d28d9; - border-color: rgba(168, 85, 247, 0.3); -} - -.tool-badge.content-extraction { - background-color: rgba(236, 72, 153, 0.15); - color: #be185d; - border-color: rgba(236, 72, 153, 0.3); -} - -.tool-badge.data-analysis { - background-color: rgba(14, 165, 233, 0.15); - color: #0369a1; - border-color: rgba(14, 165, 233, 0.3); -} - -/* Glassomorphic Workflow Card Styles */ -.workflow-card { - background: linear-gradient(135deg, rgba(239, 246, 255, 0.8) 0%, rgba(219, 234, 254, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 12px; - padding: 1.75rem; - margin-bottom: 1.5rem; - border-left: 4px solid; - border-image: linear-gradient(to bottom, rgba(37, 99, 235, 0.8), rgba(79, 70, 229, 0.8)) 1; - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - border: 1px solid rgba(255, 255, 255, 0.18); -} - -.workflow-title { - font-size: 1.3rem; - font-weight: 600; - background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - margin-bottom: 1.25rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid rgba(191, 219, 254, 0.5); -} - -.workflow-step-container { - display: flex; - margin-bottom: 1.25rem; - background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, rgba(240, 249, 255, 0.6) 100%); - border-radius: 10px; - padding: 1rem; - box-shadow: 0 4px 6px rgba(31, 38, 135, 0.1); - border: 1px solid rgba(255, 255, 255, 0.18); -} - -.workflow-step-number { - display: flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - background: linear-gradient(135deg, rgba(37, 99, 235, 0.9) 0%, rgba(79, 70, 229, 0.9) 100%); - color: white; - border-radius: 50%; - font-weight: 600; - margin-right: 1rem; - box-shadow: 0 4px 6px rgba(37, 99, 235, 0.3); -} - -.workflow-step-content { - flex: 1; -} - -.step-title { - font-weight: 600; - background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - margin-bottom: 0.5rem; - font-size: 1.05rem; -} - -.workflow-step-description { - color: #4b5563; - line-height: 1.5; - font-size: 0.95rem; -} - -/* Navigation Styles */ -.nav-header { - font-size: 2rem; - background: linear-gradient(120deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-align: center; - margin: 1.5rem 0; - font-weight: 700; - letter-spacing: -0.025em; - padding-bottom: 0.75rem; - border-bottom: 1px solid rgba(229, 231, 235, 0.5); - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); -} - -.nav-container { - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 12px; - padding: 1.75rem; - margin-bottom: 1.5rem; - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - border: 1px solid rgba(255, 255, 255, 0.18); -} - -.nav-button { - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 12px; - padding: 1.5rem; - margin-bottom: 1rem; - border: 1px solid rgba(255, 255, 255, 0.18); - border-left: 4px solid; - border-image: linear-gradient(to bottom, rgba(59, 130, 246, 0.8), rgba(79, 70, 229, 0.8)) 1; - transition: all 0.3s ease; - width: 100%; - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; -} - -.nav-button:hover { - transform: translateY(-5px); - box-shadow: 0 15px 30px rgba(31, 38, 135, 0.25); - border-image: linear-gradient(to bottom, rgba(59, 130, 246, 1), rgba(79, 70, 229, 1)) 1; - background: linear-gradient(120deg, rgba(255, 255, 255, 0.9) 0%, rgba(240, 249, 255, 0.8) 100%); -} - -.nav-button.selected { - background: linear-gradient(120deg, rgba(239, 246, 255, 0.9) 0%, rgba(219, 234, 254, 0.8) 100%); - border-image: linear-gradient(to bottom, #3b82f6, #4f46e5) 1; - box-shadow: 0 8px 20px rgba(31, 38, 135, 0.2); -} - -.nav-icon { - font-size: 1.8rem; - margin-bottom: 0.75rem; - background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.nav-title { - font-size: 1.1rem; - font-weight: 600; - background: linear-gradient(90deg, #1e3a8a 0%, #3b82f6 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-align: center; -} - -[data-testid="stSidebar"] { - background-color: rgba(248, 250, 252, 0.7); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-right: 1px solid rgba(229, 231, 235, 0.5); -} - -[data-testid="stSidebarNav"] { - background-color: transparent; -} - -.st-emotion-cache-16txtl3 { - padding-top: 2rem; -} - -.sidebar-header { - font-size: 1.3rem; - font-weight: 700; - color: #1e3a8a; - margin: 1rem 0; - padding-bottom: 0.75rem; - border-bottom: 1px solid rgba(229, 231, 235, 0.5); - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); -} - -/* Button Styles */ -.stButton>button { - background: linear-gradient(135deg, #3b82f6 0%, #4f46e5 100%); - color: white; - width: 100%; - font-weight: 500; - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, 0.18); - padding: 0.6rem 1.2rem; - transition: all 0.3s ease; - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); - box-shadow: 0 4px 6px rgba(31, 38, 135, 0.15); -} - -.stButton>button:hover { - background: linear-gradient(135deg, #2563eb 0%, #4338ca 100%); - box-shadow: 0 8px 15px rgba(31, 38, 135, 0.2); - transform: translateY(-2px); -} - -/* Glassomorphic Results Display Styles */ -.results-container { - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 12px; - padding: 1.75rem; - margin-top: 1.25rem; - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - border: 1px solid rgba(255, 255, 255, 0.18); -} - -.result-item { - padding: 1.25rem; - border-bottom: 1px solid rgba(229, 231, 235, 0.5); - margin-bottom: 1.25rem; - background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, rgba(248, 250, 252, 0.6) 100%); - border-radius: 8px; - transition: all 0.2s ease; -} - -.result-item:hover { - background-color: rgba(255, 255, 255, 0.8); - box-shadow: 0 4px 6px rgba(31, 38, 135, 0.1); -} - -.result-title { - font-size: 1.15rem; - font-weight: 600; - color: #1e3a8a; - margin-bottom: 0.6rem; -} - -.result-snippet { - color: #4b5563; - font-size: 0.95rem; - line-height: 1.6; -} - -.result-url { - color: #059669; - font-size: 0.85rem; - margin-top: 0.6rem; - word-break: break-all; -} - -/* Form Styles */ -.stTextInput>div>div>input { - border-radius: 8px; - border: 1px solid rgba(209, 213, 219, 0.5); - background-color: rgba(255, 255, 255, 0.7); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); -} - -.stTextInput>div>div>input:focus { - border-color: rgba(59, 130, 246, 0.7); - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); - background-color: rgba(255, 255, 255, 0.9); -} - -/* Dashboard Home Styles */ -.intro-container { - background: linear-gradient(135deg, rgba(239, 246, 255, 0.8) 0%, rgba(224, 242, 254, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - padding: 1.75rem; - border-radius: 12px; - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - margin-bottom: 1.5rem; - border-left: 4px solid rgba(59, 130, 246, 0.7); - border: 1px solid rgba(255, 255, 255, 0.18); -} - -.intro-text { - color: #374151; - line-height: 1.7; - font-size: 1.05rem; - margin: 0; -} - -.intro-highlight { - color: #1e3a8a; - font-weight: 600; -} - -/* Chart Container Styles */ -.chart-container { - background-color: rgba(255, 255, 255, 0.7); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 12px; - padding: 1.5rem; - margin: 1.25rem 0; - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - border: 1px solid rgba(255, 255, 255, 0.18); -} - -.chart-title { - font-size: 1.1rem; - font-weight: 600; - color: #1e3a8a; - margin-bottom: 1rem; - text-align: center; -} - -/* Tab Styles */ -.stTabs [data-baseweb="tab-list"] { - gap: 12px; - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.18); - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); -} - -.stTabs [data-baseweb="tab"] { - background: linear-gradient(120deg, rgba(255, 255, 255, 0.7) 0%, rgba(240, 249, 255, 0.6) 100%); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); - border-radius: 10px; - padding: 0.75rem 1.25rem; - border: 1px solid rgba(255, 255, 255, 0.18); - border-left: 3px solid; - border-image: linear-gradient(to bottom, rgba(59, 130, 246, 0.8), rgba(79, 70, 229, 0.8)) 1; - transition: all 0.3s ease; - font-weight: 500; - color: #1e3a8a; -} - -.stTabs [data-baseweb="tab"]:hover { - transform: translateY(-2px); - box-shadow: 0 8px 15px rgba(31, 38, 135, 0.15); - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); -} - -.stTabs [aria-selected="true"] { - background: linear-gradient(135deg, #3b82f6 0%, #4f46e5 100%) !important; - color: white !important; - border-image: none !important; - border-left: 3px solid #3b82f6 !important; - box-shadow: 0 8px 15px rgba(59, 130, 246, 0.25) !important; -} - -/* Additional Styles for Tabs Content */ -.stTabs [data-baseweb="tab-panel"] { - background: linear-gradient(120deg, rgba(255, 255, 255, 0.8) 0%, rgba(240, 249, 255, 0.7) 100%); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border-radius: 0 12px 12px 12px; - padding: 1.75rem; - border: 1px solid rgba(255, 255, 255, 0.18); - box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - margin-top: -1px; -} - -/* Responsive Adjustments */ -@media (max-width: 768px) { - .tool-card, .workflow-card, .intro-container, .results-container { - padding: 1.25rem; - } - - .main-header { - font-size: 1.8rem; - } - - .category-header { - font-size: 1.2rem; - } - - .tool-title, .workflow-title { - font-size: 1.15rem; - } -} \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/alwrity_researcher/utils.py b/ToBeMigrated/alwrity_ui/alwrity_researcher/utils.py deleted file mode 100644 index 89222257..00000000 --- a/ToBeMigrated/alwrity_ui/alwrity_researcher/utils.py +++ /dev/null @@ -1,380 +0,0 @@ -import streamlit as st -import pandas as pd -import plotly.express as px -import plotly.graph_objects as go -import sys -import os -from pathlib import Path - -# Add parent directory to path to import modules -sys.path.append(str(Path(__file__).parent.parent.parent)) - -# Import research modules (placeholder imports for now) -try: - from ai_web_researcher import ( - google_serp_search, - tavily_ai_search, - metaphor_basic_neural_web_search, - google_trends_researcher, - firecrawl_web_crawler - ) -except ImportError: - # For development/testing without actual modules - pass - -def load_css(): - """Load custom CSS""" - css_file = Path(__file__).parent / "style.css" - with open(css_file) as f: - css_content = f.read() - # Use session state to track if CSS has been loaded - if 'css_loaded' not in st.session_state: - st.session_state['css_loaded'] = False - - # Always apply CSS on each page load to ensure styles persist during navigation - st.markdown(f"", unsafe_allow_html=True) - st.session_state['css_loaded'] = True - -def display_google_serp_results(results): - """Display Google SERP search results""" - # Check if results are available - if not results: - st.warning("No search results available. Please try a different query or check your API configuration.") - return - - st.markdown('
', unsafe_allow_html=True) - - # Display organic results - st.markdown('
Search Results
', unsafe_allow_html=True) - - # Display actual organic results - organic_results = results.get('organic', []) - if organic_results: - for result in organic_results: - st.markdown(f""" -
-
{result.get('title', 'No Title')}
-
{result.get('link', '#')}
-
{result.get('snippet', 'No description available.')}
-
- """, unsafe_allow_html=True) - else: - st.info("No organic search results found.") - - # Display People Also Ask - paa_results = results.get('peopleAlsoAsk', []) - if paa_results: - st.markdown('
People Also Ask
', unsafe_allow_html=True) - - for question in paa_results: - st.markdown(f""" -
-
{question.get('question', 'No Question')}
-
{question.get('snippet', 'No answer available.')}
-
- """, unsafe_allow_html=True) - - # Display Related Searches if available - related_searches = results.get('relatedSearches', []) - if related_searches: - st.markdown('
Related Searches
', unsafe_allow_html=True) - - for search in related_searches: - st.markdown(f""" -
-
{search}
-
- """, unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - -def display_tavily_results(results): - """Display Tavily AI search results""" - # Check if results are available - if not results: - st.warning("No Tavily search results available. Please try a different query or check your API configuration.") - return - - st.markdown('
', unsafe_allow_html=True) - - # Display answer if available - answer = results.get('answer', '') - if answer: - st.markdown('
Answer
', unsafe_allow_html=True) - st.markdown(f""" -
-
{answer}
-
- """, unsafe_allow_html=True) - - # Display search results - search_results = results.get('results', []) - if search_results: - st.markdown('
Search Results
', unsafe_allow_html=True) - - for result in search_results: - st.markdown(f""" -
-
{result.get('title', 'No Title')}
-
{result.get('url', '#')}
-
{result.get('content', 'No content available.')}
-
- """, unsafe_allow_html=True) - else: - st.info("No search results found.") - - # Display follow-up questions if available - follow_up_questions = results.get('follow_up_questions', []) - if follow_up_questions: - st.markdown('
Follow-up Questions
', unsafe_allow_html=True) - - for question in follow_up_questions: - st.markdown(f""" -
-
{question}
-
- """, unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - -def display_metaphor_results(results): - """Display Metaphor Neural Search results""" - # Check if results are available - if not results: - st.warning("No Metaphor search results available. Please try a different query or check your API configuration.") - return - - st.markdown('
', unsafe_allow_html=True) - - # Display search results - st.markdown('
Similar Content
', unsafe_allow_html=True) - - # Display actual results - documents = results.get('documents', []) - if documents: - for doc in documents: - title = doc.get('title', 'No Title') - url = doc.get('url', '#') - extract = doc.get('extract', 'No content available.') - - st.markdown(f""" -
-
{title}
-
{url}
-
{extract}
-
- """, unsafe_allow_html=True) - else: - st.info("No similar content found.") - - # Display summary if available - summary = results.get('summary', '') - if summary: - st.markdown('
Content Summary
', unsafe_allow_html=True) - st.markdown(f""" -
-
{summary}
-
- """, unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - -def display_google_trends_results(results): - """Display Google Trends results""" - # Check if results are available - if not results: - st.warning("No Google Trends results available. Please try a different query or check your API configuration.") - return - - st.markdown('
', unsafe_allow_html=True) - - # Display interest over time chart if available - interest_over_time = results.get('interest_over_time') - if interest_over_time is not None and not interest_over_time.empty: - st.markdown('
Interest Over Time
', unsafe_allow_html=True) - st.markdown('
', unsafe_allow_html=True) - st.markdown('
Search Interest Over Time
', unsafe_allow_html=True) - - # Convert to DataFrame if it's not already - if not isinstance(interest_over_time, pd.DataFrame): - interest_over_time = pd.DataFrame(interest_over_time) - - # Prepare data for visualization - if 'date' in interest_over_time.columns: - # Melt the DataFrame to get it in the right format for plotting - terms = [col for col in interest_over_time.columns if col != 'date'] - df = interest_over_time.melt('date', value_vars=terms, var_name='Term', value_name='Interest') - - # Create and display the chart - fig = px.line(df, x='date', y='Interest', color='Term', title='Search Interest Over Time') - st.plotly_chart(fig, use_container_width=True) - else: - st.error("Interest over time data is not in the expected format.") - - st.markdown('
', unsafe_allow_html=True) - - # Display related queries if available - related_queries = results.get('related_queries', {}) - if related_queries: - st.markdown('
Related Queries
', unsafe_allow_html=True) - - # Display top related queries - for term, queries in related_queries.items(): - if 'top' in queries: - st.markdown(f'
Top queries for "{term}"
', unsafe_allow_html=True) - for query in queries['top'].get('query', []): - st.markdown(f""" -
-
{query}
-
- """, unsafe_allow_html=True) - - if 'rising' in queries: - st.markdown(f'
Rising queries for "{term}"
', unsafe_allow_html=True) - for query in queries['rising'].get('query', []): - st.markdown(f""" -
-
{query}
-
- """, unsafe_allow_html=True) - - # Display related topics if available - related_topics = results.get('related_topics', {}) - if related_topics: - st.markdown('
Related Topics
', unsafe_allow_html=True) - - # Display top related topics - for term, topics in related_topics.items(): - if 'top' in topics: - st.markdown(f'
Top topics for "{term}"
', unsafe_allow_html=True) - for topic in topics['top'].get('topic', []): - st.markdown(f""" -
-
{topic}
-
- """, unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) - -def display_crawler_results(results): - """Display Web Crawler results""" - # Check if results are available - if not results: - st.warning("No web crawler results available. Please try a different URL or check your API configuration.") - return - - st.markdown('
', unsafe_allow_html=True) - - # Display crawled pages - st.markdown('
Crawled Pages
', unsafe_allow_html=True) - - # Handle different result formats - if isinstance(results, dict): - # Single page result - page_data = results - st.markdown(f""" -
-
{page_data.get('title', 'No Title')}
-
{page_data.get('url', '#')}
-
- Title: {page_data.get('title', 'No Title')}
- Description: {page_data.get('description', 'No description available.')}
- Word Count: {page_data.get('word_count', 'Unknown')} -
-
- """, unsafe_allow_html=True) - - # Display content sections if available - content = page_data.get('content', []) - if content: - st.markdown('
Page Content
', unsafe_allow_html=True) - for section in content: - if isinstance(section, dict): - section_type = section.get('type', '') - section_content = section.get('content', '') - if section_type and section_content: - st.markdown(f""" -
-
{section_type}
-
{section_content}
-
- """, unsafe_allow_html=True) - - elif isinstance(results, list): - # Multiple pages result - for page_data in results: - if isinstance(page_data, dict): - st.markdown(f""" -
-
{page_data.get('title', 'No Title')}
-
{page_data.get('url', '#')}
-
- Title: {page_data.get('title', 'No Title')}
- Description: {page_data.get('description', 'No description available.')}
- Word Count: {page_data.get('word_count', 'Unknown')} -
-
- """, unsafe_allow_html=True) - - # Display content structure - st.markdown('
Content Structure
', unsafe_allow_html=True) - - # Placeholder data for chart - labels = ['Blog Posts', 'Product Pages', 'Category Pages', 'About Pages', 'Contact Pages'] - values = [38, 27, 18, 10, 7] - - fig = go.Figure(data=[go.Pie(labels=labels, values=values, hole=.3)]) - fig.update_layout(title_text='Content Type Distribution') - st.plotly_chart(fig, use_container_width=True) - - st.markdown('
', unsafe_allow_html=True) - -def display_analyzer_results(results): - """Display Website Analyzer results""" - # This is a placeholder function that will be implemented when integrated with actual modules - st.markdown('
', unsafe_allow_html=True) - - # Display content quality metrics - st.markdown('
Content Quality Metrics
', unsafe_allow_html=True) - - # Placeholder data for chart - categories = ['Readability', 'Engagement', 'Relevance', 'Uniqueness', 'Comprehensiveness'] - values = [4.2, 3.8, 4.5, 3.9, 4.1] - - fig = go.Figure() - fig.add_trace(go.Scatterpolar( - r=values, - theta=categories, - fill='toself', - name='Content Quality' - )) - fig.update_layout( - polar=dict( - radialaxis=dict( - visible=True, - range=[0, 5] - )), - showlegend=False - ) - st.plotly_chart(fig, use_container_width=True) - - # Display SEO recommendations - st.markdown('
SEO Recommendations
', unsafe_allow_html=True) - - # Placeholder data - recommendations = [ - "Improve meta descriptions for better click-through rates", - "Add more internal links to related content", - "Optimize images with descriptive alt text", - "Improve page loading speed by optimizing images", - "Add structured data markup for better search visibility" - ] - - for recommendation in recommendations: - st.markdown(f""" -
-
{recommendation}
-
- """, unsafe_allow_html=True) - - st.markdown('
', unsafe_allow_html=True) \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/content_performance_predictor_ui.py b/ToBeMigrated/alwrity_ui/content_performance_predictor_ui.py deleted file mode 100644 index c8263bca..00000000 --- a/ToBeMigrated/alwrity_ui/content_performance_predictor_ui.py +++ /dev/null @@ -1,684 +0,0 @@ -""" -Streamlit UI for Content Performance Predictor -Interactive interface for predicting and optimizing content performance -""" - -import streamlit as st -import asyncio -import json -import plotly.graph_objects as go -import plotly.express as px -from datetime import datetime, timedelta -import pandas as pd -from typing import Dict, List, Any, Optional -import logging - -# Import the predictor -try: - from lib.content_performance_predictor.content_performance_predictor import ( - ContentPerformancePredictor, - ContentType, - predict_content_performance - ) -except ImportError: - st.error("Content Performance Predictor module not found. Please check the installation.") - -logger = logging.getLogger(__name__) - -class ContentPerformancePredictorUI: - """Streamlit UI for Content Performance Predictor""" - - def __init__(self): - self.predictor = None - self.initialize_predictor() - - def initialize_predictor(self): - """Initialize the predictor with error handling""" - try: - self.predictor = ContentPerformancePredictor() - except Exception as e: - st.error(f"Failed to initialize predictor: {str(e)}") - self.predictor = None - - def render_main_interface(self): - """Render the main Content Performance Predictor interface""" - st.title("๐ŸŽฏ Content Performance Predictor") - st.markdown("### Predict content success before you publish!") - - # Create tabs for different features - tab1, tab2, tab3, tab4 = st.tabs([ - "๐Ÿ“Š Predict Performance", - "๐Ÿ“ˆ Batch Analysis", - "๐Ÿ”ง Optimization Tools", - "๐Ÿ“š Performance History" - ]) - - with tab1: - self.render_single_prediction_tab() - - with tab2: - self.render_batch_analysis_tab() - - with tab3: - self.render_optimization_tab() - - with tab4: - self.render_history_tab() - - def render_single_prediction_tab(self): - """Render single content prediction interface""" - st.header("Single Content Analysis") - - # Input section - col1, col2 = st.columns([2, 1]) - - with col1: - # Content input - content_input = st.text_area( - "Enter your content:", - height=200, - placeholder="Paste your content here...\n\nExample:\n๐Ÿš€ Just discovered an amazing AI writing tool that's changing the game!\n\n#AIWriting #ContentCreation" - ) - - # Target keywords - keywords_input = st.text_input( - "Target Keywords (optional):", - placeholder="AI writing, content creation, SEO" - ) - - with col2: - # Content type selection - content_type = st.selectbox( - "Content Type:", - ["twitter", "linkedin", "facebook", "instagram", "blog_post", "email", "youtube"], - help="Select the platform where you plan to publish" - ) - - # Analysis options - st.subheader("Analysis Options") - - include_seo = st.checkbox("Include SEO Analysis", value=True) - include_trends = st.checkbox("Include Trend Analysis", value=True) - include_competitor = st.checkbox("Include Competitor Analysis", value=False) - - # Advanced settings - with st.expander("Advanced Settings"): - target_audience = st.selectbox( - "Target Audience:", - ["General", "Business", "Tech", "Marketing", "Education", "Entertainment"] - ) - - urgency_level = st.slider( - "Content Urgency:", - 0.0, 1.0, 0.5, - help="How time-sensitive is this content?" - ) - - # Predict button - if st.button("๐ŸŽฏ Predict Performance", type="primary", use_container_width=True): - if not content_input.strip(): - st.error("Please enter content to analyze") - return - - # Process keywords - keywords = [k.strip() for k in keywords_input.split(",")] if keywords_input else None - - # Show loading spinner - with st.spinner("Analyzing content performance..."): - # Run prediction - prediction_result = self.run_prediction( - content_input, - content_type, - keywords, - include_seo, - include_trends, - include_competitor - ) - - if prediction_result: - self.display_prediction_results(prediction_result) - else: - st.error("Failed to analyze content. Please try again.") - - def run_prediction( - self, - content: str, - content_type: str, - keywords: Optional[List[str]], - include_seo: bool, - include_trends: bool, - include_competitor: bool - ) -> Optional[Dict[str, Any]]: - """Run the content performance prediction""" - try: - # Run async prediction - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - result = loop.run_until_complete( - predict_content_performance( - content=content, - content_type=content_type, - target_keywords=keywords - ) - ) - - loop.close() - return result - - except Exception as e: - logger.error(f"Error in prediction: {str(e)}") - st.error(f"Prediction failed: {str(e)}") - return None - - def display_prediction_results(self, result: Dict[str, Any]): - """Display the prediction results with visualizations""" - st.success("โœ… Analysis Complete!") - - # Overall score section - st.subheader("๐Ÿ“Š Overall Performance Score") - - col1, col2, col3 = st.columns(3) - - with col1: - # Overall score gauge - score = result.get("overall_score", 0) - self.render_score_gauge(score, "Overall Score") - - with col2: - # Success probability - prob = result.get("success_probability", 0) * 100 - self.render_score_gauge(prob, "Success Probability") - - with col3: - # Performance rating - rating = self.get_performance_rating(score) - st.metric( - "Performance Rating", - rating["label"], - help=rating["description"] - ) - - # Detailed scores breakdown - st.subheader("๐Ÿ” Detailed Score Breakdown") - scores = result.get("individual_scores", {}) - if scores: - self.render_scores_breakdown(scores) - - # Recommendations section - st.subheader("๐Ÿ’ก Optimization Recommendations") - recommendations = result.get("recommendations", []) - if recommendations: - for i, rec in enumerate(recommendations, 1): - st.markdown(f"**{i}.** {rec}") - else: - st.info("No specific recommendations available") - - # Predicted metrics - st.subheader("๐Ÿ“Š Predicted Performance Metrics") - metrics = result.get("predicted_metrics", {}) - if metrics: - self.render_predicted_metrics(metrics) - - # Content analysis details - st.subheader("๐Ÿ“ Content Analysis Details") - analysis = result.get("content_analysis", {}) - if analysis: - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.metric("Word Count", analysis.get("word_count", 0)) - with col2: - st.metric("Character Count", analysis.get("character_count", 0)) - with col3: - st.metric("Hashtags", analysis.get("hashtag_count", 0)) - with col4: - st.metric("Readability", f"{analysis.get('readability_score', 0):.1f}") - - # Export options - st.subheader("๐Ÿ“ค Export Results") - col1, col2 = st.columns(2) - - with col1: - if st.button("๐Ÿ“„ Generate Report"): - report = self.generate_text_report(result) - st.text_area("Report:", value=report, height=200) - - with col2: - if st.button("๐Ÿ“Š Download JSON"): - json_str = json.dumps(result, indent=2) - st.download_button( - label="Download JSON", - data=json_str, - file_name=f"content_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", - mime="application/json" - ) - - def render_score_gauge(self, score: float, title: str): - """Render a gauge chart for scores""" - fig = go.Figure(go.Indicator( - mode = "gauge+number+delta", - value = score, - domain = {'x': [0, 1], 'y': [0, 1]}, - title = {'text': title}, - delta = {'reference': 50}, - gauge = { - 'axis': {'range': [None, 100]}, - 'bar': {'color': "darkblue"}, - 'steps': [ - {'range': [0, 25], 'color': "lightgray"}, - {'range': [25, 50], 'color': "yellow"}, - {'range': [50, 75], 'color': "orange"}, - {'range': [75, 100], 'color': "green"}], - 'threshold': { - 'line': {'color': "red", 'width': 4}, - 'thickness': 0.75, - 'value': 90}})) - - fig.update_layout(height=300) - st.plotly_chart(fig, use_container_width=True) - - def render_scores_breakdown(self, scores: Dict[str, float]): - """Render radar chart for score breakdown""" - categories = list(scores.keys()) - values = list(scores.values()) - - # Close the radar chart - categories.append(categories[0]) - values.append(values[0]) - - fig = go.Figure() - - fig.add_trace(go.Scatterpolar( - r=values, - theta=categories, - fill='toself', - name='Performance Scores' - )) - - fig.update_layout( - polar=dict( - radialaxis=dict( - visible=True, - range=[0, 100] - )), - showlegend=True, - title="Performance Score Breakdown" - ) - - st.plotly_chart(fig, use_container_width=True) - - # Display scores as metrics - cols = st.columns(len(scores)) - for i, (category, score) in enumerate(scores.items()): - with cols[i]: - st.metric(category.title(), f"{score:.1f}") - - def render_predicted_metrics(self, metrics: Dict[str, Any]): - """Render predicted performance metrics""" - cols = st.columns(len(metrics)) - - for i, (metric, value) in enumerate(metrics.items()): - with cols[i]: - # Format metric name - display_name = metric.replace("predicted_", "").replace("_", " ").title() - st.metric(display_name, f"{value:,}") - - def get_performance_rating(self, score: float) -> Dict[str, str]: - """Get performance rating based on score""" - if score >= 80: - return {"label": "Excellent", "description": "High chance of success"} - elif score >= 60: - return {"label": "Good", "description": "Solid performance expected"} - elif score >= 40: - return {"label": "Average", "description": "Room for improvement"} - else: - return {"label": "Needs Work", "description": "Optimization recommended"} - - def render_batch_analysis_tab(self): - """Render batch analysis interface""" - st.header("Batch Content Analysis") - st.info("Analyze multiple pieces of content at once") - - # File upload - uploaded_file = st.file_uploader( - "Upload CSV with content", - type=['csv'], - help="CSV should have columns: 'content', 'content_type', 'keywords' (optional)" - ) - - if uploaded_file is not None: - try: - df = pd.read_csv(uploaded_file) - - # Validate required columns - required_cols = ['content', 'content_type'] - missing_cols = [col for col in required_cols if col not in df.columns] - - if missing_cols: - st.error(f"Missing required columns: {', '.join(missing_cols)}") - return - - st.success(f"โœ… Loaded {len(df)} content items") - - # Preview data - with st.expander("Preview Data"): - st.dataframe(df.head()) - - # Analysis options - col1, col2 = st.columns(2) - - with col1: - max_items = st.number_input( - "Max items to analyze:", - min_value=1, - max_value=100, - value=min(10, len(df)) - ) - - with col2: - export_format = st.selectbox( - "Export format:", - ["CSV", "JSON", "Excel"] - ) - - # Run batch analysis - if st.button("๐Ÿš€ Run Batch Analysis", type="primary"): - with st.spinner(f"Analyzing {max_items} content items..."): - batch_df = df.head(max_items) - results = self.run_batch_analysis(batch_df) - - if results: - self.display_batch_results(results) - else: - st.error("Batch analysis failed") - - except Exception as e: - st.error(f"Error processing file: {str(e)}") - - def run_batch_analysis(self, df: pd.DataFrame) -> List[Dict[str, Any]]: - """Run batch analysis on multiple content items""" - results = [] - progress = st.progress(0) - - for i, row in df.iterrows(): - try: - content = row['content'] - content_type = row['content_type'] - keywords = row.get('keywords', '').split(',') if row.get('keywords') else None - - result = self.run_prediction(content, content_type, keywords, True, True, False) - if result: - result['original_content'] = content - result['content_type'] = content_type - results.append(result) - - progress.progress((i + 1) / len(df)) - - except Exception as e: - st.warning(f"Error analyzing row {i}: {str(e)}") - continue - - return results - - def display_batch_results(self, results: List[Dict[str, Any]]): - """Display batch analysis results""" - st.success(f"โœ… Analyzed {len(results)} content items") - - # Summary statistics - st.subheader("๐Ÿ“Š Batch Summary") - - scores = [r.get('overall_score', 0) for r in results] - avg_score = sum(scores) / len(scores) if scores else 0 - - col1, col2, col3, col4 = st.columns(4) - - with col1: - st.metric("Average Score", f"{avg_score:.1f}") - with col2: - st.metric("Best Score", f"{max(scores):.1f}" if scores else "0") - with col3: - st.metric("Worst Score", f"{min(scores):.1f}" if scores else "0") - with col4: - good_content = len([s for s in scores if s >= 60]) - st.metric("Good Content", f"{good_content}/{len(scores)}") - - # Results table - st.subheader("๐Ÿ“‹ Detailed Results") - - # Create summary DataFrame - summary_data = [] - for i, result in enumerate(results): - summary_data.append({ - "Content": result['original_content'][:50] + "..." if len(result['original_content']) > 50 else result['original_content'], - "Type": result['content_type'], - "Overall Score": result.get('overall_score', 0), - "Success Probability": result.get('success_probability', 0) * 100, - "Engagement": result.get('individual_scores', {}).get('engagement', 0), - "SEO": result.get('individual_scores', {}).get('seo', 0), - "Virality": result.get('individual_scores', {}).get('virality', 0) - }) - - summary_df = pd.DataFrame(summary_data) - st.dataframe(summary_df, use_container_width=True) - - # Download results - if st.button("๐Ÿ“ฅ Download Results"): - csv = summary_df.to_csv(index=False) - st.download_button( - label="Download CSV", - data=csv, - file_name=f"batch_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", - mime="text/csv" - ) - - def render_optimization_tab(self): - """Render content optimization tools""" - st.header("๐Ÿ”ง Content Optimization Tools") - - # A/B Testing section - st.subheader("๐Ÿงช A/B Content Testing") - - col1, col2 = st.columns(2) - - with col1: - st.markdown("**Version A**") - content_a = st.text_area( - "Content A:", - height=150, - key="content_a", - placeholder="Enter first version of your content..." - ) - - with col2: - st.markdown("**Version B**") - content_b = st.text_area( - "Content B:", - height=150, - key="content_b", - placeholder="Enter second version of your content..." - ) - - # Common settings - content_type = st.selectbox( - "Content Type for both:", - ["twitter", "linkedin", "facebook", "instagram", "blog_post", "email", "youtube"], - key="ab_content_type" - ) - - if st.button("โšก Compare Versions", type="primary"): - if not content_a.strip() or not content_b.strip(): - st.error("Please enter both versions of content") - return - - with st.spinner("Comparing content versions..."): - result_a = self.run_prediction(content_a, content_type, None, True, True, False) - result_b = self.run_prediction(content_b, content_type, None, True, True, False) - - if result_a and result_b: - self.display_ab_comparison(result_a, result_b) - - # Optimization suggestions - st.subheader("๐Ÿ’ก Optimization Suggestions") - - optimization_content = st.text_area( - "Content to optimize:", - height=150, - placeholder="Enter content for optimization suggestions..." - ) - - if st.button("๐Ÿš€ Get Suggestions") and optimization_content.strip(): - with st.spinner("Generating optimization suggestions..."): - suggestions = self.generate_optimization_suggestions(optimization_content) - - if suggestions: - st.success("โœ… Optimization suggestions generated!") - for i, suggestion in enumerate(suggestions, 1): - st.markdown(f"**{i}.** {suggestion}") - - def display_ab_comparison(self, result_a: Dict[str, Any], result_b: Dict[str, Any]): - """Display A/B test comparison results""" - st.success("โœ… A/B Comparison Complete!") - - # Overall comparison - col1, col2, col3 = st.columns(3) - - score_a = result_a.get('overall_score', 0) - score_b = result_b.get('overall_score', 0) - winner = "A" if score_a > score_b else "B" if score_b > score_a else "Tie" - - with col1: - st.metric("Version A Score", f"{score_a:.1f}") - with col2: - st.metric("Version B Score", f"{score_b:.1f}") - with col3: - st.metric("Winner", winner, delta=f"{abs(score_a - score_b):.1f} point difference") - - # Detailed comparison chart - scores_a = result_a.get('individual_scores', {}) - scores_b = result_b.get('individual_scores', {}) - - categories = list(scores_a.keys()) - values_a = list(scores_a.values()) - values_b = list(scores_b.values()) - - # Create comparison bar chart - fig = go.Figure(data=[ - go.Bar(name='Version A', x=categories, y=values_a), - go.Bar(name='Version B', x=categories, y=values_b) - ]) - - fig.update_layout( - barmode='group', - title="Detailed Score Comparison", - yaxis_title="Score", - xaxis_title="Category" - ) - - st.plotly_chart(fig, use_container_width=True) - - # Recommendations comparison - st.subheader("๐Ÿ“‹ Recommendations Comparison") - - col1, col2 = st.columns(2) - - with col1: - st.markdown("**Version A Recommendations:**") - recs_a = result_a.get('recommendations', []) - for rec in recs_a[:5]: - st.markdown(f"โ€ข {rec}") - - with col2: - st.markdown("**Version B Recommendations:**") - recs_b = result_b.get('recommendations', []) - for rec in recs_b[:5]: - st.markdown(f"โ€ข {rec}") - - def generate_optimization_suggestions(self, content: str) -> List[str]: - """Generate optimization suggestions for content""" - suggestions = [] - - # Basic content analysis - word_count = len(content.split()) - char_count = len(content) - hashtag_count = content.count('#') - - # Length optimization - if word_count < 10: - suggestions.append("Consider adding more detail to your content for better engagement") - elif word_count > 50: - suggestions.append("Consider shortening your content for better social media performance") - - # Hashtag optimization - if hashtag_count == 0: - suggestions.append("Add relevant hashtags to increase discoverability") - elif hashtag_count > 5: - suggestions.append("Reduce the number of hashtags for better readability") - - # Engagement optimization - if '?' not in content: - suggestions.append("Consider adding a question to encourage engagement") - - if '!' not in content and '.' in content: - suggestions.append("Add some excitement with exclamation marks") - - # Call to action - cta_words = ['click', 'share', 'comment', 'like', 'follow', 'subscribe'] - has_cta = any(word in content.lower() for word in cta_words) - if not has_cta: - suggestions.append("Include a clear call-to-action (like, share, comment)") - - # Emoji usage - emoji_count = len([char for char in content if ord(char) > 127]) - if emoji_count == 0: - suggestions.append("Consider adding relevant emojis to make content more engaging") - - return suggestions[:5] # Limit to top 5 suggestions - - def render_history_tab(self): - """Render performance history interface""" - st.header("๐Ÿ“š Performance History") - st.info("Performance history tracking coming soon!") - st.markdown("This feature will allow you to:") - st.markdown("โ€ข Track your content performance over time") - st.markdown("โ€ข Compare predicted vs actual performance") - st.markdown("โ€ข Identify your best-performing content patterns") - st.markdown("โ€ข Generate performance reports") - - def generate_text_report(self, result: Dict[str, Any]) -> str: - """Generate a text report of the analysis""" - report = f""" -CONTENT PERFORMANCE ANALYSIS REPORT -Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - -OVERALL PERFORMANCE -Overall Score: {result.get('overall_score', 0):.1f}/100 -Success Probability: {result.get('success_probability', 0)*100:.1f}% -Performance Rating: {self.get_performance_rating(result.get('overall_score', 0))['label']} - -DETAILED SCORES -""" - - scores = result.get('individual_scores', {}) - for category, score in scores.items(): - report += f"{category.title()}: {score:.1f}/100\n" - - report += "\nCONTENT ANALYSIS\n" - analysis = result.get('content_analysis', {}) - for key, value in analysis.items(): - report += f"{key.replace('_', ' ').title()}: {value}\n" - - report += "\nRECOMMENDATIONS\n" - recommendations = result.get('recommendations', []) - for i, rec in enumerate(recommendations, 1): - report += f"{i}. {rec}\n" - - return report - -def render_content_performance_predictor(): - """Main function to render the Content Performance Predictor UI""" - ui = ContentPerformancePredictorUI() - ui.render_main_interface() - -if __name__ == "__main__": - render_content_performance_predictor() \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/dashboard_styles.py b/ToBeMigrated/alwrity_ui/dashboard_styles.py deleted file mode 100644 index b527ddd5..00000000 --- a/ToBeMigrated/alwrity_ui/dashboard_styles.py +++ /dev/null @@ -1,510 +0,0 @@ -import streamlit as st - -def apply_dashboard_style(): - """Apply common glassmorphic styling for all dashboards (AI Writers, SEO Tools, Social Media).""" - st.markdown(""" - - """, unsafe_allow_html=True) - -def render_dashboard_header(title, description): - """Render a standardized dashboard header.""" - st.markdown(f""" -
-

{title}

-

{description}

-
- """, unsafe_allow_html=True) - -def render_category_header(category_name): - """Render a standardized category header.""" - st.markdown(f""" -
-

{category_name}

-
-
- """, unsafe_allow_html=True) - -def render_card(icon, title, description, category, key_suffix="", help_text=""): - """Render a standardized premium card with button.""" - st.markdown(f""" -
-
-
-
-
{icon}
-
{title}
-
{description}
- -
-
-
-
- """, unsafe_allow_html=True) - - # Return button for functionality - return st.button( - f"Select {title}", - key=f"card_{key_suffix}", - help=help_text or f"Launch {title} - {description}", - use_container_width=True - ) \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/display_google_serp_results.py b/ToBeMigrated/alwrity_ui/display_google_serp_results.py deleted file mode 100644 index 636cadf1..00000000 --- a/ToBeMigrated/alwrity_ui/display_google_serp_results.py +++ /dev/null @@ -1,277 +0,0 @@ -import streamlit as st -import logging -from datetime import datetime -from typing import Dict, Optional, Any - -# Configure module logger -logger = logging.getLogger(__name__) - -def display_research_results(results: Dict[str, Any]) -> None: - """ - Display research results in a structured format with tabs. - - Args: - results (dict): Processed research results containing summary and data - """ - if not results: - st.warning("No results to display") - return - - # Create tabs for different result sections - tabs = st.tabs(["๐Ÿ“Š Summary", "๐Ÿ” Results", "๐Ÿ“ˆ Statistics"]) - - with tabs[0]: - display_summary_section(results) - - with tabs[1]: - if results['source'] == 'gemini': - display_gemini_results(results) - else: - display_serp_results(results) - - with tabs[2]: - display_statistics(results) - -def process_research_results(results: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """Process and format research results.""" - logger.info("Processing research results") - - try: - if not results: - return None - - processed = { - 'timestamp': str(datetime.now()), - 'source': results.get('source', 'unknown'), - 'summary': {}, - 'data': {} - } - - if results.get('source') == 'gemini': - processed.update(process_gemini_results(results)) - else: - processed.update(process_serp_results(results)) - - logger.info("Results processing completed") - return processed - - except Exception as err: - logger.error(f"Failed to process results: {err}", exc_info=True) - return None - -def process_search_results(search_results: Dict[str, Any], search_type: str = "general") -> Optional[Dict[str, Any]]: - """Process search results and prepare for display.""" - logger.info(f"Processing {search_type} search results") - - try: - if not search_results: - return None - - processed = { - 'organic': process_organic_results(search_results.get('organic', [])), - 'peopleAlsoAsk': process_paa_results(search_results.get('peopleAlsoAsk', [])), - 'relatedSearches': process_related_searches(search_results.get('relatedSearches', [])), - 'metadata': { - 'timestamp': str(datetime.now()), - 'type': search_type - } - } - - return processed - - except Exception as err: - logger.error(f"Error processing search results: {err}", exc_info=True) - return None - -# Helper functions for result processing -def process_organic_results(results): - """Process organic search results.""" - return [{ - 'title': result.get('title', 'No Title'), - 'link': result.get('link', '#'), - 'snippet': result.get('snippet', 'No snippet available'), - 'position': result.get('position', 'N/A') - } for result in results] - -def process_paa_results(results): - """Process People Also Ask results.""" - return [{ - 'question': result.get('title', ''), - 'answer': result.get('snippet', 'No answer available'), - 'link': result.get('link', '#') - } for result in results] - -def process_related_searches(results): - """Process related searches.""" - return [query.get('query', '') for query in results] - -def process_gemini_results(results: Dict[str, Any]) -> Dict[str, Any]: - """ - Process Gemini API research results. - - Args: - results (dict): Raw Gemini research results - - Returns: - dict: Processed results with summary and data - """ - gemini_data = results.get('results', {}) - return { - 'summary': { - 'main_findings': gemini_data.get('main_response', ''), - 'sources': gemini_data.get('grounding_data', []), - 'processing_time': gemini_data.get('metadata', {}).get('timestamp'), - 'total_sources': len(gemini_data.get('grounding_data', [])), - 'model': gemini_data.get('metadata', {}).get('model', 'unknown') - }, - 'data': gemini_data - } - -def process_serp_results(results: Dict[str, Any]) -> Dict[str, Any]: - """ - Process SERP search results. - - Args: - results (dict): Raw SERP results - - Returns: - dict: Processed results with summary and data - """ - organic_results = results.get('organic', []) - paa_results = results.get('peopleAlsoAsk', []) - related_searches = results.get('relatedSearches', []) - - return { - 'summary': { - 'total_results': len(organic_results), - 'sources': [result.get('link') for result in organic_results], - 'titles': [result.get('title') for result in organic_results], - 'total_questions': len(paa_results), - 'total_related': len(related_searches) - }, - 'data': { - 'organic': process_organic_results(organic_results), - 'peopleAlsoAsk': process_paa_results(paa_results), - 'relatedSearches': process_related_searches(related_searches) - } - } - -# Display helper functions -def display_summary_section(results): - """Display summary section of results.""" - st.markdown("### ๐Ÿ“‹ Research Summary") - st.markdown(f""" - - **Source**: {results['source'].title()} - - **Time**: {results['timestamp']} - - **Total Sources**: {len(results.get('summary', {}).get('sources', []))} - """) - -def display_gemini_results(results): - """Display Gemini-specific results.""" - st.markdown("### ๐Ÿค– Gemini Research Findings") - st.write(results['summary']['main_findings']) - - with st.expander("๐ŸŒ Sources and References", expanded=False): - st.write(results['data'].get('grounding_data', 'No sources available')) - -def display_serp_results(results): - """Display SERP-specific results.""" - st.markdown("### ๐Ÿ” Search Results") - - for result in results['data'].get('organic', []): - with st.expander(f"๐Ÿ“„ {result['title']}", expanded=False): - st.markdown(f""" - **Rank:** {result['position']} - - **Link:** [{result['link']}]({result['link']}) - - **Snippet:** - {result['snippet']} - """) - -def display_statistics(results: Dict[str, Any]) -> None: - """ - Display statistical information about search results. - - Args: - results (dict): Processed research results - """ - st.markdown("### ๐Ÿ“ˆ Research Statistics") - - # Source-specific metrics - if results['source'] == 'gemini': - col1, col2 = st.columns(2) - with col1: - st.metric( - "Sources Analyzed", - results.get('summary', {}).get('total_sources', 0) - ) - with col2: - st.metric( - "Model Used", - results.get('summary', {}).get('model', 'Unknown') - ) - - else: # SERP results - col1, col2, col3 = st.columns(3) - with col1: - st.metric( - "Organic Results", - results.get('summary', {}).get('total_results', 0) - ) - with col2: - st.metric( - "Related Questions", - results.get('summary', {}).get('total_questions', 0) - ) - with col3: - st.metric( - "Related Searches", - results.get('summary', {}).get('total_related', 0) - ) - - # Common metrics - st.markdown("#### ๐Ÿ•’ Timing Information") - st.info(f"Research completed at: {results['timestamp']}") - - # Display data quality metrics - st.markdown("#### ๐Ÿ“Š Data Quality") - quality_metrics = calculate_quality_metrics(results) - - col1, col2 = st.columns(2) - with col1: - st.progress(quality_metrics['completeness']) - st.caption("Data Completeness") - with col2: - st.progress(quality_metrics['relevance']) - st.caption("Estimated Relevance") - -def calculate_quality_metrics(results: Dict[str, Any]) -> Dict[str, float]: - """ - Calculate quality metrics for the research results. - - Args: - results (dict): Processed research results - - Returns: - dict: Quality metrics including completeness and relevance scores - """ - try: - if results['source'] == 'gemini': - completeness = 1.0 if results['summary']['main_findings'] else 0.0 - relevance = 0.8 if results['summary']['sources'] else 0.4 - else: - organic_results = results.get('summary', {}).get('total_results', 0) - completeness = min(organic_results / 10, 1.0) # Normalize to 0-1 - has_paa = bool(results.get('summary', {}).get('total_questions', 0)) - has_related = bool(results.get('summary', {}).get('total_related', 0)) - relevance = (0.6 + (0.2 if has_paa else 0) + (0.2 if has_related else 0)) - - return { - 'completeness': completeness, - 'relevance': relevance - } - - except Exception as err: - logger.error(f"Error calculating quality metrics: {err}") - return {'completeness': 0.0, 'relevance': 0.0} \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/google_trends_ui.py b/ToBeMigrated/alwrity_ui/google_trends_ui.py deleted file mode 100644 index 9fce5734..00000000 --- a/ToBeMigrated/alwrity_ui/google_trends_ui.py +++ /dev/null @@ -1,458 +0,0 @@ -""" -Module for displaying Google Trends data in the Streamlit UI. - -This module provides functions for visualizing Google Trends data, including: -- Interest over time -- Regional interest -- Related queries -- Related topics -""" - -import streamlit as st -import pandas as pd -import plotly.express as px -import plotly.graph_objects as go -import logging - -# Set up logging -logger = logging.getLogger(__name__) - -def display_google_trends_data(trends_data, search_keyword): - """ - Display Google Trends data in a structured format with tabs for different sections. - - Args: - trends_data (dict): Dictionary containing Google Trends data - search_keyword (str): The search keyword used for the analysis - """ - if not trends_data: - st.warning("No Google Trends data available for this search.") - return - - st.subheader(f"Google Trends Analysis for '{search_keyword}'") - - # Add an informative message about Google Trends - with st.expander("โ„น๏ธ About Google Trends Data", expanded=False): - st.markdown(""" - **What is Google Trends?** - - Google Trends is a public web facility that shows how often a particular search-term is entered relative to the total search-volume across various regions of the world, and in various languages. - - **What data is shown here?** - - - **Related Keywords**: Terms that are frequently searched together with your keyword - - **Interest Over Time**: How interest in your keyword has changed over the past 12 months - - **Regional Interest**: Where in the world your keyword is most popular - - **Related Queries**: What people search for before and after searching for your keyword - - **Related Topics**: Topics that are closely related to your keyword - - **How to interpret the data:** - - - Interest values range from 0 to 100, where 100 is the peak popularity for the term - - A value of 50 means the term is half as popular as the peak - - A value of 0 means there was not enough data for this term - """) - - # Create tabs for different sections - tab1, tab2, tab3, tab4, tab5 = st.tabs([ - "Related Keywords", - "Interest Over Time", - "Regional Interest", - "Related Queries", - "Related Topics" - ]) - - with tab1: - display_keywords_section(trends_data.get('related_keywords', [])) - - with tab2: - display_interest_over_time(trends_data.get('interest_over_time', pd.DataFrame())) - - with tab3: - display_regional_interest(trends_data.get('regional_interest', pd.DataFrame())) - - with tab4: - display_related_queries(trends_data.get('related_queries', pd.DataFrame())) - - with tab5: - display_related_topics(trends_data.get('related_topics', pd.DataFrame())) - - # Add a footer with data source information - st.markdown("---") - st.caption("Data source: Google Trends | Last updated: " + pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")) - -def display_keywords_section(keywords): - """Display related keywords from Google Trends in a table format.""" - if not keywords: - st.info("No related keywords data available.") - return - - st.subheader("Related Keywords") - st.write("Keywords related to your search:") - - # Add explanation about related keywords - with st.expander("โ„น๏ธ About Related Keywords", expanded=False): - st.markdown(""" - **What are Related Keywords?** - - Related keywords are terms that are frequently searched together with your main keyword. - These keywords can help you understand what topics are associated with your search term - and can be valuable for content planning and SEO strategies. - - **How to use this data:** - - - Use these keywords to expand your content strategy - - Identify gaps in your content that you could fill - - Understand what your audience is interested in - - Improve your SEO by incorporating these terms naturally in your content - """) - - # Create a DataFrame for better display - df = pd.DataFrame(keywords, columns=['Keyword']) - st.dataframe(df, use_container_width=True) - - # Add a note about the number of keywords - st.caption(f"Found {len(keywords)} related keywords") - -def display_interest_over_time(interest_df): - """Display a chart showing interest over time for a given search keyword.""" - if interest_df.empty: - st.info("No interest over time data available.") - return - - st.subheader("Interest Over Time") - - # Add explanation about interest over time - with st.expander("โ„น๏ธ About Interest Over Time", expanded=False): - st.markdown(""" - **What is Interest Over Time?** - - Interest Over Time shows how interest in your search term has changed over the past 12 months. - The data is normalized and presented on a scale from 0 to 100, where 100 is the peak popularity - for the term, 50 means the term is half as popular, and 0 means there was not enough data. - - **How to interpret this chart:** - - - Look for peaks and valleys to identify trends - - Compare with seasonal patterns or events - - Identify if interest is growing, declining, or stable - - Use this data to time your content releases for maximum impact - """) - - try: - # Ensure we have the required columns - if 'date' not in interest_df.columns: - st.error("Interest over time data is missing the 'date' column.") - return - - if 'interest' not in interest_df.columns: - st.error("Interest over time data is missing the 'interest' column.") - return - - # Create the chart - fig = px.line( - interest_df, - x='date', - y='interest', - title='Interest Over Time', - labels={'date': 'Date', 'interest': 'Interest'}, - line_shape='spline' - ) - - fig.update_layout( - xaxis_title="Date", - yaxis_title="Interest", - hovermode='x unified' - ) - - st.plotly_chart(fig, use_container_width=True) - - # Add summary statistics - if not interest_df.empty: - col1, col2, col3 = st.columns(3) - with col1: - st.metric("Average Interest", f"{interest_df['interest'].mean():.1f}") - with col2: - st.metric("Peak Interest", f"{interest_df['interest'].max():.1f}") - with col3: - st.metric("Lowest Interest", f"{interest_df['interest'].min():.1f}") - - except Exception as e: - st.error(f"Error displaying interest over time chart: {str(e)}") - logger.error(f"Error in display_interest_over_time: {e}") - -def display_regional_interest(regional_df): - """Display a chart showing interest by region for the search keyword.""" - if regional_df.empty: - st.info("No regional interest data available.") - return - - st.subheader("Regional Interest") - - # Add explanation about regional interest - with st.expander("โ„น๏ธ About Regional Interest", expanded=False): - st.markdown(""" - **What is Regional Interest?** - - Regional Interest shows how interest in your search term varies across different countries. - The data is normalized and presented on a scale from 0 to 100, where 100 is the peak popularity - for the term in that region, 50 means the term is half as popular, and 0 means there was not enough data. - - **How to interpret this map:** - - - Darker colors indicate higher interest in that region - - Lighter colors indicate lower interest - - Hover over a country to see the exact interest value - - Use this data to target your content to specific regions - """) - - try: - # Ensure we have the required columns - if 'country_code' not in regional_df.columns: - st.error("Regional interest data is missing the 'country_code' column.") - return - - if 'interest' not in regional_df.columns: - st.error("Regional interest data is missing the 'interest' column.") - return - - # Create the choropleth map - fig = go.Figure(data=go.Choropleth( - locations=regional_df['country_code'], - z=regional_df['interest'], - text=regional_df['country_code'], # This will show in the hover text - colorscale='Viridis', - colorbar_title="Interest Level", - zmin=0, - zmax=100, - marker_line_color='darkgray', - marker_line_width=0.5, - showscale=True, - colorbar=dict( - title="Interest Level", - tickformat=".0f", - tickmode="linear", - tick0=0, - dtick=20 - ) - )) - - # Update the layout for better visualization - fig.update_layout( - title=dict( - text='Regional Interest Distribution', - x=0.5, - xanchor='center' - ), - geo=dict( - showframe=False, - showcoastlines=True, - projection_type='equirectangular', - showland=True, - landcolor='lightgray', - showocean=True, - oceancolor='aliceblue', - showcountries=True, - countrycolor='darkgray' - ), - width=800, - height=500, - margin=dict(l=0, r=0, t=30, b=0) - ) - - # Display the map - st.plotly_chart(fig, use_container_width=True) - - # Display top 5 countries with highest interest - if not regional_df.empty: - st.subheader("Top Regions by Interest") - top_regions = regional_df.sort_values('interest', ascending=False).head(5) - - # Create a more visually appealing bar chart for top regions - fig_bar = go.Figure(data=[ - go.Bar( - x=top_regions['country_code'], - y=top_regions['interest'], - text=top_regions['interest'].round(1), - textposition='auto', - marker_color='rgb(55, 83, 109)' - ) - ]) - - fig_bar.update_layout( - title='Top 5 Regions by Interest Level', - xaxis_title='Region', - yaxis_title='Interest Level', - yaxis_range=[0, 100], - showlegend=False - ) - - st.plotly_chart(fig_bar, use_container_width=True) - - except Exception as e: - st.error(f"Error displaying regional interest chart: {str(e)}") - logger.error(f"Error in display_regional_interest: {e}") - -def display_related_queries(queries_df): - """Display related queries in a structured format.""" - if queries_df.empty: - st.info("No related queries data available.") - return - - st.subheader("Related Queries") - - # Add explanation about related queries - with st.expander("โ„น๏ธ About Related Queries", expanded=False): - st.markdown(""" - **What are Related Queries?** - - Related Queries show what people search for before and after searching for your keyword. - These queries can help you understand the search intent and context around your keyword. - - **How to interpret this data:** - - - The 'value' column shows the relative interest compared to your main keyword - - Higher values indicate stronger association with your keyword - - Use these queries to expand your content strategy - - Identify what questions your audience is trying to answer - """) - - try: - # Ensure we have the required columns - if 'query' not in queries_df.columns: - st.error("Related queries data is missing the 'query' column.") - return - - if 'value' not in queries_df.columns: - st.error("Related queries data is missing the 'value' column.") - return - - # Sort by value in descending order - queries_df = queries_df.sort_values('value', ascending=False) - - # Display as a table - st.dataframe(queries_df, use_container_width=True) - - # Add a note about the number of queries - st.caption(f"Found {len(queries_df)} related queries") - - except Exception as e: - st.error(f"Error displaying related queries: {str(e)}") - logger.error(f"Error in display_related_queries: {e}") - -def display_trending_searches(trending_df): - """Display trending searches in the UI.""" - if trending_df.empty: - st.info("No trending searches data available.") - return - - st.subheader("๐Ÿ“Š Trending Searches") - - # Display as numbered list with emojis - for idx, search in enumerate(trending_df[0].head(10), 1): - st.write(f"{idx}. ๐Ÿ” {search}") - -def display_realtime_trends(trends_df): - """Display realtime trending searches in the UI.""" - if trends_df.empty: - st.info("No realtime trends data available.") - return - - st.subheader("โšก Realtime Trends") - - # Create tabs for different categories - if not trends_df.empty: - # Display top 5 trends with their titles and articles - for _, row in trends_df.head(5).iterrows(): - with st.expander(f"๐Ÿ”ฅ {row.get('title', 'Trending Topic')}"): - st.write(f"**Traffic:** {row.get('traffic', 'N/A')}") - if 'articles' in row: - st.write("๐Ÿ“ฐ Related Articles:") - for article in row['articles'][:3]: # Show top 3 articles - st.write(f"- {article['title']}") - - - -def display_related_topics(topics_df): - """Display related topics in a structured format.""" - if topics_df.empty: - st.info("No related topics data available.") - return - - st.subheader("Related Topics") - - # Add explanation about related topics - with st.expander("โ„น๏ธ About Related Topics", expanded=False): - st.markdown(""" - **What are Related Topics?** - - Related Topics show broader topics that are associated with your search term. - These topics can help you understand the broader context and themes related to your keyword. - - **How to interpret this data:** - - - The 'value' column shows the relative interest compared to your main keyword - - Higher values indicate stronger association with your keyword - - Use these topics to understand the broader context of your keyword - - Identify themes that might be relevant to your content strategy - """) - - try: - # Ensure we have the required columns - if 'topic' not in topics_df.columns: - st.error("Related topics data is missing the 'topic' column.") - return - - if 'value' not in topics_df.columns: - st.error("Related topics data is missing the 'value' column.") - return - - # Sort by value in descending order - topics_df = topics_df.sort_values('value', ascending=False) - - # Display as a table - st.dataframe(topics_df, use_container_width=True) - - # Add a note about the number of topics - st.caption(f"Found {len(topics_df)} related topics") - - except Exception as e: - st.error(f"Error displaying related topics: {str(e)}") - logger.error(f"Error in display_related_topics: {e}") - -def process_trends_data(trends_data): - """ - Process and format Google Trends data for display. - - Args: - trends_data (dict): Raw Google Trends data - - Returns: - dict: Formatted data ready for display - """ - if not trends_data: - return {} - - processed_data = {} - - # Process related keywords - if 'related_keywords' in trends_data: - processed_data['related_keywords'] = trends_data['related_keywords'] - - # Process interest over time - if 'interest_over_time' in trends_data and not trends_data['interest_over_time'].empty: - processed_data['interest_over_time'] = trends_data['interest_over_time'] - - # Process regional interest - if 'regional_interest' in trends_data and not trends_data['regional_interest'].empty: - processed_data['regional_interest'] = trends_data['regional_interest'] - - # Process related queries - if 'related_queries' in trends_data and not trends_data['related_queries'].empty: - processed_data['related_queries'] = trends_data['related_queries'] - - # Process related topics - if 'related_topics' in trends_data and not trends_data['related_topics'].empty: - processed_data['related_topics'] = trends_data['related_topics'] - - return processed_data \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/keyword_web_researcher.py b/ToBeMigrated/alwrity_ui/keyword_web_researcher.py deleted file mode 100644 index a3f76dfc..00000000 --- a/ToBeMigrated/alwrity_ui/keyword_web_researcher.py +++ /dev/null @@ -1,585 +0,0 @@ -import os -import time -import logging -import streamlit as st -from datetime import datetime - -from lib.ai_web_researcher.gpt_online_researcher import gpt_web_researcher -from lib.utils.read_main_config_params import read_return_config_section - -# Configure module-level logging -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - -# Create console handler if it doesn't exist -if not logger.handlers: - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - -def reload_env_variables(): - """Reload environment variables from .env file.""" - try: - from dotenv import load_dotenv - load_dotenv(override=True) - return True - except Exception as e: - logger.error(f"Failed to reload environment variables: {str(e)}") - return False - -def save_api_key_to_env(key_name, key_value): - """Save API key to .env file.""" - try: - env_path = os.path.join(os.getcwd(), '.env') - - # Read existing .env content - existing_content = {} - if os.path.exists(env_path): - with open(env_path, 'r') as f: - for line in f: - if '=' in line: - key, value = line.strip().split('=', 1) - existing_content[key] = value - - # Update or add new key - existing_content[key_name] = key_value - - # Write back to .env - with open(env_path, 'w') as f: - for key, value in existing_content.items(): - f.write(f"{key}={value}\n") - - # Update environment variable and reload all env vars - os.environ[key_name] = key_value - if reload_env_variables(): - return True - return False - except Exception as e: - logger.error(f"Failed to save API key to .env: {str(e)}") - return False - -def validate_api_keys(): - """Validate required API keys and return their status.""" - - logger.info("Validating API keys") - - # Get API keys - api_keys = { - 'SERPER_API_KEY': os.getenv('SERPER_API_KEY'), - 'METAPHOR_API_KEY': os.getenv('METAPHOR_API_KEY'), - 'TAVILY_API_KEY': os.getenv('TAVILY_API_KEY'), - 'FIRECRAWL_API_KEY': os.getenv('FIRECRAWL_API_KEY') - } - - # Test SERPER_API_KEY validity - if api_keys['SERPER_API_KEY']: - try: - # Make a test request - import requests - test_url = "https://google.serper.dev/search" - headers = { - 'X-API-KEY': api_keys['SERPER_API_KEY'], - 'Content-Type': 'application/json' - } - test_payload = {"q": "test", "gl": "us", "hl": "en", "num": 1} - - response = requests.post(test_url, headers=headers, json=test_payload) - api_keys['SERPER_API_KEY_VALID'] = response.status_code == 200 - - if not api_keys['SERPER_API_KEY_VALID']: - logger.error(f"SERPER_API_KEY validation failed: {response.status_code} - {response.text}") - except Exception as e: - logger.error(f"Error validating SERPER_API_KEY: {str(e)}") - api_keys['SERPER_API_KEY_VALID'] = False - else: - api_keys['SERPER_API_KEY_VALID'] = False - - return api_keys - -def do_web_research(): - """Main function to perform web research based on user input.""" - - # Reset session state variables for this research operation - if 'metaphor_results_displayed' in st.session_state: - del st.session_state.metaphor_results_displayed - - logger.info("Starting do_web_research function") - - try: - # Get API keys without validation - api_keys = { - 'SERPER_API_KEY': os.getenv('SERPER_API_KEY'), - 'METAPHOR_API_KEY': os.getenv('METAPHOR_API_KEY'), - 'TAVILY_API_KEY': os.getenv('TAVILY_API_KEY'), - 'FIRECRAWL_API_KEY': os.getenv('FIRECRAWL_API_KEY') - } - - if not api_keys['SERPER_API_KEY']: - st.error(""" - ๐Ÿšซ SERPER_API_KEY is missing. Please configure your API key. - """) - with st.popover("โš™๏ธ Configure API Keys"): - st.markdown(""" - ### API Key Configuration - Enter your API keys below to enable research features. - """) - - # SERPER API Key - serper_col1, serper_col2 = st.columns([3, 1]) - with serper_col1: - serper_key = st.text_input( - "Serper API Key", - type="password", - placeholder="Enter your Serper API key", - help="Get your key at https://serper.dev" - ) - test_key = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_serper_key") - with serper_col2: - if st.button("Save Serper", use_container_width=True): - if serper_key: - if test_key: - # Test the API key - try: - import requests - test_url = "https://google.serper.dev/search" - headers = { - 'X-API-KEY': serper_key, - 'Content-Type': 'application/json' - } - test_payload = {"q": "test", "gl": "us", "hl": "en", "num": 1} - response = requests.post(test_url, headers=headers, json=test_payload) - - if response.status_code == 200: - if save_api_key_to_env('SERPER_API_KEY', serper_key): - st.success("โœ… Serper API key validated and saved!") - st.rerun() - else: - st.error("Failed to save API key") - else: - st.error(f"API key validation failed: {response.status_code} - {response.text}") - except Exception as e: - st.error(f"Error validating API key: {str(e)}") - else: - # Skip validation and save directly - if save_api_key_to_env('SERPER_API_KEY', serper_key): - st.success("โœ… Serper API key saved!") - time.sleep(0.5) # Small delay to ensure the key is saved - st.rerun() - else: - st.error("Failed to save API key") - - # METAPHOR API Key - if not api_keys.get('METAPHOR_API_KEY'): - metaphor_col1, metaphor_col2 = st.columns([3, 1]) - with metaphor_col1: - metaphor_key = st.text_input( - "Metaphor API Key", - type="password", - placeholder="Enter your Metaphor API key", - help="Get your key at https://metaphor.systems" - ) - test_metaphor = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_metaphor_key") - with metaphor_col2: - if st.button("Save Metaphor", use_container_width=True): - if metaphor_key: - if test_metaphor: - # Test the API key - try: - import requests - test_url = "https://api.metaphor.systems/v1/search" - headers = { - 'Authorization': f'Bearer {metaphor_key}', - 'Content-Type': 'application/json' - } - test_payload = {"query": "test", "numResults": 1} - response = requests.post(test_url, headers=headers, json=test_payload) - - if response.status_code == 200: - if save_api_key_to_env('METAPHOR_API_KEY', metaphor_key): - st.success("โœ… Metaphor API key validated and saved!") - st.rerun() - else: - st.error("Failed to save API key") - else: - st.error(f"API key validation failed: {response.status_code} - {response.text}") - except Exception as e: - st.error(f"Error validating API key: {str(e)}") - else: - # Skip validation and save directly - if save_api_key_to_env('METAPHOR_API_KEY', metaphor_key): - st.success("โœ… Metaphor API key saved!") - st.rerun() - else: - st.error("Failed to save API key") - - # TAVILY API Key - if not api_keys.get('TAVILY_API_KEY'): - tavily_col1, tavily_col2 = st.columns([3, 1]) - with tavily_col1: - tavily_key = st.text_input( - "Tavily API Key", - type="password", - placeholder="Enter your Tavily API key", - help="Get your key at https://tavily.com" - ) - test_tavily = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_tavily_key") - with tavily_col2: - if st.button("Save Tavily", use_container_width=True): - if tavily_key: - if test_tavily: - # Test the API key - try: - import requests - test_url = "https://api.tavily.com/v1/search" - headers = { - 'Authorization': f'Bearer {tavily_key}', - 'Content-Type': 'application/json' - } - test_payload = {"query": "test", "max_results": 1} - response = requests.post(test_url, headers=headers, json=test_payload) - - if response.status_code == 200: - if save_api_key_to_env('TAVILY_API_KEY', tavily_key): - st.success("โœ… Tavily API key validated and saved!") - st.rerun() - else: - st.error("Failed to save API key") - else: - st.error(f"API key validation failed: {response.status_code} - {response.text}") - except Exception as e: - st.error(f"Error validating API key: {str(e)}") - else: - # Skip validation and save directly - if save_api_key_to_env('TAVILY_API_KEY', tavily_key): - st.success("โœ… Tavily API key saved!") - st.rerun() - else: - st.error("Failed to save API key") - - # FIRECRAWL API Key - if not api_keys.get('FIRECRAWL_API_KEY'): - firecrawl_col1, firecrawl_col2 = st.columns([3, 1]) - with firecrawl_col1: - firecrawl_key = st.text_input( - "Firecrawl API Key", - type="password", - placeholder="Enter your Firecrawl API key", - help="Get your key at https://firecrawl.co" - ) - test_firecrawl = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving", key="test_firecrawl_key") - with firecrawl_col2: - if st.button("Save Firecrawl", use_container_width=True): - if firecrawl_key: - if test_firecrawl: - # Test the API key - try: - import requests - test_url = "https://api.firecrawl.co/v1/search" - headers = { - 'Authorization': f'Bearer {firecrawl_key}', - 'Content-Type': 'application/json' - } - test_payload = {"query": "test", "limit": 1} - response = requests.post(test_url, headers=headers, json=test_payload) - - if response.status_code == 200: - if save_api_key_to_env('FIRECRAWL_API_KEY', firecrawl_key): - st.success("โœ… Firecrawl API key validated and saved!") - st.rerun() - else: - st.error("Failed to save API key") - else: - st.error(f"API key validation failed: {response.status_code} - {response.text}") - except Exception as e: - st.error(f"Error validating API key: {str(e)}") - else: - # Skip validation and save directly - if save_api_key_to_env('FIRECRAWL_API_KEY', firecrawl_key): - st.success("โœ… Firecrawl API key saved!") - st.rerun() - else: - st.error("Failed to save API key") - - st.markdown(""" - --- - ### Need Help? - 1. Click the links above to get your API keys - 2. Enter the keys in the fields above - 3. Click Save to store them securely - 4. The app will refresh automatically - """) - return - - # Initialize session state for research options - if "research_options" not in st.session_state: - st.session_state.research_options = { - "primary_keywords": "", - "related_keywords": "", - "target_audience": ["General"], - "content_type": ["Blog Posts"], - "search_depth": 3, - "geo_location": "us", - "search_language": "en", - "num_results": 10, - "time_range": "past month", - "include_domains": "", - "similar_url": "", - "search_mode": "google" # Default search mode - } - - # Define the research options dialog function - @st.dialog("๐Ÿ” Research Options", width="large") - def show_research_options(): - tab1, tab2 = st.tabs(["Basic", "Advanced"]) - - with tab1: - st.session_state.research_options["related_keywords"] = st.text_input( - "Related Keywords", - value=st.session_state.research_options["related_keywords"], - placeholder="Enter related terms...", - help="Additional keywords to provide context and expand research" - ) - - st.session_state.research_options["target_audience"] = st.multiselect( - "Target Audience", - ["General", "Technical", "Business", "Academic", "Youth", "Senior"], - default=st.session_state.research_options["target_audience"], - help="Select your target audience to focus research" - ) - - st.session_state.research_options["content_type"] = st.multiselect( - "Content Type", - ["Blog Posts", "Articles", "Social Media", "Whitepapers", "Tutorials", "Videos"], - default=st.session_state.research_options["content_type"], - help="Select content types to tailor research results" - ) - - st.session_state.research_options["search_depth"] = st.slider( - "Search Depth", - min_value=1, - max_value=5, - value=st.session_state.research_options["search_depth"], - help="Higher depth means more comprehensive but slower research" - ) - - with tab2: - col1, col2 = st.columns(2) - with col1: - st.session_state.research_options["geo_location"] = st.selectbox( - "Geographic Location", - options=["us", "in", "uk", "fr", "de", "jp", "custom"], - index=["us", "in", "uk", "fr", "de", "jp"].index(st.session_state.research_options["geo_location"]), - help="Target specific geographic region for research" - ) - - st.session_state.research_options["num_results"] = st.number_input( - "Number of Results", - min_value=1, - max_value=100, - value=st.session_state.research_options["num_results"], - help="Number of results to analyze" - ) - - with col2: - st.session_state.research_options["search_language"] = st.selectbox( - "Search Language", - options=["en", "hi", "fr", "de", "es", "custom"], - index=["en", "hi", "fr", "de", "es"].index(st.session_state.research_options["search_language"]), - help="Primary language for search results" - ) - - st.session_state.research_options["time_range"] = st.selectbox( - "Time Range", - options=["past day", "past week", "past month", "past year", "anytime"], - index=["past day", "past week", "past month", "past year", "anytime"].index(st.session_state.research_options["time_range"]), - help="Time period for research results" - ) - - # Add the technical options to the Advanced tab - st.markdown("---") - st.markdown("### Advanced Search Parameters") - - st.session_state.research_options["include_domains"] = st.text_input( - "Include Domains", - value=st.session_state.research_options["include_domains"], - placeholder="example.com, another.com", - help="Specific domains to include in research" - ) - - st.session_state.research_options["similar_url"] = st.text_input( - "Similar URL", - value=st.session_state.research_options["similar_url"], - placeholder="https://example.com/page", - help="Find content similar to this URL" - ) - - col1, col2 = st.columns([1, 1]) - with col1: - if st.button("Apply", use_container_width=True, type="primary"): - st.session_state.show_options_dialog = False - st.rerun() - with col2: - if st.button("Cancel", use_container_width=True): - st.session_state.show_options_dialog = False - st.rerun() - - # Main interface - st.title("ALwrity Web Researcher") - - # Primary search area with help popover - with st.popover("โ„น๏ธ Keyword Research Tips"): - st.markdown(""" - ### How to Get Better Results - 1. **Primary Keywords**: Your main topic or focus - 2. **Related Keywords**: Supporting terms that add context - 3. **Search Depth**: Higher depth = more comprehensive but slower - 4. **Target Audience**: Affects content recommendations - 5. **Content Type**: Influences research focus - 6. **Search Mode**: Choose between traditional web research(Google), AI-powered search(Tavily and Metaphor) and Deep Researcher - """) - - col1, col2 = st.columns([3, 1]) - with col1: - st.session_state.research_options["primary_keywords"] = st.text_input( - "Primary Keywords", - value=st.session_state.research_options["primary_keywords"], - placeholder="Enter main keywords for research...", - help="Enter your main topic or focus keywords" - ) - with col2: - if st.button("Research Options", use_container_width=True): - show_research_options() - - # Research method selection in main container - st.markdown("### Select Research Method") - search_options = [ - ("google", "๐Ÿ” Google Search", "Traditional web research with AI analysis", bool(api_keys['SERPER_API_KEY'])), - ("ai", "๐Ÿค– AI Search", "Neural search with semantic analysis", bool(api_keys['METAPHOR_API_KEY'] and api_keys['TAVILY_API_KEY'])), - ("deep", "๐Ÿ”ฌ Deep Search (Beta)", "Advanced deep web analysis", bool(all(api_keys.values()))) - ] - - enabled_options = [opt[1] for opt in search_options if opt[3]] - if enabled_options: - selected_option = st.radio( - "Search Method", - options=enabled_options, - horizontal=True, - help="Choose your preferred research method" - ) - - # Map the selected option to the search_mode value - for mode, label, _, _ in search_options: - if label == selected_option: - st.session_state.research_options["search_mode"] = mode - break - else: - st.warning("No search methods available. Please configure API keys.") - - # Execute search button - if st.button("๐Ÿ” Start Research", type="primary", use_container_width=True): - if not st.session_state.research_options["primary_keywords"]: - st.warning("โš ๏ธ Please enter primary keywords for research") - return - - try: - # Create compact progress display - progress_container = st.container() - with progress_container: - status_col, progress_col = st.columns([3, 1]) - with status_col: - status_display = st.empty() - status_display.info("๐Ÿš€ Initializing research...") - with progress_col: - progress_bar = st.progress(0) - - def update_progress(message, progress=None, level="info"): - """Update progress bar and status display. - - Args: - message (str): The message to display - progress (float, optional): Progress value between 0 and 100. Will be converted to 0.0-1.0 - level (str, optional): Message level (info, warning, error, success) - """ - if progress is not None: - # Convert percentage to decimal (0.0-1.0) - progress = float(progress) / 100.0 - # Ensure progress stays within bounds - progress = max(0.0, min(1.0, progress)) - progress_bar.progress(progress) - - if level == "error": - status_display.error(f"๐Ÿšซ {message}") - elif level == "warning": - status_display.warning(f"โš ๏ธ {message}") - elif level == "success": - status_display.success(f"โœจ {message}") - else: - status_display.info(f"๐Ÿ”„ {message}") - logger.debug(f"Progress update [{level}]: {message}") - - # Execute search with all parameters - try: - update_progress("Starting search...", 0.25) - logger.info(f"Executing web research with mode: {st.session_state.research_options['search_mode']}") - - # Create base parameters - research_params = { - "search_keywords": st.session_state.research_options["primary_keywords"], - "search_mode": st.session_state.research_options["search_mode"], - "related_keywords": st.session_state.research_options["related_keywords"], - "target_audience": st.session_state.research_options["target_audience"], - "content_type": st.session_state.research_options["content_type"], - "search_depth": st.session_state.research_options["search_depth"], - "geo_location": st.session_state.research_options["geo_location"], - "search_language": st.session_state.research_options["search_language"], - "num_results": st.session_state.research_options["num_results"], - "time_range": st.session_state.research_options["time_range"], - "include_domains": st.session_state.research_options["include_domains"], - "similar_url": st.session_state.research_options["similar_url"] - } - - # Add UI-specific parameters - research_params.update({ - "status_container": status_display, - "update_progress": update_progress - }) - - # For AI search mode, ensure search_keywords is passed correctly - if st.session_state.research_options["search_mode"] == "ai": - research_params["tavily_params"] = { - "max_results": st.session_state.research_options["num_results"], - "search_depth": "advanced" if st.session_state.research_options["search_depth"] > 2 else "basic", - "time_range": st.session_state.research_options["time_range"], - "include_domains": st.session_state.research_options["include_domains"].split(",") if st.session_state.research_options["include_domains"] else [""] - } - # Pass search_keywords as a positional argument - research_params["tavily_search_keywords"] = st.session_state.research_options["primary_keywords"] - - # Execute the research - web_research_result = gpt_web_researcher(**research_params) - - if web_research_result: - status_display.success("โœจ Research completed!") - - # Display results in an organized way - with st.expander("๐Ÿ“Š Research Results", expanded=False): - st.write(web_research_result) - else: - st.warning("No results found for your search") - - except Exception as e: - error_msg = f"Research failed: {str(e)}" - logger.error(error_msg, exc_info=True) - st.error(f"๐Ÿšซ Research failed: {error_msg}") - - except Exception as e: - logger.error(f"Unexpected error in web research: {e}", exc_info=True) - st.error("๐Ÿšซ An unexpected error occurred. Please try again.") - except Exception as e: - logger.error(f"Unexpected error in web research: {e}", exc_info=True) - st.error("๐Ÿšซ An unexpected error occurred. Please try again.") \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/navigation_styles.py b/ToBeMigrated/alwrity_ui/navigation_styles.py deleted file mode 100644 index 2e3e3a98..00000000 --- a/ToBeMigrated/alwrity_ui/navigation_styles.py +++ /dev/null @@ -1,331 +0,0 @@ -import streamlit as st - -def apply_navigation_styles(): - """Apply navigation and UI setup specific styling.""" - st.markdown(""" - - """, unsafe_allow_html=True) - -def apply_compact_layout(): - """Apply compact layout styling for specific pages.""" - st.markdown(""" - - """, unsafe_allow_html=True) \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/settings_page.py b/ToBeMigrated/alwrity_ui/settings_page.py deleted file mode 100644 index 3bdcbefd..00000000 --- a/ToBeMigrated/alwrity_ui/settings_page.py +++ /dev/null @@ -1,973 +0,0 @@ -import streamlit as st -from loguru import logger -import asyncio -from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService -from lib.personalization.style_analyzer import StyleAnalyzer -from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header -import sys - -# Configure logger -logger.remove() # Remove default handler -logger.add( - "logs/settings_page.log", - rotation="500 MB", - retention="10 days", - level="DEBUG", - format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", - backtrace=True, - diagnose=True -) -logger.add( - sys.stdout, - level="INFO", - format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}" -) - -def display_style_analysis(analysis_results: dict): - """Display the style analysis results in a structured format with premium styling.""" - try: - # Writing Style Section - st.markdown(""" -
-
๐ŸŽจ
-

Writing Style Analysis

-
- """, unsafe_allow_html=True) - - writing_style = analysis_results.get("writing_style", {}) - col1, col2 = st.columns(2) - with col1: - st.markdown(f""" -
-
- Tone: - {writing_style.get("tone", "N/A")} -
-
- Voice: - {writing_style.get("voice", "N/A")} -
-
- """, unsafe_allow_html=True) - with col2: - st.markdown(f""" -
-
- Complexity: - {writing_style.get("complexity", "N/A")} -
-
- Engagement: - {writing_style.get("engagement_level", "N/A")} -
-
- """, unsafe_allow_html=True) - - # Content Characteristics Section - st.markdown(""" -
-
๐Ÿ“Š
-

Content Characteristics

-
- """, unsafe_allow_html=True) - - content_chars = analysis_results.get("content_characteristics", {}) - col1, col2 = st.columns(2) - with col1: - st.markdown(f""" -
-
- Sentence Structure: - {content_chars.get("sentence_structure", "N/A")} -
-
- Vocabulary Level: - {content_chars.get("vocabulary_level", "N/A")} -
-
- """, unsafe_allow_html=True) - with col2: - st.markdown(f""" -
-
- Organization: - {content_chars.get("paragraph_organization", "N/A")} -
-
- Content Flow: - {content_chars.get("content_flow", "N/A")} -
-
- """, unsafe_allow_html=True) - - # Target Audience Section - st.markdown(""" -
-
๐ŸŽฏ
-

Target Audience

-
- """, unsafe_allow_html=True) - - target_audience = analysis_results.get("target_audience", {}) - col1, col2 = st.columns(2) - with col1: - st.markdown(f""" -
-
- Demographics: - {', '.join(target_audience.get("demographics", ["N/A"]))} -
-
- Expertise Level: - {target_audience.get("expertise_level", "N/A")} -
-
- """, unsafe_allow_html=True) - with col2: - st.markdown(f""" -
-
- Industry Focus: - {target_audience.get("industry_focus", "N/A")} -
-
- Geographic Focus: - {target_audience.get("geographic_focus", "N/A")} -
-
- """, unsafe_allow_html=True) - - # Recommended Settings Section - st.markdown(""" -
-
โš™๏ธ
-

Recommended Settings

-
- """, unsafe_allow_html=True) - - recommended = analysis_results.get("recommended_settings", {}) - st.markdown(f""" -
-
-
๐ŸŽญ
-
Writing Tone
-
{recommended.get("writing_tone", "N/A")}
-
-
-
๐Ÿ‘ฅ
-
Target Audience
-
{recommended.get("target_audience", "N/A")}
-
-
-
๐Ÿ“
-
Content Type
-
{recommended.get("content_type", "N/A")}
-
-
-
๐ŸŽจ
-
Creativity Level
-
{recommended.get("creativity_level", "N/A")}
-
-
- """, unsafe_allow_html=True) - - except Exception as e: - logger.error(f"Error displaying style analysis: {str(e)}") - st.error(f"Error displaying analysis results: {str(e)}") - -def render_settings_page(): - """Renders the settings page with premium glassmorphic design and all configuration options in tabs""" - - # Apply common dashboard styling - apply_dashboard_style() - - # Add settings-specific CSS for tabs and form elements - st.markdown(""" - - """, unsafe_allow_html=True) - - # Use the common dashboard header - render_dashboard_header( - "โš™๏ธ Settings & Configuration", - "Customize your AI experience with precision controls for content generation, personalization, and optimization. Fine-tune every aspect to match your unique requirements and style." - ) - - # Create tabs for different settings categories with premium styling - tabs = st.tabs([ - "๐Ÿ“ Content", - "๐Ÿ–ผ๏ธ Images", - "๐Ÿค– LLM", - "๐Ÿ” Search", - "๐ŸŽจ AI Personalization" - ]) - - # Content Settings Tab - with tabs[0]: - st.markdown(""" -
-

๐Ÿ“ Content Personalization

-
- """, unsafe_allow_html=True) - - col1, col2 = st.columns(2) - - with col1: - blog_length = st.text_input( - "**Content Length (words)**", - value="2000", - key="settings_blog_length", - help="Approximate word count for blogs. Note: Actual length may vary based on GPT provider and max token count." - ) - - blog_tone_options = ["Casual", "Professional", "How-to", "Beginner", "Research", "Programming", "Social Media", "Customize"] - blog_tone = st.selectbox( - "**Content Tone**", - options=blog_tone_options, - key="settings_blog_tone", - help="Select the desired tone for the blog content." - ) - - # Initialize custom_tone variable - custom_tone = "" - if blog_tone == "Customize": - custom_tone = st.text_input( - "Enter the tone of your content", - key="settings_custom_tone", - help="Specify the tone of your content." - ) - if custom_tone: - blog_tone = custom_tone - else: - st.warning("Please specify the tone of your content.") - - blog_demographic_options = ["Professional", "Gen-Z", "Tech-savvy", "Student", "Digital Marketing", "Customize"] - blog_demographic = st.selectbox( - "**Target Audience**", - options=blog_demographic_options, - key="settings_blog_demographic", - help="Select the primary audience for the blog content." - ) - - with col2: - blog_type = st.selectbox( - "**Content Type**", - options=["Informational", "Commercial", "Company", "News", "Finance", "Competitor", "Programming", "Scholar"], - key="settings_blog_type", - help="Select the category that best describes the blog content." - ) - - blog_language = st.selectbox( - "**Content Language**", - options=["English", "Spanish", "German", "Chinese", "Arabic", "Nepali", "Hindi", "Hindustani", "Customize"], - key="settings_blog_language", - help="Select the language in which the blog will be written." - ) - - blog_output_format = st.selectbox( - "**Content Output Format**", - options=["markdown", "HTML", "plaintext"], - key="settings_blog_output_format", - help="Select the format for the blog output." - ) - - # Images Settings Tab - with tabs[1]: - st.markdown(""" -
-

๐Ÿ–ผ๏ธ Images Personalization

-
- """, unsafe_allow_html=True) - - col1, col2 = st.columns(2) - - with col1: - image_generation_model = st.selectbox( - "**Image Generation Model**", - options=["stable-diffusion", "dalle2", "dalle3"], - key="settings_image_model", - help="Select the model to generate images for the blog." - ) - - with col2: - number_of_blog_images = st.number_input( - "**Number of Blog Images**", - value=1, - min_value=1, - max_value=10, - key="settings_number_of_images", - help="Specify the number of images to include in the blog." - ) - - # LLM Settings Tab - with tabs[2]: - st.markdown(""" -
-

๐Ÿค– LLM Personalization

-
- """, unsafe_allow_html=True) - - col1, col2 = st.columns(2) - - with col1: - gpt_provider = st.selectbox( - "**GPT Provider**", - options=["google", "openai", "minstral"], - key="settings_gpt_provider", - help="Select the provider for the GPT model." - ) - - model = st.text_input( - "**Model**", - value="gemini-1.5-flash-latest", - key="settings_model", - help="Specify the model version to use from the selected provider." - ) - - temperature = st.slider( - "**Temperature**", - min_value=0.1, - max_value=1.0, - value=0.7, - step=0.1, - key="settings_temperature", - help="Controls the creativity level of the generated text." - ) - - max_tokens = st.selectbox( - "**Max Tokens**", - options=[500, 1000, 2000, 4000, 16000, 32000, 64000], - index=3, - key="settings_max_tokens", - help="Maximum length of the output sequence." - ) - - with col2: - top_p = st.slider( - "**Top-p**", - min_value=0.0, - max_value=1.0, - value=0.9, - step=0.1, - key="settings_top_p", - help="Controls diversity in text generation." - ) - - frequency_penalty = st.slider( - "**Frequency Penalty**", - min_value=0.0, - max_value=2.0, - value=1.0, - step=0.1, - key="settings_frequency_penalty", - help="Reduces word repetition in output." - ) - - # Search Settings Tab - with tabs[3]: - st.markdown(""" -
-

๐Ÿ” Search Engine Personalization

-
- """, unsafe_allow_html=True) - - col1, col2 = st.columns(2) - - with col1: - geographic_location = st.selectbox( - "**Geographic Location**", - options=["us", "in", "fr", "cn"], - key="settings_geographic_location", - help="Select the geographic location for tailoring search results." - ) - - search_language = st.selectbox( - "**Search Language**", - options=["en", "zn-cn", "de", "hi"], - key="settings_search_language", - help="Select the language for the search results." - ) - - number_of_results = st.number_input( - "**Number of Results**", - value=10, - min_value=1, - max_value=20, - key="settings_number_of_results", - help="Specify the number of search results to retrieve." - ) - - with col2: - time_range = st.selectbox( - "**Time Range**", - options=["anytime", "past day", "past week", "past month", "past year"], - key="settings_time_range", - help="Select the time range for filtering search results." - ) - - include_domains = st.text_input( - "**Include Domains**", - value="", - key="settings_include_domains", - help="List specific domains to include in search results (comma-separated)." - ) - - similar_url = st.text_input( - "**Similar URL**", - value="", - key="settings_similar_url", - help="Provide a URL to find similar results." - ) - - # AI Personalization Tab - with tabs[4]: - st.markdown(""" -
-

๐ŸŽจ AI Style Analysis

-
- """, unsafe_allow_html=True) - - st.markdown(""" -
-

- Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations for optimal AI content generation. -

-
- """, unsafe_allow_html=True) - - # Create two columns for the layout - col1, col2 = st.columns([2, 1]) - - with col1: - # Website URL input - st.markdown("#### ๐ŸŒ Website URL Analysis") - url = st.text_input( - "Enter your website URL", - placeholder="https://example.com", - key="settings_website_url", - help="Provide your website URL to analyze your content style. Leave empty if you want to provide written samples instead." - ) - - # Alternative: Written samples - if not url: - st.markdown("#### ๐Ÿ“ Written Samples") - st.markdown(""" -
-

- No website URL? No problem! You can provide written samples of your content instead. - Share your best articles, blog posts, or any content that represents your writing style. -

-
- """, unsafe_allow_html=True) - samples = st.text_area( - "Paste your content samples here", - key="settings_content_samples", - help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style.", - height=200 - ) - - with col2: - st.markdown("#### ๐ŸŽฏ Analysis Features") - st.markdown(""" -
-
-

โœจ Writing Style: Tone, voice, complexity analysis

-

๐Ÿ“Š Content Analysis: Structure and vocabulary assessment

-

๐ŸŽฏ Audience Insights: Target demographic identification

-

โš™๏ธ AI Recommendations: Personalized settings optimization

-
-
- """, unsafe_allow_html=True) - - # Add spacing between categories - st.markdown("
", unsafe_allow_html=True) - - if st.button("๐ŸŽจ Analyze Writing Style", use_container_width=True, key="settings_analyze_style", type="primary"): - if url: - with st.status("Starting style analysis...", expanded=True) as status: - try: - # Step 1: Initialize crawler - status.update(label="Step 1/4: Initializing web crawler...", state="running") - crawler_service = AsyncWebCrawlerService() - - # Step 2: Crawl website - status.update(label="Step 2/4: Crawling website content...", state="running") - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - result = loop.run_until_complete(crawler_service.crawl_website(url)) - loop.close() - - if result.get('success', False): - content = result.get('content', {}) - - # Step 3: Initialize style analyzer - status.update(label="Step 3/4: Analyzing content style...", state="running") - style_analyzer = StyleAnalyzer() - - # Step 4: Perform style analysis - status.update(label="Step 4/4: Generating style recommendations...", state="running") - style_analysis = style_analyzer.analyze_content_style(content) - - if style_analysis.get('error'): - status.update(label="Analysis failed", state="error") - st.error(f"Style analysis failed: {style_analysis['error']}") - else: - status.update(label="Analysis complete!", state="complete") - # Display style analysis results - display_style_analysis(style_analysis) - - # Display original content in tabs - tab1, tab2, tab3 = st.tabs(["๐Ÿ“„ Content", "๐Ÿ“‹ Metadata", "๐Ÿ”— Links"]) - - with tab1: - st.markdown("#### Main Content") - st.markdown(f""" -
-
- {content.get('main_content', 'No content found')} -
-
- """, unsafe_allow_html=True) - - with tab2: - st.markdown("#### Website Metadata") - st.markdown(f""" -
-
- Title: - {content.get('title', 'No title found')} -
-
- Description: - {content.get('description', 'No description found')} -
-
- """, unsafe_allow_html=True) - - with tab3: - st.markdown("#### Extracted Links") - links = content.get('links', []) - if links: - for link in links[:10]: # Show first 10 links - st.markdown(f""" - - """, unsafe_allow_html=True) - else: - st.markdown("No links found in the content.") - else: - status.update(label="Crawling failed", state="error") - st.error("Failed to crawl the website. Please check the URL and try again.") - except Exception as e: - status.update(label="Analysis failed", state="error") - st.error(f"An error occurred during analysis: {str(e)}") - elif samples: - with st.status("Starting style analysis...", expanded=True) as status: - try: - # Initialize style analyzer - status.update(label="Analyzing content style...", state="running") - style_analyzer = StyleAnalyzer() - - # Perform style analysis - style_analysis = style_analyzer.analyze_content_style({"main_content": samples}) - - if style_analysis.get('error'): - status.update(label="Analysis failed", state="error") - st.error(f"Style analysis failed: {style_analysis['error']}") - else: - status.update(label="Analysis complete!", state="complete") - # Display style analysis results - display_style_analysis(style_analysis) - except Exception as e: - status.update(label="Analysis failed", state="error") - st.error(f"An error occurred during analysis: {str(e)}") - else: - st.warning("Please provide either a website URL or content samples to analyze.") - - # Save Settings Button with premium styling - st.markdown("
", unsafe_allow_html=True) - if st.button("๐Ÿ’พ Save All Settings", type="primary", use_container_width=True, key="settings_save_button"): - # Save all settings to session state - st.session_state.update({ - 'blog_length': blog_length, - 'blog_tone': blog_tone, - 'blog_demographic': blog_demographic, - 'blog_type': blog_type, - 'blog_language': blog_language, - 'blog_output_format': blog_output_format, - 'image_generation_model': image_generation_model, - 'number_of_blog_images': number_of_blog_images, - 'gpt_provider': gpt_provider, - 'model': model, - 'temperature': temperature, - 'top_p': top_p, - 'max_tokens': max_tokens, - 'frequency_penalty': frequency_penalty, - 'geographic_location': geographic_location, - 'search_language': search_language, - 'number_of_results': number_of_results, - 'time_range': time_range, - 'include_domains': include_domains, - 'similar_url': similar_url - }) - st.success("โœ… Settings saved successfully! Your preferences have been applied to all AI tools.") - - # Show a summary of saved settings - st.markdown(""" -
-

๐Ÿ“‹ Settings Summary

-
-

Content: {length} words, {tone} tone, {audience} audience

-

Images: {images} images using {model}

-

AI Model: {provider} - {ai_model}

-

Search: {location} region, {results} results

-
-
- """.format( - length=blog_length, - tone=blog_tone, - audience=blog_demographic, - images=number_of_blog_images, - model=image_generation_model, - provider=gpt_provider, - ai_model=model, - location=geographic_location, - results=number_of_results - ), unsafe_allow_html=True) \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/similar_analysis.py b/ToBeMigrated/alwrity_ui/similar_analysis.py deleted file mode 100644 index d7e844a5..00000000 --- a/ToBeMigrated/alwrity_ui/similar_analysis.py +++ /dev/null @@ -1,374 +0,0 @@ -import streamlit as st -from lib.ai_web_researcher.metaphor_basic_neural_web_search import metaphor_find_similar -from datetime import datetime, timedelta -import re -import urllib.parse -import logging - -# Configure logging -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - -# Create console handler if it doesn't exist -if not logger.handlers: - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - -def is_valid_url(url): - """ - Check if the provided string is a valid URL. - - Args: - url (str): The URL to validate - - Returns: - bool: True if valid, False otherwise - """ - try: - result = urllib.parse.urlparse(url) - is_valid = all([result.scheme, result.netloc]) - logger.debug(f"URL validation for {url}: {is_valid}") - return is_valid - except Exception as e: - logger.error(f"URL validation error for {url}: {str(e)}") - return False - -def competitor_analysis(): - logger.info("Starting competitor analysis") - - # Initialize session state for progress bar visibility - if 'show_progress' not in st.session_state: - st.session_state.show_progress = True - logger.debug("Initialized show_progress session state") - - st.title("Competitor Analysis") - st.markdown("""**Use Cases:** - - Know similar companies and alternatives for the given URL. - - Write listicles, similar companies, Top tools, alternative-to, similar products, similar websites, etc. - [Read More Here](https://docs.exa.ai/reference/company-analyst) - """) - - # URL input with validation - similar_url = st.text_input( - "๐Ÿ‘‹ Enter a single valid URL for web analysis:", - placeholder="https://example.com", - help="Enter a complete URL including http:// or https://" - ) - - # Validate URL - url_valid = is_valid_url(similar_url) if similar_url else False - if similar_url and not url_valid: - logger.warning(f"Invalid URL provided: {similar_url}") - st.error("โš ๏ธ Please enter a valid URL including http:// or https://") - - # Usecase selection with improved help - usecase = st.selectbox( - "Select Usecase", - ["similar companies", "listicles", "Top tools", "alternative-to", "similar products", "similar websites"], - help="Choose the type of analysis you want to perform" - ) - logger.debug(f"Selected usecase: {usecase}") - - # Default summary query based on usecase - default_summary_queries = { - "similar companies": "Find companies similar to this one, focusing on their business model, target audience, and market position", - "listicles": "Find similar listicle articles about this topic, focusing on the structure and content", - "Top tools": "Find top tools similar to this one, focusing on features, pricing, and user reviews", - "alternative-to": "Find alternatives to this product or service, focusing on comparable features and pricing", - "similar products": "Find products similar to this one, focusing on features, specifications, and use cases", - "similar websites": "Find websites similar to this one, focusing on design, content, and functionality" - } - - # Advanced options using a modal dialog - show_advanced = st.checkbox("Show Advanced Options", help="Configure additional search parameters") - logger.debug(f"Advanced options shown: {show_advanced}") - - # Initialize default values - num_results = 5 - time_range = "Anytime" - include_domains = [] - exclude_domains = [] - include_text = None - exclude_text = None - summary_query = default_summary_queries.get(usecase, "") - - # Add custom CSS for card styling - st.markdown(""" - - """, unsafe_allow_html=True) - - # Advanced options section - if show_advanced: - logger.debug("Processing advanced options") - st.markdown("### ๐Ÿ”ง Advanced Search Options") - - # Summary query with improved help in a card - st.markdown('
๐Ÿ“ Summary Query
', unsafe_allow_html=True) - summary_query = st.text_area( - "Customize the summary query", - value=summary_query, - placeholder="Enter a custom query for summarization based on your usecase", - help="This query will be used to generate summaries of the similar content. Be specific about what you want to know." - ) - - # Number of results with improved help in a card - st.markdown('
๐Ÿ”ข Number of Results
', unsafe_allow_html=True) - num_results = st.slider( - "How many results would you like?", - min_value=1, - max_value=20, - value=5, - step=1, - help="How many similar results would you like to see?" - ) - - # Progress bar visibility toggle - st.markdown('
๐Ÿ”„ Progress Display
', unsafe_allow_html=True) - st.session_state.show_progress = st.toggle( - "Show detailed progress bars", - value=st.session_state.show_progress, - help="Toggle to show or hide detailed progress bars during analysis" - ) - - # Time range selection with improved styling in a card - st.markdown('
โฑ๏ธ Time Range
', unsafe_allow_html=True) - time_range = st.radio( - "Select time range for results", - options=["Past Week", "Past Month", "Past Year", "Anytime"], - index=3, - horizontal=True, - help="Filter results by when they were published" - ) - - # Domain filters with improved styling in a card - st.markdown('
๐ŸŒ Domain Filters
', unsafe_allow_html=True) - domain_filter_type = st.radio( - "Domain Filter Type", - options=["Include Domains", "Exclude Domains", "None"], - index=2, - horizontal=True, - help="Include or exclude specific domains from search results" - ) - - if domain_filter_type == "Include Domains": - include_domains_input = st.text_input( - "Include Domains (comma-separated)", - placeholder="example.com, another-example.com", - help="Only results from these domains will be included. Example: arxiv.org, paperswithcode.com" - ) - if include_domains_input: - include_domains = [domain.strip() for domain in include_domains_input.split(",")] - - elif domain_filter_type == "Exclude Domains": - exclude_domains_input = st.text_input( - "Exclude Domains (comma-separated)", - placeholder="example.com, another-example.com", - help="Results from these domains will be excluded from search results" - ) - if exclude_domains_input: - exclude_domains = [domain.strip() for domain in exclude_domains_input.split(",")] - - # Text filters with improved styling in a card - st.markdown('
๐Ÿ“ Text Filters
', unsafe_allow_html=True) - text_filter_type = st.radio( - "Text Filter Type", - options=["Include Text", "Exclude Text", "None"], - index=2, - horizontal=True, - help="Include or exclude results containing specific text" - ) - - if text_filter_type == "Include Text": - include_text = st.text_input( - "Include Text", - placeholder="large language model", - help="Only results containing this phrase will be included (up to 5 words)" - ) - - elif text_filter_type == "Exclude Text": - exclude_text = st.text_input( - "Exclude Text", - placeholder="course", - help="Results containing this phrase will be excluded (up to 5 words)" - ) - - # Analyze button with validation - if st.button("Analyze", disabled=not url_valid if similar_url else False): - if similar_url and url_valid: - try: - logger.info(f"Starting analysis for URL: {similar_url}") - logger.debug(f"Analysis parameters - Usecase: {usecase}, Num Results: {num_results}, Time Range: {time_range}") - - # Create a progress container - progress_container = st.empty() - status_container = st.empty() - results_container = st.empty() - - # Display initial status - status_container.info(f"Starting analysis for the URL: {similar_url}") - - # Create a progress bar - progress_bar = progress_container.progress(0.1) - logger.debug("Initialized progress bar and status containers") - - # Calculate date range based on selection - start_date = None - end_date = None - - if time_range != "Anytime": - end_date = datetime.now() - if time_range == "Past Week": - start_date = end_date - timedelta(days=7) - elif time_range == "Past Month": - start_date = end_date - timedelta(days=30) - elif time_range == "Past Year": - start_date = end_date - timedelta(days=365) - logger.debug(f"Date range: {start_date} to {end_date}") - - # Format dates for API if they exist - start_published_date = start_date.strftime("%Y-%m-%dT%H:%M:%S.000Z") if start_date else None - end_published_date = end_date.strftime("%Y-%m-%dT%H:%M:%S.999Z") if end_date else None - - # Prepare summary query - summary_query_param = None - if summary_query: - summary_query_param = {"query": summary_query} - logger.debug(f"Summary query: {summary_query}") - - # Update progress - progress_bar.progress(0.2) - status_container.info("Searching for similar content...") - logger.info("Initiating similar content search") - - # Call the metaphor_find_similar function with all parameters - with st.spinner("Performing competitor analysis..."): - logger.debug("Calling metaphor_find_similar API") - try: - df, search_response = metaphor_find_similar( - similar_url=similar_url, - usecase=usecase, - num_results=num_results, - start_published_date=start_published_date, - end_published_date=end_published_date, - include_domains=include_domains, - exclude_domains=exclude_domains, - include_text=include_text, - exclude_text=exclude_text, - summary_query=summary_query_param - ) - logger.info(f"API call successful. Found {len(df) if not df.empty else 0} results") - - # Update progress - progress_bar.progress(0.7) - status_container.info("Processing and analyzing results...") - logger.debug("Processing search results") - - # Update progress to complete - progress_bar.progress(1.0) - status_container.success("Analysis completed successfully!") - logger.info("Analysis completed successfully") - - except Exception as api_error: - logger.error(f"API call failed: {str(api_error)}", exc_info=True) - raise - - # Display results - if not df.empty: - logger.debug(f"Displaying {len(df)} results") - st.subheader("๐Ÿ“Š Competitor Analysis Results") - - # Add a download button for the results - csv = df.to_csv(index=False) - st.download_button( - label="๐Ÿ“ฅ Download Results as CSV", - data=csv, - file_name=f"competitor_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", - mime="text/csv", - ) - - # Display the data editor - st.data_editor( - df, - column_config={ - "Title": st.column_config.TextColumn( - "Title", - help="Title of the similar content", - width="large", - ), - "URL": st.column_config.LinkColumn( - "URL", - help="Link to the similar content", - width="medium", - display_text="Visit Website", - ), - "Content Summary": st.column_config.TextColumn( - "Content Summary", - help="Summary of the similar content", - width="large", - ), - }, - hide_index=True, - use_container_width=True, - ) - - # Display additional insights - st.subheader("๐Ÿ” Analysis Insights") - - # Create columns for metrics - col1, col2, col3 = st.columns(3) - - with col1: - st.metric("Total Results", len(df)) - - with col2: - # Calculate average content length - avg_content_length = df["Content Summary"].str.len().mean() - st.metric("Avg. Content Length", f"{avg_content_length:.0f} chars") - - with col3: - # Calculate unique domains - unique_domains = len(set([url.split('/')[2] for url in df["URL"]])) - st.metric("Unique Domains", unique_domains) - - # Display full summaries in expanders - st.subheader("๐Ÿ“ Detailed Competitor Summaries") - - if 'competitor_summaries' in st.session_state and st.session_state.competitor_summaries: - for url, data in st.session_state.competitor_summaries.items(): - with st.expander(f"๐Ÿ“Š {data['title']}", expanded=False): - st.markdown("### ๐Ÿ“ Detailed Competitor Analysis") - st.markdown(data['summary']) - - # Display raw data in an expander - with st.expander("View Raw Data"): - st.json(search_response) - else: - logger.warning("No results found for the given URL and parameters") - st.warning("No results found for the given URL and parameters.") - - except Exception as err: - logger.error(f"Analysis failed: {str(err)}", exc_info=True) - st.error(f"โœ– ๐Ÿšซ Failed to do similar search.\nError: {err}") - else: - logger.warning("Analysis attempted without valid URL") - st.error("Please enter a valid URL.") \ No newline at end of file diff --git a/ToBeMigrated/alwrity_ui/social_media_dashboard.py b/ToBeMigrated/alwrity_ui/social_media_dashboard.py deleted file mode 100644 index 6256947b..00000000 --- a/ToBeMigrated/alwrity_ui/social_media_dashboard.py +++ /dev/null @@ -1,116 +0,0 @@ -import streamlit as st -from lib.alwrity_ui.dashboard_styles import apply_dashboard_style, render_dashboard_header, render_card -from loguru import logger - -def render_social_tools_dashboard(): - """Render the social media tools dashboard with premium glassmorphic design.""" - logger.info("Starting Social Media Tools Dashboard") - - # Apply common dashboard styling - apply_dashboard_style() - - # Render dashboard header - render_dashboard_header( - "๐Ÿ“ฑ AI Social Media Tools", - "Create engaging social media content across all major platforms with our specialized AI writers. From viral posts to professional updates, we've got you covered." - ) - - # Define social tools with enhanced details and platform-specific styling - social_tools = { - "Facebook": { - "icon": "๐Ÿ“˜", - "description": "Create engaging Facebook posts, stories, and ads that drive meaningful interactions and build community", - "category": "Social Network", - "path": "facebook", - "features": ["Post Generation", "Story Creation", "Ad Copy", "Community Management"] - }, - "LinkedIn": { - "icon": "๐Ÿ’ผ", - "description": "Generate professional LinkedIn content, articles, and networking posts that enhance your career presence", - "category": "Professional", - "path": "linkedin", - "features": ["Professional Posts", "Article Writing", "Network Building", "Career Content"] - }, - "Twitter": { - "icon": "๐Ÿฆ", - "description": "Craft viral tweets, threads, and engaging content that sparks conversations and grows your following", - "category": "Microblogging", - "path": "twitter", - "features": ["Tweet Generation", "Thread Creation", "Hashtag Strategy", "Viral Content"] - }, - "Instagram": { - "icon": "๐Ÿ“ธ", - "description": "Create captivating Instagram captions, stories, and content that showcases your brand beautifully", - "category": "Visual Content", - "path": "instagram", - "features": ["Caption Writing", "Story Content", "Hashtag Research", "Visual Strategy"] - }, - "YouTube": { - "icon": "๐ŸŽฅ", - "description": "Generate compelling video scripts, descriptions, and content strategies for your YouTube channel", - "category": "Video Content", - "path": "youtube", - "features": ["Script Writing", "Video Descriptions", "SEO Optimization", "Content Planning"] - } - } - - # Create a responsive grid of premium cards - cols = st.columns(3) - for idx, (platform, details) in enumerate(social_tools.items()): - with cols[idx % 3]: - # Use the common card renderer - if render_card( - icon=details['icon'], - title=platform, - description=details['description'], - category=details['category'], - key_suffix=f"social_{platform}", - help_text=f"Open {platform} content creation tools - {details['description'][:50]}..." - ): - # Set query parameters to redirect to the specific tool - st.query_params["tool"] = details["path"] - st.rerun() - - # Add feature showcase section - st.markdown(""" -
-
-

โœจ Platform Features

-

Each platform comes with specialized AI tools designed for optimal engagement and growth.

-
-
- """, unsafe_allow_html=True) - - # Feature grid - feature_cols = st.columns(2) - features = [ - { - "title": "๐ŸŽฏ Smart Content Generation", - "description": "AI-powered content creation tailored to each platform's unique audience and format requirements." - }, - { - "title": "๐Ÿ“Š Engagement Optimization", - "description": "Data-driven insights and suggestions to maximize likes, shares, comments, and overall engagement." - }, - { - "title": "๐Ÿ•’ Optimal Timing", - "description": "AI recommendations for the best times to post based on your audience's activity patterns." - }, - { - "title": "๐Ÿ” Hashtag Intelligence", - "description": "Smart hashtag suggestions and trending topic analysis to increase your content's discoverability." - } - ] - - for idx, feature in enumerate(features): - with feature_cols[idx % 2]: - st.markdown(f""" -
-
-
-
{feature['title']}
-
{feature['description']}
-
-
-
- """, unsafe_allow_html=True) \ No newline at end of file diff --git a/ToBeMigrated/competitive_intelligence/README.md b/ToBeMigrated/competitive_intelligence/README.md deleted file mode 100644 index 458afb45..00000000 --- a/ToBeMigrated/competitive_intelligence/README.md +++ /dev/null @@ -1,488 +0,0 @@ -# ๐Ÿฅท AI-Powered Competitive Intelligence - -**AI Competitive Intelligence Suite for Entrepreneurs** - -Transform your competitive analysis with AI-powered intelligence gathering, content strategy insights, and market opportunity identification. Perfect for entrepreneurs and small teams who need enterprise-level competitive intelligence without the enterprise budget. - -## ๐Ÿ“‹ Table of Contents - -- [Overview](#overview) -- [Features](#features) -- [Installation](#installation) -- [Usage](#usage) -- [Architecture](#architecture) -- [AI Analysis Capabilities](#ai-analysis-capabilities) -- [API Reference](#api-reference) -- [File Structure](#file-structure) -- [Configuration](#configuration) -- [Development](#development) -- [Use Cases](#use-cases) -- [Troubleshooting](#troubleshooting) -- [Contributing](#contributing) - -## ๐Ÿ” Overview - -The AI-Powered Competitive Intelligence suite provides comprehensive competitor analysis and market insights using advanced AI capabilities: - -- **AI Competitive Intelligence**: Advanced competitive analysis with AI insights -- **AI Content Strategy Analysis**: Understand what content works for competitors -- **Market Opportunity Detection**: Identify gaps and opportunities in your market -- **Strategic Recommendations**: AI-powered actionable insights - -### Key Benefits - -- **๐Ÿง  AI-Powered Analysis**: Leverages LLM intelligence for deep competitive insights -- **โšก Quick Setup**: Get started with competitor intelligence in minutes -- **๐Ÿ’ฐ Cost-Effective**: Enterprise-level insights without enterprise costs -- **๐ŸŽฏ Actionable Insights**: Clear recommendations for competitive advantage -- **๐Ÿ“Š Strategic Intelligence**: Market gaps, opportunities, and positioning insights - -## โœจ Features - -### Core Intelligence Capabilities - -#### 1. **AI Competitor Analysis** -- Rapid competitive landscape assessment -- Competitor strength/weakness analysis -- Market positioning insights -- Content strategy evaluation - -#### 2. **AI Content Intelligence** -- Competitor content performance analysis -- Content gap identification -- Strategic content recommendations -- Optimal content strategy insights - -#### 3. **Market Opportunity Detection** -- Underserved market segment identification -- Content opportunity mapping -- Competitive advantage discovery -- Strategic positioning recommendations - -#### 4. **Strategic Recommendations** -- Competitive differentiation strategies -- Market entry recommendations -- Content strategy optimization -- Positioning improvements - -### Analysis Categories - -1. **๐ŸŽฏ Competitive Positioning**: Where you stand vs competitors -2. **๐Ÿ“ˆ Content Performance**: What content works in your space -3. **๐Ÿ” Market Gaps**: Opportunities competitors are missing -4. **๐Ÿ’ก Strategic Insights**: AI-powered competitive recommendations -5. **โšก Quick Wins**: Immediate actions for competitive advantage -6. **๐Ÿš€ Growth Opportunities**: Long-term strategic opportunities - -## ๐Ÿš€ Installation - -### Prerequisites - -```bash -# Already included in Alwrity - no additional installation required! -# Uses existing dependencies: streamlit, llm_text_gen, requests -``` - -### Setup - -1. **Auto-Integration** (already included): - ```python - # Available in AI Writer Dashboard - # Access via: "Bootstrap AI Competitive Suite" - ``` - -2. **Direct Usage**: - ```python - from lib.competitive_intelligence.ai_competitive_intelligence import AICompetitiveIntelligence - ``` - -3. **Full Suite Access**: - ```python - from lib.ai_competitive_suite.bootstrap_ai_suite import BootstrapAISuite - ``` - -4. **UI Components**: - ```python - from lib.competitive_intelligence.ai_competitive_intelligence import render_ai_competitive_intelligence_ui - from lib.ai_competitive_suite.bootstrap_ai_suite import render_bootstrap_ai_suite - ``` - -## ๐Ÿ“– Usage - -### Through AI Writer Dashboard - -1. Open Alwrity -2. Navigate to "AI Writer Dashboard" -3. Select "๐Ÿš€ Bootstrap AI Competitive Suite" -4. Enter competitor information or industry -5. Get comprehensive competitive intelligence! - -### AI Competitor Analysis - -```python -from lib.competitive_intelligence.ai_competitive_intelligence import AICompetitiveIntelligence - -# Initialize AI analyzer -intel = AICompetitiveIntelligence() - -# Quick competitor analysis -result = await intel.analyze_competitors( - competitor_urls=["https://jasper.ai", "https://copy.ai"], - industry="AI writing tools", - your_strengths=["AI-first approach", "Solo entrepreneur focus"] -) - -print(f"Key Insights: {result['competitor_insights']}") -print(f"Opportunities: {result['strategic_opportunities']}") -``` - -### Full AI Competitive Suite - -```python -from lib.ai_competitive_suite.bootstrap_ai_suite import BootstrapAISuite - -# Initialize full suite -suite = BootstrapAISuite() - -# Comprehensive analysis -analysis = await suite.get_competitive_content_strategy( - content="Your content here", - target_platform="twitter", - competitor_urls=["https://competitor1.com", "https://competitor2.com"], - industry="content creation", - your_strengths=["AI expertise", "Bootstrap approach"] -) - -print(f"Integrated Strategy: {analysis['integrated_strategy']}") -print(f"Action Plan: {analysis['action_plan']}") -``` - -### Programmatic Usage - -```python -import streamlit as st -from lib.competitive_intelligence.ai_competitive_intelligence import render_ai_competitive_intelligence_ui - -# Add to your Streamlit app -st.title("AI Competitive Intelligence") -render_ai_competitive_intelligence_ui() -``` - -## ๐Ÿ—๏ธ Architecture - -### System Architecture - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ STREAMLIT UI โ”‚ -โ”‚ (render_bootstrap_ai_suite / render_ai_intelligence_ui) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ BOOTSTRAP AI COMPETITIVE SUITE โ”‚ -โ”‚ (BootstrapAISuite) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Competitor โ”‚ โ”‚ Market Intelligence โ”‚ โ”‚ -โ”‚ โ”‚ Analysis โ”‚ โ”‚ (Opportunities & Gaps) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ BOOTSTRAP COMPETITOR INTEL โ”‚ -โ”‚ (BootstrapCompetitorIntel) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Industry โ”‚ โ”‚ Competitive Positioning โ”‚ โ”‚ -โ”‚ โ”‚ Analysis โ”‚ โ”‚ & Strategic Insights โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ALWRITY LLM ENGINE โ”‚ -โ”‚ (llm_text_gen) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Component Details - -1. **BootstrapAISuite**: Complete competitive intelligence platform -2. **BootstrapCompetitorIntel**: Core competitor analysis engine -3. **Market Intelligence**: Opportunity detection and gap analysis -4. **Strategic Insights**: AI-powered recommendations and positioning -5. **UI Components**: Interactive analysis interfaces - -## ๐Ÿง  AI Analysis Capabilities - -### Competitive Intelligence Analysis - -The suite uses sophisticated AI prompts to analyze: - -- **Competitor Strengths**: What makes competitors successful -- **Market Weaknesses**: Where competitors are failing -- **Content Strategies**: What content approaches work best -- **Positioning Opportunities**: How to differentiate effectively - -### Market Intelligence Features - -#### Industry Landscape Analysis -- Market size and growth trends -- Key player identification -- Competitive dynamics assessment -- Market maturity evaluation - -#### Competitive Positioning -- Strength/weakness matrix -- Differentiation opportunities -- Market positioning gaps -- Value proposition analysis - -#### Content Strategy Intelligence -- High-performing content identification -- Content gap analysis -- Viral content pattern recognition -- Platform-specific strategies - -#### Strategic Recommendations -- Competitive advantage opportunities -- Market entry strategies -- Product positioning advice -- Growth opportunity identification - -## ๐Ÿš€ Bootstrap Features - -### Quick Setup Intelligence - -#### 1. **Rapid Competitor Analysis** -- Input: Industry + Competitors -- Output: Comprehensive competitive landscape -- Time: 2-3 minutes -- Insight: Market positioning and opportunities - -#### 2. **Industry Assessment** -- Input: Industry description -- Output: Market dynamics and key players -- Time: 1-2 minutes -- Insight: Market opportunities and threats - -#### 3. **Strategic Positioning** -- Input: Your product + competitors -- Output: Differentiation strategy -- Time: 2-3 minutes -- Insight: Competitive advantages and positioning - -#### 4. **Content Intelligence** -- Input: Industry + content focus -- Output: Content strategy recommendations -- Time: 2-3 minutes -- Insight: What content works and content gaps - -### Bootstrap Configurations - -Located in the intelligence modules: - -```python -ANALYSIS_TEMPLATES = { - "competitor_analysis": { - "focus_areas": ["strengths", "weaknesses", "positioning"], - "output_format": "strategic_insights", - "depth": "comprehensive" - }, - "market_intelligence": { - "analysis_type": "opportunity_detection", - "scope": "industry_wide", - "recommendations": "actionable" - } -} -``` - -## ๐Ÿ“Š Analysis Output - -### Competitive Analysis Report - -```python -{ - "executive_summary": "Key findings and strategic recommendations", - "competitor_analysis": { - "direct_competitors": [...], - "indirect_competitors": [...], - "competitive_advantages": [...], - "competitive_threats": [...] - }, - "market_intelligence": { - "market_size": "Large/Medium/Small", - "growth_rate": "High/Medium/Low", - "key_trends": [...], - "opportunities": [...] - }, - "strategic_recommendations": { - "positioning": "How to position your product", - "differentiation": "Key differentiators to focus on", - "content_strategy": "What content to create", - "quick_wins": "Immediate actions to take" - }, - "content_opportunities": { - "content_gaps": [...], - "viral_patterns": [...], - "platform_strategies": {...} - } -} -``` - -### Intelligence Categories - -1. **Market Position**: Where you stand competitively -2. **Opportunities**: Gaps competitors haven't filled -3. **Threats**: Competitive risks to monitor -4. **Strategy**: Recommended competitive approach -5. **Content**: What content strategy to pursue -6. **Quick Wins**: Immediate competitive advantages - -## ๐Ÿ”ง Configuration - -### Analysis Settings - -Customize analysis depth and focus: - -```python -# Configure analysis parameters -analysis_config = { - "depth": "comprehensive", # quick, standard, comprehensive - "focus": "content_strategy", # market_position, content_strategy, opportunities - "industry": "your_industry", - "competitive_scope": "direct_competitors" # direct, indirect, all -} -``` - -### Customization Options - -- **Analysis Depth**: Quick overview vs comprehensive analysis -- **Focus Areas**: Market positioning, content strategy, opportunities -- **Industry Scope**: Narrow niche vs broad market analysis -- **Output Format**: Executive summary, detailed report, action items - -## ๐Ÿš€ Development - -### Adding New Analysis Types - -1. Extend analysis templates in configuration -2. Add new AI prompts for specific analysis -3. Update UI to support new analysis types - -### Enhancing Intelligence Gathering - -1. Add new data sources for competitive information -2. Implement automated monitoring capabilities -3. Enhance AI analysis with additional insights - -## ๐Ÿ“ˆ Use Cases - -### Solo Entrepreneurs - -- **Quick Market Assessment**: Understand competitive landscape fast -- **Content Strategy**: Identify what content works in your niche -- **Positioning**: Find your unique market position -- **Opportunities**: Discover gaps competitors are missing - -### Small Teams - -- **Competitive Strategy**: Develop comprehensive competitive approach -- **Market Intelligence**: Ongoing competitive monitoring -- **Strategic Planning**: AI-powered strategic recommendations -- **Content Planning**: Content strategy based on competitive analysis - -### Growing Businesses - -- **Market Expansion**: Identify new market opportunities -- **Competitive Advantage**: Maintain edge over competitors -- **Strategic Positioning**: Refine market positioning strategy -- **Growth Planning**: AI-powered growth recommendations - -## ๐Ÿ” Troubleshooting - -### Common Issues - -**No Analysis Generated**: -- Check LLM service availability -- Verify competitor/industry information is provided -- Ensure sufficient detail in input - -**Generic Insights**: -- Provide more specific industry information -- Include specific competitor names -- Add context about your product/service - -**UI Not Loading**: -- Check Streamlit dependencies -- Verify import paths -- Ensure LLM service is configured - -### Debug Mode - -Enable detailed logging: -```python -import logging -logging.basicConfig(level=logging.DEBUG) -``` - -## ๐Ÿ“ˆ Performance Tips - -1. **Specific Industries**: Provide detailed industry information for better analysis -2. **Competitor Details**: Include specific competitor names and details -3. **Context**: Add context about your business for relevant insights -4. **Iterate**: Use insights to refine your competitive strategy - -## ๐Ÿค Contributing - -1. Fork the repository -2. Create a feature branch -3. Add new analysis capabilities or improve existing ones -4. Test with different industries and competitors -5. Submit a pull request - -### Development Setup - -```bash -# No additional setup required! -# Uses existing Alwrity infrastructure -``` - -## ๐Ÿ“ License - -Part of the Alwrity AI Content Creation Suite. - ---- - -**Ready to gain competitive intelligence? Access the Bootstrap AI Competitive Suite through the AI Writer Dashboard now!** - -## ๐ŸŽฏ Quick Start Examples - -### Example 1: SaaS Competitive Analysis -```python -# Analyze SaaS competitive landscape -result = await intel.analyze_competitor_landscape( - industry="AI writing software", - competitors=["Jasper", "Copy.ai", "Writesonic"], - your_product="Alwrity - AI writer for solo developers" -) -``` - -### Example 2: Content Strategy Intelligence -```python -# Get content strategy insights -content_intel = await suite.analyze_content_opportunities( - industry="digital marketing", - content_focus="social media content creation" -) -``` - -### Example 3: Market Opportunity Detection -```python -# Find market gaps -opportunities = await intel.identify_market_opportunities( - industry="productivity software", - target_audience="small business owners" -) -``` - ---- - -**Transform your competitive strategy with AI-powered intelligence! ๐Ÿฅท** \ No newline at end of file diff --git a/ToBeMigrated/competitive_intelligence/ai_competitive_intelligence.py b/ToBeMigrated/competitive_intelligence/ai_competitive_intelligence.py deleted file mode 100644 index e81ff824..00000000 --- a/ToBeMigrated/competitive_intelligence/ai_competitive_intelligence.py +++ /dev/null @@ -1,725 +0,0 @@ -""" -AI-Powered Competitive Intelligence - -Advanced competitive intelligence for entrepreneurs using AI. -Provides strategic insights, competitor analysis, and market opportunities. -""" - -import asyncio -import json -from datetime import datetime -from typing import Dict, Any, List, Optional -from loguru import logger -import streamlit as st -from urllib.parse import urlparse -import requests -from bs4 import BeautifulSoup - -# Import existing Alwrity modules -from lib.ai_seo_tools.content_gap_analysis.competitor_analyzer import CompetitorAnalyzer -from lib.ai_web_researcher.gpt_online_researcher import do_google_pytrends_analysis -from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen - - -class AICompetitiveIntelligence: - """ - AI-powered competitive intelligence for entrepreneurs and startups. - Uses existing AI capabilities to provide strategic insights. - """ - - def __init__(self): - """Initialize the AI competitive intelligence.""" - self.competitor_analyzer = CompetitorAnalyzer() - self.analysis_history = [] - - logger.info("AI Competitive Intelligence initialized") - - async def analyze_competitors( - self, - competitor_urls: List[str], - industry: str, - your_strengths: List[str] = None - ) -> Dict[str, Any]: - """ - Analyze competitors using AI-powered insights. - - Args: - competitor_urls: List of competitor URLs - industry: Your industry/niche - your_strengths: Your current strengths (optional) - - Returns: - AI-powered competitive analysis - """ - logger.info(f"Starting AI competitive analysis for {len(competitor_urls)} competitors") - - try: - analysis_report = { - 'analysis_metadata': { - 'timestamp': datetime.now().isoformat(), - 'competitors_analyzed': len(competitor_urls), - 'industry': industry, - 'your_strengths': your_strengths or [] - }, - 'competitor_insights': {}, - 'strategic_opportunities': [], - 'content_gap_analysis': {}, - 'ai_recommendations': [], - 'quick_wins': [], - 'competitive_positioning': {} - } - - # Step 1: Basic competitor analysis using existing tools - st.info("๐Ÿ” Analyzing competitor basics...") - basic_analysis = self.competitor_analyzer.analyze(competitor_urls, industry) - - # Step 2: AI-powered deep analysis - st.info("๐Ÿง  AI is analyzing competitive landscape...") - ai_insights = await self._get_ai_competitive_insights( - competitor_urls, industry, basic_analysis, your_strengths - ) - - # Step 3: Content gap opportunities - st.info("๐ŸŽฏ Identifying content opportunities...") - content_opportunities = await self._find_content_opportunities( - competitor_urls, industry, basic_analysis - ) - - # Step 4: Strategic recommendations - st.info("๐Ÿ’ก Generating strategic recommendations...") - strategic_recommendations = await self._generate_strategic_recommendations( - competitor_urls, industry, ai_insights, content_opportunities, your_strengths - ) - - # Compile results - analysis_report.update({ - 'competitor_insights': ai_insights, - 'content_gap_analysis': content_opportunities, - 'ai_recommendations': strategic_recommendations, - 'quick_wins': self._extract_quick_wins(strategic_recommendations), - 'competitive_positioning': self._analyze_positioning(ai_insights, your_strengths) - }) - - # Save to history - self.analysis_history.append({ - 'timestamp': datetime.now().isoformat(), - 'industry': industry, - 'competitors_count': len(competitor_urls), - 'report_id': f"{industry}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" - }) - - logger.info("AI competitive analysis completed successfully") - return analysis_report - - except Exception as e: - error_msg = f"Error in competitive analysis: {str(e)}" - logger.error(error_msg, exc_info=True) - return {'error': error_msg} - - async def _get_ai_competitive_insights( - self, - competitor_urls: List[str], - industry: str, - basic_analysis: Dict[str, Any], - your_strengths: List[str] = None - ) -> Dict[str, Any]: - """Get AI-powered competitive insights.""" - - competitors_list = [urlparse(url).netloc for url in competitor_urls] - your_strengths_str = ', '.join(your_strengths) if your_strengths else "Not specified" - - insights_prompt = f""" - You are a competitive intelligence expert analyzing the {industry} market. - - COMPETITORS TO ANALYZE: - {', '.join(competitors_list)} - - BASIC COMPETITIVE ANALYSIS DATA: - {json.dumps(basic_analysis, indent=2)[:3000]} - - YOUR CURRENT STRENGTHS: - {your_strengths_str} - - Please provide a comprehensive competitive analysis with these insights: - - 1. MARKET LANDSCAPE OVERVIEW: - - Who are the dominant players? - - What's the competitive intensity like? - - What are the key market trends? - - Where are the market gaps? - - 2. COMPETITOR STRENGTHS & WEAKNESSES: - For each major competitor, identify: - - Their main competitive advantages - - Their key weaknesses or blind spots - - Their content strategy approach - - Their target audience focus - - 3. DIFFERENTIATION OPPORTUNITIES: - - How can you position differently? - - What unique value can you offer? - - Which competitor weaknesses can you exploit? - - What underserved market segments exist? - - 4. THREAT ASSESSMENT: - - Which competitors pose the biggest threat to you? - - What are they doing better than you? - - Where are they vulnerable? - - How quickly are they evolving? - - 5. STRATEGIC INSIGHTS: - - What patterns do you see across all competitors? - - What are they all missing that you could provide? - - Where is the market heading? - - What first-mover opportunities exist? - - Provide specific, actionable insights that a solo entrepreneur can use to compete effectively. - Focus on realistic strategies that don't require massive resources. - """ - - try: - ai_insights = llm_text_gen( - insights_prompt, - system_prompt="You are a strategic business consultant specializing in competitive analysis for startups and small businesses. Provide practical, actionable insights." - ) - - return { - 'full_analysis': ai_insights, - 'key_insights': self._extract_key_insights(ai_insights), - 'threat_levels': self._assess_threat_levels(ai_insights, competitors_list), - 'opportunities': self._extract_opportunities(ai_insights) - } - - except Exception as e: - logger.error(f"Error getting AI insights: {str(e)}") - return { - 'full_analysis': f"Error generating insights: {str(e)}", - 'key_insights': ["Unable to generate AI insights"], - 'threat_levels': {}, - 'opportunities': [] - } - - async def _find_content_opportunities( - self, - competitor_urls: List[str], - industry: str, - basic_analysis: Dict[str, Any] - ) -> Dict[str, Any]: - """Find content opportunities using AI analysis.""" - - content_gaps = basic_analysis.get('content_gaps', []) - advantages = basic_analysis.get('advantages', []) - - content_prompt = f""" - Analyze content opportunities in the {industry} space based on competitor analysis: - - COMPETITOR CONTENT GAPS IDENTIFIED: - {json.dumps(content_gaps[:10], indent=2)} - - COMPETITOR ADVANTAGES: - {json.dumps(advantages[:10], indent=2)} - - INDUSTRY: {industry} - - Provide specific content opportunities analysis: - - 1. HIGH-PRIORITY CONTENT GAPS: - - What topics are competitors not covering well? - - What questions are audiences asking that aren't being answered? - - What content formats are underutilized? - - What pain points are being ignored? - - 2. CONTENT DIFFERENTIATION OPPORTUNITIES: - - How can you approach common topics differently? - - What unique perspective can you bring? - - What content formats can you innovate with? - - How can you provide more value than competitors? - - 3. TRENDING CONTENT OPPORTUNITIES: - - What emerging topics should you cover first? - - What seasonal content opportunities exist? - - What industry changes create content needs? - - What tools/technologies need better content coverage? - - 4. AUDIENCE-SPECIFIC CONTENT GAPS: - - Which audience segments are underserved? - - What skill levels need better content? - - What use cases are poorly addressed? - - What demographics are being ignored? - - 5. QUICK CONTENT WINS: - - What content can you create quickly that competitors lack? - - What simple explanations are missing from the market? - - What FAQ content is needed but not provided? - - What beginner content opportunities exist? - - Prioritize opportunities that a solo creator can realistically execute. - """ - - try: - content_analysis = llm_text_gen( - content_prompt, - system_prompt="You are a content strategist specializing in competitive content analysis. Provide specific, actionable content opportunities." - ) - - return { - 'full_analysis': content_analysis, - 'priority_gaps': self._extract_priority_gaps(content_analysis), - 'quick_wins': self._extract_content_quick_wins(content_analysis), - 'differentiation_opportunities': self._extract_differentiation_opps(content_analysis) - } - - except Exception as e: - logger.error(f"Error finding content opportunities: {str(e)}") - return { - 'full_analysis': f"Error analyzing content opportunities: {str(e)}", - 'priority_gaps': content_gaps[:5], - 'quick_wins': [], - 'differentiation_opportunities': [] - } - - async def _generate_strategic_recommendations( - self, - competitor_urls: List[str], - industry: str, - ai_insights: Dict[str, Any], - content_opportunities: Dict[str, Any], - your_strengths: List[str] = None - ) -> List[Dict[str, Any]]: - """Generate strategic recommendations using AI.""" - - your_strengths_str = ', '.join(your_strengths) if your_strengths else "Not specified" - - strategy_prompt = f""" - Based on the competitive analysis, provide strategic recommendations for competing in {industry}: - - AI COMPETITIVE INSIGHTS: - {ai_insights.get('full_analysis', '')[:2000]} - - CONTENT OPPORTUNITIES: - {content_opportunities.get('full_analysis', '')[:2000]} - - YOUR CURRENT STRENGTHS: - {your_strengths_str} - - BUDGET CONSTRAINT: Solo entrepreneur with limited resources - - Provide specific, actionable recommendations in these categories: - - 1. IMMEDIATE ACTIONS (0-30 days): - - What can you implement this week? - - Which competitor weaknesses can you exploit quickly? - - What low-cost marketing tactics should you try? - - Which content should you create first? - - 2. SHORT-TERM STRATEGY (1-3 months): - - How should you position against competitors? - - What features/content should you prioritize? - - Which partnerships should you pursue? - - How should you differentiate your messaging? - - 3. MEDIUM-TERM POSITIONING (3-6 months): - - How can you build sustainable competitive advantages? - - Which market segments should you focus on? - - What unique value proposition should you develop? - - How can you build barriers to competition? - - 4. RESOURCE ALLOCATION: - - Where should you spend your limited time? - - Which marketing channels offer best ROI? - - What tools/software investments are worthwhile? - - How should you prioritize feature development? - - 5. COMPETITIVE DEFENSE: - - How can you protect against competitor moves? - - What should you do if competitors copy you? - - How can you build customer loyalty? - - What contingency plans should you have? - - For each recommendation: - - Specify the exact action to take - - Estimate time/resource requirements - - Explain expected impact - - Rate priority (High/Medium/Low) - - Provide success metrics - - Focus on David vs Goliath strategies that work for solo entrepreneurs. - """ - - try: - strategic_recommendations = llm_text_gen( - strategy_prompt, - system_prompt="You are a startup strategist specializing in helping solo entrepreneurs compete against established players. Provide practical, executable strategies." - ) - - return self._parse_strategic_recommendations(strategic_recommendations) - - except Exception as e: - logger.error(f"Error generating strategic recommendations: {str(e)}") - return [ - { - 'category': 'Content Strategy', - 'priority': 'High', - 'timeframe': '0-30 days', - 'action': 'Create content addressing competitor blind spots', - 'expected_impact': 'Attract underserved audience segments', - 'resources_needed': 'Time for content creation', - 'success_metrics': 'Website traffic, engagement rates' - } - ] - - def _extract_key_insights(self, ai_analysis: str) -> List[str]: - """Extract key insights from AI analysis.""" - insights = [] - - # Simple parsing to extract key points - lines = ai_analysis.split('\n') - for line in lines: - line = line.strip() - if line and (line.startswith('โ€ข') or line.startswith('-') or line.startswith('*')): - insight = line.lstrip('โ€ข-* ').strip() - if len(insight) > 20: # Only substantial insights - insights.append(insight) - - return insights[:8] # Return top 8 insights - - def _assess_threat_levels(self, ai_analysis: str, competitors: List[str]) -> Dict[str, str]: - """Assess threat levels for each competitor.""" - threat_levels = {} - - # Simple heuristic based on AI analysis content - for competitor in competitors: - if competitor.lower() in ai_analysis.lower(): - if any(word in ai_analysis.lower() for word in ['dominant', 'leader', 'strong', 'established']): - threat_levels[competitor] = 'High' - elif any(word in ai_analysis.lower() for word in ['weak', 'vulnerable', 'gaps', 'opportunity']): - threat_levels[competitor] = 'Low' - else: - threat_levels[competitor] = 'Medium' - else: - threat_levels[competitor] = 'Medium' # Default - - return threat_levels - - def _extract_opportunities(self, ai_analysis: str) -> List[str]: - """Extract opportunities from AI analysis.""" - opportunities = [] - - # Look for opportunity-related keywords - opportunity_keywords = ['opportunity', 'gap', 'underserved', 'missing', 'lack', 'could', 'should'] - - lines = ai_analysis.split('\n') - for line in lines: - line = line.strip() - if any(keyword in line.lower() for keyword in opportunity_keywords) and len(line) > 30: - opportunities.append(line.lstrip('โ€ข-* ').strip()) - - return opportunities[:6] # Return top 6 opportunities - - def _extract_priority_gaps(self, content_analysis: str) -> List[str]: - """Extract priority content gaps.""" - gaps = [] - - # Look for gaps in the analysis - gap_keywords = ['gap', 'missing', 'lack', 'absent', 'underserved', 'neglected'] - - lines = content_analysis.split('\n') - for line in lines: - line = line.strip() - if any(keyword in line.lower() for keyword in gap_keywords) and len(line) > 20: - gaps.append(line.lstrip('โ€ข-* ').strip()) - - return gaps[:5] # Top 5 priority gaps - - def _extract_content_quick_wins(self, content_analysis: str) -> List[str]: - """Extract content quick wins.""" - quick_wins = [] - - # Look for quick win indicators - quick_keywords = ['quick', 'easy', 'simple', 'immediately', 'now', 'today'] - - lines = content_analysis.split('\n') - for line in lines: - line = line.strip() - if any(keyword in line.lower() for keyword in quick_keywords) and len(line) > 15: - quick_wins.append(line.lstrip('โ€ข-* ').strip()) - - return quick_wins[:4] # Top 4 quick wins - - def _extract_differentiation_opps(self, content_analysis: str) -> List[str]: - """Extract differentiation opportunities.""" - diff_opps = [] - - # Look for differentiation keywords - diff_keywords = ['different', 'unique', 'innovative', 'better', 'superior', 'distinct'] - - lines = content_analysis.split('\n') - for line in lines: - line = line.strip() - if any(keyword in line.lower() for keyword in diff_keywords) and len(line) > 20: - diff_opps.append(line.lstrip('โ€ข-* ').strip()) - - return diff_opps[:4] # Top 4 differentiation opportunities - - def _parse_strategic_recommendations(self, recommendations: str) -> List[Dict[str, Any]]: - """Parse strategic recommendations into structured format.""" - structured_recs = [] - - # Split by sections and parse - sections = recommendations.split('\n\n') - - for section in sections: - lines = section.split('\n') - for line in lines: - line = line.strip() - if line and len(line) > 30: # Substantial recommendations - structured_recs.append({ - 'category': 'Strategic Recommendation', - 'priority': 'Medium', # Default - 'timeframe': 'Short-term', - 'action': line.lstrip('โ€ข-* ').strip()[:300], # Limit length - 'expected_impact': 'Competitive advantage', - 'resources_needed': 'Time and effort', - 'success_metrics': 'Market position improvement' - }) - - return structured_recs[:10] # Return top 10 recommendations - - def _extract_quick_wins(self, recommendations: List[Dict[str, Any]]) -> List[Dict[str, str]]: - """Extract quick wins from recommendations.""" - quick_wins = [] - - for rec in recommendations: - action = rec.get('action', '').lower() - if any(word in action for word in ['quick', 'immediate', 'now', 'today', 'easy', 'simple']): - quick_wins.append({ - 'action': rec.get('action', ''), - 'timeframe': rec.get('timeframe', '0-30 days'), - 'impact': rec.get('expected_impact', 'Quick improvement') - }) - - # Add some default quick wins if none found - if len(quick_wins) < 3: - quick_wins.extend([ - { - 'action': 'Create content addressing competitor blind spots', - 'timeframe': '1-2 weeks', - 'impact': 'Immediate traffic from underserved topics' - }, - { - 'action': 'Optimize social media with competitor hashtag gaps', - 'timeframe': '1 week', - 'impact': 'Better discoverability' - }, - { - 'action': 'Set up Google Alerts for competitor mentions', - 'timeframe': '1 day', - 'impact': 'Real-time competitive intelligence' - } - ]) - - return quick_wins[:5] # Top 5 quick wins - - def _analyze_positioning(self, ai_insights: Dict[str, Any], your_strengths: List[str]) -> Dict[str, Any]: - """Analyze competitive positioning.""" - return { - 'your_advantages': your_strengths or ['Agility', 'Personal touch', 'Niche focus'], - 'competitor_weaknesses': ai_insights.get('opportunities', [])[:3], - 'positioning_strategy': 'Focus on agility and personal service vs big competitors', - 'unique_value_prop': 'Personalized solutions that big players can\'t match' - } - - def get_analysis_summary(self) -> Dict[str, Any]: - """Get summary of analysis activities.""" - return { - 'total_analyses': len(self.analysis_history), - 'recent_analyses': self.analysis_history[-3:] if self.analysis_history else [], - 'capabilities': [ - 'AI-powered competitive insights', - 'Content gap analysis', - 'Strategic recommendations', - 'Quick win identification', - 'Positioning strategy' - ] - } - - -# Streamlit interface for AI competitive intelligence -def render_ai_competitive_intelligence_ui(): - """Render the AI competitive intelligence interface.""" - st.title("๐Ÿฅท AI Competitive Intelligence") - st.markdown("AI-powered competitive analysis for solo entrepreneurs and bootstrapped startups") - - # Initialize intelligence engine - if 'ai_intel' not in st.session_state: - st.session_state.ai_intel = AICompetitiveIntelligence() - - intel_engine = st.session_state.ai_intel - - # Input section - st.header("๐ŸŽฏ Competitor Analysis Setup") - - col1, col2 = st.columns(2) - - with col1: - industry = st.text_input( - "Your Industry/Niche", - value="AI Content Creation", - help="What industry are you competing in?" - ) - - competitor_urls = st.text_area( - "Competitor URLs (one per line)", - value="https://jasper.ai\nhttps://copy.ai\nhttps://writesonic.com", - height=100, - help="Enter competitor websites to analyze" - ) - - with col2: - your_strengths = st.text_area( - "Your Current Strengths (optional)", - value="Personal touch, Agility, Niche expertise", - height=100, - help="What are your competitive advantages?" - ) - - # Process inputs - urls = [url.strip() for url in competitor_urls.split('\n') if url.strip()] - strengths = [s.strip() for s in your_strengths.split(',') if s.strip()] if your_strengths else [] - - if st.button("๐Ÿง  Analyze Competitors", type="primary"): - if urls and industry: - with st.spinner("๐Ÿ•ต๏ธ AI is analyzing your competition..."): - results = asyncio.run( - intel_engine.analyze_competitors(urls, industry, strengths) - ) - - if 'error' not in results: - st.success("โœ… Competitive analysis complete!") - - # Analysis overview - metadata = results.get('analysis_metadata', {}) - st.header("๐Ÿ“Š Analysis Overview") - - col1, col2, col3 = st.columns(3) - with col1: - st.metric("Competitors Analyzed", metadata.get('competitors_analyzed', 0)) - with col2: - st.metric("Industry", metadata.get('industry', 'N/A')) - with col3: - st.metric("Your Strengths", len(metadata.get('your_strengths', []))) - - # Quick wins first (most important for bootstrapped entrepreneurs) - quick_wins = results.get('quick_wins', []) - if quick_wins: - st.header("๐Ÿš€ Quick Wins (Do These First!)") - - for i, win in enumerate(quick_wins): - with st.expander(f"Quick Win #{i+1}: {win.get('timeframe', 'N/A')}"): - st.write(f"**Action:** {win.get('action', 'N/A')}") - st.write(f"**Expected Impact:** {win.get('impact', 'N/A')}") - st.write(f"**Timeframe:** {win.get('timeframe', 'N/A')}") - - # Key competitive insights - insights = results.get('competitor_insights', {}) - if insights: - st.header("๐Ÿง  AI Competitive Insights") - - key_insights = insights.get('key_insights', []) - for insight in key_insights[:5]: - st.info(f"๐Ÿ’ก {insight}") - - # Threat levels - threat_levels = insights.get('threat_levels', {}) - if threat_levels: - st.subheader("โš ๏ธ Competitor Threat Assessment") - - for competitor, threat in threat_levels.items(): - color = {'High': '๐Ÿ”ด', 'Medium': '๐ŸŸก', 'Low': '๐ŸŸข'}.get(threat, 'โšช') - st.write(f"{color} **{competitor}**: {threat} threat level") - - # Content opportunities - content_opps = results.get('content_gap_analysis', {}) - if content_opps: - st.header("โœ๏ธ Content Opportunities") - - priority_gaps = content_opps.get('priority_gaps', []) - if priority_gaps: - st.subheader("๐ŸŽฏ Priority Content Gaps") - for gap in priority_gaps[:4]: - st.write(f"โ€ข {gap}") - - quick_content_wins = content_opps.get('quick_wins', []) - if quick_content_wins: - st.subheader("โšก Quick Content Wins") - for win in quick_content_wins[:3]: - st.write(f"โ€ข {win}") - - # Strategic recommendations - recommendations = results.get('ai_recommendations', []) - if recommendations: - st.header("๐ŸŽฏ Strategic Recommendations") - - for i, rec in enumerate(recommendations[:6]): - with st.expander(f"Strategy #{i+1}: {rec.get('category', 'Strategic Move')}"): - st.write(f"**Action:** {rec.get('action', 'N/A')}") - st.write(f"**Timeframe:** {rec.get('timeframe', 'N/A')}") - st.write(f"**Expected Impact:** {rec.get('expected_impact', 'N/A')}") - st.write(f"**Priority:** {rec.get('priority', 'Medium')}") - - # Competitive positioning - positioning = results.get('competitive_positioning', {}) - if positioning: - st.header("๐Ÿ† Your Competitive Position") - - st.subheader("Your Key Advantages:") - for advantage in positioning.get('your_advantages', []): - st.write(f"โœ… {advantage}") - - st.subheader("Competitor Weaknesses to Exploit:") - for weakness in positioning.get('competitor_weaknesses', []): - st.write(f"๐ŸŽฏ {weakness}") - - # Full AI analysis - with st.expander("๐Ÿค– Complete AI Analysis"): - ai_analysis = insights.get('full_analysis', 'No detailed analysis available') - st.write(ai_analysis) - - # Export functionality - st.subheader("๐Ÿ“ฅ Export Analysis") - if st.button("Download Report"): - report_json = json.dumps(results, indent=2, default=str) - st.download_button( - label="Download JSON Report", - data=report_json, - file_name=f"competitor_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", - mime="application/json" - ) - - else: - st.error(f"โŒ Analysis failed: {results.get('error')}") - else: - st.warning("โš ๏ธ Please provide competitor URLs and industry information") - - # Sidebar with tips - st.sidebar.header("๐Ÿ’ก AI Competition Tips") - st.sidebar.info(""" - **David vs Goliath Strategies:** - - โ€ข Focus on what big competitors can't do - โ€ข Be more personal and responsive - โ€ข Serve niche audiences they ignore - โ€ข Move faster than they can - โ€ข Provide better customer service - โ€ข Build deeper relationships - โ€ข Innovate in areas they neglect - """) - - # Analysis history - summary = intel_engine.get_analysis_summary() - st.sidebar.metric("Total Analyses", summary.get('total_analyses', 0)) - - -# Main execution -if __name__ == "__main__": - render_ai_competitive_intelligence_ui() \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/README.md b/ToBeMigrated/content_scheduler/README.md deleted file mode 100644 index e5125e55..00000000 --- a/ToBeMigrated/content_scheduler/README.md +++ /dev/null @@ -1,804 +0,0 @@ -# Alwrity Content Scheduler - -A robust, reusable content scheduling system for Alwrity that integrates with existing features and provides comprehensive scheduling capabilities. - -## Overview - -The Content Scheduler is a standalone module that provides advanced scheduling capabilities for content publishing across multiple platforms. It uses APScheduler for reliable task scheduling and includes features for monitoring, error handling, and integration with existing Alwrity features. - -## Features - -### Core Scheduling Features -- [x] One-time content scheduling -- [x] Recurring content scheduling (cron-based) -- [x] Platform-specific scheduling -- [x] Batch scheduling -- [x] Schedule optimization based on platform analytics -- [x] Timezone support -- [x] Schedule conflict detection and resolution - -### Monitoring & Management -- [x] Real-time schedule monitoring -- [x] Job status tracking (pending, running, completed, failed) -- [x] Failed job handling and retry mechanisms -- [x] Schedule health checks -- [x] Performance metrics and analytics -- [x] Schedule audit logs - -### Integration Features -- [x] Seamless integration with Content Calendar - - Bidirectional sync with existing content calendar - - Real-time event synchronization - - Schedule-to-event conversion - - Calendar event management -- [x] Platform adapter system for different publishing platforms -- [ ] Webhook support for external integrations -- [ ] API endpoints for external access -- [ ] Event system for custom integrations - -### Safety & Reliability -- [x] Persistent job storage -- [x] Automatic job recovery on system restart -- [x] Missed schedule detection and handling -- [x] Schedule validation and verification -- [x] Error handling and notification system -- [ ] Backup and restore capabilities - -### User Interface -- [x] Interactive scheduling dashboard -- [x] Schedule visualization (calendar, timeline, list views) -- [x] Schedule management interface -- [x] Performance analytics dashboard -- [x] Schedule health monitoring -- [x] Alert and notification center - -### Dashboard Capabilities - -#### Overview Dashboard -- Real-time metrics display: - - Active schedules count - - Pending jobs count - - Completed jobs today - - Success rate percentage -- Upcoming schedules table with: - - Schedule title and content preview - - Platform information - - Scheduled time - - Current status -- Quick action buttons for common tasks - -#### Schedule Management - -- Create new schedules with: - - One-time or recurring options - - Multiple platform selection - - Content type specification - - Priority settings - - Advanced scheduling options - -- Manage existing schedules: - - Edit schedule details - - Delete schedules - - Pause/Resume schedules - - Clone schedules - -- Schedule visualization: - - Calendar view with color-coded status - - Timeline view for chronological display - - List view with sorting and filtering - - Drag-and-drop rescheduling - - -#### Job Monitor - -- Real-time job status tracking: - - Pending jobs - - Running jobs - - Completed jobs - - Failed jobs - -- Advanced filtering: - - By status - - By platform - - By date range - - By content type - -- Job timeline visualization: - - Interactive timeline chart - - Job execution history - - Status changes tracking - -- Detailed job information: - - Execution time - - Platform responses - - Error messages - - Retry attempts - -#### Analytics Dashboard - -- Performance metrics: - - Success rate trends - - Average execution time - - Error rate analysis - - Platform-specific metrics - -- Content distribution: - - Platform-wise distribution - - Content type distribution - - Time-based distribution - -- Schedule optimization insights: - - Best posting times - - Platform performance comparison - - Content type effectiveness - -- Custom reports: - - Exportable metrics - - Custom date ranges - - Platform-specific reports - - Performance comparisons - -#### Health Monitoring - -- System health indicators: - - Scheduler status - - Database connection - - Platform connectivity - - Resource usage - -- Alert system: - - Failed job notifications - - Schedule conflicts - - System warnings - - Performance alerts - -- Health check history: - - Status changes - - Error logs - - Resolution tracking - - Maintenance records - - -#### User Experience Features - -- Responsive design for all devices -- Dark/Light theme support -- Customizable dashboard layouts -- Keyboard shortcuts -- Bulk operations support -- Export/Import functionality -- Multi-language support -- Accessibility features - -## Module Structure - -``` -lib/content_scheduler/ -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ requirements.txt -โ”œโ”€โ”€ core/ -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ scheduler.py # Main scheduler implementation -โ”‚ โ”œโ”€โ”€ job_manager.py # Job management and persistence -โ”‚ โ”œโ”€โ”€ schedule_validator.py # Schedule validation and verification -โ”‚ โ”œโ”€โ”€ health_checker.py # Schedule health monitoring -โ”‚ โ”œโ”€โ”€ conflict_resolver.py # Schedule conflict detection and resolution -โ”‚ โ””โ”€โ”€ schedule_optimizer.py # Schedule optimization engine -โ”œโ”€โ”€ models/ -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ schedule.py # Schedule data models -โ”‚ โ”œโ”€โ”€ job.py # Job data models -โ”‚ โ””โ”€โ”€ platform.py # Platform-specific models -โ”œโ”€โ”€ integrations/ -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ platform_adapters/ # Platform-specific adapters -โ”‚ โ”œโ”€โ”€ calendar_integration.py # Content calendar integration -โ”‚ โ””โ”€โ”€ webhook_handler.py -โ”œโ”€โ”€ ui/ -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ dashboard.py # Main scheduling dashboard -โ”‚ โ”œโ”€โ”€ components/ # UI components -โ”‚ โ””โ”€โ”€ views/ # Different view implementations -โ”œโ”€โ”€ utils/ -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ date_utils.py -โ”‚ โ”œโ”€โ”€ error_handling.py -โ”‚ โ””โ”€โ”€ logging.py -โ””โ”€โ”€ tests/ - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ test_scheduler.py - โ””โ”€โ”€ test_integrations.py -``` - -## Implementation Phases - -### Phase 1: Core Scheduler โœ… -- [x] Basic scheduler implementation with APScheduler -- [x] Job persistence and recovery -- [x] Basic error handling -- [x] Simple scheduling interface - -### Phase 2: Integration & Platform Support โœ… -- [x] Platform adapter system -- [x] Content Calendar integration -- [x] Basic monitoring system -- [x] Schedule validation - -### Phase 3: Advanced Features โœ… -- [x] Schedule optimization -- [x] Advanced error handling -- [x] Performance metrics -- [x] Health monitoring - -### Phase 4: UI & Dashboard โœ… -- [x] Interactive dashboard -- [x] Schedule visualization -- [x] Analytics dashboard -- [x] Alert system - -## Technical Requirements - -### Dependencies -- APScheduler >= 3.9.1 -- SQLAlchemy (for job persistence) -- FastAPI (for API endpoints) -- Streamlit >= 1.24.0 (for dashboard) -- Pandas >= 1.5.0 (for data handling) -- Plotly >= 5.13.0 (for visualizations) -- Redis (optional, for distributed scheduling) - -## Integration Points - -### Content Calendar -- [x] Direct integration with existing calendar system -- [x] Bidirectional sync of schedules -- [x] Shared data models -- [x] Real-time event synchronization -- [x] Calendar event management - -### Platform Adapters -- [x] Twitter -- [ ] Facebook -- [ ] LinkedIn -- [ ] Instagram -- [ ] WordPress -- [ ] Custom platform support - -### External Systems -- [ ] Webhook support -- [ ] REST API -- [ ] Event system -- [ ] Notification system - -## Monitoring & Maintenance - -### Health Checks -- [x] Schedule validation -- [x] Job execution monitoring -- [x] System resource monitoring -- [x] Integration health checks - -### Maintenance Tasks -- [ ] Log rotation -- [ ] Database cleanup -- [ ] Performance optimization -- [ ] Security updates - -## Security Considerations - -- [ ] API authentication -- [ ] Job execution security -- [ ] Data encryption -- [ ] Access control -- [ ] Audit logging - -## Future Enhancements - -### Short-term (Next Release) -- [ ] Webhook support for external integrations -- [ ] REST API endpoints -- [ ] Additional platform adapters -- [ ] Backup and restore capabilities - -### Medium-term -- [ ] AI-powered schedule optimization - - Smart posting time recommendations - - Platform-specific optimal posting times - - Audience engagement pattern analysis - - Content type-specific timing optimization - - Content performance prediction - - Engagement rate forecasting - - Reach and visibility predictions - - Viral potential assessment - - Automated schedule adjustments - - Dynamic rescheduling based on performance - - A/B testing of posting times - - Real-time optimization based on engagement - - Audience behavior analysis - - Timezone-based audience activity patterns - - Content consumption patterns - - Engagement trend analysis - - Content type optimization - - Best content type for specific times - - Platform-specific content recommendations - - Content mix optimization -- [ ] Advanced analytics with ML insights - - Predictive analytics for content performance - - Audience growth forecasting - - Engagement trend analysis - - ROI prediction for scheduled content -- [ ] Multi-account support -- [ ] Custom scheduling algorithms - -### Long-term -- [ ] Distributed scheduling support -- [ ] Advanced reporting system -- [ ] Machine learning for optimal posting times - - Deep learning models for engagement prediction - - Reinforcement learning for schedule optimization - - Natural language processing for content analysis - - Computer vision for visual content optimization -- [ ] Integration with external analytics tools -- [ ] AI-powered content recommendations - - Content type suggestions based on performance - - Topic and format recommendations - - Platform-specific content optimization - - Audience interest prediction -- [ ] Smart content repurposing - - Automated content adaptation for different platforms - - Format optimization based on platform performance - - Content refresh recommendations - - Cross-platform content strategy optimization -- [ ] Automated A/B testing framework - - Schedule timing experiments - - Content format testing - - Platform-specific optimization - - Audience segment testing -- [ ] Intelligent resource allocation - - Automated workload distribution - - Resource optimization based on content priority - - Smart queue management - - Performance-based resource allocation - -### AI-Enhanced User Experience -- [ ] Smart scheduling assistant - - Natural language schedule creation - - Context-aware scheduling suggestions - - Automated conflict resolution - - Intelligent schedule adjustments -- [ ] Predictive maintenance - - System health forecasting - - Proactive issue detection - - Automated recovery suggestions - - Performance optimization recommendations -- [ ] Personalized dashboard - - AI-curated insights - - Custom metric recommendations - - Automated report generation - - Smart alert configuration -- [ ] Intelligent automation - - Smart schedule templates - - Automated content categorization - - Platform-specific optimization rules - - Dynamic workflow automation -- [ ] Advanced analytics visualization - - Interactive AI-powered insights - - Real-time performance predictions - - Trend analysis and forecasting - - Custom visualization recommendations - -## Suggested Improvements & Enhancements - -### Performance Optimizations -- [ ] Implement caching layer for frequently accessed data - - Schedule metadata caching - - Platform analytics caching - - Calendar event caching -- [ ] Optimize database queries - - Add database indexes for common queries - - Implement query result caching - - Optimize join operations -- [ ] Enhance job processing - - Implement job batching for similar tasks - - Add parallel processing for independent jobs - - Optimize resource allocation - -### Reliability Enhancements -- [ ] Implement advanced error recovery - - Automatic retry with exponential backoff - - Circuit breaker pattern for external services - - Graceful degradation during failures -- [ ] Add comprehensive monitoring - - Real-time performance metrics - - Resource usage tracking - - Error rate monitoring -- [ ] Enhance data consistency - - Implement distributed transactions - - Add data validation layers - - Implement optimistic locking - -### User Experience Improvements - -#### Enhanced Dashboard Features -- [ ] Smart Dashboard Layout - - Drag-and-drop widget arrangement - - Customizable dashboard themes - - Responsive grid layout - - Collapsible sections - - Quick action toolbar - - Keyboard shortcuts support - -- [ ] Advanced Content Management - - Bulk content scheduling - - Content templates library - - Content preview with platform simulation - - Content performance predictions - - Content recycling suggestions - - Content calendar sync status - -- [ ] Intelligent Schedule Management - - Smart schedule suggestions - - Conflict-free scheduling - - Schedule templates - - Recurring schedule patterns - - Schedule optimization recommendations - - Schedule health indicators - -- [ ] Platform-Specific Features - - Platform-specific scheduling rules - - Platform analytics integration - - Platform-specific content guidelines - - Platform performance metrics - - Platform-specific templates - - Platform health status - -#### Improved Visualization - -##### Interactive Calendar Views -- [ ] Multi-view Calendar System - - Day View - - Hour-by-hour schedule display - - Time slot availability indicators - - Schedule conflict highlighting - - Quick schedule creation - - Drag-and-drop rescheduling - - Schedule details on hover - - Week View - - 7-day calendar layout - - Daily schedule summaries - - Cross-day schedule visualization - - Week-over-week comparison - - Schedule density indicators - - Quick navigation controls - - Month View - - Full month calendar display - - Schedule count indicators - - Color-coded schedule types - - Month navigation - - Schedule preview on hover - - Bulk schedule management - - Year View - - Annual schedule overview - - Quarter-by-quarter breakdown - - Schedule distribution heatmap - - Year-over-year comparison - - Major milestone markers - - Schedule trend visualization - -- [ ] Advanced Calendar Features - - Schedule Conflict Management - - Real-time conflict detection - - Visual conflict indicators - - Conflict resolution suggestions - - Automatic conflict avoidance - - Conflict history tracking - - Resolution workflow - - Calendar Overlay System - - Multiple calendar layers - - Platform-specific overlays - - Team schedule overlays - - Content type overlays - - Custom overlay creation - - Overlay visibility controls - - Interactive Controls - - Zoom and pan functionality - - Quick date navigation - - Schedule filtering - - View customization - - Export options - - Print layouts - -##### Advanced Analytics Visualization -- [ ] Real-time Performance Charts - - Engagement Metrics - - Likes, shares, comments tracking - - Engagement rate trends - - Audience growth charts - - Platform-specific metrics - - Custom metric tracking - - Real-time updates - - Content Performance - - Content type effectiveness - - Best performing content - - Performance predictions - - A/B test results - - ROI visualization - - Trend analysis - - Platform Analytics - - Platform comparison charts - - Platform-specific metrics - - Cross-platform analysis - - Platform health indicators - - Performance benchmarks - - Growth tracking - -- [ ] Custom Chart Builder - - Chart Types - - Line charts for trends - - Bar charts for comparisons - - Pie charts for distribution - - Scatter plots for correlation - - Heat maps for patterns - - Custom chart types - - Data Configuration - - Metric selection - - Time range control - - Data filtering - - Aggregation options - - Custom calculations - - Data export - - Visualization Options - - Color schemes - - Chart layouts - - Annotation tools - - Interactive elements - - Export formats - - Sharing options - -##### Schedule Timeline Views -- [ ] Interactive Gantt Charts - - Schedule Visualization - - Task dependencies - - Progress tracking - - Milestone markers - - Resource allocation - - Timeline scaling - - Critical path highlighting - - Dependency Management - - Dependency creation - - Dependency visualization - - Conflict detection - - Resolution suggestions - - Impact analysis - - Dependency history - - Timeline Controls - - Zoom levels - - Pan navigation - - Filter options - - Group by options - - Export capabilities - - Print layouts - -- [ ] Progress Tracking - - Visual Indicators - - Progress bars - - Status icons - - Completion percentages - - Delay indicators - - Risk markers - - Health status - - Milestone Tracking - - Milestone creation - - Due date tracking - - Completion status - - Dependency impact - - Notification triggers - - History tracking - -##### Content Performance Dashboards -- [ ] Performance Scorecards - - Key Metrics - - Engagement rates - - Reach metrics - - Conversion rates - - ROI calculations - - Growth indicators - - Platform performance - - Custom Metrics - - Metric creation - - Formula builder - - Threshold setting - - Alert configuration - - Trend analysis - - Benchmark comparison - -- [ ] ROI Visualization - - Financial Metrics - - Cost tracking - - Revenue attribution - - ROI calculations - - Budget allocation - - Cost efficiency - - Profitability analysis - - Performance Metrics - - Engagement value - - Conversion value - - Customer lifetime value - - Platform value - - Content value - - Campaign value - -- [ ] Audience Insights - - Demographics - - Age distribution - - Gender breakdown - - Location data - - Device usage - - Platform preference - - Engagement patterns - - Behavior Analysis - - Content preferences - - Time patterns - - Platform usage - - Engagement trends - - Conversion paths - - Retention metrics - -#### Better Notification System -- [ ] Smart Notification Center - - Centralized notification hub - - Notification categories - - Priority-based sorting - - Read/unread status - - Notification history - - Bulk notification actions - -- [ ] Customizable Alert Rules - - Schedule status alerts - - Performance threshold alerts - - Platform-specific alerts - - Content engagement alerts - - System health alerts - - Custom alert conditions - -- [ ] Multi-channel Notifications - - Email notifications - - In-app notifications - - Mobile push notifications - - SMS alerts - - Slack/Teams integration - - Webhook notifications - -- [ ] Intelligent Notification Management - - Smart notification grouping - - Notification frequency control - - Quiet hours setting - - Do not disturb mode - - Notification preferences - - Notification templates - -- [ ] Action-oriented Notifications - - One-click actions - - Quick response options - - Context-aware suggestions - - Batch action support - - Follow-up reminders - - Escalation paths - -- [ ] Notification Analytics - - Notification engagement tracking - - Response time metrics - - Alert effectiveness analysis - - User preference insights - - Notification optimization - - Usage patterns - -### Integration Enhancements -- [ ] Extended platform support - - Additional social media platforms - - Blog platforms integration - - Email marketing platforms - - Custom platform adapters -- [ ] Enhanced API capabilities - - GraphQL API support - - Webhook event system - - API rate limiting - - API versioning -- [ ] Advanced calendar features - - Multiple calendar support - - Calendar conflict resolution - - Calendar sharing and collaboration - - Calendar analytics - -### Security Improvements -- [ ] Enhanced authentication - - OAuth 2.0 support - - Multi-factor authentication - - Role-based access control - - API key management -- [ ] Data protection - - End-to-end encryption - - Data masking - - Audit logging - - Compliance features -- [ ] Security monitoring - - Real-time security alerts - - Access pattern analysis - - Security audit reports - - Vulnerability scanning - -### Scalability Enhancements -- [ ] Distributed architecture - - Horizontal scaling support - - Load balancing - - Service discovery - - Distributed caching -- [ ] High availability - - Multi-region deployment - - Automatic failover - - Data replication - - Disaster recovery -- [ ] Resource optimization - - Dynamic resource allocation - - Auto-scaling support - - Resource usage optimization - - Cost optimization - -### Analytics & Insights -- [ ] Advanced analytics - - Predictive analytics - - Trend analysis - - Performance forecasting - - ROI tracking -- [ ] Custom reporting - - Report builder - - Custom metrics - - Export capabilities - - Scheduled reports -- [ ] Business intelligence - - KPI tracking - - Goal setting - - Performance benchmarking - - Competitive analysis - -### Development & Maintenance -- [ ] Code quality improvements - - Enhanced test coverage - - Code documentation - - Performance profiling - - Code analysis tools -- [ ] Development workflow - - CI/CD pipeline - - Automated testing - - Code review process - - Release management -- [ ] Maintenance tools - - Automated backups - - Database maintenance - - System health checks - - Performance monitoring - -### Future-Proofing -- [ ] Technology updates - - Framework upgrades - - Dependency updates - - Security patches - - Performance optimizations -- [ ] Feature extensibility - - Plugin system - - Custom integrations - - Extension points - - API evolution -- [ ] Innovation opportunities - - AI/ML integration - - Blockchain integration - - IoT integration - - Emerging technologies - -## Contributing - -Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests. - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/core/conflict_resolver.py b/ToBeMigrated/content_scheduler/core/conflict_resolver.py deleted file mode 100644 index 6e74d55b..00000000 --- a/ToBeMigrated/content_scheduler/core/conflict_resolver.py +++ /dev/null @@ -1,403 +0,0 @@ -""" -Conflict resolution system for content scheduling. -""" - -import logging -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional, Tuple -from dataclasses import dataclass - -# Use unified database models -from lib.database.models import ContentItem, Schedule, ScheduleStatus - -logger = logging.getLogger(__name__) - -@dataclass -class ConflictInfo: - """Information about a scheduling conflict.""" - schedule_1: Schedule - schedule_2: Schedule - conflict_type: str - severity: str - description: str - suggested_resolution: str - -class ConflictResolver: - """Resolve scheduling conflicts automatically.""" - - def __init__(self): - """Initialize the conflict resolver.""" - self.logger = logger - self.resolution_strategies = { - 'time_overlap': self._resolve_time_overlap, - 'platform_conflict': self._resolve_platform_conflict, - 'resource_conflict': self._resolve_resource_conflict, - 'priority_conflict': self._resolve_priority_conflict - } - - def detect_conflicts(self, schedules: List[Schedule]) -> List[ConflictInfo]: - """Detect conflicts between schedules. - - Args: - schedules: List of Schedule objects to check - - Returns: - List of detected conflicts - """ - try: - conflicts = [] - - # Sort schedules by time - sorted_schedules = sorted(schedules, key=lambda x: x.scheduled_time) - - for i in range(len(sorted_schedules)): - for j in range(i + 1, len(sorted_schedules)): - schedule_1 = sorted_schedules[i] - schedule_2 = sorted_schedules[j] - - # Check for time overlap conflicts - time_conflicts = self._check_time_overlap(schedule_1, schedule_2) - conflicts.extend(time_conflicts) - - # Check for platform conflicts - platform_conflicts = self._check_platform_conflict(schedule_1, schedule_2) - conflicts.extend(platform_conflicts) - - # Check for priority conflicts - priority_conflicts = self._check_priority_conflict(schedule_1, schedule_2) - conflicts.extend(priority_conflicts) - - return conflicts - - except Exception as e: - self.logger.error(f"Error detecting conflicts: {str(e)}") - return [] - - def _check_time_overlap(self, schedule_1: Schedule, schedule_2: Schedule) -> List[ConflictInfo]: - """Check for time overlap conflicts.""" - conflicts = [] - - try: - # Assume each schedule takes 1 hour (can be made configurable) - duration = timedelta(hours=1) - - end_1 = schedule_1.scheduled_time + duration - end_2 = schedule_2.scheduled_time + duration - - # Check for overlap - if (schedule_1.scheduled_time < end_2 and end_1 > schedule_2.scheduled_time): - time_diff = abs((schedule_2.scheduled_time - schedule_1.scheduled_time).total_seconds() / 60) - - severity = 'high' if time_diff < 30 else 'medium' - - conflicts.append(ConflictInfo( - schedule_1=schedule_1, - schedule_2=schedule_2, - conflict_type='time_overlap', - severity=severity, - description=f"Schedules overlap by {60 - time_diff:.0f} minutes", - suggested_resolution=f"Move one schedule by at least {60 - time_diff + 15:.0f} minutes" - )) - - except Exception as e: - self.logger.error(f"Error checking time overlap: {str(e)}") - - return conflicts - - def _check_platform_conflict(self, schedule_1: Schedule, schedule_2: Schedule) -> List[ConflictInfo]: - """Check for platform conflicts.""" - conflicts = [] - - try: - # This is a placeholder - platform conflicts would depend on specific platform limitations - # For now, we'll check if schedules are too close on the same platform - - time_diff = abs((schedule_2.scheduled_time - schedule_1.scheduled_time).total_seconds() / 60) - - # If schedules are within 15 minutes, it might be a platform conflict - if time_diff < 15: - conflicts.append(ConflictInfo( - schedule_1=schedule_1, - schedule_2=schedule_2, - conflict_type='platform_conflict', - severity='medium', - description=f"Schedules too close for optimal platform performance", - suggested_resolution="Space schedules at least 15 minutes apart" - )) - - except Exception as e: - self.logger.error(f"Error checking platform conflict: {str(e)}") - - return conflicts - - def _check_priority_conflict(self, schedule_1: Schedule, schedule_2: Schedule) -> List[ConflictInfo]: - """Check for priority conflicts.""" - conflicts = [] - - try: - # Check if high priority items are scheduled too close to low priority items - if schedule_1.priority > 7 and schedule_2.priority < 4: - time_diff = abs((schedule_2.scheduled_time - schedule_1.scheduled_time).total_seconds() / 60) - - if time_diff < 60: # Within 1 hour - conflicts.append(ConflictInfo( - schedule_1=schedule_1, - schedule_2=schedule_2, - conflict_type='priority_conflict', - severity='low', - description="High priority content scheduled close to low priority content", - suggested_resolution="Consider spacing high and low priority content further apart" - )) - - except Exception as e: - self.logger.error(f"Error checking priority conflict: {str(e)}") - - return conflicts - - def resolve_conflicts(self, conflicts: List[ConflictInfo]) -> Dict[str, Any]: - """Resolve detected conflicts automatically. - - Args: - conflicts: List of conflicts to resolve - - Returns: - Dictionary containing resolution results - """ - try: - resolved_conflicts = [] - unresolved_conflicts = [] - schedule_adjustments = {} - - for conflict in conflicts: - try: - # Get resolution strategy - strategy = self.resolution_strategies.get(conflict.conflict_type) - - if strategy: - resolution = strategy(conflict) - - if resolution['success']: - resolved_conflicts.append({ - 'conflict': conflict, - 'resolution': resolution - }) - - # Track schedule adjustments - for schedule_id, adjustments in resolution.get('adjustments', {}).items(): - if schedule_id not in schedule_adjustments: - schedule_adjustments[schedule_id] = {} - schedule_adjustments[schedule_id].update(adjustments) - else: - unresolved_conflicts.append(conflict) - else: - unresolved_conflicts.append(conflict) - - except Exception as e: - self.logger.error(f"Error resolving conflict: {str(e)}") - unresolved_conflicts.append(conflict) - - return { - 'resolved_conflicts': resolved_conflicts, - 'unresolved_conflicts': unresolved_conflicts, - 'schedule_adjustments': schedule_adjustments, - 'success_rate': len(resolved_conflicts) / len(conflicts) if conflicts else 1.0 - } - - except Exception as e: - self.logger.error(f"Error resolving conflicts: {str(e)}") - return { - 'resolved_conflicts': [], - 'unresolved_conflicts': conflicts, - 'schedule_adjustments': {}, - 'success_rate': 0.0 - } - - def _resolve_time_overlap(self, conflict: ConflictInfo) -> Dict[str, Any]: - """Resolve time overlap conflicts.""" - try: - # Strategy: Move the lower priority schedule - schedule_1 = conflict.schedule_1 - schedule_2 = conflict.schedule_2 - - # Determine which schedule to move - if schedule_1.priority >= schedule_2.priority: - schedule_to_move = schedule_2 - anchor_schedule = schedule_1 - else: - schedule_to_move = schedule_1 - anchor_schedule = schedule_2 - - # Calculate new time (move 1.5 hours after anchor) - new_time = anchor_schedule.scheduled_time + timedelta(hours=1.5) - - return { - 'success': True, - 'strategy': 'move_lower_priority', - 'adjustments': { - str(schedule_to_move.id): { - 'new_scheduled_time': new_time, - 'reason': 'Resolved time overlap conflict' - } - }, - 'description': f"Moved schedule {schedule_to_move.id} to {new_time}" - } - - except Exception as e: - self.logger.error(f"Error resolving time overlap: {str(e)}") - return {'success': False, 'error': str(e)} - - def _resolve_platform_conflict(self, conflict: ConflictInfo) -> Dict[str, Any]: - """Resolve platform conflicts.""" - try: - # Strategy: Space schedules 20 minutes apart - schedule_1 = conflict.schedule_1 - schedule_2 = conflict.schedule_2 - - # Move the later schedule - if schedule_1.scheduled_time < schedule_2.scheduled_time: - schedule_to_move = schedule_2 - anchor_time = schedule_1.scheduled_time - else: - schedule_to_move = schedule_1 - anchor_time = schedule_2.scheduled_time - - new_time = anchor_time + timedelta(minutes=20) - - return { - 'success': True, - 'strategy': 'space_schedules', - 'adjustments': { - str(schedule_to_move.id): { - 'new_scheduled_time': new_time, - 'reason': 'Resolved platform conflict' - } - }, - 'description': f"Spaced schedule {schedule_to_move.id} to {new_time}" - } - - except Exception as e: - self.logger.error(f"Error resolving platform conflict: {str(e)}") - return {'success': False, 'error': str(e)} - - def _resolve_resource_conflict(self, conflict: ConflictInfo) -> Dict[str, Any]: - """Resolve resource conflicts.""" - try: - # This is a placeholder for resource conflict resolution - return { - 'success': False, - 'reason': 'Resource conflict resolution not implemented' - } - - except Exception as e: - self.logger.error(f"Error resolving resource conflict: {str(e)}") - return {'success': False, 'error': str(e)} - - def _resolve_priority_conflict(self, conflict: ConflictInfo) -> Dict[str, Any]: - """Resolve priority conflicts.""" - try: - # Strategy: Move low priority content away from high priority content - schedule_1 = conflict.schedule_1 - schedule_2 = conflict.schedule_2 - - # Identify high and low priority schedules - if schedule_1.priority > schedule_2.priority: - high_priority = schedule_1 - low_priority = schedule_2 - else: - high_priority = schedule_2 - low_priority = schedule_1 - - # Move low priority content 2 hours away - new_time = high_priority.scheduled_time + timedelta(hours=2) - - return { - 'success': True, - 'strategy': 'separate_priorities', - 'adjustments': { - str(low_priority.id): { - 'new_scheduled_time': new_time, - 'reason': 'Resolved priority conflict' - } - }, - 'description': f"Moved low priority schedule {low_priority.id} to {new_time}" - } - - except Exception as e: - self.logger.error(f"Error resolving priority conflict: {str(e)}") - return {'success': False, 'error': str(e)} - - def suggest_optimal_schedule( - self, - new_schedule: Schedule, - existing_schedules: List[Schedule] - ) -> Dict[str, Any]: - """Suggest optimal scheduling for new content. - - Args: - new_schedule: New schedule to optimize - existing_schedules: List of existing schedules - - Returns: - Dictionary containing optimization suggestions - """ - try: - suggestions = [] - - # Check for conflicts with proposed time - all_schedules = existing_schedules + [new_schedule] - conflicts = self.detect_conflicts(all_schedules) - - if not conflicts: - return { - 'optimal_time': new_schedule.scheduled_time, - 'conflicts': [], - 'suggestions': ['Current time is optimal'] - } - - # Generate alternative times - base_time = new_schedule.scheduled_time - alternative_times = [] - - # Try different time slots - for hours_offset in [1, 2, 3, -1, -2, -3]: - alt_time = base_time + timedelta(hours=hours_offset) - alt_schedule = Schedule( - content_item_id=new_schedule.content_item_id, - scheduled_time=alt_time, - status=new_schedule.status, - recurrence=new_schedule.recurrence, - priority=new_schedule.priority - ) - - # Check conflicts for this alternative - alt_conflicts = self.detect_conflicts(existing_schedules + [alt_schedule]) - - alternative_times.append({ - 'time': alt_time, - 'conflicts': len(alt_conflicts), - 'severity': max([c.severity for c in alt_conflicts], default='none') - }) - - # Sort by number of conflicts and severity - alternative_times.sort(key=lambda x: (x['conflicts'], x['severity'])) - - optimal_time = alternative_times[0]['time'] if alternative_times else new_schedule.scheduled_time - - return { - 'optimal_time': optimal_time, - 'conflicts': conflicts, - 'alternatives': alternative_times[:3], # Top 3 alternatives - 'suggestions': [ - f"Consider scheduling at {optimal_time}", - f"Current time has {len(conflicts)} conflicts", - "Review alternative times for better optimization" - ] - } - - except Exception as e: - self.logger.error(f"Error suggesting optimal schedule: {str(e)}") - return { - 'optimal_time': new_schedule.scheduled_time, - 'conflicts': [], - 'suggestions': ['Error occurred during optimization'] - } \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/core/health_checker.py b/ToBeMigrated/content_scheduler/core/health_checker.py deleted file mode 100644 index 735b63ad..00000000 --- a/ToBeMigrated/content_scheduler/core/health_checker.py +++ /dev/null @@ -1,584 +0,0 @@ -""" -Schedule health monitoring system. -""" - -import logging -import asyncio -from typing import Dict, Any, List, Optional -from datetime import datetime, timedelta -from dataclasses import dataclass -from enum import Enum - -from ..utils.error_handling import SchedulingError - -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - -class HealthStatus(Enum): - """Health check status.""" - HEALTHY = "healthy" - WARNING = "warning" - CRITICAL = "critical" - UNKNOWN = "unknown" - -@dataclass -class HealthCheck: - """Health check result.""" - component: str - status: HealthStatus - message: str - details: Dict[str, Any] - timestamp: datetime - -class ScheduleHealthChecker: - """Schedule health monitoring system.""" - - def __init__( - self, - scheduler, - check_interval: int = 300, # 5 minutes - warning_threshold: int = 3, - critical_threshold: int = 5 - ): - """Initialize the health checker. - - Args: - scheduler: ContentScheduler instance - check_interval: Health check interval in seconds - warning_threshold: Number of failures before warning - critical_threshold: Number of failures before critical - """ - self.logger = logger - self.scheduler = scheduler - self.check_interval = check_interval - self.warning_threshold = warning_threshold - self.critical_threshold = critical_threshold - - # Initialize health check history - self.health_history = [] - - # Initialize failure counters - self.failure_counts = { - 'job_execution': 0, - 'platform_publish': 0, - 'schedule_conflicts': 0, - 'resource_usage': 0 - } - - # Initialize monitoring task - self.monitoring_task = None - - async def start_monitoring(self): - """Start the health monitoring system.""" - try: - if not self.monitoring_task: - self.monitoring_task = asyncio.create_task(self._monitor_health()) - self.logger.info("Health monitoring started") - except Exception as e: - self.logger.error(f"Failed to start health monitoring: {str(e)}") - raise SchedulingError(f"Health monitoring start failed: {str(e)}") - - async def stop_monitoring(self): - """Stop the health monitoring system.""" - try: - if self.monitoring_task: - self.monitoring_task.cancel() - self.monitoring_task = None - self.logger.info("Health monitoring stopped") - except Exception as e: - self.logger.error(f"Failed to stop health monitoring: {str(e)}") - raise SchedulingError(f"Health monitoring stop failed: {str(e)}") - - async def _monitor_health(self): - """Monitor system health periodically.""" - while True: - try: - # Perform health checks - health_checks = await self._perform_health_checks() - - # Update health history - self.health_history.extend(health_checks) - - # Trim history if too long - if len(self.health_history) > 1000: - self.health_history = self.health_history[-1000:] - - # Check for critical issues - critical_checks = [ - check for check in health_checks - if check.status == HealthStatus.CRITICAL - ] - - if critical_checks: - await self._handle_critical_issues(critical_checks) - - # Wait for next check - await asyncio.sleep(self.check_interval) - - except asyncio.CancelledError: - break - except Exception as e: - self.logger.error(f"Health monitoring error: {str(e)}") - await asyncio.sleep(self.check_interval) - - async def _perform_health_checks(self) -> List[HealthCheck]: - """Perform all health checks. - - Returns: - List of health check results - """ - checks = [] - - try: - # Check scheduler status - checks.append(await self._check_scheduler_status()) - - # Check job execution - checks.append(await self._check_job_execution()) - - # Check platform connectivity - checks.append(await self._check_platform_connectivity()) - - # Check resource usage - checks.append(await self._check_resource_usage()) - - # Check schedule conflicts - checks.append(await self._check_schedule_conflicts()) - - # Check database connection - checks.append(await self._check_database_connection()) - - # Check job store - checks.append(await self._check_job_store()) - - return checks - - except Exception as e: - self.logger.error(f"Health check failed: {str(e)}") - return [ - HealthCheck( - component="health_checker", - status=HealthStatus.CRITICAL, - message=f"Health check system error: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - ] - - async def _check_scheduler_status(self) -> HealthCheck: - """Check scheduler status. - - Returns: - Health check result - """ - try: - is_running = self.scheduler.scheduler.running - job_count = len(self.scheduler.scheduler.get_jobs()) - - if not is_running: - return HealthCheck( - component="scheduler", - status=HealthStatus.CRITICAL, - message="Scheduler is not running", - details={'job_count': job_count}, - timestamp=datetime.utcnow() - ) - - return HealthCheck( - component="scheduler", - status=HealthStatus.HEALTHY, - message="Scheduler is running", - details={'job_count': job_count}, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="scheduler", - status=HealthStatus.CRITICAL, - message=f"Scheduler check failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _check_job_execution(self) -> HealthCheck: - """Check job execution health. - - Returns: - Health check result - """ - try: - # Get recent job history - recent_jobs = [ - job for job in self.scheduler.job_status.values() - if datetime.utcnow() - job['created_at'] < timedelta(hours=24) - ] - - # Calculate failure rate - total_jobs = len(recent_jobs) - failed_jobs = len([ - job for job in recent_jobs - if job['status'] == 'FAILED' - ]) - - failure_rate = failed_jobs / total_jobs if total_jobs > 0 else 0 - - # Update failure counter - self.failure_counts['job_execution'] = failed_jobs - - if failure_rate >= 0.2: # 20% failure rate - return HealthCheck( - component="job_execution", - status=HealthStatus.CRITICAL, - message="High job failure rate detected", - details={ - 'total_jobs': total_jobs, - 'failed_jobs': failed_jobs, - 'failure_rate': failure_rate - }, - timestamp=datetime.utcnow() - ) - elif failure_rate >= 0.1: # 10% failure rate - return HealthCheck( - component="job_execution", - status=HealthStatus.WARNING, - message="Elevated job failure rate", - details={ - 'total_jobs': total_jobs, - 'failed_jobs': failed_jobs, - 'failure_rate': failure_rate - }, - timestamp=datetime.utcnow() - ) - - return HealthCheck( - component="job_execution", - status=HealthStatus.HEALTHY, - message="Job execution is healthy", - details={ - 'total_jobs': total_jobs, - 'failed_jobs': failed_jobs, - 'failure_rate': failure_rate - }, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="job_execution", - status=HealthStatus.CRITICAL, - message=f"Job execution check failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _check_platform_connectivity(self) -> HealthCheck: - """Check platform connectivity. - - Returns: - Health check result - """ - try: - # Get unique platforms from recent jobs - platforms = set() - for job in self.scheduler.job_status.values(): - if 'schedule' in job: - platforms.update(job['schedule'].platforms) - - # Check each platform - platform_status = {} - for platform in platforms: - try: - adapter = self.scheduler._get_platform_adapter(platform) - # Try to get platform status - status = await adapter.get_platform_status() - platform_status[platform] = status['status'] - except Exception as e: - platform_status[platform] = 'error' - self.failure_counts['platform_publish'] += 1 - - # Check overall status - if any(status == 'error' for status in platform_status.values()): - return HealthCheck( - component="platform_connectivity", - status=HealthStatus.CRITICAL, - message="Platform connectivity issues detected", - details={'platform_status': platform_status}, - timestamp=datetime.utcnow() - ) - - return HealthCheck( - component="platform_connectivity", - status=HealthStatus.HEALTHY, - message="Platform connectivity is healthy", - details={'platform_status': platform_status}, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="platform_connectivity", - status=HealthStatus.CRITICAL, - message=f"Platform connectivity check failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _check_resource_usage(self) -> HealthCheck: - """Check system resource usage. - - Returns: - Health check result - """ - try: - import psutil - - # Get system metrics - cpu_percent = psutil.cpu_percent() - memory_percent = psutil.virtual_memory().percent - disk_percent = psutil.disk_usage('/').percent - - # Check thresholds - if cpu_percent > 90 or memory_percent > 90 or disk_percent > 90: - self.failure_counts['resource_usage'] += 1 - return HealthCheck( - component="resource_usage", - status=HealthStatus.CRITICAL, - message="High resource usage detected", - details={ - 'cpu_percent': cpu_percent, - 'memory_percent': memory_percent, - 'disk_percent': disk_percent - }, - timestamp=datetime.utcnow() - ) - elif cpu_percent > 70 or memory_percent > 70 or disk_percent > 70: - return HealthCheck( - component="resource_usage", - status=HealthStatus.WARNING, - message="Elevated resource usage", - details={ - 'cpu_percent': cpu_percent, - 'memory_percent': memory_percent, - 'disk_percent': disk_percent - }, - timestamp=datetime.utcnow() - ) - - return HealthCheck( - component="resource_usage", - status=HealthStatus.HEALTHY, - message="Resource usage is healthy", - details={ - 'cpu_percent': cpu_percent, - 'memory_percent': memory_percent, - 'disk_percent': disk_percent - }, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="resource_usage", - status=HealthStatus.CRITICAL, - message=f"Resource usage check failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _check_schedule_conflicts(self) -> HealthCheck: - """Check for schedule conflicts. - - Returns: - Health check result - """ - try: - # Get all pending schedules - pending_schedules = [ - job['schedule'] for job in self.scheduler.job_status.values() - if job['status'] == 'PENDING' - ] - - # Check for conflicts - conflicts = await self.scheduler.conflict_resolver.detect_conflicts( - pending_schedules - ) - - if conflicts: - self.failure_counts['schedule_conflicts'] += len(conflicts) - return HealthCheck( - component="schedule_conflicts", - status=HealthStatus.WARNING, - message="Schedule conflicts detected", - details={ - 'conflict_count': len(conflicts), - 'conflicts': [c.dict() for c in conflicts] - }, - timestamp=datetime.utcnow() - ) - - return HealthCheck( - component="schedule_conflicts", - status=HealthStatus.HEALTHY, - message="No schedule conflicts detected", - details={'conflict_count': 0}, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="schedule_conflicts", - status=HealthStatus.CRITICAL, - message=f"Schedule conflict check failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _check_database_connection(self) -> HealthCheck: - """Check database connection health. - - Returns: - Health check result - """ - try: - session = self.scheduler.Session() - session.execute("SELECT 1") - session.close() - - return HealthCheck( - component="database", - status=HealthStatus.HEALTHY, - message="Database connection is healthy", - details={}, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="database", - status=HealthStatus.CRITICAL, - message=f"Database connection failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _check_job_store(self) -> HealthCheck: - """Check job store health. - - Returns: - Health check result - """ - try: - # Get job store statistics - job_count = len(self.scheduler.scheduler.get_jobs()) - store_size = len(self.scheduler.job_status) - - if job_count != store_size: - return HealthCheck( - component="job_store", - status=HealthStatus.WARNING, - message="Job store inconsistency detected", - details={ - 'job_count': job_count, - 'store_size': store_size - }, - timestamp=datetime.utcnow() - ) - - return HealthCheck( - component="job_store", - status=HealthStatus.HEALTHY, - message="Job store is healthy", - details={ - 'job_count': job_count, - 'store_size': store_size - }, - timestamp=datetime.utcnow() - ) - - except Exception as e: - return HealthCheck( - component="job_store", - status=HealthStatus.CRITICAL, - message=f"Job store check failed: {str(e)}", - details={'error': str(e)}, - timestamp=datetime.utcnow() - ) - - async def _handle_critical_issues(self, critical_checks: List[HealthCheck]): - """Handle critical health issues. - - Args: - critical_checks: List of critical health checks - """ - try: - # Log critical issues - for check in critical_checks: - self.logger.error( - f"Critical health issue in {check.component}: {check.message}" - ) - - # Attempt recovery actions - for check in critical_checks: - if check.component == "scheduler" and not self.scheduler.scheduler.running: - await self.scheduler.start() - - elif check.component == "database": - # Attempt to reconnect - self.scheduler.engine.dispose() - self.scheduler.engine = create_engine(self.scheduler.db_url) - self.scheduler.Session = sessionmaker(bind=self.scheduler.engine) - - elif check.component == "job_store": - # Attempt to recover job store - await self.scheduler._recover_jobs() - - # Reset failure counters if recovery successful - self.failure_counts = {k: 0 for k in self.failure_counts} - - except Exception as e: - self.logger.error(f"Failed to handle critical issues: {str(e)}") - - def get_health_summary(self) -> Dict[str, Any]: - """Get health check summary. - - Returns: - Dictionary containing health summary - """ - try: - # Get latest health checks - latest_checks = { - check.component: check - for check in self.health_history[-len(self.health_history):] - } - - # Calculate overall status - if any(check.status == HealthStatus.CRITICAL for check in latest_checks.values()): - overall_status = HealthStatus.CRITICAL - elif any(check.status == HealthStatus.WARNING for check in latest_checks.values()): - overall_status = HealthStatus.WARNING - else: - overall_status = HealthStatus.HEALTHY - - return { - 'status': overall_status.value, - 'components': { - component: { - 'status': check.status.value, - 'message': check.message, - 'details': check.details, - 'timestamp': check.timestamp.isoformat() - } - for component, check in latest_checks.items() - }, - 'failure_counts': self.failure_counts, - 'last_check': datetime.utcnow().isoformat() - } - - except Exception as e: - self.logger.error(f"Failed to get health summary: {str(e)}") - return { - 'status': HealthStatus.UNKNOWN.value, - 'error': str(e), - 'last_check': datetime.utcnow().isoformat() - } \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/core/schedule_optimizer.py b/ToBeMigrated/content_scheduler/core/schedule_optimizer.py deleted file mode 100644 index c2e901ab..00000000 --- a/ToBeMigrated/content_scheduler/core/schedule_optimizer.py +++ /dev/null @@ -1,597 +0,0 @@ -""" -Schedule optimization system for content scheduling. -""" - -import logging -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional, Tuple -from dataclasses import dataclass -import numpy as np -from collections import defaultdict - -# Use unified database models -from lib.database.models import ContentItem, Schedule, ScheduleStatus, ContentType, Platform, get_session - -logger = logging.getLogger(__name__) - -@dataclass -class OptimizationResult: - """Result of schedule optimization.""" - original_schedule: Schedule - optimized_time: datetime - improvement_score: float - optimization_reason: str - confidence: float - -class ScheduleOptimizer: - """Optimize content scheduling for maximum engagement.""" - - def __init__(self): - """Initialize the schedule optimizer.""" - self.logger = logger - self.session = get_session() - - # Platform-specific optimal times (can be made configurable) - self.platform_optimal_times = { - Platform.TWITTER: [9, 12, 15, 18], # Hours of day - Platform.FACEBOOK: [9, 13, 15], - Platform.LINKEDIN: [8, 12, 17], - Platform.INSTAGRAM: [11, 14, 17, 19], - Platform.YOUTUBE: [14, 16, 18, 20] - } - - # Content type engagement patterns - self.content_type_patterns = { - ContentType.ARTICLE: {'peak_hours': [9, 14, 16], 'duration': 2}, - ContentType.VIDEO: {'peak_hours': [12, 18, 20], 'duration': 3}, - ContentType.IMAGE: {'peak_hours': [11, 15, 19], 'duration': 1}, - ContentType.SOCIAL_POST: {'peak_hours': [8, 12, 17, 21], 'duration': 1} - } - - def optimize_schedule(self, schedule: Schedule) -> OptimizationResult: - """Optimize a single schedule for better engagement. - - Args: - schedule: Schedule to optimize - - Returns: - OptimizationResult with optimization details - """ - try: - # Get content item details - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if not content_item: - return OptimizationResult( - original_schedule=schedule, - optimized_time=schedule.scheduled_time, - improvement_score=0.0, - optimization_reason="Content item not found", - confidence=0.0 - ) - - # Calculate current engagement score - current_score = self._calculate_engagement_score( - schedule.scheduled_time, - content_item.content_type, - schedule.priority - ) - - # Find optimal time - optimal_time, optimal_score = self._find_optimal_time( - schedule, - content_item - ) - - # Calculate improvement - improvement_score = optimal_score - current_score - confidence = min(improvement_score / current_score, 1.0) if current_score > 0 else 0.0 - - # Generate optimization reason - reason = self._generate_optimization_reason( - schedule.scheduled_time, - optimal_time, - content_item.content_type, - improvement_score - ) - - return OptimizationResult( - original_schedule=schedule, - optimized_time=optimal_time, - improvement_score=improvement_score, - optimization_reason=reason, - confidence=confidence - ) - - except Exception as e: - self.logger.error(f"Error optimizing schedule: {str(e)}") - return OptimizationResult( - original_schedule=schedule, - optimized_time=schedule.scheduled_time, - improvement_score=0.0, - optimization_reason=f"Optimization error: {str(e)}", - confidence=0.0 - ) - - def optimize_multiple_schedules( - self, - schedules: List[Schedule], - avoid_conflicts: bool = True - ) -> List[OptimizationResult]: - """Optimize multiple schedules considering conflicts. - - Args: - schedules: List of schedules to optimize - avoid_conflicts: Whether to avoid scheduling conflicts - - Returns: - List of optimization results - """ - try: - results = [] - optimized_times = [] - - # Sort schedules by priority (high priority first) - sorted_schedules = sorted(schedules, key=lambda x: x.priority, reverse=True) - - for schedule in sorted_schedules: - # Optimize individual schedule - result = self.optimize_schedule(schedule) - - if avoid_conflicts: - # Check for conflicts with already optimized schedules - conflict_free_time = self._find_conflict_free_time( - result.optimized_time, - optimized_times, - schedule - ) - - if conflict_free_time != result.optimized_time: - # Recalculate scores for conflict-free time - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if content_item: - new_score = self._calculate_engagement_score( - conflict_free_time, - content_item.content_type, - schedule.priority - ) - - original_score = self._calculate_engagement_score( - schedule.scheduled_time, - content_item.content_type, - schedule.priority - ) - - result.optimized_time = conflict_free_time - result.improvement_score = new_score - original_score - result.optimization_reason += " (adjusted to avoid conflicts)" - - results.append(result) - optimized_times.append(result.optimized_time) - - return results - - except Exception as e: - self.logger.error(f"Error optimizing multiple schedules: {str(e)}") - return [] - - def suggest_optimal_times( - self, - content_type: ContentType, - date_range: Tuple[datetime, datetime], - count: int = 5 - ) -> List[Dict[str, Any]]: - """Suggest optimal times for new content. - - Args: - content_type: Type of content to schedule - date_range: Date range to consider - count: Number of suggestions to return - - Returns: - List of suggested optimal times with scores - """ - try: - suggestions = [] - start_date, end_date = date_range - - # Generate candidate times - current_date = start_date - while current_date <= end_date: - # Get optimal hours for this content type - if content_type in self.content_type_patterns: - optimal_hours = self.content_type_patterns[content_type]['peak_hours'] - else: - optimal_hours = [9, 12, 15, 18] # Default hours - - for hour in optimal_hours: - candidate_time = current_date.replace( - hour=hour, - minute=0, - second=0, - microsecond=0 - ) - - if start_date <= candidate_time <= end_date: - score = self._calculate_engagement_score( - candidate_time, - content_type, - priority=5 # Default priority - ) - - suggestions.append({ - 'time': candidate_time, - 'score': score, - 'day_of_week': candidate_time.strftime('%A'), - 'hour': hour, - 'reason': self._get_time_suggestion_reason(candidate_time, content_type) - }) - - current_date += timedelta(days=1) - - # Sort by score and return top suggestions - suggestions.sort(key=lambda x: x['score'], reverse=True) - return suggestions[:count] - - except Exception as e: - self.logger.error(f"Error suggesting optimal times: {str(e)}") - return [] - - def _calculate_engagement_score( - self, - scheduled_time: datetime, - content_type: ContentType, - priority: int - ) -> float: - """Calculate engagement score for a given time and content type.""" - try: - score = 0.0 - - # Base score from priority - score += priority * 10 - - # Hour of day factor - hour = scheduled_time.hour - if content_type in self.content_type_patterns: - optimal_hours = self.content_type_patterns[content_type]['peak_hours'] - if hour in optimal_hours: - score += 50 - else: - # Penalty for non-optimal hours - min_distance = min(abs(hour - oh) for oh in optimal_hours) - score += max(0, 30 - min_distance * 5) - - # Day of week factor - day_of_week = scheduled_time.weekday() # 0 = Monday, 6 = Sunday - - if content_type == ContentType.ARTICLE: - # Articles perform better on weekdays - if day_of_week < 5: # Monday to Friday - score += 20 - else: - score += 5 - elif content_type == ContentType.VIDEO: - # Videos perform better on weekends and evenings - if day_of_week >= 5 or hour >= 18: - score += 25 - else: - score += 10 - elif content_type == ContentType.SOCIAL_POST: - # Social posts are consistent throughout the week - score += 15 - - # Time spacing factor (avoid clustering) - existing_schedules = self.session.query(Schedule).filter( - Schedule.scheduled_time.between( - scheduled_time - timedelta(hours=2), - scheduled_time + timedelta(hours=2) - ) - ).all() - - if len(existing_schedules) > 3: - score -= len(existing_schedules) * 5 - - return max(score, 0.0) - - except Exception as e: - self.logger.error(f"Error calculating engagement score: {str(e)}") - return 0.0 - - def _find_optimal_time( - self, - schedule: Schedule, - content_item: ContentItem - ) -> Tuple[datetime, float]: - """Find the optimal time for a schedule.""" - try: - best_time = schedule.scheduled_time - best_score = self._calculate_engagement_score( - schedule.scheduled_time, - content_item.content_type, - schedule.priority - ) - - # Search within a week of the original time - base_date = schedule.scheduled_time.date() - - for day_offset in range(-3, 4): # ยฑ3 days - candidate_date = base_date + timedelta(days=day_offset) - - # Get optimal hours for this content type - if content_item.content_type in self.content_type_patterns: - optimal_hours = self.content_type_patterns[content_item.content_type]['peak_hours'] - else: - optimal_hours = [9, 12, 15, 18] - - for hour in optimal_hours: - candidate_time = datetime.combine(candidate_date, datetime.min.time()).replace(hour=hour) - - score = self._calculate_engagement_score( - candidate_time, - content_item.content_type, - schedule.priority - ) - - if score > best_score: - best_time = candidate_time - best_score = score - - return best_time, best_score - - except Exception as e: - self.logger.error(f"Error finding optimal time: {str(e)}") - return schedule.scheduled_time, 0.0 - - def _find_conflict_free_time( - self, - preferred_time: datetime, - existing_times: List[datetime], - schedule: Schedule, - min_gap: timedelta = timedelta(minutes=30) - ) -> datetime: - """Find a conflict-free time close to the preferred time.""" - try: - # Check if preferred time has conflicts - has_conflict = any( - abs((preferred_time - existing_time).total_seconds()) < min_gap.total_seconds() - for existing_time in existing_times - ) - - if not has_conflict: - return preferred_time - - # Search for nearby conflict-free times - for offset_minutes in [30, 60, 90, 120, -30, -60, -90, -120]: - candidate_time = preferred_time + timedelta(minutes=offset_minutes) - - has_conflict = any( - abs((candidate_time - existing_time).total_seconds()) < min_gap.total_seconds() - for existing_time in existing_times - ) - - if not has_conflict: - return candidate_time - - # If no conflict-free time found nearby, return preferred time - return preferred_time - - except Exception as e: - self.logger.error(f"Error finding conflict-free time: {str(e)}") - return preferred_time - - def _generate_optimization_reason( - self, - original_time: datetime, - optimized_time: datetime, - content_type: ContentType, - improvement_score: float - ) -> str: - """Generate a human-readable optimization reason.""" - try: - if improvement_score <= 0: - return "Current time is already optimal" - - reasons = [] - - # Time difference - time_diff = optimized_time - original_time - if abs(time_diff.total_seconds()) > 3600: # More than 1 hour - if time_diff.total_seconds() > 0: - reasons.append(f"Moved {time_diff.total_seconds() / 3600:.1f} hours later") - else: - reasons.append(f"Moved {abs(time_diff.total_seconds()) / 3600:.1f} hours earlier") - - # Hour optimization - original_hour = original_time.hour - optimized_hour = optimized_time.hour - - if content_type in self.content_type_patterns: - optimal_hours = self.content_type_patterns[content_type]['peak_hours'] - if optimized_hour in optimal_hours and original_hour not in optimal_hours: - reasons.append(f"Moved to peak engagement hour ({optimized_hour}:00)") - - # Day optimization - original_day = original_time.strftime('%A') - optimized_day = optimized_time.strftime('%A') - - if original_day != optimized_day: - reasons.append(f"Moved from {original_day} to {optimized_day}") - - # Improvement score - reasons.append(f"Expected {improvement_score:.1f}% engagement improvement") - - return "; ".join(reasons) if reasons else "Optimized for better engagement" - - except Exception as e: - self.logger.error(f"Error generating optimization reason: {str(e)}") - return "Optimized for better engagement" - - def _get_time_suggestion_reason(self, time: datetime, content_type: ContentType) -> str: - """Get reason for suggesting a specific time.""" - try: - reasons = [] - - hour = time.hour - day_name = time.strftime('%A') - - # Hour-based reasons - if content_type in self.content_type_patterns: - optimal_hours = self.content_type_patterns[content_type]['peak_hours'] - if hour in optimal_hours: - reasons.append(f"Peak engagement hour for {content_type.value}") - - # Day-based reasons - if content_type == ContentType.ARTICLE and time.weekday() < 5: - reasons.append("Weekday optimal for articles") - elif content_type == ContentType.VIDEO and (time.weekday() >= 5 or hour >= 18): - reasons.append("Evening/weekend optimal for videos") - - return "; ".join(reasons) if reasons else f"Good time for {content_type.value}" - - except Exception as e: - self.logger.error(f"Error getting suggestion reason: {str(e)}") - return "Recommended time" - - def analyze_schedule_performance(self, days_back: int = 30) -> Dict[str, Any]: - """Analyze historical schedule performance.""" - try: - # Get schedules from the last N days - cutoff_date = datetime.now() - timedelta(days=days_back) - - schedules = self.session.query(Schedule).filter( - Schedule.created_at >= cutoff_date - ).all() - - if not schedules: - return {'error': 'No schedules found for analysis'} - - # Analyze by hour - hour_performance = defaultdict(list) - day_performance = defaultdict(list) - content_type_performance = defaultdict(list) - - for schedule in schedules: - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if content_item: - hour = schedule.scheduled_time.hour - day = schedule.scheduled_time.strftime('%A') - - # Calculate performance score (simplified) - performance_score = self._calculate_performance_score(schedule) - - hour_performance[hour].append(performance_score) - day_performance[day].append(performance_score) - content_type_performance[content_item.content_type.value].append(performance_score) - - # Calculate averages - analysis = { - 'total_schedules': len(schedules), - 'analysis_period_days': days_back, - 'best_hours': self._get_top_performers(hour_performance), - 'best_days': self._get_top_performers(day_performance), - 'content_type_performance': self._get_top_performers(content_type_performance), - 'recommendations': self._generate_performance_recommendations( - hour_performance, - day_performance, - content_type_performance - ) - } - - return analysis - - except Exception as e: - self.logger.error(f"Error analyzing schedule performance: {str(e)}") - return {'error': str(e)} - - def _calculate_performance_score(self, schedule: Schedule) -> float: - """Calculate a performance score for a schedule (simplified).""" - try: - # This is a simplified performance calculation - # In a real implementation, this would use actual engagement metrics - - base_score = 50.0 # Base performance - - # Status-based scoring - if schedule.status == ScheduleStatus.COMPLETED: - base_score += 30 - elif schedule.status == ScheduleStatus.RUNNING: - base_score += 15 - elif schedule.status == ScheduleStatus.FAILED: - base_score -= 20 - - # Priority-based scoring - base_score += schedule.priority * 2 - - return max(base_score, 0.0) - - except Exception as e: - self.logger.error(f"Error calculating performance score: {str(e)}") - return 0.0 - - def _get_top_performers(self, performance_data: Dict[str, List[float]]) -> List[Dict[str, Any]]: - """Get top performing items from performance data.""" - try: - performers = [] - - for key, scores in performance_data.items(): - if scores: - avg_score = np.mean(scores) - performers.append({ - 'key': key, - 'average_score': avg_score, - 'sample_count': len(scores) - }) - - # Sort by average score - performers.sort(key=lambda x: x['average_score'], reverse=True) - - return performers[:5] # Top 5 - - except Exception as e: - self.logger.error(f"Error getting top performers: {str(e)}") - return [] - - def _generate_performance_recommendations( - self, - hour_performance: Dict[int, List[float]], - day_performance: Dict[str, List[float]], - content_type_performance: Dict[str, List[float]] - ) -> List[str]: - """Generate performance-based recommendations.""" - try: - recommendations = [] - - # Hour recommendations - if hour_performance: - best_hours = self._get_top_performers(hour_performance) - if best_hours: - best_hour = best_hours[0]['key'] - recommendations.append(f"Schedule more content around {best_hour}:00 for better performance") - - # Day recommendations - if day_performance: - best_days = self._get_top_performers(day_performance) - if best_days: - best_day = best_days[0]['key'] - recommendations.append(f"Consider scheduling more content on {best_day}s") - - # Content type recommendations - if content_type_performance: - best_types = self._get_top_performers(content_type_performance) - if best_types: - best_type = best_types[0]['key'] - recommendations.append(f"{best_type} content shows the best performance") - - return recommendations - - except Exception as e: - self.logger.error(f"Error generating recommendations: {str(e)}") - return [] \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/core/schedule_validator.py b/ToBeMigrated/content_scheduler/core/schedule_validator.py deleted file mode 100644 index 8ce19a6e..00000000 --- a/ToBeMigrated/content_scheduler/core/schedule_validator.py +++ /dev/null @@ -1,611 +0,0 @@ -""" -Schedule validation system for content scheduling. -""" - -import logging -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional, Tuple -from dataclasses import dataclass -import re - -# Use unified database models -from lib.database.models import ContentItem, Schedule, ScheduleStatus, ContentType, Platform, get_session - -logger = logging.getLogger(__name__) - -@dataclass -class ValidationResult: - """Result of schedule validation.""" - is_valid: bool - errors: List[str] - warnings: List[str] - suggestions: List[str] - confidence: float - -class ScheduleValidator: - """Validate content schedules for compliance and optimization.""" - - def __init__(self): - """Initialize the schedule validator.""" - self.logger = logger - self.session = get_session() - - # Platform-specific validation rules - self.platform_rules = { - Platform.TWITTER: { - 'max_text_length': 280, - 'max_images': 4, - 'max_videos': 1, - 'allowed_formats': ['jpg', 'png', 'gif', 'mp4'], - 'max_file_size_mb': 5, - 'posting_frequency_limit': {'per_hour': 10, 'per_day': 100} - }, - Platform.FACEBOOK: { - 'max_text_length': 63206, - 'max_images': 10, - 'max_videos': 1, - 'allowed_formats': ['jpg', 'png', 'gif', 'mp4', 'mov'], - 'max_file_size_mb': 100, - 'posting_frequency_limit': {'per_hour': 5, 'per_day': 25} - }, - Platform.LINKEDIN: { - 'max_text_length': 3000, - 'max_images': 9, - 'max_videos': 1, - 'allowed_formats': ['jpg', 'png', 'gif', 'mp4'], - 'max_file_size_mb': 200, - 'posting_frequency_limit': {'per_hour': 3, 'per_day': 20} - }, - Platform.INSTAGRAM: { - 'max_text_length': 2200, - 'max_images': 10, - 'max_videos': 1, - 'allowed_formats': ['jpg', 'png', 'mp4'], - 'max_file_size_mb': 100, - 'posting_frequency_limit': {'per_hour': 2, 'per_day': 10} - } - } - - # Content type validation rules - self.content_type_rules = { - ContentType.ARTICLE: { - 'min_title_length': 10, - 'max_title_length': 200, - 'min_content_length': 100, - 'required_fields': ['title', 'content', 'summary'] - }, - ContentType.VIDEO: { - 'min_duration_sec': 5, - 'max_duration_sec': 3600, - 'required_fields': ['title', 'description'], - 'recommended_formats': ['mp4', 'mov'] - }, - ContentType.IMAGE: { - 'min_width': 400, - 'min_height': 400, - 'max_width': 4096, - 'max_height': 4096, - 'required_fields': ['title', 'alt_text'] - }, - ContentType.SOCIAL_POST: { - 'min_length': 10, - 'max_length': 500, - 'required_fields': ['content'] - } - } - - def validate_schedule(self, schedule: Schedule) -> ValidationResult: - """Validate a single schedule. - - Args: - schedule: Schedule to validate - - Returns: - ValidationResult with validation details - """ - try: - errors = [] - warnings = [] - suggestions = [] - - # Get content item details - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if not content_item: - return ValidationResult( - is_valid=False, - errors=["Content item not found"], - warnings=[], - suggestions=[], - confidence=0.0 - ) - - # Validate basic schedule properties - basic_validation = self._validate_basic_properties(schedule) - errors.extend(basic_validation['errors']) - warnings.extend(basic_validation['warnings']) - suggestions.extend(basic_validation['suggestions']) - - # Validate content properties - content_validation = self._validate_content_properties(content_item) - errors.extend(content_validation['errors']) - warnings.extend(content_validation['warnings']) - suggestions.extend(content_validation['suggestions']) - - # Validate timing - timing_validation = self._validate_timing(schedule) - errors.extend(timing_validation['errors']) - warnings.extend(timing_validation['warnings']) - suggestions.extend(timing_validation['suggestions']) - - # Validate conflicts - conflict_validation = self._validate_conflicts(schedule) - errors.extend(conflict_validation['errors']) - warnings.extend(conflict_validation['warnings']) - suggestions.extend(conflict_validation['suggestions']) - - # Calculate confidence - confidence = self._calculate_validation_confidence(errors, warnings) - - return ValidationResult( - is_valid=len(errors) == 0, - errors=errors, - warnings=warnings, - suggestions=suggestions, - confidence=confidence - ) - - except Exception as e: - self.logger.error(f"Error validating schedule: {str(e)}") - return ValidationResult( - is_valid=False, - errors=[f"Validation error: {str(e)}"], - warnings=[], - suggestions=[], - confidence=0.0 - ) - - def validate_multiple_schedules(self, schedules: List[Schedule]) -> Dict[str, ValidationResult]: - """Validate multiple schedules and check for cross-schedule issues. - - Args: - schedules: List of schedules to validate - - Returns: - Dictionary mapping schedule IDs to validation results - """ - try: - results = {} - - # Validate individual schedules - for schedule in schedules: - results[str(schedule.id)] = self.validate_schedule(schedule) - - # Check for cross-schedule conflicts - cross_validation = self._validate_cross_schedule_conflicts(schedules) - - # Add cross-validation issues to individual results - for schedule_id, issues in cross_validation.items(): - if schedule_id in results: - results[schedule_id].warnings.extend(issues.get('warnings', [])) - results[schedule_id].suggestions.extend(issues.get('suggestions', [])) - - return results - - except Exception as e: - self.logger.error(f"Error validating multiple schedules: {str(e)}") - return {} - - def _validate_basic_properties(self, schedule: Schedule) -> Dict[str, List[str]]: - """Validate basic schedule properties.""" - errors = [] - warnings = [] - suggestions = [] - - try: - # Check required fields - if not schedule.content_item_id: - errors.append("Content item ID is required") - - if not schedule.scheduled_time: - errors.append("Scheduled time is required") - - if not schedule.status: - errors.append("Schedule status is required") - - # Check priority range - if schedule.priority < 1 or schedule.priority > 10: - warnings.append(f"Priority {schedule.priority} is outside recommended range (1-10)") - - # Check if schedule is in the past - if schedule.scheduled_time < datetime.now(): - if schedule.status == ScheduleStatus.PENDING: - errors.append("Cannot schedule content in the past") - else: - warnings.append("Schedule time is in the past") - - # Check if schedule is too far in the future - max_future_days = 365 # 1 year - if schedule.scheduled_time > datetime.now() + timedelta(days=max_future_days): - warnings.append(f"Schedule is more than {max_future_days} days in the future") - suggestions.append("Consider scheduling closer to the current date for better relevance") - - # Validate recurrence pattern - if schedule.recurrence: - recurrence_validation = self._validate_recurrence_pattern(schedule.recurrence) - errors.extend(recurrence_validation['errors']) - warnings.extend(recurrence_validation['warnings']) - suggestions.extend(recurrence_validation['suggestions']) - - except Exception as e: - self.logger.error(f"Error validating basic properties: {str(e)}") - errors.append(f"Basic validation error: {str(e)}") - - return {'errors': errors, 'warnings': warnings, 'suggestions': suggestions} - - def _validate_content_properties(self, content_item: ContentItem) -> Dict[str, List[str]]: - """Validate content item properties.""" - errors = [] - warnings = [] - suggestions = [] - - try: - # Check required fields - if not content_item.title or len(content_item.title.strip()) == 0: - errors.append("Content title is required") - - if not content_item.content or len(content_item.content.strip()) == 0: - errors.append("Content body is required") - - # Validate based on content type - if content_item.content_type: - type_rules = self.content_type_rules.get(content_item.content_type) - if type_rules: - type_validation = self._validate_content_type_rules(content_item, type_rules) - errors.extend(type_validation['errors']) - warnings.extend(type_validation['warnings']) - suggestions.extend(type_validation['suggestions']) - - # Check for potentially problematic content - content_check = self._check_content_quality(content_item) - warnings.extend(content_check['warnings']) - suggestions.extend(content_check['suggestions']) - - except Exception as e: - self.logger.error(f"Error validating content properties: {str(e)}") - errors.append(f"Content validation error: {str(e)}") - - return {'errors': errors, 'warnings': warnings, 'suggestions': suggestions} - - def _validate_timing(self, schedule: Schedule) -> Dict[str, List[str]]: - """Validate schedule timing.""" - errors = [] - warnings = [] - suggestions = [] - - try: - scheduled_time = schedule.scheduled_time - - # Check if it's a reasonable time to post - hour = scheduled_time.hour - day_of_week = scheduled_time.weekday() # 0 = Monday, 6 = Sunday - - # Check for very early or very late hours - if hour < 6 or hour > 23: - warnings.append(f"Scheduled for {hour}:00 - consider posting during peak hours (6 AM - 11 PM)") - suggestions.append("Peak engagement typically occurs between 9 AM and 9 PM") - - # Check for weekend posting (depending on content type) - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if content_item and content_item.content_type == ContentType.ARTICLE: - if day_of_week >= 5: # Weekend - warnings.append("Business content typically performs better on weekdays") - suggestions.append("Consider rescheduling to Monday-Friday for better engagement") - - # Check for holidays or special dates (simplified) - if self._is_holiday(scheduled_time.date()): - warnings.append("Scheduled for a holiday - engagement may be lower") - suggestions.append("Consider rescheduling to avoid holidays for better reach") - - # Check frequency limits - frequency_check = self._check_posting_frequency(schedule) - warnings.extend(frequency_check['warnings']) - suggestions.extend(frequency_check['suggestions']) - - except Exception as e: - self.logger.error(f"Error validating timing: {str(e)}") - errors.append(f"Timing validation error: {str(e)}") - - return {'errors': errors, 'warnings': warnings, 'suggestions': suggestions} - - def _validate_conflicts(self, schedule: Schedule) -> Dict[str, List[str]]: - """Validate for scheduling conflicts.""" - errors = [] - warnings = [] - suggestions = [] - - try: - # Check for nearby schedules - time_window = timedelta(minutes=30) - nearby_schedules = self.session.query(Schedule).filter( - Schedule.id != schedule.id, - Schedule.scheduled_time.between( - schedule.scheduled_time - time_window, - schedule.scheduled_time + time_window - ) - ).all() - - if nearby_schedules: - warnings.append(f"Found {len(nearby_schedules)} other schedule(s) within 30 minutes") - suggestions.append("Consider spacing schedules at least 30 minutes apart for better visibility") - - # Check for same-day content overload - same_day_schedules = self.session.query(Schedule).filter( - Schedule.id != schedule.id, - Schedule.scheduled_time >= schedule.scheduled_time.replace(hour=0, minute=0, second=0), - Schedule.scheduled_time < schedule.scheduled_time.replace(hour=0, minute=0, second=0) + timedelta(days=1) - ).all() - - if len(same_day_schedules) > 5: - warnings.append(f"Found {len(same_day_schedules)} other schedules on the same day") - suggestions.append("Consider distributing content across multiple days to avoid overwhelming your audience") - - except Exception as e: - self.logger.error(f"Error validating conflicts: {str(e)}") - errors.append(f"Conflict validation error: {str(e)}") - - return {'errors': errors, 'warnings': warnings, 'suggestions': suggestions} - - def _validate_recurrence_pattern(self, recurrence: str) -> Dict[str, List[str]]: - """Validate recurrence pattern.""" - errors = [] - warnings = [] - suggestions = [] - - try: - # Define valid recurrence patterns - valid_patterns = [ - 'daily', 'weekly', 'monthly', 'yearly', - 'weekdays', 'weekends', - 'every 2 days', 'every 3 days', 'every 7 days', - 'every 2 weeks', 'every 2 months' - ] - - if recurrence.lower() not in valid_patterns: - # Check if it's a cron-like pattern - if not self._is_valid_cron_pattern(recurrence): - errors.append(f"Invalid recurrence pattern: {recurrence}") - suggestions.append(f"Valid patterns include: {', '.join(valid_patterns[:5])}") - - # Check for overly frequent recurrence - if 'hour' in recurrence.lower(): - warnings.append("Hourly recurrence may overwhelm your audience") - suggestions.append("Consider daily or weekly recurrence for better engagement") - - except Exception as e: - self.logger.error(f"Error validating recurrence: {str(e)}") - errors.append(f"Recurrence validation error: {str(e)}") - - return {'errors': errors, 'warnings': warnings, 'suggestions': suggestions} - - def _validate_content_type_rules(self, content_item: ContentItem, rules: Dict[str, Any]) -> Dict[str, List[str]]: - """Validate content against type-specific rules.""" - errors = [] - warnings = [] - suggestions = [] - - try: - # Check title length - if 'min_title_length' in rules and len(content_item.title) < rules['min_title_length']: - errors.append(f"Title too short (minimum {rules['min_title_length']} characters)") - - if 'max_title_length' in rules and len(content_item.title) > rules['max_title_length']: - errors.append(f"Title too long (maximum {rules['max_title_length']} characters)") - - # Check content length - if 'min_content_length' in rules and len(content_item.content) < rules['min_content_length']: - errors.append(f"Content too short (minimum {rules['min_content_length']} characters)") - - if 'max_length' in rules and len(content_item.content) > rules['max_length']: - errors.append(f"Content too long (maximum {rules['max_length']} characters)") - - # Check required fields - if 'required_fields' in rules: - for field in rules['required_fields']: - if not hasattr(content_item, field) or not getattr(content_item, field): - errors.append(f"Required field missing: {field}") - - except Exception as e: - self.logger.error(f"Error validating content type rules: {str(e)}") - errors.append(f"Content type validation error: {str(e)}") - - return {'errors': errors, 'warnings': warnings, 'suggestions': suggestions} - - def _check_content_quality(self, content_item: ContentItem) -> Dict[str, List[str]]: - """Check content quality and provide suggestions.""" - warnings = [] - suggestions = [] - - try: - content = content_item.content - title = content_item.title - - # Check for excessive capitalization - if title and title.isupper(): - warnings.append("Title is in all caps") - suggestions.append("Consider using proper capitalization for better readability") - - # Check for excessive punctuation - if content and content.count('!') > 3: - warnings.append("Excessive exclamation marks detected") - suggestions.append("Reduce exclamation marks for more professional tone") - - # Check for spelling/grammar (simplified) - if content: - # Simple checks for common issues - if ' ' in content: # Double spaces - suggestions.append("Remove extra spaces for cleaner formatting") - - if content.count('?') > 5: - warnings.append("Many question marks detected") - suggestions.append("Consider reducing questions for clearer messaging") - - # Check for hashtag usage - hashtag_count = len(re.findall(r'#\w+', content)) if content else 0 - if hashtag_count > 10: - warnings.append(f"High number of hashtags ({hashtag_count})") - suggestions.append("Consider using 3-5 relevant hashtags for optimal reach") - - # Check for URL presence - url_count = len(re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', content)) if content else 0 - if url_count > 2: - warnings.append(f"Multiple URLs detected ({url_count})") - suggestions.append("Consider limiting to 1-2 URLs to avoid appearing spammy") - - except Exception as e: - self.logger.error(f"Error checking content quality: {str(e)}") - - return {'warnings': warnings, 'suggestions': suggestions} - - def _check_posting_frequency(self, schedule: Schedule) -> Dict[str, List[str]]: - """Check posting frequency limits.""" - warnings = [] - suggestions = [] - - try: - # Check hourly frequency - hour_start = schedule.scheduled_time.replace(minute=0, second=0, microsecond=0) - hour_end = hour_start + timedelta(hours=1) - - hourly_schedules = self.session.query(Schedule).filter( - Schedule.scheduled_time >= hour_start, - Schedule.scheduled_time < hour_end - ).count() - - if hourly_schedules > 3: - warnings.append(f"High posting frequency: {hourly_schedules} posts in the same hour") - suggestions.append("Consider spacing posts throughout the day for better engagement") - - # Check daily frequency - day_start = schedule.scheduled_time.replace(hour=0, minute=0, second=0, microsecond=0) - day_end = day_start + timedelta(days=1) - - daily_schedules = self.session.query(Schedule).filter( - Schedule.scheduled_time >= day_start, - Schedule.scheduled_time < day_end - ).count() - - if daily_schedules > 10: - warnings.append(f"High daily posting frequency: {daily_schedules} posts") - suggestions.append("Consider reducing daily posts to 3-5 for optimal audience engagement") - - except Exception as e: - self.logger.error(f"Error checking posting frequency: {str(e)}") - - return {'warnings': warnings, 'suggestions': suggestions} - - def _validate_cross_schedule_conflicts(self, schedules: List[Schedule]) -> Dict[str, Dict[str, List[str]]]: - """Validate conflicts across multiple schedules.""" - conflicts = {} - - try: - # Sort schedules by time - sorted_schedules = sorted(schedules, key=lambda x: x.scheduled_time) - - for i, schedule in enumerate(sorted_schedules): - schedule_id = str(schedule.id) - conflicts[schedule_id] = {'warnings': [], 'suggestions': []} - - # Check with subsequent schedules - for j in range(i + 1, len(sorted_schedules)): - other_schedule = sorted_schedules[j] - time_diff = other_schedule.scheduled_time - schedule.scheduled_time - - # Check if schedules are too close - if time_diff < timedelta(minutes=15): - conflicts[schedule_id]['warnings'].append( - f"Schedule conflicts with another schedule {time_diff.total_seconds() / 60:.0f} minutes later" - ) - conflicts[schedule_id]['suggestions'].append( - "Consider spacing schedules at least 15 minutes apart" - ) - - # Stop checking if schedules are more than 2 hours apart - if time_diff > timedelta(hours=2): - break - - except Exception as e: - self.logger.error(f"Error validating cross-schedule conflicts: {str(e)}") - - return conflicts - - def _calculate_validation_confidence(self, errors: List[str], warnings: List[str]) -> float: - """Calculate confidence in validation results.""" - try: - # Start with full confidence - confidence = 1.0 - - # Reduce confidence based on errors and warnings - confidence -= len(errors) * 0.2 # Each error reduces confidence by 20% - confidence -= len(warnings) * 0.05 # Each warning reduces confidence by 5% - - # Ensure confidence is between 0 and 1 - return max(0.0, min(1.0, confidence)) - - except Exception as e: - self.logger.error(f"Error calculating validation confidence: {str(e)}") - return 0.0 - - def _is_holiday(self, date) -> bool: - """Check if a date is a holiday (simplified implementation).""" - try: - # This is a simplified implementation - # In a real system, you would use a proper holiday library - - # Check for some common holidays - month = date.month - day = date.day - - # New Year's Day - if month == 1 and day == 1: - return True - - # Christmas - if month == 12 and day == 25: - return True - - # Independence Day (US) - if month == 7 and day == 4: - return True - - return False - - except Exception as e: - self.logger.error(f"Error checking holiday: {str(e)}") - return False - - def _is_valid_cron_pattern(self, pattern: str) -> bool: - """Check if a string is a valid cron pattern (simplified).""" - try: - # This is a very simplified cron validation - # A proper implementation would use a cron parsing library - - parts = pattern.split() - if len(parts) != 5: - return False - - # Basic validation for each part - for part in parts: - if not (part.isdigit() or part == '*' or '/' in part or '-' in part or ',' in part): - return False - - return True - - except Exception as e: - self.logger.error(f"Error validating cron pattern: {str(e)}") - return False \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/core/scheduler.py b/ToBeMigrated/content_scheduler/core/scheduler.py deleted file mode 100644 index 78105a64..00000000 --- a/ToBeMigrated/content_scheduler/core/scheduler.py +++ /dev/null @@ -1,402 +0,0 @@ -""" -Core scheduler implementation using APScheduler. -""" - -import logging -import asyncio -from typing import Dict, Any, List, Optional, Union -from datetime import datetime, timedelta -from apscheduler.schedulers.asyncio import AsyncIOScheduler -from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore -from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor -from apscheduler.triggers.date import DateTrigger -from apscheduler.triggers.cron import CronTrigger -from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, EVENT_JOB_MISSED -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -# Use unified database models -from lib.database.models import ContentItem, Schedule, ScheduleStatus, get_engine, get_session, init_db -from ..utils.error_handling import SchedulingError -from .conflict_resolver import ConflictResolver -from .health_checker import ScheduleHealthChecker -from .schedule_validator import ScheduleValidator - -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - -class ContentScheduler: - """Core content scheduler implementation.""" - - def __init__( - self, - db_url: str = "sqlite:///content_scheduler.db", - max_workers: int = 10, - job_timeout: int = 300, - max_retries: int = 3, - retry_delay: int = 300, - health_check_interval: int = 300, - validation_config: Dict[str, Any] = None - ): - """Initialize the content scheduler. - - Args: - db_url: Database URL for job persistence - max_workers: Maximum number of worker threads - job_timeout: Job execution timeout in seconds - max_retries: Maximum number of retry attempts - retry_delay: Delay between retries in seconds - health_check_interval: Health check interval in seconds - validation_config: Configuration for schedule validation - """ - self.logger = logger - self.db_url = db_url - self.max_workers = max_workers - self.job_timeout = job_timeout - self.max_retries = max_retries - self.retry_delay = retry_delay - - # Use unified database connection - self.engine = get_engine(db_url) - init_db(self.engine) - self.Session = sessionmaker(bind=self.engine) - - # Initialize job stores - self.jobstores = { - 'default': SQLAlchemyJobStore(url=db_url) - } - - # Initialize executors - self.executors = { - 'default': ThreadPoolExecutor(max_workers), - 'processpool': ProcessPoolExecutor(max_workers) - } - - # Initialize scheduler - self.scheduler = AsyncIOScheduler( - jobstores=self.jobstores, - executors=self.executors, - timezone='UTC', - job_defaults={ - 'coalesce': True, - 'max_instances': 1, - 'misfire_grace_time': 60 - } - ) - - # Initialize conflict resolver - self.conflict_resolver = ConflictResolver() - - # Initialize health checker - self.health_checker = ScheduleHealthChecker( - scheduler=self, - check_interval=health_check_interval - ) - - # Initialize validator - self.validator = ScheduleValidator(validation_config or {}) - - # Add event listeners - self.scheduler.add_listener( - self._handle_job_event, - EVENT_JOB_EXECUTED | EVENT_JOB_ERROR | EVENT_JOB_MISSED - ) - - # Track active jobs - self.active_jobs = {} - self.job_stats = { - 'total_scheduled': 0, - 'successful': 0, - 'failed': 0, - 'retries': 0 - } - - async def start(self): - """Start the scheduler.""" - try: - if not self.scheduler.running: - self.scheduler.start() - await self._recover_jobs() - await self.health_checker.start() - self.logger.info("Content scheduler started successfully") - except Exception as e: - self.logger.error(f"Failed to start scheduler: {str(e)}") - raise SchedulingError(f"Scheduler startup failed: {str(e)}") - - async def stop(self): - """Stop the scheduler.""" - try: - if self.scheduler.running: - self.scheduler.shutdown(wait=True) - await self.health_checker.stop() - self.logger.info("Content scheduler stopped successfully") - except Exception as e: - self.logger.error(f"Failed to stop scheduler: {str(e)}") - raise SchedulingError(f"Scheduler shutdown failed: {str(e)}") - - async def schedule_content(self, content_item: ContentItem, schedule_time: datetime, - platforms: List[str], recurrence: str = None, - validate: bool = True) -> str: - """Schedule content for publishing. - - Args: - content_item: ContentItem to schedule - schedule_time: When to publish - platforms: List of platforms to publish to - recurrence: Recurrence pattern (optional) - validate: Whether to validate the schedule - - Returns: - Schedule ID - """ - try: - session = self.Session() - - # Create schedule record - schedule = Schedule( - content_item_id=content_item.id, - scheduled_time=schedule_time, - status=ScheduleStatus.SCHEDULED, - recurrence=recurrence, - priority=1 - ) - - session.add(schedule) - session.commit() - - # Schedule the job - if recurrence: - job_id = await self._schedule_recurring(schedule, platforms) - else: - job_id = await self._schedule_one_time(schedule, platforms) - - # Update schedule with job ID - schedule.result = f"job_id:{job_id}" - session.commit() - session.close() - - self.job_stats['total_scheduled'] += 1 - self.logger.info(f"Scheduled content {content_item.id} for {schedule_time}") - - return str(schedule.id) - - except Exception as e: - self.logger.error(f"Failed to schedule content: {str(e)}") - if 'session' in locals(): - session.rollback() - session.close() - raise SchedulingError(f"Content scheduling failed: {str(e)}") - - async def _schedule_one_time(self, schedule: Schedule, platforms: List[str]) -> str: - """Schedule a one-time content publish. - - Args: - schedule: Schedule object - platforms: List of platforms - - Returns: - Job ID - """ - try: - job_id = f"one_time_{schedule.content_item_id}_{int(schedule.scheduled_time.timestamp())}" - - self.scheduler.add_job( - self._run_async_job, - trigger=DateTrigger(run_date=schedule.scheduled_time), - args=[schedule, platforms], - id=job_id, - replace_existing=True, - misfire_grace_time=self.job_timeout - ) - - return job_id - - except Exception as e: - self.logger.error(f"Failed to schedule one-time job: {str(e)}") - raise SchedulingError(f"One-time scheduling failed: {str(e)}") - - async def _schedule_recurring(self, schedule: Schedule, platforms: List[str]) -> str: - """Schedule a recurring content publish. - - Args: - schedule: Schedule object - platforms: List of platforms - - Returns: - Job ID - """ - try: - job_id = f"recurring_{schedule.content_item_id}_{int(datetime.utcnow().timestamp())}" - - # Parse recurrence pattern (simplified) - if schedule.recurrence == "daily": - trigger = CronTrigger(hour=schedule.scheduled_time.hour, minute=schedule.scheduled_time.minute) - elif schedule.recurrence == "weekly": - trigger = CronTrigger(day_of_week=schedule.scheduled_time.weekday(), - hour=schedule.scheduled_time.hour, - minute=schedule.scheduled_time.minute) - else: - # Default to daily - trigger = CronTrigger(hour=schedule.scheduled_time.hour, minute=schedule.scheduled_time.minute) - - self.scheduler.add_job( - self._run_async_job, - trigger=trigger, - args=[schedule, platforms], - id=job_id, - replace_existing=True, - misfire_grace_time=self.job_timeout - ) - - return job_id - - except Exception as e: - self.logger.error(f"Failed to schedule recurring job: {str(e)}") - raise SchedulingError(f"Recurring scheduling failed: {str(e)}") - - async def _run_async_job(self, schedule: Schedule, platforms: List[str]): - """Run an async job in the event loop. - - Args: - schedule: Schedule object - platforms: List of platforms - """ - try: - await self._publish_content(schedule, platforms) - except Exception as e: - self.logger.error(f"Job execution failed: {str(e)}") - await self._handle_job_failure(schedule, str(e)) - - async def _publish_content(self, schedule: Schedule, platforms: List[str]): - """Publish content to specified platforms. - - Args: - schedule: Schedule object - platforms: List of platforms - """ - try: - session = self.Session() - content_item = session.query(ContentItem).get(schedule.content_item_id) - - if not content_item: - raise SchedulingError(f"Content item {schedule.content_item_id} not found") - - # Update schedule status - schedule.status = ScheduleStatus.RUNNING - session.commit() - - # Simulate content publishing (replace with actual platform publishing logic) - self.logger.info(f"Publishing content '{content_item.title}' to platforms: {platforms}") - - # Mark as completed - schedule.status = ScheduleStatus.COMPLETED - schedule.result = f"Published to {', '.join(platforms)} at {datetime.utcnow()}" - session.commit() - session.close() - - self.job_stats['successful'] += 1 - - except Exception as e: - session = self.Session() - schedule.status = ScheduleStatus.FAILED - schedule.result = f"Failed: {str(e)}" - session.commit() - session.close() - - self.job_stats['failed'] += 1 - raise - - async def _handle_job_failure(self, schedule: Schedule, error: str): - """Handle job failure and retry logic. - - Args: - schedule: Schedule object - error: Error message - """ - try: - session = self.Session() - schedule.status = ScheduleStatus.FAILED - schedule.result = f"Failed: {error}" - session.commit() - session.close() - - self.job_stats['failed'] += 1 - self.logger.error(f"Job failed for schedule {schedule.id}: {error}") - - except Exception as e: - self.logger.error(f"Error handling job failure: {str(e)}") - - def _handle_job_event(self, event): - """Handle scheduler events. - - Args: - event: Scheduler event - """ - try: - job_id = event.job_id - - if event.code == EVENT_JOB_EXECUTED: - self.logger.info(f"Job {job_id} executed successfully") - - elif event.code == EVENT_JOB_ERROR: - self.logger.error(f"Job {job_id} failed: {str(event.exception)}") - - elif event.code == EVENT_JOB_MISSED: - self.logger.warning(f"Job {job_id} missed execution time") - - except Exception as e: - self.logger.error(f"Error handling job event: {str(e)}") - - async def _recover_jobs(self): - """Recover pending jobs from the database.""" - try: - session = self.Session() - - # Get all scheduled jobs - pending_schedules = session.query(Schedule).filter( - Schedule.status == ScheduleStatus.SCHEDULED - ).all() - - # Reschedule each job - for schedule in pending_schedules: - try: - content_item = session.query(ContentItem).get(schedule.content_item_id) - if content_item: - platforms = content_item.platforms if isinstance(content_item.platforms, list) else [] - await self.schedule_content(content_item, schedule.scheduled_time, platforms, - schedule.recurrence, validate=False) - except Exception as e: - self.logger.error(f"Failed to recover schedule {schedule.id}: {str(e)}") - - session.close() - - except Exception as e: - self.logger.error(f"Job recovery failed: {str(e)}") - raise SchedulingError(f"Job recovery failed: {str(e)}") - - def get_job_stats(self) -> Dict[str, int]: - """Get job statistics. - - Returns: - Dictionary with job statistics - """ - return self.job_stats.copy() - - def get_active_jobs(self) -> List[Dict[str, Any]]: - """Get list of active jobs. - - Returns: - List of active job information - """ - try: - jobs = [] - for job in self.scheduler.get_jobs(): - jobs.append({ - 'id': job.id, - 'next_run_time': job.next_run_time.isoformat() if job.next_run_time else None, - 'trigger': str(job.trigger) - }) - return jobs - except Exception as e: - self.logger.error(f"Error getting active jobs: {str(e)}") - return [] \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/integrations/calendar_integration.py b/ToBeMigrated/content_scheduler/integrations/calendar_integration.py deleted file mode 100644 index 2e7bcc80..00000000 --- a/ToBeMigrated/content_scheduler/integrations/calendar_integration.py +++ /dev/null @@ -1,651 +0,0 @@ -""" -Calendar integration for content scheduling. -""" - -import logging -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional, Tuple -from dataclasses import dataclass -import json - -# Use unified database models -from lib.database.models import ContentItem, Schedule, ScheduleStatus, ContentType, Platform, get_session - -logger = logging.getLogger(__name__) - -@dataclass -class CalendarEvent: - """Calendar event representation.""" - id: str - title: str - description: str - start_time: datetime - end_time: datetime - location: Optional[str] = None - attendees: List[str] = None - event_type: str = "content_schedule" - metadata: Dict[str, Any] = None - -class CalendarIntegration: - """Integration with calendar systems for content scheduling.""" - - def __init__(self, calendar_provider: str = "google"): - """Initialize calendar integration. - - Args: - calendar_provider: Calendar provider (google, outlook, etc.) - """ - self.logger = logger - self.session = get_session() - self.calendar_provider = calendar_provider - - # Calendar provider configurations - self.provider_configs = { - 'google': { - 'api_endpoint': 'https://www.googleapis.com/calendar/v3', - 'scopes': ['https://www.googleapis.com/auth/calendar'], - 'event_duration_minutes': 30 - }, - 'outlook': { - 'api_endpoint': 'https://graph.microsoft.com/v1.0', - 'scopes': ['https://graph.microsoft.com/calendars.readwrite'], - 'event_duration_minutes': 30 - }, - 'apple': { - 'api_endpoint': 'https://caldav.icloud.com', - 'scopes': ['calendar'], - 'event_duration_minutes': 30 - } - } - - # Event templates for different content types - self.event_templates = { - ContentType.ARTICLE: { - 'title_prefix': '๐Ÿ“ Publish Article:', - 'description_template': 'Publish article "{title}" to {platforms}', - 'duration_minutes': 15 - }, - ContentType.VIDEO: { - 'title_prefix': '๐ŸŽฅ Publish Video:', - 'description_template': 'Publish video "{title}" to {platforms}', - 'duration_minutes': 30 - }, - ContentType.IMAGE: { - 'title_prefix': '๐Ÿ“ธ Publish Image:', - 'description_template': 'Publish image "{title}" to {platforms}', - 'duration_minutes': 10 - }, - ContentType.SOCIAL_POST: { - 'title_prefix': '๐Ÿ“ฑ Social Post:', - 'description_template': 'Publish social post "{title}" to {platforms}', - 'duration_minutes': 5 - } - } - - def sync_schedules_to_calendar(self, schedules: List[Schedule] = None) -> Dict[str, Any]: - """Sync content schedules to calendar. - - Args: - schedules: List of schedules to sync (if None, sync all pending schedules) - - Returns: - Dictionary with sync results - """ - try: - if schedules is None: - schedules = self.session.query(Schedule).filter( - Schedule.status == ScheduleStatus.PENDING - ).all() - - sync_results = { - 'total_schedules': len(schedules), - 'synced_successfully': 0, - 'failed_syncs': 0, - 'errors': [], - 'created_events': [] - } - - for schedule in schedules: - try: - # Get content item details - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if not content_item: - sync_results['errors'].append(f"Content item not found for schedule {schedule.id}") - sync_results['failed_syncs'] += 1 - continue - - # Create calendar event - event = self._create_calendar_event(schedule, content_item) - - # Sync to calendar provider - event_id = self._sync_event_to_provider(event) - - if event_id: - # Update schedule with calendar event ID - schedule.metadata = schedule.metadata or {} - schedule.metadata['calendar_event_id'] = event_id - self.session.commit() - - sync_results['synced_successfully'] += 1 - sync_results['created_events'].append({ - 'schedule_id': schedule.id, - 'event_id': event_id, - 'title': event.title - }) - else: - sync_results['failed_syncs'] += 1 - sync_results['errors'].append(f"Failed to create calendar event for schedule {schedule.id}") - - except Exception as e: - self.logger.error(f"Error syncing schedule {schedule.id}: {str(e)}") - sync_results['failed_syncs'] += 1 - sync_results['errors'].append(f"Schedule {schedule.id}: {str(e)}") - - return sync_results - - except Exception as e: - self.logger.error(f"Error syncing schedules to calendar: {str(e)}") - return { - 'total_schedules': 0, - 'synced_successfully': 0, - 'failed_syncs': 0, - 'errors': [f"Sync error: {str(e)}"], - 'created_events': [] - } - - def import_calendar_events(self, calendar_id: str = None, date_range: Tuple[datetime, datetime] = None) -> Dict[str, Any]: - """Import events from calendar and suggest content schedules. - - Args: - calendar_id: Calendar ID to import from - date_range: Date range to import events from - - Returns: - Dictionary with import results and suggestions - """ - try: - if date_range is None: - start_date = datetime.now() - end_date = start_date + timedelta(days=30) - date_range = (start_date, end_date) - - # Get events from calendar provider - events = self._get_events_from_provider(calendar_id, date_range) - - import_results = { - 'total_events': len(events), - 'content_suggestions': [], - 'scheduling_gaps': [], - 'optimal_times': [] - } - - # Analyze events for content scheduling opportunities - for event in events: - suggestions = self._analyze_event_for_content_opportunities(event) - import_results['content_suggestions'].extend(suggestions) - - # Find scheduling gaps - gaps = self._find_scheduling_gaps(events, date_range) - import_results['scheduling_gaps'] = gaps - - # Suggest optimal posting times - optimal_times = self._suggest_optimal_posting_times(events, date_range) - import_results['optimal_times'] = optimal_times - - return import_results - - except Exception as e: - self.logger.error(f"Error importing calendar events: {str(e)}") - return { - 'total_events': 0, - 'content_suggestions': [], - 'scheduling_gaps': [], - 'optimal_times': [], - 'error': str(e) - } - - def create_content_schedule_from_event(self, event: CalendarEvent, content_item_id: int) -> Optional[Schedule]: - """Create a content schedule from a calendar event. - - Args: - event: Calendar event - content_item_id: ID of content item to schedule - - Returns: - Created schedule or None if failed - """ - try: - # Get content item - content_item = self.session.query(ContentItem).filter( - ContentItem.id == content_item_id - ).first() - - if not content_item: - self.logger.error(f"Content item {content_item_id} not found") - return None - - # Create schedule - schedule = Schedule( - content_item_id=content_item_id, - scheduled_time=event.start_time, - status=ScheduleStatus.PENDING, - priority=5, # Default priority - metadata={ - 'calendar_event_id': event.id, - 'created_from_calendar': True, - 'original_event_title': event.title - } - ) - - self.session.add(schedule) - self.session.commit() - - self.logger.info(f"Created schedule {schedule.id} from calendar event {event.id}") - return schedule - - except Exception as e: - self.logger.error(f"Error creating schedule from event: {str(e)}") - self.session.rollback() - return None - - def update_calendar_event_from_schedule(self, schedule: Schedule) -> bool: - """Update calendar event when schedule changes. - - Args: - schedule: Updated schedule - - Returns: - True if successful, False otherwise - """ - try: - # Check if schedule has associated calendar event - if not schedule.metadata or 'calendar_event_id' not in schedule.metadata: - return False - - event_id = schedule.metadata['calendar_event_id'] - - # Get content item - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if not content_item: - return False - - # Create updated event - updated_event = self._create_calendar_event(schedule, content_item) - updated_event.id = event_id - - # Update event in calendar provider - success = self._update_event_in_provider(updated_event) - - if success: - self.logger.info(f"Updated calendar event {event_id} for schedule {schedule.id}") - else: - self.logger.error(f"Failed to update calendar event {event_id}") - - return success - - except Exception as e: - self.logger.error(f"Error updating calendar event: {str(e)}") - return False - - def delete_calendar_event_from_schedule(self, schedule: Schedule) -> bool: - """Delete calendar event when schedule is deleted. - - Args: - schedule: Schedule being deleted - - Returns: - True if successful, False otherwise - """ - try: - # Check if schedule has associated calendar event - if not schedule.metadata or 'calendar_event_id' not in schedule.metadata: - return True # No event to delete - - event_id = schedule.metadata['calendar_event_id'] - - # Delete event from calendar provider - success = self._delete_event_from_provider(event_id) - - if success: - self.logger.info(f"Deleted calendar event {event_id} for schedule {schedule.id}") - else: - self.logger.error(f"Failed to delete calendar event {event_id}") - - return success - - except Exception as e: - self.logger.error(f"Error deleting calendar event: {str(e)}") - return False - - def get_calendar_view(self, date_range: Tuple[datetime, datetime] = None) -> Dict[str, Any]: - """Get calendar view of scheduled content. - - Args: - date_range: Date range for calendar view - - Returns: - Dictionary with calendar view data - """ - try: - if date_range is None: - start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - end_date = start_date + timedelta(days=30) - date_range = (start_date, end_date) - - # Get schedules in date range - schedules = self.session.query(Schedule).filter( - Schedule.scheduled_time >= date_range[0], - Schedule.scheduled_time <= date_range[1] - ).all() - - calendar_events = [] - for schedule in schedules: - content_item = self.session.query(ContentItem).filter( - ContentItem.id == schedule.content_item_id - ).first() - - if content_item: - event = self._create_calendar_event(schedule, content_item) - calendar_events.append({ - 'id': str(schedule.id), - 'title': event.title, - 'description': event.description, - 'start': event.start_time.isoformat(), - 'end': event.end_time.isoformat(), - 'status': schedule.status.value, - 'priority': schedule.priority, - 'content_type': content_item.content_type.value if content_item.content_type else 'unknown', - 'platforms': schedule.platforms or [] - }) - - # Group events by day - events_by_day = {} - for event in calendar_events: - day = datetime.fromisoformat(event['start']).date() - if day not in events_by_day: - events_by_day[day] = [] - events_by_day[day].append(event) - - return { - 'date_range': { - 'start': date_range[0].isoformat(), - 'end': date_range[1].isoformat() - }, - 'total_events': len(calendar_events), - 'events': calendar_events, - 'events_by_day': {day.isoformat(): events for day, events in events_by_day.items()}, - 'summary': self._generate_calendar_summary(calendar_events) - } - - except Exception as e: - self.logger.error(f"Error getting calendar view: {str(e)}") - return { - 'date_range': None, - 'total_events': 0, - 'events': [], - 'events_by_day': {}, - 'summary': {}, - 'error': str(e) - } - - def _create_calendar_event(self, schedule: Schedule, content_item: ContentItem) -> CalendarEvent: - """Create calendar event from schedule and content item.""" - try: - # Get event template based on content type - template = self.event_templates.get( - content_item.content_type, - self.event_templates[ContentType.SOCIAL_POST] - ) - - # Create event title - title = f"{template['title_prefix']} {content_item.title}" - - # Create event description - platforms_str = ', '.join(schedule.platforms) if schedule.platforms else 'Default platforms' - description = template['description_template'].format( - title=content_item.title, - platforms=platforms_str - ) - - # Add content summary if available - if content_item.summary: - description += f"\n\nSummary: {content_item.summary}" - - # Calculate end time - duration = timedelta(minutes=template['duration_minutes']) - end_time = schedule.scheduled_time + duration - - # Create metadata - metadata = { - 'schedule_id': schedule.id, - 'content_item_id': content_item.id, - 'content_type': content_item.content_type.value if content_item.content_type else 'unknown', - 'platforms': schedule.platforms or [], - 'priority': schedule.priority, - 'status': schedule.status.value - } - - return CalendarEvent( - id=f"schedule_{schedule.id}", - title=title, - description=description, - start_time=schedule.scheduled_time, - end_time=end_time, - metadata=metadata - ) - - except Exception as e: - self.logger.error(f"Error creating calendar event: {str(e)}") - # Return a basic event as fallback - return CalendarEvent( - id=f"schedule_{schedule.id}", - title=f"Content Schedule: {content_item.title}", - description="Content publishing schedule", - start_time=schedule.scheduled_time, - end_time=schedule.scheduled_time + timedelta(minutes=30) - ) - - def _sync_event_to_provider(self, event: CalendarEvent) -> Optional[str]: - """Sync event to calendar provider (mock implementation).""" - try: - # This is a mock implementation - # In a real system, you would integrate with actual calendar APIs - - self.logger.info(f"Syncing event to {self.calendar_provider}: {event.title}") - - # Simulate API call - event_id = f"{self.calendar_provider}_{event.id}_{int(datetime.now().timestamp())}" - - return event_id - - except Exception as e: - self.logger.error(f"Error syncing event to provider: {str(e)}") - return None - - def _get_events_from_provider(self, calendar_id: str, date_range: Tuple[datetime, datetime]) -> List[CalendarEvent]: - """Get events from calendar provider (mock implementation).""" - try: - # This is a mock implementation - # In a real system, you would fetch from actual calendar APIs - - self.logger.info(f"Fetching events from {self.calendar_provider} calendar {calendar_id}") - - # Return empty list for mock - return [] - - except Exception as e: - self.logger.error(f"Error fetching events from provider: {str(e)}") - return [] - - def _update_event_in_provider(self, event: CalendarEvent) -> bool: - """Update event in calendar provider (mock implementation).""" - try: - # This is a mock implementation - self.logger.info(f"Updating event in {self.calendar_provider}: {event.id}") - return True - - except Exception as e: - self.logger.error(f"Error updating event in provider: {str(e)}") - return False - - def _delete_event_from_provider(self, event_id: str) -> bool: - """Delete event from calendar provider (mock implementation).""" - try: - # This is a mock implementation - self.logger.info(f"Deleting event from {self.calendar_provider}: {event_id}") - return True - - except Exception as e: - self.logger.error(f"Error deleting event from provider: {str(e)}") - return False - - def _analyze_event_for_content_opportunities(self, event: CalendarEvent) -> List[Dict[str, Any]]: - """Analyze calendar event for content opportunities.""" - suggestions = [] - - try: - # Look for keywords that suggest content opportunities - content_keywords = ['meeting', 'conference', 'launch', 'announcement', 'webinar', 'presentation'] - - event_text = f"{event.title} {event.description}".lower() - - for keyword in content_keywords: - if keyword in event_text: - suggestions.append({ - 'type': 'content_opportunity', - 'keyword': keyword, - 'suggested_time': event.end_time, # Suggest posting after the event - 'content_type': self._suggest_content_type_for_keyword(keyword), - 'description': f"Consider creating content about the {keyword}" - }) - - except Exception as e: - self.logger.error(f"Error analyzing event for opportunities: {str(e)}") - - return suggestions - - def _find_scheduling_gaps(self, events: List[CalendarEvent], date_range: Tuple[datetime, datetime]) -> List[Dict[str, Any]]: - """Find gaps in schedule that could be used for content posting.""" - gaps = [] - - try: - # Sort events by start time - sorted_events = sorted(events, key=lambda x: x.start_time) - - current_time = date_range[0] - - for event in sorted_events: - # Check if there's a gap before this event - if event.start_time > current_time + timedelta(hours=2): - gaps.append({ - 'start': current_time.isoformat(), - 'end': event.start_time.isoformat(), - 'duration_hours': (event.start_time - current_time).total_seconds() / 3600, - 'suggested_use': 'Content posting opportunity' - }) - - current_time = max(current_time, event.end_time) - - # Check for gap after last event - if current_time < date_range[1] - timedelta(hours=2): - gaps.append({ - 'start': current_time.isoformat(), - 'end': date_range[1].isoformat(), - 'duration_hours': (date_range[1] - current_time).total_seconds() / 3600, - 'suggested_use': 'Content posting opportunity' - }) - - except Exception as e: - self.logger.error(f"Error finding scheduling gaps: {str(e)}") - - return gaps - - def _suggest_optimal_posting_times(self, events: List[CalendarEvent], date_range: Tuple[datetime, datetime]) -> List[Dict[str, Any]]: - """Suggest optimal times for content posting based on calendar.""" - optimal_times = [] - - try: - # Define optimal posting hours (9 AM, 1 PM, 5 PM) - optimal_hours = [9, 13, 17] - - current_date = date_range[0].date() - end_date = date_range[1].date() - - while current_date <= end_date: - for hour in optimal_hours: - suggested_time = datetime.combine(current_date, datetime.min.time().replace(hour=hour)) - - # Check if this time conflicts with any events - conflicts = any( - event.start_time <= suggested_time <= event.end_time - for event in events - ) - - if not conflicts: - optimal_times.append({ - 'time': suggested_time.isoformat(), - 'reason': f'Optimal posting time ({hour}:00) with no calendar conflicts', - 'confidence': 0.8 - }) - - current_date += timedelta(days=1) - - except Exception as e: - self.logger.error(f"Error suggesting optimal posting times: {str(e)}") - - return optimal_times - - def _suggest_content_type_for_keyword(self, keyword: str) -> str: - """Suggest content type based on keyword.""" - keyword_mapping = { - 'meeting': 'social_post', - 'conference': 'article', - 'launch': 'video', - 'announcement': 'social_post', - 'webinar': 'video', - 'presentation': 'article' - } - - return keyword_mapping.get(keyword, 'social_post') - - def _generate_calendar_summary(self, events: List[Dict[str, Any]]) -> Dict[str, Any]: - """Generate summary statistics for calendar events.""" - try: - if not events: - return {} - - # Count by status - status_counts = {} - for event in events: - status = event.get('status', 'unknown') - status_counts[status] = status_counts.get(status, 0) + 1 - - # Count by content type - type_counts = {} - for event in events: - content_type = event.get('content_type', 'unknown') - type_counts[content_type] = type_counts.get(content_type, 0) + 1 - - # Count by day - daily_counts = {} - for event in events: - day = datetime.fromisoformat(event['start']).date().isoformat() - daily_counts[day] = daily_counts.get(day, 0) + 1 - - return { - 'total_events': len(events), - 'by_status': status_counts, - 'by_content_type': type_counts, - 'by_day': daily_counts, - 'busiest_day': max(daily_counts.items(), key=lambda x: x[1]) if daily_counts else None - } - - except Exception as e: - self.logger.error(f"Error generating calendar summary: {str(e)}") - return {} \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/models/job.py b/ToBeMigrated/content_scheduler/models/job.py deleted file mode 100644 index 1ed98116..00000000 --- a/ToBeMigrated/content_scheduler/models/job.py +++ /dev/null @@ -1,112 +0,0 @@ -from datetime import datetime -from typing import Dict, Any, Optional, List -from enum import Enum -from dataclasses import dataclass, field -from pydantic import BaseModel - -class JobStatus(str, Enum): - """Status of a scheduled job.""" - PENDING = "pending" - RUNNING = "running" - COMPLETED = "completed" - FAILED = "failed" - CANCELLED = "cancelled" - MISSED = "missed" - -class JobType(str, Enum): - """Type of scheduled job.""" - ONE_TIME = "one_time" - RECURRING = "recurring" - BATCH = "batch" - -class JobPriority(int, Enum): - """Priority of a scheduled job.""" - LOW = 0 - MEDIUM = 1 - HIGH = 2 - CRITICAL = 3 - -@dataclass -class JobMetadata: - """Metadata for a scheduled job.""" - retry_count: int = 0 - max_retries: int = 3 - retry_delay: int = 300 # seconds - priority: JobPriority = JobPriority.MEDIUM - tags: List[str] = field(default_factory=list) - custom_data: Dict[str, Any] = field(default_factory=dict) - -class ScheduledJob(BaseModel): - """Model for a scheduled job.""" - job_id: str - content_id: str - schedule_type: JobType - status: JobStatus - platforms: List[str] - publish_date: datetime - created_at: datetime = field(default_factory=datetime.now) - updated_at: datetime = field(default_factory=datetime.now) - cron_expression: Optional[str] = None - end_date: Optional[datetime] = None - metadata: JobMetadata = field(default_factory=JobMetadata) - error: Optional[str] = None - last_run: Optional[datetime] = None - next_run: Optional[datetime] = None - - class Config: - arbitrary_types_allowed = True - - def to_dict(self) -> Dict[str, Any]: - """Convert job to dictionary.""" - return { - 'job_id': self.job_id, - 'content_id': self.content_id, - 'schedule_type': self.schedule_type, - 'status': self.status, - 'platforms': self.platforms, - 'publish_date': self.publish_date.isoformat(), - 'created_at': self.created_at.isoformat(), - 'updated_at': self.updated_at.isoformat(), - 'cron_expression': self.cron_expression, - 'end_date': self.end_date.isoformat() if self.end_date else None, - 'metadata': { - 'retry_count': self.metadata.retry_count, - 'max_retries': self.metadata.max_retries, - 'retry_delay': self.metadata.retry_delay, - 'priority': self.metadata.priority, - 'tags': self.metadata.tags, - 'custom_data': self.metadata.custom_data - }, - 'error': self.error, - 'last_run': self.last_run.isoformat() if self.last_run else None, - 'next_run': self.next_run.isoformat() if self.next_run else None - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> 'ScheduledJob': - """Create job from dictionary.""" - metadata = JobMetadata( - retry_count=data['metadata']['retry_count'], - max_retries=data['metadata']['max_retries'], - retry_delay=data['metadata']['retry_delay'], - priority=data['metadata']['priority'], - tags=data['metadata']['tags'], - custom_data=data['metadata']['custom_data'] - ) - - return cls( - job_id=data['job_id'], - content_id=data['content_id'], - schedule_type=data['schedule_type'], - status=data['status'], - platforms=data['platforms'], - publish_date=datetime.fromisoformat(data['publish_date']), - created_at=datetime.fromisoformat(data['created_at']), - updated_at=datetime.fromisoformat(data['updated_at']), - cron_expression=data.get('cron_expression'), - end_date=datetime.fromisoformat(data['end_date']) if data.get('end_date') else None, - metadata=metadata, - error=data.get('error'), - last_run=datetime.fromisoformat(data['last_run']) if data.get('last_run') else None, - next_run=datetime.fromisoformat(data['next_run']) if data.get('next_run') else None - ) \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/models/job_status.py b/ToBeMigrated/content_scheduler/models/job_status.py deleted file mode 100644 index 55ade28c..00000000 --- a/ToBeMigrated/content_scheduler/models/job_status.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Job status model for content scheduling. -""" - -from enum import Enum - -class JobStatus(str, Enum): - """Enum representing the status of a scheduled job.""" - - PENDING = "pending" - RUNNING = "running" - COMPLETED = "completed" - FAILED = "failed" - CANCELLED = "cancelled" - RETRYING = "retrying" \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/models/schedule.py b/ToBeMigrated/content_scheduler/models/schedule.py deleted file mode 100644 index 21913726..00000000 --- a/ToBeMigrated/content_scheduler/models/schedule.py +++ /dev/null @@ -1,153 +0,0 @@ -from datetime import datetime -from typing import Dict, Any, Optional, List -from enum import Enum -from dataclasses import dataclass, field -from pydantic import BaseModel, Field - -class ScheduleType(str, Enum): - """Type of schedule.""" - ONE_TIME = "one_time" - RECURRING = "recurring" - BATCH = "batch" - -class ScheduleStatus(str, Enum): - """Status of a schedule.""" - ACTIVE = "active" - PAUSED = "paused" - COMPLETED = "completed" - CANCELLED = "cancelled" - ERROR = "error" - -@dataclass -class ScheduleMetadata: - """Metadata for a schedule.""" - description: Optional[str] = None - tags: List[str] = field(default_factory=list) - priority: int = 0 - custom_data: Dict[str, Any] = field(default_factory=dict) - notification_settings: Dict[str, Any] = field(default_factory=dict) - -class Schedule(BaseModel): - """Model representing a content publishing schedule.""" - - content_id: str = Field(..., description="ID of the content to be published") - content: Dict[str, Any] = Field(..., description="Content to be published") - publish_date: datetime = Field(..., description="When to publish the content") - platforms: List[str] = Field(..., description="List of platforms to publish to") - schedule_type: str = Field(default="one_time", description="Type of schedule ('one_time' or 'recurring')") - cron_expression: Optional[str] = Field(None, description="Cron expression for recurring schedules") - end_date: Optional[datetime] = Field(None, description="End date for recurring schedules") - metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata for the schedule") - - class Config: - """Pydantic model configuration.""" - arbitrary_types_allowed = True - - def to_dict(self) -> Dict[str, Any]: - """Convert schedule to dictionary.""" - return { - 'schedule_id': self.schedule_id, - 'content_id': self.content_id, - 'schedule_type': self.schedule_type, - 'status': self.status, - 'platforms': self.platforms, - 'publish_date': self.publish_date.isoformat(), - 'created_at': self.created_at.isoformat(), - 'updated_at': self.updated_at.isoformat(), - 'cron_expression': self.cron_expression, - 'end_date': self.end_date.isoformat() if self.end_date else None, - 'metadata': { - 'description': self.metadata.description, - 'tags': self.metadata.tags, - 'priority': self.metadata.priority, - 'custom_data': self.metadata.custom_data, - 'notification_settings': self.metadata.notification_settings - }, - 'error': self.error, - 'last_run': self.last_run.isoformat() if self.last_run else None, - 'next_run': self.next_run.isoformat() if self.next_run else None, - 'job_ids': self.job_ids - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> 'Schedule': - """Create schedule from dictionary.""" - metadata = ScheduleMetadata( - description=data['metadata'].get('description'), - tags=data['metadata'].get('tags', []), - priority=data['metadata'].get('priority', 0), - custom_data=data['metadata'].get('custom_data', {}), - notification_settings=data['metadata'].get('notification_settings', {}) - ) - - return cls( - schedule_id=data['schedule_id'], - content_id=data['content_id'], - schedule_type=data['schedule_type'], - status=data['status'], - platforms=data['platforms'], - publish_date=datetime.fromisoformat(data['publish_date']), - created_at=datetime.fromisoformat(data['created_at']), - updated_at=datetime.fromisoformat(data['updated_at']), - cron_expression=data.get('cron_expression'), - end_date=datetime.fromisoformat(data['end_date']) if data.get('end_date') else None, - metadata=metadata, - error=data.get('error'), - last_run=datetime.fromisoformat(data['last_run']) if data.get('last_run') else None, - next_run=datetime.fromisoformat(data['next_run']) if data.get('next_run') else None, - job_ids=data.get('job_ids', []) - ) - - def is_active(self) -> bool: - """Check if schedule is active.""" - return self.status == ScheduleStatus.ACTIVE - - def is_completed(self) -> bool: - """Check if schedule is completed.""" - return self.status == ScheduleStatus.COMPLETED - - def is_cancelled(self) -> bool: - """Check if schedule is cancelled.""" - return self.status == ScheduleStatus.CANCELLED - - def is_error(self) -> bool: - """Check if schedule has error.""" - return self.status == ScheduleStatus.ERROR - - def is_recurring(self) -> bool: - """Check if schedule is recurring.""" - return self.schedule_type == ScheduleType.RECURRING - - def is_one_time(self) -> bool: - """Check if schedule is one-time.""" - return self.schedule_type == ScheduleType.ONE_TIME - - def is_batch(self) -> bool: - """Check if schedule is batch.""" - return self.schedule_type == ScheduleType.BATCH - - def add_job_id(self, job_id: str): - """Add a job ID to the schedule.""" - if job_id not in self.job_ids: - self.job_ids.append(job_id) - - def remove_job_id(self, job_id: str): - """Remove a job ID from the schedule.""" - if job_id in self.job_ids: - self.job_ids.remove(job_id) - - def update_status(self, status: ScheduleStatus, error: Optional[str] = None): - """Update schedule status.""" - self.status = status - self.error = error - self.updated_at = datetime.now() - - def update_next_run(self, next_run: datetime): - """Update next run time.""" - self.next_run = next_run - self.updated_at = datetime.now() - - def update_last_run(self, last_run: datetime): - """Update last run time.""" - self.last_run = last_run - self.updated_at = datetime.now() \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/models/timeline.py b/ToBeMigrated/content_scheduler/models/timeline.py deleted file mode 100644 index e2b2e9d4..00000000 --- a/ToBeMigrated/content_scheduler/models/timeline.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Timeline models for the Content Scheduler. -""" - -from dataclasses import dataclass -from datetime import datetime -from typing import List, Dict, Any, Optional -from enum import Enum - -class TimelineViewType(Enum): - """Types of timeline views.""" - GANTT = "gantt" - TIMELINE = "timeline" - LIST = "list" - -class TimelineDependencyType(Enum): - """Types of timeline dependencies.""" - FINISH_TO_START = "finish_to_start" - START_TO_START = "start_to_start" - FINISH_TO_FINISH = "finish_to_finish" - START_TO_FINISH = "start_to_finish" - -@dataclass -class TimelineDependency: - """Timeline dependency model.""" - source_id: str - target_id: str - dependency_type: TimelineDependencyType - lag: Optional[int] = None # Lag time in minutes - -@dataclass -class TimelineTask: - """Timeline task model.""" - id: str - title: str - start_time: datetime - end_time: datetime - platform: str - status: str - progress: float - dependencies: List[TimelineDependency] - metadata: Dict[str, Any] - -@dataclass -class TimelineMilestone: - """Timeline milestone model.""" - id: str - title: str - date: datetime - description: Optional[str] = None - status: str = "pending" - metadata: Dict[str, Any] = None - -@dataclass -class TimelineView: - """Timeline view model.""" - view_type: TimelineViewType - start_date: datetime - end_date: datetime - tasks: List[TimelineTask] - milestones: List[TimelineMilestone] - dependencies: List[TimelineDependency] - metadata: Dict[str, Any] - -@dataclass -class TimelineProgress: - """Timeline progress model.""" - total_tasks: int - completed_tasks: int - in_progress_tasks: int - pending_tasks: int - progress_percentage: float - by_platform: Dict[str, float] - by_date: Dict[str, float] - metadata: Dict[str, Any] \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/requirements.txt b/ToBeMigrated/content_scheduler/requirements.txt deleted file mode 100644 index 1b366ffb..00000000 --- a/ToBeMigrated/content_scheduler/requirements.txt +++ /dev/null @@ -1,26 +0,0 @@ -APScheduler>=3.9.1 -SQLAlchemy>=1.4.0 -FastAPI>=0.68.0 -Streamlit>=1.24.0 -Pandas>=1.5.0 -Plotly>=5.13.0 -python-dateutil>=2.8.2 -pytz>=2021.3 -redis>=4.0.0 -pydantic>=1.8.2 -python-multipart>=0.0.5 -aiohttp>=3.8.1 -asyncio>=3.4.3 -typing-extensions>=4.0.0 -python-jose[cryptography]>=3.3.0 -passlib[bcrypt]>=1.7.4 -pytest>=6.2.5 -pytest-asyncio>=0.16.0 -pytest-cov>=2.12.1 -black>=21.9b0 -isort>=5.9.3 -flake8>=3.9.2 -mypy>=0.910 -google-auth-oauthlib>=0.4.6 -google-auth-httplib2>=0.1.0 -google-api-python-client>=2.0.0 \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/utils/date_utils.py b/ToBeMigrated/content_scheduler/utils/date_utils.py deleted file mode 100644 index e2b7206b..00000000 --- a/ToBeMigrated/content_scheduler/utils/date_utils.py +++ /dev/null @@ -1,201 +0,0 @@ -from typing import List, Optional, Dict, Any -from datetime import datetime, timedelta -import pytz -from dateutil import rrule -from .error_handling import ScheduleValidationError - -def get_optimal_publish_time( - platform: str, - content_type: str, - target_audience: Optional[Dict[str, Any]] = None -) -> datetime: - """Calculate optimal publish time based on platform and content type.""" - now = datetime.now(pytz.UTC) - - # Default optimal times by platform and content type - optimal_times = { - 'TWITTER': { - 'POST': {'hour': 12, 'minute': 0}, # Noon UTC - 'THREAD': {'hour': 15, 'minute': 0}, # 3 PM UTC - 'POLL': {'hour': 18, 'minute': 0}, # 6 PM UTC - }, - 'FACEBOOK': { - 'POST': {'hour': 15, 'minute': 0}, # 3 PM UTC - 'LIVE': {'hour': 19, 'minute': 0}, # 7 PM UTC - 'EVENT': {'hour': 10, 'minute': 0}, # 10 AM UTC - }, - 'LINKEDIN': { - 'POST': {'hour': 9, 'minute': 0}, # 9 AM UTC - 'ARTICLE': {'hour': 11, 'minute': 0}, # 11 AM UTC - 'POLL': {'hour': 14, 'minute': 0}, # 2 PM UTC - }, - 'INSTAGRAM': { - 'POST': {'hour': 17, 'minute': 0}, # 5 PM UTC - 'STORY': {'hour': 20, 'minute': 0}, # 8 PM UTC - 'REEL': {'hour': 21, 'minute': 0}, # 9 PM UTC - } - } - - if platform not in optimal_times: - raise ScheduleValidationError( - f"Unsupported platform: {platform}", - {'supported_platforms': list(optimal_times.keys())} - ) - - if content_type not in optimal_times[platform]: - raise ScheduleValidationError( - f"Unsupported content type for {platform}: {content_type}", - {'supported_types': list(optimal_times[platform].keys())} - ) - - optimal_time = optimal_times[platform][content_type] - publish_time = now.replace( - hour=optimal_time['hour'], - minute=optimal_time['minute'], - second=0, - microsecond=0 - ) - - # If the optimal time has passed for today, schedule for tomorrow - if publish_time < now: - publish_time += timedelta(days=1) - - return publish_time - -def calculate_recurrence_dates( - start_date: datetime, - frequency: str, - interval: int, - end_date: Optional[datetime] = None, - count: Optional[int] = None -) -> List[datetime]: - """Calculate recurrence dates based on frequency and interval.""" - if not isinstance(start_date, datetime): - raise ScheduleValidationError( - "Start date must be a datetime object", - {'type': type(start_date).__name__} - ) - - if start_date.tzinfo is None: - raise ScheduleValidationError( - "Start date must be timezone-aware", - {'date': str(start_date)} - ) - - frequency_map = { - 'DAILY': rrule.DAILY, - 'WEEKLY': rrule.WEEKLY, - 'MONTHLY': rrule.MONTHLY, - 'YEARLY': rrule.YEARLY - } - - if frequency not in frequency_map: - raise ScheduleValidationError( - f"Invalid frequency: {frequency}", - {'valid_frequencies': list(frequency_map.keys())} - ) - - if not isinstance(interval, int) or interval < 1: - raise ScheduleValidationError( - "Interval must be a positive integer", - {'interval': interval} - ) - - if end_date is not None and not isinstance(end_date, datetime): - raise ScheduleValidationError( - "End date must be a datetime object", - {'type': type(end_date).__name__} - ) - - if end_date is not None and end_date.tzinfo is None: - raise ScheduleValidationError( - "End date must be timezone-aware", - {'date': str(end_date)} - ) - - if count is not None and (not isinstance(count, int) or count < 1): - raise ScheduleValidationError( - "Count must be a positive integer", - {'count': count} - ) - - rule = rrule.rrule( - freq=frequency_map[frequency], - interval=interval, - dtstart=start_date, - until=end_date, - count=count - ) - - return list(rule) - -def adjust_for_timezone( - date: datetime, - target_timezone: str -) -> datetime: - """Adjust datetime to target timezone.""" - if not isinstance(date, datetime): - raise ScheduleValidationError( - "Date must be a datetime object", - {'type': type(date).__name__} - ) - - if date.tzinfo is None: - raise ScheduleValidationError( - "Date must be timezone-aware", - {'date': str(date)} - ) - - try: - target_tz = pytz.timezone(target_timezone) - except pytz.exceptions.UnknownTimeZoneError: - raise ScheduleValidationError( - f"Invalid timezone: {target_timezone}", - {'timezone': target_timezone} - ) - - return date.astimezone(target_tz) - -def calculate_time_difference( - date1: datetime, - date2: datetime -) -> timedelta: - """Calculate time difference between two dates.""" - if not isinstance(date1, datetime) or not isinstance(date2, datetime): - raise ScheduleValidationError( - "Both dates must be datetime objects", - { - 'date1_type': type(date1).__name__, - 'date2_type': type(date2).__name__ - } - ) - - if date1.tzinfo is None or date2.tzinfo is None: - raise ScheduleValidationError( - "Both dates must be timezone-aware", - { - 'date1': str(date1), - 'date2': str(date2) - } - ) - - return date2 - date1 - -def format_date_for_display( - date: datetime, - format_str: str = "%Y-%m-%d %H:%M:%S %Z" -) -> str: - """Format datetime for display.""" - if not isinstance(date, datetime): - raise ScheduleValidationError( - "Date must be a datetime object", - {'type': type(date).__name__} - ) - - if date.tzinfo is None: - raise ScheduleValidationError( - "Date must be timezone-aware", - {'date': str(date)} - ) - - return date.strftime(format_str) \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/utils/error_handling.py b/ToBeMigrated/content_scheduler/utils/error_handling.py deleted file mode 100644 index c054ed2e..00000000 --- a/ToBeMigrated/content_scheduler/utils/error_handling.py +++ /dev/null @@ -1,134 +0,0 @@ -from typing import Optional, Dict, Any -import logging -from functools import wraps -import traceback - -logger = logging.getLogger('content_scheduler') - -class SchedulingError(Exception): - """Exception raised for errors in content scheduling.""" - - def __init__(self, message: str): - """Initialize the error with a message. - - Args: - message: Error message - """ - self.message = message - super().__init__(self.message) - -class JobExecutionError(SchedulingError): - """Exception for job execution errors.""" - pass - -class ScheduleValidationError(SchedulingError): - """Exception for schedule validation errors.""" - pass - -class PlatformError(SchedulingError): - """Exception for platform-specific errors.""" - pass - -class DatabaseError(SchedulingError): - """Exception for database-related errors.""" - pass - -def handle_scheduler_error(func): - """Decorator for handling scheduler errors.""" - @wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except SchedulingError as e: - logger.error(f"Scheduling error in {func.__name__}: {str(e)}") - raise - except Exception as e: - logger.error(f"Unexpected error in {func.__name__}: {str(e)}") - logger.error(traceback.format_exc()) - raise SchedulingError( - f"Unexpected error in {func.__name__}: {str(e)}", - {'traceback': traceback.format_exc()} - ) - return wrapper - -def handle_job_error(func): - """Decorator for handling job execution errors.""" - @wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception as e: - logger.error(f"Job execution error in {func.__name__}: {str(e)}") - logger.error(traceback.format_exc()) - raise JobExecutionError( - f"Job execution failed: {str(e)}", - { - 'function': func.__name__, - 'traceback': traceback.format_exc() - } - ) - return wrapper - -def handle_platform_error(func): - """Decorator for handling platform-specific errors.""" - @wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception as e: - logger.error(f"Platform error in {func.__name__}: {str(e)}") - logger.error(traceback.format_exc()) - raise PlatformError( - f"Platform operation failed: {str(e)}", - { - 'function': func.__name__, - 'traceback': traceback.format_exc() - } - ) - return wrapper - -def handle_database_error(func): - """Decorator for handling database errors.""" - @wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception as e: - logger.error(f"Database error in {func.__name__}: {str(e)}") - logger.error(traceback.format_exc()) - raise DatabaseError( - f"Database operation failed: {str(e)}", - { - 'function': func.__name__, - 'traceback': traceback.format_exc() - } - ) - return wrapper - -def format_error(error: Exception) -> Dict[str, Any]: - """Format error for logging and reporting.""" - if isinstance(error, SchedulingError): - return { - 'type': error.__class__.__name__, - 'message': str(error), - 'details': error.details - } - else: - return { - 'type': 'UnexpectedError', - 'message': str(error), - 'details': { - 'traceback': traceback.format_exc() - } - } - -def log_error(error: Exception, context: Optional[Dict[str, Any]] = None): - """Log error with context.""" - error_data = format_error(error) - if context: - error_data['context'] = context - - logger.error( - f"Error: {error_data['type']} - {error_data['message']}", - extra={'error_data': error_data} - ) \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/utils/logging.py b/ToBeMigrated/content_scheduler/utils/logging.py deleted file mode 100644 index f096111a..00000000 --- a/ToBeMigrated/content_scheduler/utils/logging.py +++ /dev/null @@ -1,11 +0,0 @@ -import logging - -def setup_logger(name: str = "content_scheduler", level=logging.INFO): - logger = logging.getLogger(name) - if not logger.handlers: - handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - logger.setLevel(level) - return logger \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/utils/notification.py b/ToBeMigrated/content_scheduler/utils/notification.py deleted file mode 100644 index 47ed7bd6..00000000 --- a/ToBeMigrated/content_scheduler/utils/notification.py +++ /dev/null @@ -1,285 +0,0 @@ -from typing import Dict, Any, List, Optional -import logging -import smtplib -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart -import aiohttp -import json -from .error_handling import PlatformError - -logger = logging.getLogger('content_scheduler') - -class NotificationManager: - """Manages notifications for scheduled content.""" - - def __init__(self, config: Dict[str, Any]): - """Initialize notification manager with configuration.""" - self.config = config - self.email_config = config.get('email', {}) - self.slack_config = config.get('slack', {}) - self.webhook_config = config.get('webhook', {}) - - async def send_notification( - self, - event_type: str, - content: Dict[str, Any], - channels: List[str], - metadata: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Send notification through specified channels.""" - results = {} - - for channel in channels: - try: - if channel == 'EMAIL': - results['email'] = await self._send_email_notification( - event_type, content, metadata - ) - elif channel == 'SLACK': - results['slack'] = await self._send_slack_notification( - event_type, content, metadata - ) - elif channel == 'WEBHOOK': - results['webhook'] = await self._send_webhook_notification( - event_type, content, metadata - ) - else: - logger.warning(f"Unsupported notification channel: {channel}") - except Exception as e: - logger.error(f"Failed to send {channel} notification: {str(e)}") - results[channel] = { - 'success': False, - 'error': str(e) - } - - return results - - async def _send_email_notification( - self, - event_type: str, - content: Dict[str, Any], - metadata: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Send email notification.""" - if not self.email_config: - raise PlatformError( - "Email configuration not found", - {'event_type': event_type} - ) - - try: - msg = MIMEMultipart() - msg['From'] = self.email_config['from_email'] - msg['To'] = self.email_config['to_email'] - msg['Subject'] = self._get_email_subject(event_type, content) - - body = self._format_email_body(event_type, content, metadata) - msg.attach(MIMEText(body, 'html')) - - with smtplib.SMTP( - self.email_config['smtp_server'], - self.email_config['smtp_port'] - ) as server: - if self.email_config.get('use_tls'): - server.starttls() - if self.email_config.get('username'): - server.login( - self.email_config['username'], - self.email_config['password'] - ) - server.send_message(msg) - - return {'success': True} - except Exception as e: - raise PlatformError( - f"Failed to send email notification: {str(e)}", - { - 'event_type': event_type, - 'error': str(e) - } - ) - - async def _send_slack_notification( - self, - event_type: str, - content: Dict[str, Any], - metadata: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Send Slack notification.""" - if not self.slack_config: - raise PlatformError( - "Slack configuration not found", - {'event_type': event_type} - ) - - try: - message = self._format_slack_message(event_type, content, metadata) - - async with aiohttp.ClientSession() as session: - async with session.post( - self.slack_config['webhook_url'], - json=message - ) as response: - if response.status != 200: - raise PlatformError( - f"Slack API returned status {response.status}", - {'response': await response.text()} - ) - return {'success': True} - except Exception as e: - raise PlatformError( - f"Failed to send Slack notification: {str(e)}", - { - 'event_type': event_type, - 'error': str(e) - } - ) - - async def _send_webhook_notification( - self, - event_type: str, - content: Dict[str, Any], - metadata: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Send webhook notification.""" - if not self.webhook_config: - raise PlatformError( - "Webhook configuration not found", - {'event_type': event_type} - ) - - try: - payload = self._format_webhook_payload(event_type, content, metadata) - - async with aiohttp.ClientSession() as session: - async with session.post( - self.webhook_config['url'], - json=payload, - headers=self.webhook_config.get('headers', {}) - ) as response: - if response.status != 200: - raise PlatformError( - f"Webhook returned status {response.status}", - {'response': await response.text()} - ) - return {'success': True} - except Exception as e: - raise PlatformError( - f"Failed to send webhook notification: {str(e)}", - { - 'event_type': event_type, - 'error': str(e) - } - ) - - def _get_email_subject( - self, - event_type: str, - content: Dict[str, Any] - ) -> str: - """Generate email subject based on event type.""" - subjects = { - 'ON_SUCCESS': f"Content Published Successfully: {content.get('title', 'Untitled')}", - 'ON_FAILURE': f"Content Publication Failed: {content.get('title', 'Untitled')}", - 'ON_RETRY': f"Content Publication Retry: {content.get('title', 'Untitled')}", - 'ON_CANCELLATION': f"Content Publication Cancelled: {content.get('title', 'Untitled')}" - } - return subjects.get(event_type, f"Content Update: {content.get('title', 'Untitled')}") - - def _format_email_body( - self, - event_type: str, - content: Dict[str, Any], - metadata: Optional[Dict[str, Any]] = None - ) -> str: - """Format email body.""" - template = f""" - - -

Content Update Notification

-

Event Type: {event_type}

-

Content Title: {content.get('title', 'Untitled')}

-

Platform: {content.get('platform', 'Unknown')}

-

Status: {content.get('status', 'Unknown')}

- """ - - if metadata: - template += "

Additional Details:

    " - for key, value in metadata.items(): - template += f"
  • {key}: {value}
  • " - template += "
" - - template += """ - - - """ - - return template - - def _format_slack_message( - self, - event_type: str, - content: Dict[str, Any], - metadata: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Format Slack message.""" - message = { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": self._get_email_subject(event_type, content) - } - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": f"*Event Type:*\n{event_type}" - }, - { - "type": "mrkdwn", - "text": f"*Platform:*\n{content.get('platform', 'Unknown')}" - }, - { - "type": "mrkdwn", - "text": f"*Status:*\n{content.get('status', 'Unknown')}" - } - ] - } - ] - } - - if metadata: - fields = [] - for key, value in metadata.items(): - fields.append({ - "type": "mrkdwn", - "text": f"*{key}:*\n{value}" - }) - message["blocks"].append({ - "type": "section", - "fields": fields - }) - - return message - - def _format_webhook_payload( - self, - event_type: str, - content: Dict[str, Any], - metadata: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Format webhook payload.""" - payload = { - 'event_type': event_type, - 'content': content, - 'timestamp': datetime.now(pytz.UTC).isoformat() - } - - if metadata: - payload['metadata'] = metadata - - return payload \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/utils/timeline_utils.py b/ToBeMigrated/content_scheduler/utils/timeline_utils.py deleted file mode 100644 index a2fea347..00000000 --- a/ToBeMigrated/content_scheduler/utils/timeline_utils.py +++ /dev/null @@ -1,381 +0,0 @@ -""" -Timeline utilities for content scheduling. -""" - -import logging -from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional, Tuple -import pandas as pd -import plotly.express as px -import plotly.graph_objects as go -from plotly.subplots import make_subplots - -# Use unified database models -from lib.database.models import ContentItem, Schedule, ScheduleStatus - -logger = logging.getLogger(__name__) - -class TimelineAnalyzer: - """Analyze and visualize content scheduling timelines.""" - - def __init__(self): - """Initialize the timeline analyzer.""" - self.logger = logger - - def analyze_schedule_distribution( - self, - schedules: List[Schedule], - time_range: str = "week" - ) -> Dict[str, Any]: - """Analyze the distribution of schedules over time. - - Args: - schedules: List of Schedule objects - time_range: Time range for analysis ('day', 'week', 'month') - - Returns: - Dictionary containing analysis results - """ - try: - if not schedules: - return { - 'total_schedules': 0, - 'distribution': {}, - 'peak_times': [], - 'gaps': [] - } - - # Group schedules by time period - distribution = {} - for schedule in schedules: - if time_range == "day": - key = schedule.scheduled_time.strftime("%Y-%m-%d") - elif time_range == "week": - # Get week start (Monday) - week_start = schedule.scheduled_time - timedelta(days=schedule.scheduled_time.weekday()) - key = week_start.strftime("%Y-%m-%d") - else: # month - key = schedule.scheduled_time.strftime("%Y-%m") - - distribution[key] = distribution.get(key, 0) + 1 - - # Find peak times - peak_times = sorted(distribution.items(), key=lambda x: x[1], reverse=True)[:3] - - # Find gaps (periods with no content) - gaps = self._find_gaps(schedules, time_range) - - return { - 'total_schedules': len(schedules), - 'distribution': distribution, - 'peak_times': peak_times, - 'gaps': gaps - } - - except Exception as e: - self.logger.error(f"Error analyzing schedule distribution: {str(e)}") - return {} - - def _find_gaps( - self, - schedules: List[Schedule], - time_range: str - ) -> List[str]: - """Find gaps in the schedule timeline. - - Args: - schedules: List of Schedule objects - time_range: Time range for analysis - - Returns: - List of time periods with no scheduled content - """ - try: - if not schedules: - return [] - - # Get date range - dates = [s.scheduled_time.date() for s in schedules] - start_date = min(dates) - end_date = max(dates) - - # Generate all periods in range - current_date = start_date - all_periods = set() - - while current_date <= end_date: - if time_range == "day": - period = current_date.strftime("%Y-%m-%d") - current_date += timedelta(days=1) - elif time_range == "week": - # Get week start (Monday) - week_start = current_date - timedelta(days=current_date.weekday()) - period = week_start.strftime("%Y-%m-%d") - current_date += timedelta(weeks=1) - else: # month - period = current_date.strftime("%Y-%m") - # Move to next month - if current_date.month == 12: - current_date = current_date.replace(year=current_date.year + 1, month=1) - else: - current_date = current_date.replace(month=current_date.month + 1) - - all_periods.add(period) - - # Find periods with schedules - scheduled_periods = set() - for schedule in schedules: - if time_range == "day": - period = schedule.scheduled_time.strftime("%Y-%m-%d") - elif time_range == "week": - week_start = schedule.scheduled_time - timedelta(days=schedule.scheduled_time.weekday()) - period = week_start.strftime("%Y-%m-%d") - else: # month - period = schedule.scheduled_time.strftime("%Y-%m") - - scheduled_periods.add(period) - - # Return gaps - gaps = list(all_periods - scheduled_periods) - return sorted(gaps) - - except Exception as e: - self.logger.error(f"Error finding gaps: {str(e)}") - return [] - - def create_timeline_chart( - self, - schedules: List[Schedule], - chart_type: str = "gantt" - ) -> go.Figure: - """Create a timeline visualization chart. - - Args: - schedules: List of Schedule objects - chart_type: Type of chart ('gantt', 'scatter', 'bar') - - Returns: - Plotly figure object - """ - try: - if not schedules: - fig = go.Figure() - fig.add_annotation( - text="No schedules to display", - xref="paper", yref="paper", - x=0.5, y=0.5, - showarrow=False - ) - return fig - - if chart_type == "gantt": - return self._create_gantt_chart(schedules) - elif chart_type == "scatter": - return self._create_scatter_chart(schedules) - else: # bar - return self._create_bar_chart(schedules) - - except Exception as e: - self.logger.error(f"Error creating timeline chart: {str(e)}") - fig = go.Figure() - fig.add_annotation( - text=f"Error creating chart: {str(e)}", - xref="paper", yref="paper", - x=0.5, y=0.5, - showarrow=False - ) - return fig - - def _create_gantt_chart(self, schedules: List[Schedule]) -> go.Figure: - """Create a Gantt chart for schedules.""" - try: - # Prepare data for Gantt chart - data = [] - for i, schedule in enumerate(schedules): - # Estimate duration (default 1 hour) - start_time = schedule.scheduled_time - end_time = start_time + timedelta(hours=1) - - data.append({ - 'Task': f"Schedule {schedule.id}", - 'Start': start_time, - 'Finish': end_time, - 'Status': schedule.status.value - }) - - df = pd.DataFrame(data) - - # Create Gantt chart - fig = px.timeline( - df, - x_start="Start", - x_end="Finish", - y="Task", - color="Status", - title="Content Schedule Timeline" - ) - - fig.update_layout( - xaxis_title="Time", - yaxis_title="Schedules", - height=max(400, len(schedules) * 30) - ) - - return fig - - except Exception as e: - self.logger.error(f"Error creating Gantt chart: {str(e)}") - return go.Figure() - - def _create_scatter_chart(self, schedules: List[Schedule]) -> go.Figure: - """Create a scatter plot for schedules.""" - try: - # Prepare data - dates = [s.scheduled_time for s in schedules] - statuses = [s.status.value for s in schedules] - ids = [s.id for s in schedules] - - # Create scatter plot - fig = px.scatter( - x=dates, - y=statuses, - title="Schedule Status Over Time", - labels={'x': 'Scheduled Time', 'y': 'Status'}, - hover_data={'Schedule ID': ids} - ) - - fig.update_layout( - xaxis_title="Scheduled Time", - yaxis_title="Status" - ) - - return fig - - except Exception as e: - self.logger.error(f"Error creating scatter chart: {str(e)}") - return go.Figure() - - def _create_bar_chart(self, schedules: List[Schedule]) -> go.Figure: - """Create a bar chart for schedule distribution.""" - try: - # Group by date - date_counts = {} - for schedule in schedules: - date_key = schedule.scheduled_time.strftime("%Y-%m-%d") - date_counts[date_key] = date_counts.get(date_key, 0) + 1 - - # Create bar chart - fig = px.bar( - x=list(date_counts.keys()), - y=list(date_counts.values()), - title="Scheduled Content by Date", - labels={'x': 'Date', 'y': 'Number of Schedules'} - ) - - fig.update_layout( - xaxis_title="Date", - yaxis_title="Number of Schedules" - ) - - return fig - - except Exception as e: - self.logger.error(f"Error creating bar chart: {str(e)}") - return go.Figure() - - def get_schedule_conflicts( - self, - schedules: List[Schedule], - time_window: int = 60 # minutes - ) -> List[Dict[str, Any]]: - """Identify potential scheduling conflicts. - - Args: - schedules: List of Schedule objects - time_window: Time window in minutes to check for conflicts - - Returns: - List of conflict information - """ - try: - conflicts = [] - - # Sort schedules by time - sorted_schedules = sorted(schedules, key=lambda x: x.scheduled_time) - - for i in range(len(sorted_schedules) - 1): - current = sorted_schedules[i] - next_schedule = sorted_schedules[i + 1] - - # Check if schedules are too close - time_diff = (next_schedule.scheduled_time - current.scheduled_time).total_seconds() / 60 - - if time_diff < time_window: - conflicts.append({ - 'schedule_1': current.id, - 'schedule_2': next_schedule.id, - 'time_1': current.scheduled_time, - 'time_2': next_schedule.scheduled_time, - 'gap_minutes': time_diff, - 'severity': 'high' if time_diff < 30 else 'medium' - }) - - return conflicts - - except Exception as e: - self.logger.error(f"Error finding conflicts: {str(e)}") - return [] - - def suggest_optimal_times( - self, - existing_schedules: List[Schedule], - target_date: datetime, - duration_hours: int = 1 - ) -> List[datetime]: - """Suggest optimal times for new content based on existing schedules. - - Args: - existing_schedules: List of existing Schedule objects - target_date: Target date for new content - duration_hours: Expected duration of content in hours - - Returns: - List of suggested optimal times - """ - try: - suggestions = [] - - # Get schedules for target date - target_schedules = [ - s for s in existing_schedules - if s.scheduled_time.date() == target_date.date() - ] - - # Define business hours (9 AM to 6 PM) - business_start = target_date.replace(hour=9, minute=0, second=0, microsecond=0) - business_end = target_date.replace(hour=18, minute=0, second=0, microsecond=0) - - # Generate potential time slots (every 30 minutes) - current_time = business_start - while current_time < business_end: - # Check if this slot conflicts with existing schedules - conflict = False - for schedule in target_schedules: - schedule_end = schedule.scheduled_time + timedelta(hours=duration_hours) - slot_end = current_time + timedelta(hours=duration_hours) - - # Check for overlap - if (current_time < schedule_end and slot_end > schedule.scheduled_time): - conflict = True - break - - if not conflict: - suggestions.append(current_time) - - current_time += timedelta(minutes=30) - - return suggestions[:5] # Return top 5 suggestions - - except Exception as e: - self.logger.error(f"Error suggesting optimal times: {str(e)}") - return [] \ No newline at end of file diff --git a/ToBeMigrated/content_scheduler/utils/validation.py b/ToBeMigrated/content_scheduler/utils/validation.py deleted file mode 100644 index 3574c1af..00000000 --- a/ToBeMigrated/content_scheduler/utils/validation.py +++ /dev/null @@ -1,162 +0,0 @@ -from typing import Dict, Any, List, Optional -from datetime import datetime, timedelta -import pytz -from .error_handling import ScheduleValidationError - -def validate_schedule_data(schedule_data: Dict[str, Any]) -> None: - """Validate schedule data before creation.""" - required_fields = ['content_id', 'schedule_type', 'platforms', 'publish_date'] - missing_fields = [field for field in required_fields if field not in schedule_data] - - if missing_fields: - raise ScheduleValidationError( - f"Missing required fields: {', '.join(missing_fields)}", - {'missing_fields': missing_fields} - ) - - validate_schedule_type(schedule_data['schedule_type']) - validate_platforms(schedule_data['platforms']) - validate_publish_date(schedule_data['publish_date']) - - if 'recurrence' in schedule_data: - validate_recurrence(schedule_data['recurrence']) - -def validate_schedule_type(schedule_type: str) -> None: - """Validate schedule type.""" - valid_types = ['ONE_TIME', 'RECURRING', 'BATCH'] - if schedule_type not in valid_types: - raise ScheduleValidationError( - f"Invalid schedule type: {schedule_type}", - {'valid_types': valid_types} - ) - -def validate_platforms(platforms: List[str]) -> None: - """Validate platform list.""" - valid_platforms = ['TWITTER', 'FACEBOOK', 'LINKEDIN', 'INSTAGRAM'] - invalid_platforms = [p for p in platforms if p not in valid_platforms] - - if invalid_platforms: - raise ScheduleValidationError( - f"Invalid platforms: {', '.join(invalid_platforms)}", - {'valid_platforms': valid_platforms} - ) - - if not platforms: - raise ScheduleValidationError( - "At least one platform must be specified", - {'valid_platforms': valid_platforms} - ) - -def validate_publish_date(publish_date: datetime) -> None: - """Validate publish date.""" - if not isinstance(publish_date, datetime): - raise ScheduleValidationError( - "Publish date must be a datetime object", - {'type': type(publish_date).__name__} - ) - - if publish_date.tzinfo is None: - raise ScheduleValidationError( - "Publish date must be timezone-aware", - {'date': str(publish_date)} - ) - - if publish_date < datetime.now(pytz.UTC): - raise ScheduleValidationError( - "Publish date must be in the future", - {'date': str(publish_date)} - ) - -def validate_recurrence(recurrence: Dict[str, Any]) -> None: - """Validate recurrence settings.""" - required_fields = ['frequency', 'interval'] - missing_fields = [field for field in required_fields if field not in recurrence] - - if missing_fields: - raise ScheduleValidationError( - f"Missing required recurrence fields: {', '.join(missing_fields)}", - {'missing_fields': missing_fields} - ) - - valid_frequencies = ['DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'] - if recurrence['frequency'] not in valid_frequencies: - raise ScheduleValidationError( - f"Invalid recurrence frequency: {recurrence['frequency']}", - {'valid_frequencies': valid_frequencies} - ) - - if not isinstance(recurrence['interval'], int) or recurrence['interval'] < 1: - raise ScheduleValidationError( - "Recurrence interval must be a positive integer", - {'interval': recurrence['interval']} - ) - - if 'end_date' in recurrence: - if not isinstance(recurrence['end_date'], datetime): - raise ScheduleValidationError( - "End date must be a datetime object", - {'type': type(recurrence['end_date']).__name__} - ) - - if recurrence['end_date'].tzinfo is None: - raise ScheduleValidationError( - "End date must be timezone-aware", - {'date': str(recurrence['end_date'])} - ) - -def validate_job_data(job_data: Dict[str, Any]) -> None: - """Validate job data before creation.""" - required_fields = ['content_id', 'schedule_id', 'platform'] - missing_fields = [field for field in required_fields if field not in job_data] - - if missing_fields: - raise ScheduleValidationError( - f"Missing required job fields: {', '.join(missing_fields)}", - {'missing_fields': missing_fields} - ) - - validate_platforms([job_data['platform']]) - -def validate_retry_settings(retry_settings: Optional[Dict[str, Any]]) -> None: - """Validate retry settings.""" - if retry_settings is None: - return - - if 'max_retries' in retry_settings: - if not isinstance(retry_settings['max_retries'], int) or retry_settings['max_retries'] < 0: - raise ScheduleValidationError( - "Max retries must be a non-negative integer", - {'max_retries': retry_settings['max_retries']} - ) - - if 'retry_delay' in retry_settings: - if not isinstance(retry_settings['retry_delay'], (int, float)) or retry_settings['retry_delay'] < 0: - raise ScheduleValidationError( - "Retry delay must be a non-negative number", - {'retry_delay': retry_settings['retry_delay']} - ) - -def validate_notification_settings(notification_settings: Optional[Dict[str, Any]]) -> None: - """Validate notification settings.""" - if notification_settings is None: - return - - if 'channels' in notification_settings: - valid_channels = ['EMAIL', 'SLACK', 'WEBHOOK'] - invalid_channels = [c for c in notification_settings['channels'] if c not in valid_channels] - - if invalid_channels: - raise ScheduleValidationError( - f"Invalid notification channels: {', '.join(invalid_channels)}", - {'valid_channels': valid_channels} - ) - - if 'events' in notification_settings: - valid_events = ['ON_SUCCESS', 'ON_FAILURE', 'ON_RETRY', 'ON_CANCELLATION'] - invalid_events = [e for e in notification_settings['events'] if e not in valid_events] - - if invalid_events: - raise ScheduleValidationError( - f"Invalid notification events: {', '.join(invalid_events)}", - {'valid_events': valid_events} - ) \ No newline at end of file diff --git a/backend/api/podcast/handlers/images.py b/backend/api/podcast/handlers/images.py index 80090483..5c3c9d91 100644 --- a/backend/api/podcast/handlers/images.py +++ b/backend/api/podcast/handlers/images.py @@ -14,7 +14,7 @@ import uuid from services.database import get_db from middleware.auth_middleware import get_current_user, get_current_user_with_query_token from api.story_writer.utils.auth import require_authenticated_user -from services.llm_providers.main_image_generation import generate_image +from services.llm_providers.main_image_generation import generate_image, generate_character_image from utils.asset_tracker import save_asset_to_library from loguru import logger from ..constants import PODCAST_IMAGES_DIR @@ -139,10 +139,7 @@ async def generate_podcast_scene_image( logger.info(f"[Podcast] Using Ideogram Character for scene {request.scene_id} with base avatar") logger.info(f"[Podcast] Scene prompt: {image_prompt[:150]}...") - # Use Ideogram Character API via WaveSpeed client - from services.wavespeed.client import WaveSpeedClient - wavespeed_client = WaveSpeedClient() - + # Use centralized character image generation with subscription checks and tracking # Use custom settings if provided, otherwise use defaults style = request.style or "Realistic" # Default to Realistic for professional podcast presenters rendering_speed = request.rendering_speed or "Quality" # Default to Quality for podcast videos @@ -163,9 +160,10 @@ async def generate_podcast_scene_image( logger.info(f"[Podcast] Ideogram Character settings: style={style}, rendering_speed={rendering_speed}, aspect_ratio={aspect_ratio}") try: - image_bytes = wavespeed_client.generate_character_image( + image_bytes = generate_character_image( prompt=image_prompt, reference_image_bytes=base_avatar_bytes, + user_id=user_id, style=style, aspect_ratio=aspect_ratio, rendering_speed=rendering_speed, @@ -308,39 +306,9 @@ async def generate_podcast_scene_image( # Create image URL (served via API endpoint) image_url = f"/api/podcast/images/{image_filename}" - # Estimate cost (rough estimate: ~$0.04 per image for most providers, ~$0.08 for Ideogram Character Quality) - cost = 0.08 if result.provider == "wavespeed" and result.model == "ideogram-ai/ideogram-character" else 0.04 - - # TRACK USAGE after successful image generation - try: - from models.subscription_models import UsageSummary, APIProvider - from sqlalchemy import text as sql_text - from datetime import datetime - - current_period = pricing_service.get_current_billing_period(user_id) or datetime.now().strftime("%Y-%m") - - # Update stability_calls and stability_cost (used for all image generation) - # Note: stability_calls is used for all image generation providers, not just Stability AI - update_query = sql_text(""" - UPDATE usage_summaries - SET stability_calls = COALESCE(stability_calls, 0) + 1, - stability_cost = COALESCE(stability_cost, 0) + :cost, - total_calls = COALESCE(total_calls, 0) + 1, - total_cost = COALESCE(total_cost, 0) + :cost - WHERE user_id = :user_id AND billing_period = :period - """) - db.execute(update_query, { - 'cost': cost, - 'user_id': user_id, - 'period': current_period - }) - db.commit() - - logger.info(f"[Podcast] โœ… Tracked image generation usage: user={user_id}, cost=${cost:.4f}, provider={result.provider}") - except Exception as usage_error: - logger.error(f"[Podcast] Failed to track image generation usage: {usage_error}") - db.rollback() - # Don't fail the request if usage tracking fails + # Estimate cost (rough estimate: ~$0.04 per image for most providers, ~$0.10 for Ideogram Character) + # Note: Actual usage tracking is handled by centralized generate_image()/generate_character_image() functions + cost = 0.10 if result.provider == "wavespeed" and result.model == "ideogram-ai/ideogram-character" else 0.04 # Save to asset library try: diff --git a/backend/api/youtube/handlers/audio.py b/backend/api/youtube/handlers/audio.py new file mode 100644 index 00000000..2efdf516 --- /dev/null +++ b/backend/api/youtube/handlers/audio.py @@ -0,0 +1,376 @@ +"""YouTube Creator scene audio generation handlers.""" + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import FileResponse +from sqlalchemy.orm import Session +from typing import Dict, Any, Optional +from pydantic import BaseModel + +from services.database import get_db +from middleware.auth_middleware import get_current_user, get_current_user_with_query_token +from api.story_writer.utils.auth import require_authenticated_user +from utils.asset_tracker import save_asset_to_library +from models.story_models import StoryAudioResult +from services.story_writer.audio_generation_service import StoryAudioGenerationService +from pathlib import Path +from utils.logger_utils import get_service_logger + +router = APIRouter(tags=["youtube-audio"]) +logger = get_service_logger("api.youtube.audio") + +# Audio output directory +base_dir = Path(__file__).parent.parent.parent.parent +YOUTUBE_AUDIO_DIR = base_dir / "youtube_audio" +YOUTUBE_AUDIO_DIR.mkdir(parents=True, exist_ok=True) + +# Initialize audio service +audio_service = StoryAudioGenerationService(output_dir=str(YOUTUBE_AUDIO_DIR)) + + +def select_optimal_emotion(scene_title: str, narration: str, video_plan_context: Optional[Dict[str, Any]] = None) -> str: + """ + Intelligently select the best emotion for YouTube content based on scene analysis. + + Available emotions: "happy", "sad", "angry", "fearful", "disgusted", "surprised", "neutral" + + Returns the selected emotion string. + """ + # Default to happy for engaging YouTube content + selected_emotion = "happy" + + scene_text = f"{scene_title} {narration}".lower() + + # Hook scenes need excitement and energy + if "hook" in scene_title.lower() or any(word in scene_text for word in ["exciting", "amazing", "unbelievable", "shocking", "wow"]): + selected_emotion = "surprised" # Excited and attention-grabbing + + # Emotional stories or inspirational content + elif any(word in scene_text for word in ["emotional", "touching", "heartwarming", "inspiring", "motivational"]): + selected_emotion = "happy" # Warm and uplifting + + # Serious or professional content + elif any(word in scene_text for word in ["important", "critical", "serious", "professional", "expert"]): + selected_emotion = "neutral" # Professional and serious + + # Problem-solving or tutorial content + elif any(word in scene_text for word in ["problem", "solution", "fix", "help", "guide"]): + selected_emotion = "happy" # Helpful and encouraging + + # Call-to-action scenes + elif "cta" in scene_title.lower() or any(word in scene_text for word in ["subscribe", "like", "comment", "share", "action"]): + selected_emotion = "happy" # Confident and encouraging + + # Negative or concerning topics + elif any(word in scene_text for word in ["warning", "danger", "risk", "problem", "issue"]): + selected_emotion = "neutral" # Serious but not alarming + + # Check video plan context for overall tone + if video_plan_context: + tone = video_plan_context.get("tone", "").lower() + if "serious" in tone or "professional" in tone: + selected_emotion = "neutral" + elif "fun" in tone or "entertaining" in tone: + selected_emotion = "happy" + + return selected_emotion + + +def select_optimal_voice(scene_title: str, narration: str, video_plan_context: Optional[Dict[str, Any]] = None) -> str: + """ + Intelligently select the best voice for YouTube content based on scene analysis. + + Analyzes scene title, narration content, and video plan context to choose + the most appropriate voice from available Minimax voices. + + Available voices: Wise_Woman, Friendly_Person, Inspirational_girl, Deep_Voice_Man, + Calm_Woman, Casual_Guy, Lively_Girl, Patient_Man, Young_Knight, Determined_Man, + Lovely_Girl, Decent_Boy, Imposing_Manner, Elegant_Man, Abbess, Sweet_Girl_2, Exuberant_Girl + + Returns the selected voice_id string. + """ + # Default to Casual_Guy for engaging YouTube content + selected_voice = "Casual_Guy" + + # Analyze video plan context for content type + if video_plan_context: + video_type = video_plan_context.get("video_type", "").lower() + target_audience = video_plan_context.get("target_audience", "").lower() + tone = video_plan_context.get("tone", "").lower() + + # Educational/Professional content + if any(keyword in video_type for keyword in ["tutorial", "educational", "how-to", "guide", "course"]): + if "professional" in tone or "expert" in target_audience: + selected_voice = "Wise_Woman" # Authoritative and trustworthy + else: + selected_voice = "Patient_Man" # Clear and instructional + + # Entertainment/Casual content + elif any(keyword in video_type for keyword in ["entertainment", "vlog", "lifestyle", "story", "review"]): + if "young" in target_audience or "millennial" in target_audience: + selected_voice = "Casual_Guy" # Friendly and relatable + elif "female" in target_audience or "women" in target_audience: + selected_voice = "Lively_Girl" # Energetic and engaging + else: + selected_voice = "Friendly_Person" # Approachable + + # Motivational/Inspirational content + elif any(keyword in video_type for keyword in ["motivational", "inspirational", "success", "mindset"]): + selected_voice = "Inspirational_girl" # Uplifting and motivational + + # Business/Corporate content + elif any(keyword in video_type for keyword in ["business", "corporate", "finance", "marketing"]): + selected_voice = "Elegant_Man" # Professional and sophisticated + + # Tech/Gaming content + elif any(keyword in video_type for keyword in ["tech", "gaming", "software", "app"]): + selected_voice = "Young_Knight" # Energetic and modern + + # Analyze scene content for specific voice requirements + scene_text = f"{scene_title} {narration}".lower() + + # Hook scenes need energetic, attention-grabbing voices + if "hook" in scene_title.lower() or any(word in scene_text for word in ["attention", "grab", "exciting", "amazing", "unbelievable"]): + selected_voice = "Exuberant_Girl" # Very energetic and enthusiastic + + # Emotional/stories need more expressive voices + elif any(word in scene_text for word in ["story", "emotional", "heartwarming", "touching", "inspiring"]): + selected_voice = "Inspirational_girl" # Emotional and inspiring + + # Technical explanations need clear, precise voices + elif any(word in scene_text for word in ["technical", "explain", "step-by-step", "process", "how-to"]): + selected_voice = "Calm_Woman" # Clear and methodical + + # Call-to-action scenes need confident, persuasive voices + elif "cta" in scene_title.lower() or any(word in scene_text for word in ["subscribe", "like", "comment", "share", "now", "today"]): + selected_voice = "Determined_Man" # Confident and persuasive + + logger.info(f"[VoiceSelection] Selected '{selected_voice}' for scene: {scene_title[:50]}...") + return selected_voice + + +class YouTubeAudioRequest(BaseModel): + scene_id: str + scene_title: str + text: str + voice_id: Optional[str] = None # Will auto-select based on content if not provided + speed: float = 1.0 + volume: float = 1.0 + pitch: float = 0.0 + emotion: str = "happy" # More engaging for YouTube content + english_normalization: bool = False + # Enhanced defaults for high-quality YouTube audio using Minimax Speech 02 HD + # Higher quality settings for professional YouTube content + sample_rate: Optional[int] = 44100 # CD quality: 44100 Hz (valid values: 8000, 16000, 22050, 24000, 32000, 44100) + bitrate: int = 256000 # Highest quality: 256kbps (valid values: 32000, 64000, 128000, 256000) + channel: Optional[str] = "2" # Stereo for richer audio (valid values: "1" or "2") + format: Optional[str] = "mp3" # Universal format for web + language_boost: Optional[str] = "English" # Optimize for English content + enable_sync_mode: bool = True + # Context for intelligent voice/emotion selection + video_plan_context: Optional[Dict[str, Any]] = None # Optional video plan for context-aware voice selection + + +class YouTubeAudioResponse(BaseModel): + scene_id: str + scene_title: str + audio_filename: str + audio_url: str + provider: str + model: str + voice_id: str + text_length: int + file_size: int + cost: float + + +@router.post("/audio", response_model=YouTubeAudioResponse) +async def generate_youtube_scene_audio( + request: YouTubeAudioRequest, + current_user: Dict[str, Any] = Depends(get_current_user), + db: Session = Depends(get_db), +): + """ + Generate AI audio for a YouTube scene using shared audio service. + Similar to Podcast's audio generation endpoint. + """ + user_id = require_authenticated_user(current_user) + + if not request.text or not request.text.strip(): + raise HTTPException(status_code=400, detail="Text is required") + + try: + # Preprocess text to remove instructional markers that shouldn't be spoken + # Remove patterns like [Pacing: slow], [Instructions: ...], etc. + import re + processed_text = request.text.strip() + + # Remove instructional markers that contain pacing, timing, or other non-spoken content + instructional_patterns = [ + r'\[Pacing:\s*[^\]]+\]', # [Pacing: slow] + r'\[Instructions?:\s*[^\]]+\]', # [Instructions: ...] + r'\[Timing:\s*[^\]]+\]', # [Timing: ...] + r'\[Note:\s*[^\]]+\]', # [Note: ...] + r'\[Internal:\s*[^\]]+\]', # [Internal: ...] + ] + + for pattern in instructional_patterns: + processed_text = re.sub(pattern, '', processed_text, flags=re.IGNORECASE) + + # Clean up extra whitespace and normalize + processed_text = re.sub(r'\s+', ' ', processed_text).strip() + + if not processed_text: + raise HTTPException(status_code=400, detail="Text became empty after removing instructions. Please provide clean narration text.") + + logger.info(f"[YouTubeAudio] Text preprocessing: {len(request.text)} -> {len(processed_text)} characters") + + # Intelligent voice and emotion selection based on content analysis + if not request.voice_id: + selected_voice = select_optimal_voice( + request.scene_title, + processed_text, + request.video_plan_context + ) + else: + selected_voice = request.voice_id + + # Auto-select emotion if not specified or if using defaults + if request.emotion == "happy": # This means it wasn't specifically set by user + selected_emotion = select_optimal_emotion( + request.scene_title, + processed_text, + request.video_plan_context + ) + else: + selected_emotion = request.emotion + + logger.info(f"[YouTubeAudio] Voice selection: {selected_voice}, Emotion: {selected_emotion}") + + # Build kwargs for optional parameters - use defaults if None + # WaveSpeed API requires specific values, so we provide sensible defaults + # This matches Podcast's approach but with explicit defaults to avoid None errors + optional_kwargs = {} + + # DEBUG: Log what values we received + logger.info(f"[YouTubeAudio] Request parameters: sample_rate={request.sample_rate}, bitrate={request.bitrate}, channel={request.channel}, format={request.format}, language_boost={request.language_boost}") + + # sample_rate: Use provided value or omit (WaveSpeed will use default) + if request.sample_rate is not None: + optional_kwargs["sample_rate"] = request.sample_rate + + # bitrate: Always provide a value (default: 128000 = 128kbps) + # Valid values: 32000, 64000, 128000, 256000 + # Model already has default of 128000, so request.bitrate will never be None + optional_kwargs["bitrate"] = request.bitrate + + # channel: Only include if valid (WaveSpeed only accepts "1" or "2" as strings) + # If None, empty string, or invalid, omit it and WaveSpeed will use default + # NEVER include channel if it's not exactly "1" or "2" + if request.channel is not None and str(request.channel).strip() in ["1", "2"]: + optional_kwargs["channel"] = str(request.channel).strip() + logger.info(f"[YouTubeAudio] Including valid channel: {optional_kwargs['channel']}") + else: + logger.info(f"[YouTubeAudio] Omitting invalid channel: {request.channel}") + + # format: Use provided value or omit (WaveSpeed will use default) + if request.format is not None: + optional_kwargs["format"] = request.format + + # language_boost: Use provided value or omit (WaveSpeed will use default) + if request.language_boost is not None: + optional_kwargs["language_boost"] = request.language_boost + + logger.info(f"[YouTubeAudio] Final optional_kwargs: {optional_kwargs}") + + result: StoryAudioResult = audio_service.generate_ai_audio( + scene_number=0, + scene_title=request.scene_title, + text=processed_text, + user_id=user_id, + voice_id=selected_voice, + speed=request.speed or 1.0, + volume=request.volume or 1.0, + pitch=request.pitch or 0.0, + emotion=selected_emotion, + english_normalization=request.english_normalization or False, + enable_sync_mode=request.enable_sync_mode, + **optional_kwargs, + ) + + # Override URL to use YouTube endpoint instead of story endpoint + if result.get("audio_url") and "/api/story/audio/" in result.get("audio_url", ""): + audio_filename = result.get("audio_filename", "") + result["audio_url"] = f"/api/youtube/audio/{audio_filename}" + except Exception as exc: + logger.error(f"[YouTube] Audio generation failed: {exc}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Audio generation failed: {exc}") + + # Save to asset library (youtube_creator module) + try: + if result.get("audio_url"): + save_asset_to_library( + db=db, + user_id=user_id, + asset_type="audio", + source_module="youtube_creator", + filename=result.get("audio_filename", ""), + file_url=result.get("audio_url", ""), + file_path=result.get("audio_path"), + file_size=result.get("file_size"), + mime_type="audio/mpeg", + title=f"{request.scene_title} - YouTube", + description="YouTube scene narration", + tags=["youtube_creator", "audio", request.scene_id], + provider=result.get("provider"), + model=result.get("model"), + cost=result.get("cost"), + asset_metadata={ + "scene_id": request.scene_id, + "scene_title": request.scene_title, + "status": "completed", + }, + ) + except Exception as e: + logger.warning(f"[YouTube] Failed to save audio asset: {e}") + + return YouTubeAudioResponse( + scene_id=request.scene_id, + scene_title=request.scene_title, + audio_filename=result.get("audio_filename", ""), + audio_url=result.get("audio_url", ""), + provider=result.get("provider", "wavespeed"), + model=result.get("model", "minimax/speech-02-hd"), + voice_id=result.get("voice_id", selected_voice), + text_length=result.get("text_length", len(request.text)), + file_size=result.get("file_size", 0), + cost=result.get("cost", 0.0), + ) + + +@router.get("/audio/{filename}") +async def serve_youtube_audio( + filename: str, + current_user: Dict[str, Any] = Depends(get_current_user_with_query_token), +): + """Serve generated YouTube scene audio files. + + Supports authentication via Authorization header or token query parameter. + Query parameter is useful for HTML elements like