From bf7493c3661ce972ef02d24fee0f9b6d02e62bf0 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Mon, 17 Nov 2025 17:38:23 +0530 Subject: [PATCH] AI Video Generation Implementation --- .github/README.md | 6 +- .gitignore | 3 +- backend/api/images.py | 164 ++++ backend/api/story_writer/router.py | 255 +++-- backend/api/story_writer/utils/__init__.py | 8 + backend/api/story_writer/utils/auth.py | 23 + backend/api/story_writer/utils/hd_video.py | 154 +++ backend/api/story_writer/utils/media_utils.py | 69 ++ backend/api/subscription_api.py | 125 ++- backend/logging_config.py | 11 + backend/models/subscription_models.py | 8 + backend/requirements.txt | 4 +- backend/scripts/update_image_edit_limits.py | 102 ++ .../llm_providers/main_image_editing.py | 165 ++++ .../llm_providers/main_text_generation.py | 19 + .../llm_providers/main_video_generation.py | 355 +++++++ .../story_writer/prompt_enhancer_service.py | 352 +++++++ .../story_writer/video_generation_service.py | 57 +- .../services/story_writer/video_preflight.py | 46 + .../services/subscription/limit_validation.py | 38 + .../subscription/preflight_validator.py | 121 +++ .../services/subscription/pricing_service.py | 38 +- backend/services/subscription/schema_utils.py | 6 + .../subscription/usage_tracking_service.py | 9 + backend/start_alwrity_backend.py | 20 + .../docs/features/ai/assistive-writing.md | 93 +- docs-site/docs/features/ai/grounding-ui.md | 41 +- .../docs/features/integrations/wix/api.md | 58 ++ .../features/integrations/wix/overview.md | 33 + .../features/integrations/wix/publishing.md | 28 + .../features/integrations/wix/seo-metadata.md | 52 + .../docs/features/integrations/wix/setup.md | 40 + .../integrations/wix/testing-bypass.md | 34 + .../features/subscription/api-reference.md | 445 +++++++++ .../subscription/frontend-integration.md | 339 +++++++ .../subscription/implementation-status.md | 143 +-- .../docs/features/subscription/overview.md | 157 +++ .../docs/features/subscription/pricing.md | 98 ++ .../docs/features/subscription/roadmap.md | 232 +++++ docs-site/docs/features/subscription/setup.md | 241 +++++ docs-site/mkdocs.yml | 16 + docs/AI_BLOG_WRITER_IMPLEMENTATION_SPEC.md | 427 -------- .../ALPHA_SUBSCRIPTION_IMPLEMENTATION_PLAN.md | 207 ---- docs/ALPHA_TESTING_SETUP_COMPLETE.md | 291 ------ docs/API_KEY_FLOW_DIAGRAM.md | 370 ------- docs/API_KEY_INJECTION_EXPLAINED.md | 326 ------- docs/ASSISTIVE_WRITING_QUICK_REFERENCE.md | 42 - docs/ASSISTIVE_WRITING_USER_GUIDE.md | 151 --- docs/ASSISTIVE_WRITING_WORKFLOW.md | 131 --- .../BILLING_FRONTEND_INTEGRATION_PLAN.md | 347 ------- .../BILLING_IMPLEMENTATION_ROADMAP.md | 374 ------- .../BILLING_TECHNICAL_SPECIFICATION.md | 515 ---------- .../HUGGINGFACE_PRICING.md | 103 -- .../SUBSCRIPTION_IMPLEMENTATION_SUMMARY.md | 268 ----- docs/CONTENT_GENERATOR_REFACTORING.md | 399 -------- .../CRITICAL_ONBOARDING_DATABASE_MIGRATION.md | 264 ----- docs/EXAMPLES_USER_API_KEYS.md | 489 ---------- docs/GOOGLE_GROUNDING_UI_IMPLEMENTATION.md | 123 --- ...INATION_DETECTOR_IMPLEMENTATION_SUMMARY.md | 215 ----- docs/HALLUCINATION_DETECTOR_SETUP.md | 250 ----- docs/IMPLEMENTATION_SUMMARY_OCT_1_2025.md | 460 --------- docs/NEXT_QUICK_WINS_SUGGESTIONS.md | 348 ------- docs/ONBOARDING_CONTEXT_IMPLEMENTATION.md | 912 ------------------ docs/ONBOARDING_DATA_PERSISTENCE_FIX.md | 318 ------ docs/ONBOARDING_STEP_4_IMPLEMENTATION_PLAN.md | 373 ------- docs/ONBOARDING_SYSTEM_COMPLETE.md | 136 --- docs/PERSONA_DATA_MIGRATION_GUIDE.md | 215 ----- docs/PHASE2_QUICK_WINS_IMPLEMENTED.md | 280 ------ docs/PLATFORM_SPECIFIC_EDITOR_ARCHITECTURE.md | 429 -------- docs/REMAINING_SESSION_ID_ISSUES.md | 105 -- docs/STEP3_USER_ISOLATION_FIX.md | 255 ----- docs/STEP_2_BACKWARD_COMPATIBLE_FIX.md | 67 -- docs/STEP_2_COLUMN_ERROR_FIX.md | 63 -- docs/STEP_2_COMPLETE_DATA_FLOW_ANALYSIS.md | 435 --------- docs/STEP_2_DUAL_PERSISTENCE_ISSUE_AND_FIX.md | 170 ---- docs/STEP_2_REVERT_SUMMARY.md | 99 -- docs/STEP_2_SQLALCHEMY_CACHE_FIX.md | 84 -- ...EBSITE_ANALYSIS_DATA_TRANSFORMATION_FIX.md | 188 ---- docs/STEP_6_DATABASE_MIGRATION_COMPLETE.md | 273 ------ docs/STORY_GENERATION_READINESS_ASSESSMENT.md | 157 --- ...STORY_WRITER_BACKEND_MIGRATION_COMPLETE.md | 137 --- docs/STORY_WRITER_NEXT_STEPS.md | 312 ------ docs/STORY_WRITER_REVIEW_AND_NEXT_STEPS.md | 436 --------- docs/STORY_WRITER_TESTING_GUIDE.md | 424 -------- docs/SUBSCRIPTION_DOCS_UPDATE_PLAN.md | 222 +++++ docs/SUBSCRIPTION_SYSTEM_README.md | 372 ------- docs/WIX_INTEGRATION_README.md | 300 ------ docs/WIX_INTEGRATION_SUMMARY.md | 188 ---- docs/WIX_SEO_METADATA_COMPLETE.md | 150 --- docs/WIX_SEO_METADATA_REVIEW.md | 102 -- docs/WIX_TESTING_BYPASS_GUIDE.md | 95 -- .../PHASE1_IMPLEMENTATION_SUMMARY.md | 280 ------ .../test_ai_integration.py | 145 --- .../test_ai_service_debug.py | 127 --- .../test_api_database_integration.py | 512 ---------- .../test_database_integration.py | 637 ------------ .../test_endpoint_fixes.py | 118 --- .../test_enhanced_strategy_phase1.py | 589 ----------- docs/alwrity_test_scripts/test_env_check.py | 142 --- .../test_final_ai_integration.py | 154 --- docs/alwrity_test_scripts/test_fixes.py | 95 -- .../alwrity_test_scripts/test_gemini_debug.py | 104 -- docs/alwrity_test_scripts/test_gemini_fix.py | 119 --- docs/alwrity_test_scripts/test_gemini_real.py | 86 -- .../test_gemini_structure.py | 159 --- docs/alwrity_test_scripts/test_imports.py | 55 -- .../test_json_compatibility.py | 135 --- .../test_onboarding_data.py | 463 --------- .../test_phase2_ai_integration.py | 202 ---- .../test_phase3_ai_optimization.py | 263 ----- .../test_phase4_ai_service_integration.py | 330 ------- .../alwrity_test_scripts/test_schema_fixes.py | 173 ---- .../test_service_integration.py | 435 --------- .../test_structured_output.py | 121 --- docs/debug_wix_oauth.py | 67 -- .../StoryWriter/Phases/StoryExport.tsx | 447 ++++++++- .../StoryWriter/Phases/StoryOutline.tsx | 24 +- .../components/StoryWriter/StoryWriter.tsx | 22 +- .../components/AudioPlayerList.tsx | 104 ++ .../StoryWriter/components/AudioSection.tsx | 177 ++++ .../StoryWriter/components/HdVideoSection.tsx | 430 +++++++++ .../components/MultimediaSection.tsx | 524 +--------- .../StoryWriter/components/SceneSelection.tsx | 119 +++ .../components/SceneVideoApproval.tsx | 216 +++++ .../StoryWriter/components/VideoSection.tsx | 260 +++++ .../billing/ComprehensiveAPIBreakdown.tsx | 9 +- frontend/src/hooks/useStoryWriterState.ts | 23 + frontend/src/services/billingService.ts | 2 + frontend/src/services/storyWriterApi.ts | 98 ++ frontend/src/types/billing.ts | 4 + frontend/src/utils/fetchMediaBlobUrl.ts | 9 + scripts/wix_reconsent_helper.py | 91 -- 132 files changed, 6200 insertions(+), 19475 deletions(-) create mode 100644 backend/api/story_writer/utils/__init__.py create mode 100644 backend/api/story_writer/utils/auth.py create mode 100644 backend/api/story_writer/utils/hd_video.py create mode 100644 backend/api/story_writer/utils/media_utils.py create mode 100644 backend/scripts/update_image_edit_limits.py create mode 100644 backend/services/llm_providers/main_image_editing.py create mode 100644 backend/services/llm_providers/main_video_generation.py create mode 100644 backend/services/story_writer/prompt_enhancer_service.py create mode 100644 backend/services/story_writer/video_preflight.py create mode 100644 docs-site/docs/features/integrations/wix/api.md create mode 100644 docs-site/docs/features/integrations/wix/overview.md create mode 100644 docs-site/docs/features/integrations/wix/publishing.md create mode 100644 docs-site/docs/features/integrations/wix/seo-metadata.md create mode 100644 docs-site/docs/features/integrations/wix/setup.md create mode 100644 docs-site/docs/features/integrations/wix/testing-bypass.md create mode 100644 docs-site/docs/features/subscription/api-reference.md create mode 100644 docs-site/docs/features/subscription/frontend-integration.md rename docs/Billing_Subscription/BILLING_IMPLEMENTATION_STATUS.md => docs-site/docs/features/subscription/implementation-status.md (80%) create mode 100644 docs-site/docs/features/subscription/overview.md create mode 100644 docs-site/docs/features/subscription/pricing.md create mode 100644 docs-site/docs/features/subscription/roadmap.md create mode 100644 docs-site/docs/features/subscription/setup.md delete mode 100644 docs/AI_BLOG_WRITER_IMPLEMENTATION_SPEC.md delete mode 100644 docs/ALPHA_SUBSCRIPTION_IMPLEMENTATION_PLAN.md delete mode 100644 docs/ALPHA_TESTING_SETUP_COMPLETE.md delete mode 100644 docs/API_KEY_FLOW_DIAGRAM.md delete mode 100644 docs/API_KEY_INJECTION_EXPLAINED.md delete mode 100644 docs/ASSISTIVE_WRITING_QUICK_REFERENCE.md delete mode 100644 docs/ASSISTIVE_WRITING_USER_GUIDE.md delete mode 100644 docs/ASSISTIVE_WRITING_WORKFLOW.md delete mode 100644 docs/Billing_Subscription/BILLING_FRONTEND_INTEGRATION_PLAN.md delete mode 100644 docs/Billing_Subscription/BILLING_IMPLEMENTATION_ROADMAP.md delete mode 100644 docs/Billing_Subscription/BILLING_TECHNICAL_SPECIFICATION.md delete mode 100644 docs/Billing_Subscription/HUGGINGFACE_PRICING.md delete mode 100644 docs/Billing_Subscription/SUBSCRIPTION_IMPLEMENTATION_SUMMARY.md delete mode 100644 docs/CONTENT_GENERATOR_REFACTORING.md delete mode 100644 docs/CRITICAL_ONBOARDING_DATABASE_MIGRATION.md delete mode 100644 docs/EXAMPLES_USER_API_KEYS.md delete mode 100644 docs/GOOGLE_GROUNDING_UI_IMPLEMENTATION.md delete mode 100644 docs/HALLUCINATION_DETECTOR_IMPLEMENTATION_SUMMARY.md delete mode 100644 docs/HALLUCINATION_DETECTOR_SETUP.md delete mode 100644 docs/IMPLEMENTATION_SUMMARY_OCT_1_2025.md delete mode 100644 docs/NEXT_QUICK_WINS_SUGGESTIONS.md delete mode 100644 docs/ONBOARDING_CONTEXT_IMPLEMENTATION.md delete mode 100644 docs/ONBOARDING_DATA_PERSISTENCE_FIX.md delete mode 100644 docs/ONBOARDING_STEP_4_IMPLEMENTATION_PLAN.md delete mode 100644 docs/ONBOARDING_SYSTEM_COMPLETE.md delete mode 100644 docs/PERSONA_DATA_MIGRATION_GUIDE.md delete mode 100644 docs/PHASE2_QUICK_WINS_IMPLEMENTED.md delete mode 100644 docs/PLATFORM_SPECIFIC_EDITOR_ARCHITECTURE.md delete mode 100644 docs/REMAINING_SESSION_ID_ISSUES.md delete mode 100644 docs/STEP3_USER_ISOLATION_FIX.md delete mode 100644 docs/STEP_2_BACKWARD_COMPATIBLE_FIX.md delete mode 100644 docs/STEP_2_COLUMN_ERROR_FIX.md delete mode 100644 docs/STEP_2_COMPLETE_DATA_FLOW_ANALYSIS.md delete mode 100644 docs/STEP_2_DUAL_PERSISTENCE_ISSUE_AND_FIX.md delete mode 100644 docs/STEP_2_REVERT_SUMMARY.md delete mode 100644 docs/STEP_2_SQLALCHEMY_CACHE_FIX.md delete mode 100644 docs/STEP_2_WEBSITE_ANALYSIS_DATA_TRANSFORMATION_FIX.md delete mode 100644 docs/STEP_6_DATABASE_MIGRATION_COMPLETE.md delete mode 100644 docs/STORY_GENERATION_READINESS_ASSESSMENT.md delete mode 100644 docs/STORY_WRITER_BACKEND_MIGRATION_COMPLETE.md delete mode 100644 docs/STORY_WRITER_NEXT_STEPS.md delete mode 100644 docs/STORY_WRITER_REVIEW_AND_NEXT_STEPS.md delete mode 100644 docs/STORY_WRITER_TESTING_GUIDE.md create mode 100644 docs/SUBSCRIPTION_DOCS_UPDATE_PLAN.md delete mode 100644 docs/SUBSCRIPTION_SYSTEM_README.md delete mode 100644 docs/WIX_INTEGRATION_README.md delete mode 100644 docs/WIX_INTEGRATION_SUMMARY.md delete mode 100644 docs/WIX_SEO_METADATA_COMPLETE.md delete mode 100644 docs/WIX_SEO_METADATA_REVIEW.md delete mode 100644 docs/WIX_TESTING_BYPASS_GUIDE.md delete mode 100644 docs/alwrity_test_scripts/PHASE1_IMPLEMENTATION_SUMMARY.md delete mode 100644 docs/alwrity_test_scripts/test_ai_integration.py delete mode 100644 docs/alwrity_test_scripts/test_ai_service_debug.py delete mode 100644 docs/alwrity_test_scripts/test_api_database_integration.py delete mode 100644 docs/alwrity_test_scripts/test_database_integration.py delete mode 100644 docs/alwrity_test_scripts/test_endpoint_fixes.py delete mode 100644 docs/alwrity_test_scripts/test_enhanced_strategy_phase1.py delete mode 100644 docs/alwrity_test_scripts/test_env_check.py delete mode 100644 docs/alwrity_test_scripts/test_final_ai_integration.py delete mode 100644 docs/alwrity_test_scripts/test_fixes.py delete mode 100644 docs/alwrity_test_scripts/test_gemini_debug.py delete mode 100644 docs/alwrity_test_scripts/test_gemini_fix.py delete mode 100644 docs/alwrity_test_scripts/test_gemini_real.py delete mode 100644 docs/alwrity_test_scripts/test_gemini_structure.py delete mode 100644 docs/alwrity_test_scripts/test_imports.py delete mode 100644 docs/alwrity_test_scripts/test_json_compatibility.py delete mode 100644 docs/alwrity_test_scripts/test_onboarding_data.py delete mode 100644 docs/alwrity_test_scripts/test_phase2_ai_integration.py delete mode 100644 docs/alwrity_test_scripts/test_phase3_ai_optimization.py delete mode 100644 docs/alwrity_test_scripts/test_phase4_ai_service_integration.py delete mode 100644 docs/alwrity_test_scripts/test_schema_fixes.py delete mode 100644 docs/alwrity_test_scripts/test_service_integration.py delete mode 100644 docs/alwrity_test_scripts/test_structured_output.py delete mode 100644 docs/debug_wix_oauth.py create mode 100644 frontend/src/components/StoryWriter/components/AudioPlayerList.tsx create mode 100644 frontend/src/components/StoryWriter/components/AudioSection.tsx create mode 100644 frontend/src/components/StoryWriter/components/HdVideoSection.tsx create mode 100644 frontend/src/components/StoryWriter/components/SceneSelection.tsx create mode 100644 frontend/src/components/StoryWriter/components/SceneVideoApproval.tsx create mode 100644 frontend/src/components/StoryWriter/components/VideoSection.tsx create mode 100644 frontend/src/utils/fetchMediaBlobUrl.ts delete mode 100644 scripts/wix_reconsent_helper.py diff --git a/.github/README.md b/.github/README.md index 02b5f43e..f9a24ed1 100644 --- a/.github/README.md +++ b/.github/README.md @@ -15,9 +15,9 @@

- ALwrity dashboard overview - Story Writer workflow - SEO dashboard insights + ALwrity dashboard overview + Story Writer workflow + SEO dashboard insights

--- diff --git a/.gitignore b/.gitignore index f6e1e32a..4ebc7107 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ backend/.onboarding_progress.json backend/database/migrations/* .cursorignore - +story_videos +story_videos/* # Environment .env .env.* diff --git a/backend/api/images.py b/backend/api/images.py index d6095ec3..433d0e52 100644 --- a/backend/api/images.py +++ b/backend/api/images.py @@ -9,6 +9,7 @@ from fastapi import APIRouter, HTTPException, Depends from pydantic import BaseModel, Field from services.llm_providers.main_image_generation import generate_image +from services.llm_providers.main_image_editing import edit_image from services.llm_providers.main_text_generation import llm_text_gen from utils.logger_utils import get_service_logger from middleware.auth_middleware import get_current_user @@ -125,6 +126,14 @@ def generate( tier = limits.get('tier', 'unknown') if limits else 'unknown' call_limit = limits['limits'].get("stability_calls", 0) if limits else 0 + # Get image editing stats for unified log + current_image_edit_calls = getattr(summary, "image_edit_calls", 0) or 0 + image_edit_limit = limits['limits'].get("image_edit_calls", 0) if limits else 0 + + # Get video stats for unified log + current_video_calls = getattr(summary, "video_calls", 0) or 0 + video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + db_track.commit() logger.info(f"[images.generate] ✅ Successfully tracked usage: user {user_id} -> stability -> {new_calls} calls") @@ -137,6 +146,8 @@ def generate( ├─ Actual Provider: {result.provider} ├─ Model: {result.model or 'default'} ├─ Calls: {current_calls_before} → {new_calls} / {call_limit if call_limit > 0 else '∞'} +├─ Image Editing: {current_image_edit_calls} / {image_edit_limit if image_edit_limit > 0 else '∞'} +├─ Videos: {current_video_calls} / {video_limit if video_limit > 0 else '∞'} └─ Status: ✅ Allowed & Tracked """) except Exception as track_error: @@ -195,6 +206,26 @@ class ImagePromptSuggestResponse(BaseModel): suggestions: list[PromptSuggestion] +class ImageEditRequest(BaseModel): + image_base64: str + prompt: str + provider: Optional[str] = Field(None, pattern="^(huggingface)$") + model: Optional[str] = None + guidance_scale: Optional[float] = None + steps: Optional[int] = None + seed: Optional[int] = None + + +class ImageEditResponse(BaseModel): + success: bool = True + image_base64: str + width: int + height: int + provider: str + model: Optional[str] = None + seed: Optional[int] = None + + @router.post("/suggest-prompts", response_model=ImagePromptSuggestResponse) def suggest_prompts( req: ImagePromptSuggestRequest, @@ -316,3 +347,136 @@ def suggest_prompts( logger.error(f"Prompt suggestion failed: {e}") raise HTTPException(status_code=500, detail=str(e)) + +@router.post("/edit", response_model=ImageEditResponse) +def edit( + req: ImageEditRequest, + current_user: Dict[str, Any] = Depends(get_current_user) +) -> ImageEditResponse: + """Edit image with subscription checking.""" + try: + # Extract Clerk user ID (required) + if not current_user: + raise HTTPException(status_code=401, detail="Authentication required") + + user_id = str(current_user.get('id', '')) + if not user_id: + raise HTTPException(status_code=401, detail="Invalid user ID in authentication token") + + # Decode base64 image + try: + input_image_bytes = base64.b64decode(req.image_base64) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Invalid image_base64: {str(e)}") + + # Validation is now handled inside edit_image function + result = edit_image( + input_image_bytes=input_image_bytes, + prompt=req.prompt, + options={ + "provider": req.provider, + "model": req.model, + "guidance_scale": req.guidance_scale, + "steps": req.steps, + "seed": req.seed, + }, + user_id=user_id, # Pass user_id for validation inside edit_image + ) + edited_image_b64 = base64.b64encode(result.image_bytes).decode("utf-8") + + # TRACK USAGE after successful image editing + if result: + logger.info(f"[images.edit] ✅ Image editing successful, tracking usage for user {user_id}") + try: + db_track = next(get_db()) + try: + # Get or create usage summary + pricing = PricingService(db_track) + current_period = pricing.get_current_billing_period(user_id) or datetime.now().strftime("%Y-%m") + + logger.debug(f"[images.edit] Looking for usage summary: user_id={user_id}, period={current_period}") + + summary = db_track.query(UsageSummary).filter( + UsageSummary.user_id == user_id, + UsageSummary.billing_period == current_period + ).first() + + if not summary: + logger.info(f"[images.edit] Creating new usage summary for user {user_id}, period {current_period}") + summary = UsageSummary( + user_id=user_id, + billing_period=current_period + ) + db_track.add(summary) + db_track.flush() # Ensure summary is persisted before updating + + # Get "before" state for unified log + current_calls_before = getattr(summary, "image_edit_calls", 0) or 0 + + # Update image editing counters (separate from image generation) + new_calls = current_calls_before + 1 + setattr(summary, "image_edit_calls", new_calls) + logger.debug(f"[images.edit] Updated image_edit_calls: {current_calls_before} -> {new_calls}") + + # Update totals + old_total_calls = summary.total_calls or 0 + summary.total_calls = old_total_calls + 1 + logger.debug(f"[images.edit] Updated totals: calls {old_total_calls} -> {summary.total_calls}") + + # Get plan details for unified log + limits = pricing.get_user_limits(user_id) + plan_name = limits.get('plan_name', 'unknown') if limits else 'unknown' + tier = limits.get('tier', 'unknown') if limits else 'unknown' + call_limit = limits['limits'].get("image_edit_calls", 0) if limits else 0 + + # Get image generation stats for unified log + current_image_gen_calls = getattr(summary, "stability_calls", 0) or 0 + image_gen_limit = limits['limits'].get("stability_calls", 0) if limits else 0 + + # Get video stats for unified log + current_video_calls = getattr(summary, "video_calls", 0) or 0 + video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + + db_track.commit() + logger.info(f"[images.edit] ✅ Successfully tracked usage: user {user_id} -> image_edit -> {new_calls} calls") + + # UNIFIED SUBSCRIPTION LOG - Shows before/after state in one message + print(f""" +[SUBSCRIPTION] Image Editing +├─ User: {user_id} +├─ Plan: {plan_name} ({tier}) +├─ Provider: image_edit +├─ Actual Provider: {result.provider} +├─ Model: {result.model or 'default'} +├─ Calls: {current_calls_before} → {new_calls} / {call_limit if call_limit > 0 else '∞'} +├─ Images: {current_image_gen_calls} / {image_gen_limit if image_gen_limit > 0 else '∞'} +├─ Videos: {current_video_calls} / {video_limit if video_limit > 0 else '∞'} +└─ Status: ✅ Allowed & Tracked +""") + except Exception as track_error: + logger.error(f"[images.edit] ❌ Error tracking usage (non-blocking): {track_error}", exc_info=True) + db_track.rollback() + finally: + db_track.close() + except Exception as usage_error: + # Non-blocking: log error but don't fail the request + logger.error(f"[images.edit] ❌ Failed to track usage: {usage_error}", exc_info=True) + + return ImageEditResponse( + image_base64=edited_image_b64, + width=result.width, + height=result.height, + provider=result.provider, + model=result.model, + seed=result.seed, + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"Image editing failed: {e}", exc_info=True) + # Provide a clean, actionable message to the client + raise HTTPException( + status_code=500, + detail="Image editing service is temporarily unavailable or the connection was reset. Please try again." + ) + diff --git a/backend/api/story_writer/router.py b/backend/api/story_writer/router.py index 6fcf1b8e..086a7b13 100644 --- a/backend/api/story_writer/router.py +++ b/backend/api/story_writer/router.py @@ -6,7 +6,7 @@ content generation, and full story creation. """ from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks -from typing import Any, Dict, Union, List +from typing import Any, Dict, Union, List, Optional from loguru import logger from middleware.auth_middleware import get_current_user @@ -37,6 +37,16 @@ from models.story_models import ( from services.story_writer.story_service import StoryWriterService from .task_manager import task_manager from .cache_manager import cache_manager +from uuid import uuid4 +from pydantic import BaseModel +from pathlib import Path + +from .utils.auth import require_authenticated_user +from .utils.media_utils import resolve_media_file +from .utils.hd_video import ( + generate_hd_video_payload, + generate_hd_video_scene_payload, +) router = APIRouter(prefix="/api/story", tags=["Story Writer"]) @@ -503,11 +513,11 @@ async def get_task_status( raise HTTPException(status_code=500, detail=str(e)) -@router.get("/task/{task_id}/result", response_model=StoryFullGenerationResponse) +@router.get("/task/{task_id}/result") async def get_task_result( task_id: str, current_user: Dict[str, Any] = Depends(get_current_user) -) -> StoryFullGenerationResponse: +) -> Dict[str, Any]: """Get the result of a completed story generation task.""" try: if not current_user: @@ -528,7 +538,19 @@ async def get_task_result( if not result: raise HTTPException(status_code=404, detail=f"No result found for task {task_id}") - return StoryFullGenerationResponse(**result, success=True, task_id=task_id) + # Some tasks return a full-story payload compatible with StoryFullGenerationResponse, + # others (e.g., video-only) return a dict like {"video": {...}, "success": True}. + # To avoid model conflicts, return a generic payload and include task_id. + # Frontend callers can branch on keys present (e.g., "video"). + if isinstance(result, dict): + # Ensure success flag present without duplicating + payload = {**result} + payload.setdefault("success", True) + payload["task_id"] = task_id + return payload + + # Fallback: wrap non-dict results + return {"result": result, "success": True, "task_id": task_id} except HTTPException: raise @@ -536,6 +558,69 @@ async def get_task_result( logger.error(f"[StoryWriter] Failed to get task result: {e}") raise HTTPException(status_code=500, detail=str(e)) +class HDVideoRequest(BaseModel): + prompt: str + provider: str = "huggingface" + model: str | None = None + num_frames: int | None = None + guidance_scale: float | None = None + num_inference_steps: int | None = None + negative_prompt: str | None = None + seed: int | None = None + +@router.post("/hd-video") +async def generate_hd_video( + request: HDVideoRequest, + current_user: Dict[str, Any] = Depends(get_current_user) +) -> Dict[str, Any]: + """ + Generate an HD AI animation using provider text-to-video (Hugging Face for now). + Saves the returned bytes as a video file and returns the secured URL. + """ + try: + user_id = require_authenticated_user(current_user) + return generate_hd_video_payload(request, user_id) + except HTTPException: + raise + except Exception as e: + logger.error(f"[StoryWriter] Failed to generate HD video: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +class HDVideoSceneRequest(BaseModel): + scene_number: int + scene_data: Dict[str, Any] + story_context: Dict[str, Any] + all_scenes: List[Dict[str, Any]] + scene_image_url: Optional[str] = None + provider: str = "huggingface" + model: str | None = None + num_frames: int | None = None + guidance_scale: float | None = None + num_inference_steps: int | None = None + negative_prompt: str | None = None + seed: int | None = None + + +@router.post("/hd-video-scene") +async def generate_hd_video_scene( + request: HDVideoSceneRequest, + current_user: Dict[str, Any] = Depends(get_current_user) +) -> Dict[str, Any]: + """ + Generate HD AI video for a single scene with AI-enhanced prompt. + Uses prompt enhancer to create HunyuanVideo-optimized prompt from story context. + """ + try: + user_id = require_authenticated_user(current_user) + return generate_hd_video_scene_payload(request, user_id) + + except HTTPException: + raise + except Exception as e: + logger.error(f"[StoryWriter] Failed to generate HD video for scene: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + # --------------------------- # Image Generation Endpoints @@ -614,31 +699,20 @@ async def serve_scene_image( ): """Serve a generated story scene image.""" try: - if not current_user: - raise HTTPException(status_code=401, detail="Authentication required") - - # Import image generation service to get output directory + require_authenticated_user(current_user) + from services.story_writer.image_generation_service import StoryImageGenerationService from fastapi.responses import FileResponse - + image_service = StoryImageGenerationService() - image_path = image_service.output_dir / image_filename - - if not image_path.exists(): - raise HTTPException(status_code=404, detail=f"Image not found: {image_filename}") - - # Validate that the file is within the output directory (security check) - try: - image_path.resolve().relative_to(image_service.output_dir.resolve()) - except ValueError: - raise HTTPException(status_code=403, detail="Access denied") - + image_path = resolve_media_file(image_service.output_dir, image_filename) + return FileResponse( path=str(image_path), media_type="image/png", filename=image_filename ) - + except HTTPException: raise except Exception as e: @@ -726,31 +800,20 @@ async def serve_scene_audio( ): """Serve a generated story scene audio file.""" try: - if not current_user: - raise HTTPException(status_code=401, detail="Authentication required") - - # Import audio generation service to get output directory + require_authenticated_user(current_user) + from services.story_writer.audio_generation_service import StoryAudioGenerationService from fastapi.responses import FileResponse - + audio_service = StoryAudioGenerationService() - audio_path = audio_service.output_dir / audio_filename - - if not audio_path.exists(): - raise HTTPException(status_code=404, detail=f"Audio not found: {audio_filename}") - - # Validate that the file is within the output directory (security check) - try: - audio_path.resolve().relative_to(audio_service.output_dir.resolve()) - except ValueError: - raise HTTPException(status_code=403, detail="Access denied") - + audio_path = resolve_media_file(audio_service.output_dir, audio_filename) + return FileResponse( path=str(audio_path), media_type="audio/mpeg", filename=audio_filename ) - + except HTTPException: raise except Exception as e: @@ -869,6 +932,99 @@ async def generate_story_video( logger.error(f"[StoryWriter] Failed to generate video: {e}") raise HTTPException(status_code=500, detail=str(e)) +@router.post("/generate-video-async", response_model=Dict[str, Any]) +async def generate_story_video_async( + request: StoryVideoGenerationRequest, + background_tasks: BackgroundTasks, + current_user: Dict[str, Any] = Depends(get_current_user) +) -> Dict[str, Any]: + """ + Generate a video asynchronously with progress updates via task manager. + Frontend can poll /api/story/task/{task_id}/status to show progress messages. + """ + try: + if not current_user: + raise HTTPException(status_code=401, detail="Authentication required") + user_id = str(current_user.get('id', '')) + if not user_id: + raise HTTPException(status_code=401, detail="Invalid user ID in authentication token") + if not request.scenes or len(request.scenes) == 0: + raise HTTPException(status_code=400, detail="At least one scene is required") + if len(request.scenes) != len(request.image_urls) or len(request.scenes) != len(request.audio_urls): + raise HTTPException(status_code=400, detail="Number of scenes, image URLs, and audio URLs must match") + + task_id = task_manager.create_task("story_video_generation") + background_tasks.add_task( + _execute_video_generation_task, + task_id=task_id, + request=request, + user_id=user_id + ) + return {"task_id": task_id, "status": "pending", "message": "Video generation started"} + except HTTPException: + raise + except Exception as e: + logger.error(f"[StoryWriter] Failed to start async video generation: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +def _execute_video_generation_task(task_id: str, request: StoryVideoGenerationRequest, user_id: str): + """Background task to generate story video with progress mapped to task manager.""" + from services.story_writer.video_generation_service import StoryVideoGenerationService + from services.story_writer.image_generation_service import StoryImageGenerationService + from services.story_writer.audio_generation_service import StoryAudioGenerationService + try: + task_manager.update_task_status(task_id, "processing", progress=2.0, message="Initializing video generation...") + video_service = StoryVideoGenerationService() + image_service = StoryImageGenerationService() + audio_service = StoryAudioGenerationService() + + # Prepare assets + scenes_data = [scene.dict() if isinstance(scene, StoryScene) else scene for scene in request.scenes] + image_paths, audio_paths, valid_scenes = [], [], [] + for idx, (scene, image_url, audio_url) in enumerate(zip(scenes_data, request.image_urls, request.audio_urls)): + image_filename = (image_url.split('/')[-1] if '/' in image_url else image_url).split('?')[0] + audio_filename = (audio_url.split('/')[-1] if '/' in audio_url else audio_url).split('?')[0] + image_path = image_service.output_dir / image_filename + audio_path = audio_service.output_dir / audio_filename + if not image_path.exists(): + logger.warning(f"[StoryWriter] Image not found: {image_path} (from URL: {image_url})") + continue + if not audio_path.exists(): + logger.warning(f"[StoryWriter] Audio not found: {audio_path} (from URL: {audio_url})") + continue + image_paths.append(str(image_path)) + audio_paths.append(str(audio_path)) + valid_scenes.append(scene) + + if not image_paths or not audio_paths or len(image_paths) != len(audio_paths): + raise RuntimeError("No valid or mismatched image/audio assets for video generation.") + + # Map service progress (0-100) to task progress (5-95) + def progress_callback(sub_progress: float, msg: str): + overall = 5.0 + max(0.0, min(100.0, sub_progress)) * 0.9 + task_manager.update_task_status(task_id, "processing", progress=overall, message=msg) + + result = video_service.generate_story_video( + scenes=valid_scenes, + image_paths=image_paths, + audio_paths=audio_paths, + user_id=user_id, + story_title=request.story_title or "Story", + fps=request.fps or 24, + transition_duration=request.transition_duration or 0.5, + progress_callback=progress_callback + ) + + task_manager.update_task_status( + task_id, + "completed", + progress=100.0, + message="Video generation complete!", + result={"video": result, "success": True} + ) + except Exception as e: + logger.error(f"[StoryWriter] Async video generation failed: {e}", exc_info=True) + task_manager.update_task_status(task_id, "failed", error=str(e), message=f"Video generation failed: {e}") @router.post("/generate-complete-video", response_model=Dict[str, Any]) async def generate_complete_story_video( @@ -1111,31 +1267,20 @@ async def serve_story_video( ): """Serve a generated story video file.""" try: - if not current_user: - raise HTTPException(status_code=401, detail="Authentication required") - - # Import video generation service to get output directory + require_authenticated_user(current_user) + from services.story_writer.video_generation_service import StoryVideoGenerationService from fastapi.responses import FileResponse - + video_service = StoryVideoGenerationService() - video_path = video_service.output_dir / video_filename - - if not video_path.exists(): - raise HTTPException(status_code=404, detail=f"Video not found: {video_filename}") - - # Validate that the file is within the output directory (security check) - try: - video_path.resolve().relative_to(video_service.output_dir.resolve()) - except ValueError: - raise HTTPException(status_code=403, detail="Access denied") - + video_path = resolve_media_file(video_service.output_dir, video_filename) + return FileResponse( path=str(video_path), media_type="video/mp4", filename=video_filename ) - + except HTTPException: raise except Exception as e: diff --git a/backend/api/story_writer/utils/__init__.py b/backend/api/story_writer/utils/__init__.py new file mode 100644 index 00000000..fa943891 --- /dev/null +++ b/backend/api/story_writer/utils/__init__.py @@ -0,0 +1,8 @@ +""" +Utility helpers for Story Writer API routes. + +Grouped here to keep the main router lean while reusing common logic +such as authentication guards, media resolution, and HD video helpers. +""" + + diff --git a/backend/api/story_writer/utils/auth.py b/backend/api/story_writer/utils/auth.py new file mode 100644 index 00000000..90d5b7e9 --- /dev/null +++ b/backend/api/story_writer/utils/auth.py @@ -0,0 +1,23 @@ +from typing import Any, Dict + +from fastapi import HTTPException, status + + +def require_authenticated_user(current_user: Dict[str, Any] | None) -> str: + """ + Validates the current user dictionary provided by Clerk middleware and + returns the normalized user_id. Raises HTTP 401 if authentication fails. + """ + if not current_user: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required") + + user_id = str(current_user.get("id", "")).strip() + if not user_id: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid user ID in authentication token", + ) + + return user_id + + diff --git a/backend/api/story_writer/utils/hd_video.py b/backend/api/story_writer/utils/hd_video.py new file mode 100644 index 00000000..6de19634 --- /dev/null +++ b/backend/api/story_writer/utils/hd_video.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from typing import Any, Dict, Optional + +from fastapi import HTTPException +from loguru import logger +from uuid import uuid4 + +from .media_utils import load_story_image_bytes + + +def generate_hd_video_payload(request: Any, user_id: str) -> Dict[str, Any]: + """Handles synchronous HD video generation.""" + from services.llm_providers.main_video_generation import ai_video_generate + from services.story_writer.video_generation_service import StoryVideoGenerationService + + video_service = StoryVideoGenerationService() + output_dir = video_service.output_dir + output_dir.mkdir(parents=True, exist_ok=True) + + kwargs: Dict[str, Any] = {} + if getattr(request, "model", None): + kwargs["model"] = request.model + if getattr(request, "num_frames", None): + kwargs["num_frames"] = request.num_frames + if getattr(request, "guidance_scale", None) is not None: + kwargs["guidance_scale"] = request.guidance_scale + if getattr(request, "num_inference_steps", None): + kwargs["num_inference_steps"] = request.num_inference_steps + if getattr(request, "negative_prompt", None): + kwargs["negative_prompt"] = request.negative_prompt + if getattr(request, "seed", None) is not None: + kwargs["seed"] = request.seed + + logger.info(f"[StoryWriter] Generating HD video via {getattr(request, 'provider', 'huggingface')} for user {user_id}") + raw_bytes = ai_video_generate( + prompt=request.prompt, + provider=getattr(request, "provider", None) or "huggingface", + user_id=user_id, + **kwargs, + ) + + filename = f"hd_{uuid4().hex}.mp4" + file_path = output_dir / filename + with open(file_path, "wb") as fh: + fh.write(raw_bytes) + + logger.info(f"[StoryWriter] HD video saved to {file_path}") + return { + "success": True, + "video_filename": filename, + "video_url": f"/api/story/videos/{filename}", + "provider": getattr(request, "provider", None) or "huggingface", + "model": getattr(request, "model", None) or "tencent/HunyuanVideo", + } + + +def generate_hd_video_scene_payload(request: Any, user_id: str) -> Dict[str, Any]: + """ + Handles per-scene HD video generation including prompt enhancement, + subscription validation, and optional image conditioning. + """ + from services.database import get_db as get_db_validation + from services.onboarding.api_key_manager import APIKeyManager + from services.subscription import PricingService + from services.subscription.preflight_validator import validate_video_generation_operations + from services.story_writer.prompt_enhancer_service import enhance_scene_prompt_for_video + from services.llm_providers.main_video_generation import ai_video_generate + from services.story_writer.video_generation_service import StoryVideoGenerationService + + scene_number = request.scene_number + logger.info(f"[StoryWriter] Generating HD video for scene {scene_number} for user {user_id}") + + # Step 1: Validate API key + hf_token = APIKeyManager().get_api_key("hf_token") + if not hf_token: + logger.error("[StoryWriter] Pre-flight: HF token not configured - blocking video generation") + raise HTTPException( + status_code=400, + detail={ + "error": "Hugging Face API token is not configured. Please configure your HF token in settings.", + "message": "Hugging Face API token is not configured. Please configure your HF token in settings.", + }, + ) + + # Step 2: Subscription limits + db_validation = next(get_db_validation()) + try: + pricing_service = PricingService(db_validation) + logger.info(f"[StoryWriter] Pre-flight: Checking video generation limits for user {user_id}...") + validate_video_generation_operations(pricing_service=pricing_service, user_id=user_id) + logger.info("[StoryWriter] Pre-flight: ✅ Video generation limits validated - proceeding") + finally: + db_validation.close() + + # Stage 1: Prompt enhancement + enhanced_prompt = enhance_scene_prompt_for_video( + current_scene=request.scene_data, + story_context=request.story_context, + all_scenes=request.all_scenes, + user_id=user_id, + ) + logger.info(f"[StoryWriter] Generated enhanced prompt ({len(enhanced_prompt)} chars) for scene {scene_number}") + + # Stage 2: Optional image reference + scene_image_bytes: Optional[bytes] = None + if getattr(request, "scene_image_url", None): + scene_image_bytes = load_story_image_bytes(request.scene_image_url) + if scene_image_bytes: + logger.info(f"[StoryWriter] Using scene image reference for scene {scene_number}") + else: + logger.warning(f"[StoryWriter] Scene image could not be loaded for scene {scene_number}, falling back to text-only video") + + kwargs: Dict[str, Any] = {} + if getattr(request, "model", None): + kwargs["model"] = request.model + if getattr(request, "num_frames", None): + kwargs["num_frames"] = request.num_frames + if getattr(request, "guidance_scale", None) is not None: + kwargs["guidance_scale"] = request.guidance_scale + if getattr(request, "num_inference_steps", None): + kwargs["num_inference_steps"] = request.num_inference_steps + if getattr(request, "negative_prompt", None): + kwargs["negative_prompt"] = request.negative_prompt + if getattr(request, "seed", None) is not None: + kwargs["seed"] = request.seed + + raw_bytes = ai_video_generate( + prompt=enhanced_prompt, + provider=getattr(request, "provider", None) or "huggingface", + user_id=user_id, + input_image_bytes=scene_image_bytes, + **kwargs, + ) + + video_service = StoryVideoGenerationService() + save_result = video_service.save_scene_video( + video_bytes=raw_bytes, + scene_number=scene_number, + user_id=user_id, + ) + + logger.info(f"[StoryWriter] HD video saved for scene {scene_number}: {save_result.get('video_filename')}") + return { + "success": True, + "scene_number": scene_number, + "video_filename": save_result.get("video_filename"), + "video_url": save_result.get("video_url"), + "prompt_used": enhanced_prompt, + "provider": getattr(request, "provider", None) or "huggingface", + "model": getattr(request, "model", None) or "tencent/HunyuanVideo", + } + + diff --git a/backend/api/story_writer/utils/media_utils.py b/backend/api/story_writer/utils/media_utils.py new file mode 100644 index 00000000..42b28bf7 --- /dev/null +++ b/backend/api/story_writer/utils/media_utils.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Optional +from urllib.parse import urlparse + +from fastapi import HTTPException, status +from loguru import logger + + +BASE_DIR = Path(__file__).resolve().parents[3] # backend/ +STORY_IMAGES_DIR = (BASE_DIR / "story_images").resolve() +STORY_IMAGES_DIR.mkdir(parents=True, exist_ok=True) + + +def load_story_image_bytes(image_url: str) -> Optional[bytes]: + """ + Resolve an authenticated story image URL (e.g., /api/story/images/) to raw bytes. + Returns None if the file cannot be located. + """ + if not image_url: + return None + + try: + parsed = urlparse(image_url) + path = parsed.path if parsed.scheme else image_url + prefix = "/api/story/images/" + if prefix not in path: + logger.warning(f"[StoryWriter] Unsupported image URL for video reference: {image_url}") + return None + + filename = path.split(prefix, 1)[1].split("?", 1)[0].strip() + if not filename: + return None + + file_path = (STORY_IMAGES_DIR / filename).resolve() + if not str(file_path).startswith(str(STORY_IMAGES_DIR)): + logger.error(f"[StoryWriter] Attempted path traversal when resolving image: {image_url}") + return None + + if not file_path.exists(): + logger.warning(f"[StoryWriter] Referenced scene image not found on disk: {file_path}") + return None + + return file_path.read_bytes() + except Exception as exc: + logger.error(f"[StoryWriter] Failed to load reference image for video gen: {exc}") + return None + + +def resolve_media_file(base_dir: Path, filename: str) -> Path: + """ + Returns a safe resolved path for a media file stored under base_dir. + Guards against directory traversal and ensures the file exists. + """ + filename = filename.split("?")[0].strip() + resolved = (base_dir / filename).resolve() + + try: + resolved.relative_to(base_dir.resolve()) + except ValueError: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") + + if not resolved.exists(): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"File not found: {filename}") + + return resolved + + diff --git a/backend/api/subscription_api.py b/backend/api/subscription_api.py index cb02204f..3cae081b 100644 --- a/backend/api/subscription_api.py +++ b/backend/api/subscription_api.py @@ -14,7 +14,7 @@ from functools import lru_cache from services.database import get_db from services.subscription import UsageTrackingService, PricingService from services.subscription.log_wrapping_service import LogWrappingService -from services.subscription.schema_utils import ensure_subscription_plan_columns +from services.subscription.schema_utils import ensure_subscription_plan_columns, ensure_usage_summaries_columns import sqlite3 from middleware.auth_middleware import get_current_user from models.subscription_models import ( @@ -114,6 +114,8 @@ async def get_subscription_plans( "metaphor_calls": plan.metaphor_calls_limit, "firecrawl_calls": plan.firecrawl_calls_limit, "stability_calls": plan.stability_calls_limit, + "video_calls": getattr(plan, 'video_calls_limit', 0), + "image_edit_calls": getattr(plan, 'image_edit_calls_limit', 0), "gemini_tokens": plan.gemini_tokens_limit, "openai_tokens": plan.openai_tokens_limit, "anthropic_tokens": plan.anthropic_tokens_limit, @@ -132,7 +134,7 @@ async def get_subscription_plans( except (sqlite3.OperationalError, Exception) as e: error_str = str(e).lower() - if 'no such column' in error_str and 'exa_calls_limit' in error_str: + if 'no such column' in error_str and ('exa_calls_limit' in error_str or 'video_calls_limit' in error_str or 'image_edit_calls_limit' in error_str): logger.warning("Missing column detected in subscription plans query, attempting schema fix...") try: import services.subscription.schema_utils as schema_utils @@ -237,6 +239,8 @@ async def get_user_subscription( "metaphor_calls": free_plan.metaphor_calls_limit, "firecrawl_calls": free_plan.firecrawl_calls_limit, "stability_calls": free_plan.stability_calls_limit, + "video_calls": getattr(free_plan, 'video_calls_limit', 0), + "image_edit_calls": getattr(free_plan, 'image_edit_calls_limit', 0), "monthly_cost": free_plan.monthly_cost_limit } } @@ -334,6 +338,8 @@ async def get_subscription_status( "metaphor_calls": free_plan.metaphor_calls_limit, "firecrawl_calls": free_plan.firecrawl_calls_limit, "stability_calls": free_plan.stability_calls_limit, + "video_calls": getattr(free_plan, 'video_calls_limit', 0), + "image_edit_calls": getattr(free_plan, 'image_edit_calls_limit', 0), "monthly_cost": free_plan.monthly_cost_limit } } @@ -399,15 +405,16 @@ async def get_subscription_status( except (sqlite3.OperationalError, Exception) as e: error_str = str(e).lower() - if 'no such column' in error_str and 'exa_calls_limit' in error_str: + if 'no such column' in error_str and ('exa_calls_limit' in error_str or 'video_calls_limit' in error_str or 'image_edit_calls_limit' in error_str): # Try to fix schema and retry once logger.warning("Missing column detected in subscription status query, attempting schema fix...") try: import services.subscription.schema_utils as schema_utils schema_utils._checked_subscription_plan_columns = False ensure_subscription_plan_columns(db) + db.commit() # Ensure schema changes are committed db.expire_all() - # Retry the query + # Retry the query - query subscription without eager loading plan subscription = db.query(UserSubscription).filter( UserSubscription.user_id == user_id, UserSubscription.is_active == True @@ -437,11 +444,21 @@ async def get_subscription_status( "metaphor_calls": free_plan.metaphor_calls_limit, "firecrawl_calls": free_plan.firecrawl_calls_limit, "stability_calls": free_plan.stability_calls_limit, + "video_calls": getattr(free_plan, 'video_calls_limit', 0), + "image_edit_calls": getattr(free_plan, 'image_edit_calls_limit', 0), "monthly_cost": free_plan.monthly_cost_limit } } } elif subscription: + # Query plan separately after schema fix to avoid lazy loading issues + plan = db.query(SubscriptionPlan).filter( + SubscriptionPlan.id == subscription.plan_id + ).first() + + if not plan: + raise HTTPException(status_code=404, detail="Plan not found") + now = datetime.utcnow() if subscription.current_period_end < now: if getattr(subscription, 'auto_renew', False): @@ -456,8 +473,8 @@ async def get_subscription_status( "success": True, "data": { "active": False, - "plan": subscription.plan.tier.value, - "tier": subscription.plan.tier.value, + "plan": plan.tier.value, + "tier": plan.tier.value, "can_use_api": False, "reason": "Subscription expired" } @@ -466,21 +483,23 @@ async def get_subscription_status( "success": True, "data": { "active": True, - "plan": subscription.plan.tier.value, - "tier": subscription.plan.tier.value, + "plan": plan.tier.value, + "tier": plan.tier.value, "can_use_api": True, "limits": { - "ai_text_generation_calls": getattr(subscription.plan, 'ai_text_generation_calls_limit', None) or 0, - "gemini_calls": subscription.plan.gemini_calls_limit, - "openai_calls": subscription.plan.openai_calls_limit, - "anthropic_calls": subscription.plan.anthropic_calls_limit, - "mistral_calls": subscription.plan.mistral_calls_limit, - "tavily_calls": subscription.plan.tavily_calls_limit, - "serper_calls": subscription.plan.serper_calls_limit, - "metaphor_calls": subscription.plan.metaphor_calls_limit, - "firecrawl_calls": subscription.plan.firecrawl_calls_limit, - "stability_calls": subscription.plan.stability_calls_limit, - "monthly_cost": subscription.plan.monthly_cost_limit + "ai_text_generation_calls": getattr(plan, 'ai_text_generation_calls_limit', None) or 0, + "gemini_calls": plan.gemini_calls_limit, + "openai_calls": plan.openai_calls_limit, + "anthropic_calls": plan.anthropic_calls_limit, + "mistral_calls": plan.mistral_calls_limit, + "tavily_calls": plan.tavily_calls_limit, + "serper_calls": plan.serper_calls_limit, + "metaphor_calls": plan.metaphor_calls_limit, + "firecrawl_calls": plan.firecrawl_calls_limit, + "stability_calls": plan.stability_calls_limit, + "video_calls": getattr(plan, 'video_calls_limit', 0), + "image_edit_calls": getattr(plan, 'image_edit_calls_limit', 0), + "monthly_cost": plan.monthly_cost_limit } } } @@ -893,6 +912,7 @@ async def get_dashboard_data( try: ensure_subscription_plan_columns(db) + ensure_usage_summaries_columns(db) # Serve from short TTL cache to avoid hammering DB on bursts import time now = time.time() @@ -966,7 +986,72 @@ async def get_dashboard_data( _dashboard_cache_ts[user_id] = now return response_payload - except Exception as e: + except (sqlite3.OperationalError, Exception) as e: + error_str = str(e).lower() + if 'no such column' in error_str and ('exa_calls' in error_str or 'exa_cost' in error_str or 'video_calls' in error_str or 'video_cost' in error_str or 'image_edit_calls' in error_str or 'image_edit_cost' in error_str): + logger.warning("Missing column detected in dashboard query, attempting schema fix...") + try: + import services.subscription.schema_utils as schema_utils + schema_utils._checked_usage_summaries_columns = False + schema_utils._checked_subscription_plan_columns = False + ensure_usage_summaries_columns(db) + ensure_subscription_plan_columns(db) + db.expire_all() + # Retry the query + usage_service = UsageTrackingService(db) + pricing_service = PricingService(db) + + current_usage = usage_service.get_user_usage_stats(user_id) + trends = usage_service.get_usage_trends(user_id, 6) + limits = pricing_service.get_user_limits(user_id) + + alerts = db.query(UsageAlert).filter( + UsageAlert.user_id == user_id, + UsageAlert.is_read == False + ).order_by(UsageAlert.created_at.desc()).limit(5).all() + + alerts_data = [ + { + "id": alert.id, + "type": alert.alert_type, + "title": alert.title, + "message": alert.message, + "severity": alert.severity, + "created_at": alert.created_at.isoformat() + } + for alert in alerts + ] + + current_cost = current_usage.get('total_cost', 0) + days_in_period = 30 + current_day = datetime.now().day + projected_cost = (current_cost / current_day) * days_in_period if current_day > 0 else 0 + + response_payload = { + "success": True, + "data": { + "current_usage": current_usage, + "trends": trends, + "limits": limits, + "alerts": alerts_data, + "projections": { + "projected_monthly_cost": round(projected_cost, 2), + "cost_limit": limits.get('limits', {}).get('monthly_cost', 0) if limits else 0, + "projected_usage_percentage": (projected_cost / max(limits.get('limits', {}).get('monthly_cost', 1), 1)) * 100 if limits else 0 + }, + "summary": { + "total_api_calls_this_month": current_usage.get('total_calls', 0), + "total_cost_this_month": current_usage.get('total_cost', 0), + "usage_status": current_usage.get('usage_status', 'active'), + "unread_alerts": len(alerts_data) + } + } + } + return response_payload + except Exception as retry_err: + logger.error(f"Schema fix and retry failed: {retry_err}") + raise HTTPException(status_code=500, detail=f"Database schema error: {str(e)}") + logger.error(f"Error getting dashboard data: {e}") raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/logging_config.py b/backend/logging_config.py index 1ec2ef11..649983bc 100644 --- a/backend/logging_config.py +++ b/backend/logging_config.py @@ -93,6 +93,17 @@ def setup_clean_logging(): format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n", filter=warning_only_filter ) + # Add a focused sink to surface Story Video Generation INFO logs in console + def video_generation_filter(record): + msg = record.get("message", "") + name = record.get("name", "") + return "[StoryVideoGeneration]" in msg or "services.story_writer.video_generation_service" in name + logger.add( + sys.stdout.write, + level="INFO", + format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n", + filter=video_generation_filter + ) else: # In verbose mode, show all log levels with detailed formatting logger.add( diff --git a/backend/models/subscription_models.py b/backend/models/subscription_models.py index 0b1e12b9..0de5b176 100644 --- a/backend/models/subscription_models.py +++ b/backend/models/subscription_models.py @@ -35,6 +35,8 @@ class APIProvider(enum.Enum): FIRECRAWL = "firecrawl" STABILITY = "stability" EXA = "exa" + VIDEO = "video" + IMAGE_EDIT = "image_edit" class BillingCycle(enum.Enum): MONTHLY = "monthly" @@ -68,6 +70,8 @@ class SubscriptionPlan(Base): firecrawl_calls_limit = Column(Integer, default=0) stability_calls_limit = Column(Integer, default=0) # Image generation exa_calls_limit = Column(Integer, default=0) # Exa neural search + video_calls_limit = Column(Integer, default=0) # AI video generation + image_edit_calls_limit = Column(Integer, default=0) # AI image editing # Token Limits (for LLM providers) gemini_tokens_limit = Column(Integer, default=0) @@ -185,6 +189,8 @@ class UsageSummary(Base): firecrawl_calls = Column(Integer, default=0) stability_calls = Column(Integer, default=0) exa_calls = Column(Integer, default=0) + video_calls = Column(Integer, default=0) # AI video generation + image_edit_calls = Column(Integer, default=0) # AI image editing # Token Usage gemini_tokens = Column(Integer, default=0) @@ -203,6 +209,8 @@ class UsageSummary(Base): firecrawl_cost = Column(Float, default=0.0) stability_cost = Column(Float, default=0.0) exa_cost = Column(Float, default=0.0) + video_cost = Column(Float, default=0.0) # AI video generation + image_edit_cost = Column(Float, default=0.0) # AI image editing # Totals total_calls = Column(Integer, default=0) diff --git a/backend/requirements.txt b/backend/requirements.txt index 5734dd24..0fb3da2b 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -53,7 +53,7 @@ nltk>=3.8.0 # Image and audio processing for Stability AI Pillow>=10.0.0 -huggingface_hub>=0.24.0 +huggingface_hub>=1.1.4 scikit-learn>=1.3.0 # Text-to-Speech (TTS) dependencies @@ -61,7 +61,7 @@ gtts>=2.4.0 pyttsx3>=2.90 # Video composition dependencies -moviepy>=1.0.3 +moviepy==2.1.2 imageio>=2.31.0 imageio-ffmpeg>=0.4.9 diff --git a/backend/scripts/update_image_edit_limits.py b/backend/scripts/update_image_edit_limits.py new file mode 100644 index 00000000..a6617a77 --- /dev/null +++ b/backend/scripts/update_image_edit_limits.py @@ -0,0 +1,102 @@ +""" +Script to update existing subscription plans with image_edit_calls_limit values. + +This script updates the SubscriptionPlan table to set image_edit_calls_limit +for plans that were created before this column was added. + +Limits: +- Free: 10 image editing calls/month +- Basic: 30 image editing calls/month +- Pro: 100 image editing calls/month +- Enterprise: 0 (unlimited) +""" + +import sys +import os +from pathlib import Path +from datetime import datetime, timezone + +# Add the backend directory to Python path +backend_dir = Path(__file__).parent.parent +sys.path.insert(0, str(backend_dir)) + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from loguru import logger + +from models.subscription_models import SubscriptionPlan, SubscriptionTier +from services.database import DATABASE_URL + +def update_image_edit_limits(): + """Update existing subscription plans with image_edit_calls_limit values.""" + + try: + # Create engine + engine = create_engine(DATABASE_URL, echo=False) + SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + db = SessionLocal() + + try: + # Ensure schema columns exist + from services.subscription.schema_utils import ensure_subscription_plan_columns + ensure_subscription_plan_columns(db) + + # Define limits for each tier + limits_by_tier = { + SubscriptionTier.FREE: 10, + SubscriptionTier.BASIC: 30, + SubscriptionTier.PRO: 100, + SubscriptionTier.ENTERPRISE: 0, # Unlimited + } + + updated_count = 0 + + # Update each plan + for tier, limit in limits_by_tier.items(): + plans = db.query(SubscriptionPlan).filter( + SubscriptionPlan.tier == tier, + SubscriptionPlan.is_active == True + ).all() + + for plan in plans: + current_limit = getattr(plan, 'image_edit_calls_limit', 0) or 0 + + # Only update if limit is 0 (not set) or if it's different + if current_limit != limit: + setattr(plan, 'image_edit_calls_limit', limit) + plan.updated_at = datetime.now(timezone.utc) + updated_count += 1 + logger.info(f"Updated {plan.name} plan ({tier.value}): image_edit_calls_limit = {current_limit} -> {limit}") + else: + logger.debug(f"Plan {plan.name} ({tier.value}) already has image_edit_calls_limit = {limit}") + + # Commit changes + db.commit() + + if updated_count > 0: + logger.info(f"✅ Successfully updated {updated_count} subscription plan(s) with image_edit_calls_limit") + else: + logger.info("✅ All subscription plans already have correct image_edit_calls_limit values") + + return True + + except Exception as e: + logger.error(f"❌ Error updating image_edit_limits: {e}") + db.rollback() + raise + finally: + db.close() + + except Exception as e: + logger.error(f"❌ Error creating database connection: {e}") + raise + +if __name__ == "__main__": + logger.info("🔄 Updating subscription plans with image_edit_calls_limit...") + success = update_image_edit_limits() + if success: + logger.info("🎉 Image edit limits update completed successfully!") + else: + logger.error("❌ Image edit limits update failed") + sys.exit(1) + diff --git a/backend/services/llm_providers/main_image_editing.py b/backend/services/llm_providers/main_image_editing.py new file mode 100644 index 00000000..a09f9328 --- /dev/null +++ b/backend/services/llm_providers/main_image_editing.py @@ -0,0 +1,165 @@ +from __future__ import annotations + +import os +import io +from typing import Optional, Dict, Any +from PIL import Image + +from .image_generation import ( + ImageGenerationOptions, + ImageGenerationResult, +) +from utils.logger_utils import get_service_logger + +try: + from huggingface_hub import InferenceClient + HF_HUB_AVAILABLE = True +except ImportError: + HF_HUB_AVAILABLE = False + + +logger = get_service_logger("image_editing.facade") + + +DEFAULT_IMAGE_EDIT_MODEL = os.getenv( + "HF_IMAGE_EDIT_MODEL", + "Qwen/Qwen-Image-Edit", +) + + +def _select_provider(explicit: Optional[str]) -> str: + """Select provider for image editing. Defaults to huggingface with fal-ai.""" + if explicit: + return explicit + # Default to huggingface for image editing (best support for image-to-image) + return "huggingface" + + +def _get_provider_client(provider_name: str, api_key: Optional[str] = None): + """Get InferenceClient for the specified provider.""" + if not HF_HUB_AVAILABLE: + raise RuntimeError("huggingface_hub is not installed. Install with: pip install huggingface_hub") + + if provider_name == "huggingface": + api_key = api_key or os.getenv("HF_TOKEN") + if not api_key: + raise RuntimeError("HF_TOKEN is required for Hugging Face image editing") + # Use fal-ai provider for fast inference + return InferenceClient(provider="fal-ai", api_key=api_key) + + raise ValueError(f"Unknown image editing provider: {provider_name}") + + +def edit_image( + input_image_bytes: bytes, + prompt: str, + options: Optional[Dict[str, Any]] = None, + user_id: Optional[str] = None +) -> ImageGenerationResult: + """Edit image with pre-flight validation. + + Args: + input_image_bytes: Input image as bytes (PNG/JPEG) + prompt: Natural language prompt describing desired edits (e.g., "Turn the cat into a tiger") + options: Image editing options (provider, model, etc.) + user_id: User ID for subscription checking (optional, but required for validation) + + Returns: + ImageGenerationResult with edited image bytes and metadata + + Best Practices for Prompts: + - Use clear, specific language describing desired changes + - Describe what should change and what should remain + - Examples: "Turn the cat into a tiger", "Change background to forest", + "Make it look like a watercolor painting" + """ + # PRE-FLIGHT VALIDATION: Validate image editing before API call + # MUST happen BEFORE any API calls - return immediately if validation fails + if user_id: + from services.database import get_db + from services.subscription import PricingService + from services.subscription.preflight_validator import validate_image_editing_operations + from fastapi import HTTPException + + db = next(get_db()) + try: + pricing_service = PricingService(db) + # Raises HTTPException immediately if validation fails - frontend gets immediate response + validate_image_editing_operations( + pricing_service=pricing_service, + user_id=user_id + ) + except HTTPException as http_ex: + # Re-raise immediately - don't proceed with API call + logger.error(f"[Image Editing] ❌ Pre-flight validation failed - blocking API call") + raise + finally: + db.close() + + logger.info(f"[Image Editing] ✅ Pre-flight validation passed - proceeding with image editing") + + # Validate input + if not input_image_bytes: + raise ValueError("input_image_bytes is required") + if not prompt or not prompt.strip(): + raise ValueError("prompt is required for image editing") + + opts = options or {} + provider_name = _select_provider(opts.get("provider")) + model = opts.get("model") or DEFAULT_IMAGE_EDIT_MODEL + + logger.info(f"[Image Editing] Editing image via provider={provider_name} model={model}") + + # Get provider client + client = _get_provider_client(provider_name, opts.get("api_key")) + + # Prepare parameters for image-to-image + params: Dict[str, Any] = {} + if opts.get("guidance_scale") is not None: + params["guidance_scale"] = opts.get("guidance_scale") + if opts.get("steps") is not None: + params["num_inference_steps"] = opts.get("steps") + if opts.get("seed") is not None: + params["seed"] = opts.get("seed") + + try: + # Convert input image bytes to PIL Image for validation + input_image = Image.open(io.BytesIO(input_image_bytes)) + width = input_image.width + height = input_image.height + + # Use image_to_image method from Hugging Face InferenceClient + # This follows the pattern from the Hugging Face documentation + # Docs: https://huggingface.co/docs/inference-providers/en/guides/image-editor + edited_image: Image.Image = client.image_to_image( + image=input_image, + prompt=prompt.strip(), + model=model, + **params, + ) + + # Convert edited image back to bytes + with io.BytesIO() as buf: + edited_image.save(buf, format="PNG") + edited_image_bytes = buf.getvalue() + + logger.info(f"[Image Editing] ✅ Successfully edited image: {len(edited_image_bytes)} bytes") + + return ImageGenerationResult( + image_bytes=edited_image_bytes, + width=edited_image.width, + height=edited_image.height, + provider="huggingface", + model=model, + seed=opts.get("seed"), + metadata={ + "provider": "fal-ai", + "operation": "image_editing", + "original_width": width, + "original_height": height, + }, + ) + except Exception as e: + logger.error(f"[Image Editing] ❌ Error editing image: {e}", exc_info=True) + raise RuntimeError(f"Image editing failed: {str(e)}") + diff --git a/backend/services/llm_providers/main_text_generation.py b/backend/services/llm_providers/main_text_generation.py index 2d7b76ae..04aeb311 100644 --- a/backend/services/llm_providers/main_text_generation.py +++ b/backend/services/llm_providers/main_text_generation.py @@ -507,6 +507,14 @@ def llm_text_gen(prompt: str, system_prompt: Optional[str] = None, json_struct: current_images_before = getattr(summary, "stability_calls", 0) or 0 image_limit = limits['limits'].get("stability_calls", 0) if limits else 0 + # Get image editing stats for unified log + current_image_edit_calls = getattr(summary, "image_edit_calls", 0) or 0 + image_edit_limit = limits['limits'].get("image_edit_calls", 0) if limits else 0 + + # Get video stats for unified log + current_video_calls = getattr(summary, "video_calls", 0) or 0 + video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + # CRITICAL DEBUG: Print diagnostic info BEFORE commit (always visible, flushed immediately) import sys debug_msg = f"[DEBUG] BEFORE COMMIT - Record count: {record_count}, Raw SQL values: calls={current_calls_before}, tokens={current_tokens_before}, Provider: {provider_name}, Period: {current_period}, New calls will be: {new_calls}, New tokens will be: {new_tokens}" @@ -562,6 +570,7 @@ def llm_text_gen(prompt: str, system_prompt: Optional[str] = None, json_struct: ├─ Calls: {current_calls_before} → {new_calls} / {call_limit if call_limit > 0 else '∞'} ├─ Tokens: {current_tokens_before} → {new_tokens} / {token_limit if token_limit > 0 else '∞'} ├─ Images: {current_images_before} / {image_limit if image_limit > 0 else '∞'} +├─ Image Editing: {current_image_edit_calls} / {image_edit_limit if image_edit_limit > 0 else '∞'} └─ Status: ✅ Allowed & Tracked """) except Exception as track_error: @@ -802,6 +811,14 @@ def llm_text_gen(prompt: str, system_prompt: Optional[str] = None, json_struct: current_images_before = getattr(summary, "stability_calls", 0) or 0 image_limit = limits['limits'].get("stability_calls", 0) if limits else 0 + # Get image editing stats for unified log + current_image_edit_calls = getattr(summary, "image_edit_calls", 0) or 0 + image_edit_limit = limits['limits'].get("image_edit_calls", 0) if limits else 0 + + # Get video stats for unified log + current_video_calls = getattr(summary, "video_calls", 0) or 0 + video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + # CRITICAL: Flush before commit to ensure changes are immediately visible to other sessions db_track.flush() # Flush to ensure changes are in DB (not just in transaction) db_track.commit() # Commit transaction to make changes visible to other sessions @@ -819,6 +836,8 @@ def llm_text_gen(prompt: str, system_prompt: Optional[str] = None, json_struct: ├─ Calls: {current_calls_before} → {new_calls} / {call_limit if call_limit > 0 else '∞'} ├─ Tokens: {current_tokens_before} → {new_tokens} / {token_limit if token_limit > 0 else '∞'} ├─ Images: {current_images_before} / {image_limit if image_limit > 0 else '∞'} +├─ Image Editing: {current_image_edit_calls} / {image_edit_limit if image_edit_limit > 0 else '∞'} +├─ Videos: {current_video_calls} / {video_limit if video_limit > 0 else '∞'} └─ Status: ✅ Allowed & Tracked """) except Exception as track_error: diff --git a/backend/services/llm_providers/main_video_generation.py b/backend/services/llm_providers/main_video_generation.py new file mode 100644 index 00000000..a33f9cd5 --- /dev/null +++ b/backend/services/llm_providers/main_video_generation.py @@ -0,0 +1,355 @@ +""" +Main Video Generation Service + +Provides a unified interface for AI video generation providers. +Initial support: Hugging Face Inference Providers (text-to-video). +Stubs included for Gemini (Veo 3) and OpenAI (Sora) for future use. +""" +from __future__ import annotations + +import os +import base64 +import io +from typing import Any, Dict, Optional, Union + +from fastapi import HTTPException + +try: + from huggingface_hub import InferenceClient + HF_HUB_AVAILABLE = True +except ImportError: + HF_HUB_AVAILABLE = False + InferenceClient = None + +from ..onboarding.api_key_manager import APIKeyManager +from utils.logger_utils import get_service_logger + +logger = get_service_logger("video_generation_service") + + +class VideoProviderNotImplemented(Exception): + pass + + +def _get_api_key(provider: str) -> Optional[str]: + try: + manager = APIKeyManager() + mapping = { + "huggingface": "hf_token", + "gemini": "gemini", # placeholder for Veo 3 + "openai": "openai_api_key", # placeholder for Sora + } + return manager.get_api_key(mapping.get(provider, provider)) + except Exception as e: + logger.error(f"[video_gen] Failed to read API key for {provider}: {e}") + return None + + +def _coerce_video_bytes(output: Any) -> bytes: + """ + Normalizes the different return shapes that huggingface_hub may emit for video tasks. + Depending on the provider/library version we may get: + - raw bytes + - an object with `.video` or `.bytes` attributes (plus optional `.save`) + - a dict containing a `video` key with bytes/base64 data + """ + data: Union[bytes, bytearray, memoryview, io.BufferedIOBase, None] = None + + if isinstance(output, (bytes, bytearray, memoryview)): + return bytes(output) + + # Objects with direct attribute access + if hasattr(output, "video"): + data = getattr(output, "video") + elif hasattr(output, "bytes"): + data = getattr(output, "bytes") + elif isinstance(output, dict) and "video" in output: + data = output["video"] + else: + data = output + + # Handle file-like responses + if hasattr(data, "read"): + data = data.read() + + if isinstance(data, (bytes, bytearray, memoryview)): + return bytes(data) + + if isinstance(data, str): + # Expecting data URI or raw base64 string + if data.startswith("data:"): + _, encoded = data.split(",", 1) + return base64.b64decode(encoded) + try: + return base64.b64decode(data) + except Exception as exc: + raise TypeError(f"Unable to decode string video payload: {exc}") from exc + + raise TypeError(f"Unsupported video payload type: {type(data)}") + + +def _generate_with_huggingface( + prompt: str, + num_frames: int = 24 * 4, + guidance_scale: float = 7.5, + num_inference_steps: int = 30, + negative_prompt: Optional[str] = None, + seed: Optional[int] = None, + model: str = "tencent/HunyuanVideo", + input_image_bytes: Optional[bytes] = None, +) -> bytes: + """ + Generates video bytes using Hugging Face's InferenceClient. + """ + if not HF_HUB_AVAILABLE: + raise RuntimeError("huggingface_hub is not installed. Install with: pip install huggingface_hub") + + token = _get_api_key("huggingface") + if not token: + raise RuntimeError("HF token not configured. Set an hf_token in APIKeyManager.") + + client = InferenceClient( + model=model, + provider="fal-ai", + token=token, + ) + logger.info("[video_gen] Using HuggingFace provider 'fal-ai'") + + params: Dict[str, Any] = { + "num_frames": num_frames, + "guidance_scale": guidance_scale, + "num_inference_steps": num_inference_steps, + } + if negative_prompt: + params["negative_prompt"] = negative_prompt if isinstance(negative_prompt, list) else [negative_prompt] + if seed is not None: + params["seed"] = seed + + logger.info( + "[video_gen] HuggingFace request model=%s frames=%s steps=%s mode=%s", + model, + num_frames, + num_inference_steps, + "image-to-video" if input_image_bytes else "text-to-video", + ) + + try: + call_kwargs = {**params, "model": model} + if input_image_bytes: + video_output = client.image_to_video( + image=input_image_bytes, + prompt=prompt, + **call_kwargs, + ) + else: + video_output = client.text_to_video( + prompt, + **call_kwargs, + ) + + video_bytes = _coerce_video_bytes(video_output) + + if not isinstance(video_bytes, bytes): + raise TypeError(f"Expected bytes from text_to_video, got {type(video_bytes)}") + + if len(video_bytes) == 0: + raise ValueError("Received empty video bytes from Hugging Face API") + + logger.info(f"[video_gen] Successfully generated video: {len(video_bytes)} bytes") + return video_bytes + + except Exception as e: + error_msg = str(e) + error_type = type(e).__name__ + logger.error(f"[video_gen] HF error ({error_type}): {error_msg}", exc_info=True) + raise HTTPException(status_code=502, detail={ + "error": f"Hugging Face video generation failed: {error_msg}", + "error_type": error_type + }) + + +def _generate_with_gemini(prompt: str, **kwargs) -> bytes: + raise VideoProviderNotImplemented("Gemini Veo 3 integration coming soon.") + +def _generate_with_openai(prompt: str, **kwargs) -> bytes: + raise VideoProviderNotImplemented("OpenAI Sora integration coming soon.") + + +def ai_video_generate( + prompt: str, + provider: str = "huggingface", + user_id: Optional[str] = None, + input_image_bytes: Optional[bytes] = None, + **kwargs, +) -> bytes: + """ + Unified video generation entry point. + + - provider: 'huggingface' (default), 'gemini' (veo3 stub), 'openai' (sora stub) + - kwargs: num_frames, guidance_scale, num_inference_steps, negative_prompt, seed, model + - input_image_bytes: optional bytes for image-to-video flows (uses image as motion anchor) + + Returns raw video bytes (mp4/webm depending on provider). + """ + logger.info(f"[video_gen] provider={provider}") + + # Enforce authentication usage like text gen does + if not user_id: + raise RuntimeError("user_id is required for subscription/usage tracking.") + + # PRE-FLIGHT VALIDATION: Validate video generation before API call + # MUST happen BEFORE any API calls - return immediately if validation fails + from services.database import get_db + from services.subscription import PricingService + from services.subscription.preflight_validator import validate_video_generation_operations + from fastapi import HTTPException + + db = next(get_db()) + try: + pricing_service = PricingService(db) + # Raises HTTPException immediately if validation fails - frontend gets immediate response + validate_video_generation_operations( + pricing_service=pricing_service, + user_id=user_id + ) + except HTTPException: + # Re-raise immediately - don't proceed with API call + logger.error(f"[Video Generation] ❌ Pre-flight validation failed - blocking API call") + raise + finally: + db.close() + + logger.info(f"[Video Generation] ✅ Pre-flight validation passed - proceeding with video generation") + + # Generate video + model_name = kwargs.get("model", "tencent/HunyuanVideo") + try: + if provider == "huggingface": + video_bytes = _generate_with_huggingface( + prompt=prompt, + input_image_bytes=input_image_bytes, + **kwargs, + ) + elif provider == "gemini": + video_bytes = _generate_with_gemini(prompt=prompt, **kwargs) + elif provider == "openai": + video_bytes = _generate_with_openai(prompt=prompt, **kwargs) + else: + raise RuntimeError(f"Unknown video provider: {provider}") + + # Track usage AFTER successful generation + db_track = next(get_db()) + try: + from models.subscription_models import APIProvider, UsageSummary, APIUsageLog + from datetime import datetime + from services.subscription import PricingService + + # Create pricing service for tracking (uses same DB session) + pricing_service_track = PricingService(db_track) + + # Get current billing period + current_period = pricing_service_track.get_current_billing_period(user_id) or datetime.now().strftime("%Y-%m") + + # Get or create usage summary + usage_summary = db_track.query(UsageSummary).filter( + UsageSummary.user_id == user_id, + UsageSummary.billing_period == current_period + ).first() + + if not usage_summary: + usage_summary = UsageSummary( + user_id=user_id, + billing_period=current_period + ) + db_track.add(usage_summary) + db_track.commit() + + # Calculate cost using pricing service + cost_info = pricing_service_track.get_pricing_for_provider_model( + APIProvider.VIDEO, + model_name + ) + cost_per_video = cost_info.get('cost_per_request', 0.10) if cost_info else 0.10 + + # Get "before" state for unified log + current_video_calls_before = getattr(usage_summary, 'video_calls', 0) or 0 + current_video_cost = getattr(usage_summary, 'video_cost', 0.0) or 0.0 + + # Increment video_calls and track cost + new_video_calls = current_video_calls_before + 1 + usage_summary.video_calls = new_video_calls + usage_summary.video_cost = current_video_cost + cost_per_video + usage_summary.total_calls = (usage_summary.total_calls or 0) + 1 + usage_summary.total_cost = (usage_summary.total_cost or 0.0) + cost_per_video + + # Get plan details for unified log (before commit, in case commit fails) + limits = pricing_service_track.get_user_limits(user_id) + plan_name = limits.get('plan_name', 'unknown') if limits else 'unknown' + tier = limits.get('tier', 'unknown') if limits else 'unknown' + video_limit = limits['limits'].get("video_calls", 0) if limits else 0 + + # Get image and image editing stats for unified log + current_image_calls = getattr(usage_summary, "stability_calls", 0) or 0 + image_limit = limits['limits'].get("stability_calls", 0) if limits else 0 + current_image_edit_calls = getattr(usage_summary, "image_edit_calls", 0) or 0 + image_edit_limit = limits['limits'].get("image_edit_calls", 0) if limits else 0 + + # Create usage log entry for audit trail + usage_log = APIUsageLog( + user_id=user_id, + provider=APIProvider.VIDEO, + endpoint=f"/video-generation/{provider}", + method="POST", + model_used=model_name, + tokens_input=0, + tokens_output=0, + tokens_total=0, + cost_input=0.0, + cost_output=0.0, + cost_total=cost_per_video, + response_time=0.0, # Could track actual time if needed + status_code=200, + request_size=len(prompt.encode('utf-8')), + response_size=len(video_bytes), + billing_period=current_period + ) + db_track.add(usage_log) + + db_track.commit() + logger.info(f"[video_gen] ✅ Successfully tracked usage: user {user_id} -> 1 video call, ${cost_per_video:.4f} cost") + + # UNIFIED SUBSCRIPTION LOG - Shows before/after state in one message + # Flush immediately to ensure it's visible in console/logs + import sys + log_message = f""" +[SUBSCRIPTION] Video Generation +├─ User: {user_id} +├─ Plan: {plan_name} ({tier}) +├─ Provider: video +├─ Actual Provider: {provider} +├─ Model: {model_name or 'default'} +├─ Calls: {current_video_calls_before} → {new_video_calls} / {video_limit if video_limit > 0 else '∞'} +├─ Images: {current_image_calls} / {image_limit if image_limit > 0 else '∞'} +├─ Image Editing: {current_image_edit_calls} / {image_edit_limit if image_edit_limit > 0 else '∞'} +└─ Status: ✅ Allowed & Tracked +""" + print(log_message, flush=True) + sys.stdout.flush() + + except Exception as track_error: + logger.error(f"[video_gen] Error tracking usage: {track_error}", exc_info=True) + db_track.rollback() + # Don't fail video generation if tracking fails - video is already generated + finally: + db_track.close() + + return video_bytes + + except HTTPException: + # Re-raise HTTPExceptions (e.g., from validation or API errors) + raise + except Exception as e: + logger.error(f"[video_gen] Error during video generation: {e}", exc_info=True) + raise HTTPException(status_code=500, detail={"error": str(e)}) + + diff --git a/backend/services/story_writer/prompt_enhancer_service.py b/backend/services/story_writer/prompt_enhancer_service.py new file mode 100644 index 00000000..30f3aa6b --- /dev/null +++ b/backend/services/story_writer/prompt_enhancer_service.py @@ -0,0 +1,352 @@ +""" +Prompt Enhancement Service for HunyuanVideo Generation + +Uses AI to deeply understand story context and generate optimized +HunyuanVideo prompts following best practices with 7 components. +""" + +from typing import Dict, Any, List, Optional +from loguru import logger +from fastapi import HTTPException +from services.llm_providers.main_text_generation import llm_text_gen + + +class PromptEnhancerService: + """Service for generating HunyuanVideo-optimized prompts from story context.""" + + def __init__(self): + """Initialize the prompt enhancer service.""" + logger.info("[PromptEnhancer] Service initialized") + + def enhance_scene_prompt( + self, + current_scene: Dict[str, Any], + story_context: Dict[str, Any], + all_scenes: List[Dict[str, Any]], + user_id: str + ) -> str: + """ + Generate a HunyuanVideo-optimized prompt for a scene using two-stage AI analysis. + + Args: + current_scene: Scene data for the scene being processed + story_context: Complete story context (setup, premise, outline, story text) + all_scenes: List of all scenes for consistency analysis + user_id: Clerk user ID for subscription checking + + Returns: + str: Optimized HunyuanVideo prompt (300-500 words) with 7 components + """ + try: + logger.info(f"[PromptEnhancer] Enhancing prompt for scene {current_scene.get('scene_number', 'unknown')}") + + # Stage 1: Deep story context analysis + story_insights = self._analyze_story_context( + current_scene=current_scene, + story_context=story_context, + all_scenes=all_scenes, + user_id=user_id + ) + + # Stage 2: Generate optimized HunyuanVideo prompt + optimized_prompt = self._generate_hunyuan_prompt( + current_scene=current_scene, + story_context=story_context, + story_insights=story_insights, + all_scenes=all_scenes, + user_id=user_id + ) + + logger.info(f"[PromptEnhancer] Generated prompt length: {len(optimized_prompt)} characters") + return optimized_prompt + + except HTTPException as http_err: + # Propagate subscription limit errors (429) to frontend for modal display + # Only fallback for other HTTP errors (5xx, etc.) + if http_err.status_code == 429: + error_msg = self._extract_error_message(http_err) + logger.warning(f"[PromptEnhancer] Subscription limit exceeded (HTTP 429): {error_msg}") + # Re-raise to propagate to frontend for subscription modal + raise + else: + # For other HTTP errors, log and fallback + error_msg = self._extract_error_message(http_err) + logger.error(f"[PromptEnhancer] Error enhancing prompt (HTTP {http_err.status_code}): {error_msg}", exc_info=True) + return self._generate_fallback_prompt(current_scene, story_context) + except Exception as e: + logger.error(f"[PromptEnhancer] Error enhancing prompt: {str(e)}", exc_info=True) + # Fallback to basic prompt if enhancement fails + return self._generate_fallback_prompt(current_scene, story_context) + + def _analyze_story_context( + self, + current_scene: Dict[str, Any], + story_context: Dict[str, Any], + all_scenes: List[Dict[str, Any]], + user_id: str + ) -> str: + """ + Stage 1: Use AI to analyze complete story context and extract insights. + + Returns: + str: Story insights as JSON string for use in prompt generation + """ + # Build comprehensive context for analysis + analysis_prompt = f"""You are analyzing a complete story to extract key insights for AI video generation. + +**STORY SETUP:** +- Persona: {story_context.get('persona', 'N/A')} +- Setting: {story_context.get('story_setting', 'N/A')} +- Characters: {story_context.get('characters', 'N/A')} +- Plot Elements: {story_context.get('plot_elements', 'N/A')} +- Writing Style: {story_context.get('writing_style', 'N/A')} +- Tone: {story_context.get('story_tone', 'N/A')} +- Narrative POV: {story_context.get('narrative_pov', 'N/A')} +- Audience: {story_context.get('audience_age_group', 'N/A')} +- Content Rating: {story_context.get('content_rating', 'N/A')} + +**STORY PREMISE:** +{story_context.get('premise', 'N/A')} + +**STORY CONTENT:** +{story_context.get('story_content', 'N/A')[:2000]}... + +**ALL SCENES OVERVIEW:** +""" + # Add summary of all scenes + for idx, scene in enumerate(all_scenes, 1): + scene_num = scene.get('scene_number', idx) + analysis_prompt += f"\nScene {scene_num}: {scene.get('title', 'Untitled')}" + analysis_prompt += f"\n Description: {scene.get('description', '')[:150]}..." + analysis_prompt += f"\n Image Prompt: {scene.get('image_prompt', '')[:150]}..." + if scene.get('character_descriptions'): + chars = ', '.join(scene.get('character_descriptions', [])[:3]) + analysis_prompt += f"\n Characters: {chars}" + analysis_prompt += "\n" + + analysis_prompt += f""" +**CURRENT SCENE FOR VIDEO GENERATION:** +Scene {current_scene.get('scene_number', 'N/A')}: {current_scene.get('title', 'Untitled')} +Description: {current_scene.get('description', '')} +Image Prompt: {current_scene.get('image_prompt', '')} +Key Events: {', '.join(current_scene.get('key_events', [])[:5])} +Character Descriptions: {', '.join(current_scene.get('character_descriptions', [])[:5])} + +**YOUR TASK:** +Analyze this story and extract key insights for video generation. Focus on: +1. Narrative arc and position of current scene within it +2. Character consistency (how characters appear across scenes) +3. Visual style patterns from image prompts +4. Tone and atmosphere progression +5. Key themes and motifs +6. Visual narrative flow +7. Camera and composition needs for this specific scene + +Provide your analysis as structured insights that can guide prompt generation. +""" + + try: + insights = llm_text_gen( + prompt=analysis_prompt, + system_prompt="You are an expert story analyst specializing in visual narrative and cinematic storytelling. Provide detailed, actionable insights for video generation.", + user_id=user_id + ) + logger.debug(f"[PromptEnhancer] Story insights extracted: {insights[:200]}...") + return insights + except HTTPException as http_err: + # Propagate subscription limit errors (429) to frontend + if http_err.status_code == 429: + error_msg = self._extract_error_message(http_err) + logger.warning(f"[PromptEnhancer] Subscription limit exceeded during story analysis (HTTP 429): {error_msg}") + # Re-raise to propagate to frontend for subscription modal + raise + else: + # For other HTTP errors, log and fallback + error_msg = self._extract_error_message(http_err) + logger.warning(f"[PromptEnhancer] Story analysis failed (HTTP {http_err.status_code}): {error_msg}, using basic context") + return "Standard narrative flow with consistent character presentation" + except Exception as e: + logger.warning(f"[PromptEnhancer] Story analysis failed, using basic context: {str(e)}") + return "Standard narrative flow with consistent character presentation" + + def _generate_hunyuan_prompt( + self, + current_scene: Dict[str, Any], + story_context: Dict[str, Any], + story_insights: str, + all_scenes: List[Dict[str, Any]], + user_id: str + ) -> str: + """ + Stage 2: Generate scene-specific HunyuanVideo prompt with all 7 components. + + Returns: + str: Complete HunyuanVideo prompt (300-500 words) + """ + # Collect character descriptions across all scenes for consistency + all_characters = {} + for scene in all_scenes: + for char_desc in scene.get('character_descriptions', []): + if char_desc and char_desc not in all_characters: + all_characters[char_desc] = scene.get('scene_number', 0) + + # Collect image prompts for visual style reference + image_prompts = [scene.get('image_prompt', '') for scene in all_scenes if scene.get('image_prompt')] + + # Determine scene position in narrative arc + current_scene_num = current_scene.get('scene_number', 0) + total_scenes = len(all_scenes) + scene_position = "beginning" if current_scene_num <= total_scenes // 3 else ("middle" if current_scene_num <= 2 * total_scenes // 3 else "climax") + + prompt_generation_request = f"""Generate a professional HunyuanVideo prompt for this story scene. + +**STORY INSIGHTS (from deep analysis):** +{story_insights} + +**STORY SETUP:** +- Setting: {story_context.get('story_setting', 'N/A')} +- Tone: {story_context.get('story_tone', 'N/A')} +- Style: {story_context.get('writing_style', 'N/A')} +- Audience: {story_context.get('audience_age_group', 'N/A')} + +**VISUAL STYLE REFERENCE (from generated images):** +{chr(10).join([f"- {prompt[:100]}..." for prompt in image_prompts[:3]])} + +**CHARACTER CONSISTENCY (across all scenes):** +{chr(10).join([f"- {char}" for char in list(all_characters.keys())[:5]])} + +**CURRENT SCENE DETAILS:** +- Scene {current_scene.get('scene_number', 'N/A')} of {total_scenes} (narrative position: {scene_position}) +- Title: {current_scene.get('title', 'Untitled')} +- Description: {current_scene.get('description', '')} +- Image Prompt: {current_scene.get('image_prompt', '')} +- Key Events: {', '.join(current_scene.get('key_events', [])[:5])} +- Characters in scene: {', '.join(current_scene.get('character_descriptions', [])[:5])} +- Audio Narration: {current_scene.get('audio_narration', '')[:200]} + +**REQUIREMENTS:** +Create a comprehensive HunyuanVideo prompt (300-500 words) following the 7-component structure: + +1. **SUBJECT**: Clearly define the main focus - characters, objects, or action. Include character descriptions that match the visual style from image prompts and maintain consistency across scenes. + +2. **SCENE**: Describe the environment and setting. Ensure it matches the story_setting and aligns with the visual style established in previous scenes. + +3. **MOTION**: Detail the specific actions and movements. Reference key_events and ensure motion fits the narrative flow and story_insights about the scene's position in the arc. + +4. **CAMERA MOVEMENT**: Specify cinematic camera work appropriate for this moment in the story. Consider the narrative position ({scene_position}) - use establishing shots for beginning, dynamic shots for climax. + +5. **ATMOSPHERE**: Set the emotional tone. This should reflect the story_tone but also consider where we are in the narrative arc based on story_insights. + +6. **LIGHTING**: Define lighting that matches the visual style from image prompts and supports the atmosphere. Ensure consistency with the established visual aesthetic. + +7. **SHOT COMPOSITION**: Describe framing and composition that serves the visual narrative. Consider the story's visual style and ensure it flows naturally with the overall story. + +Write the prompt as a flowing, detailed description (not a list) that integrates all 7 components naturally. Make it vivid, cinematic, and consistent with the story's established visual and narrative style. The prompt should be between 300-500 words. +""" + + try: + optimized_prompt = llm_text_gen( + prompt=prompt_generation_request, + system_prompt="You are an expert video prompt engineer specializing in HunyuanVideo text-to-video generation. Create detailed, cinematic prompts that follow best practices and ensure high-quality video output.", + user_id=user_id + ) + + # Clean up and validate prompt length + optimized_prompt = optimized_prompt.strip() + word_count = len(optimized_prompt.split()) + + if word_count < 200: + logger.warning(f"[PromptEnhancer] Generated prompt is too short ({word_count} words), enhancing...") + # Add more detail if too short + optimized_prompt += self._add_cinematic_details(current_scene, story_context) + elif word_count > 600: + logger.warning(f"[PromptEnhancer] Generated prompt is too long ({word_count} words), trimming...") + # Trim if too long (keep first ~500 words) + words = optimized_prompt.split() + optimized_prompt = ' '.join(words[:500]) + + logger.info(f"[PromptEnhancer] Generated prompt: {len(optimized_prompt.split())} words") + return optimized_prompt + + except HTTPException as http_err: + # Propagate subscription limit errors (429) to frontend + if http_err.status_code == 429: + error_msg = self._extract_error_message(http_err) + logger.warning(f"[PromptEnhancer] Subscription limit exceeded during prompt generation (HTTP 429): {error_msg}") + # Re-raise to propagate to frontend for subscription modal + raise + else: + # For other HTTP errors, log and fallback + error_msg = self._extract_error_message(http_err) + logger.error(f"[PromptEnhancer] Prompt generation failed (HTTP {http_err.status_code}): {error_msg}", exc_info=True) + return self._generate_fallback_prompt(current_scene, story_context) + except Exception as e: + logger.error(f"[PromptEnhancer] Prompt generation failed: {str(e)}", exc_info=True) + return self._generate_fallback_prompt(current_scene, story_context) + + def _add_cinematic_details( + self, + current_scene: Dict[str, Any], + story_context: Dict[str, Any] + ) -> str: + """Add cinematic details to enhance a too-short prompt.""" + return f""" + +The scene unfolds with careful attention to visual storytelling. The {story_context.get('story_setting', 'environment')} serves as more than background - it actively participates in the narrative. Lighting and composition work together to emphasize the emotional weight of this moment, with camera movements that guide the viewer's attention naturally through the space. Every element - from the way light falls to the positioning of characters - contributes to the overall narrative impact. +""" + + def _extract_error_message(self, http_err: HTTPException) -> str: + """ + Extract meaningful error message from HTTPException. + + Handles both dict-based details (from subscription limit errors) and string details. + """ + if isinstance(http_err.detail, dict): + # For subscription limit errors, extract the 'message' or 'error' field + return http_err.detail.get('message') or http_err.detail.get('error') or str(http_err.detail) + elif isinstance(http_err.detail, str): + return http_err.detail + else: + return str(http_err.detail) + + def _generate_fallback_prompt( + self, + current_scene: Dict[str, Any], + story_context: Dict[str, Any] + ) -> str: + """Generate a basic fallback prompt if AI enhancement fails.""" + scene_title = current_scene.get('title', 'Untitled Scene') + scene_desc = current_scene.get('description', '') + image_prompt = current_scene.get('image_prompt', '') + setting = story_context.get('story_setting', 'the scene') + tone = story_context.get('story_tone', 'engaging') + + return f"""A cinematic scene titled "{scene_title}" set in {setting}. {scene_desc[:200]}. +The scene features {', '.join(current_scene.get('character_descriptions', [])[:2]) if current_scene.get('character_descriptions') else 'the main characters'}. +Visual style follows: {image_prompt[:150]}. +The {tone} atmosphere is enhanced by natural lighting and dynamic camera movements that follow the action. +Shot composition emphasizes the narrative importance of this moment, with careful framing that draws attention to key elements. +The scene maintains visual consistency with previous moments while advancing the story's visual narrative.""" + + +def enhance_scene_prompt_for_video( + current_scene: Dict[str, Any], + story_context: Dict[str, Any], + all_scenes: List[Dict[str, Any]], + user_id: str +) -> str: + """ + Convenience function to enhance a scene prompt for HunyuanVideo generation. + + Args: + current_scene: Scene data for the scene being processed + story_context: Complete story context dictionary + all_scenes: List of all scenes for consistency + user_id: Clerk user ID for subscription checking + + Returns: + str: Optimized HunyuanVideo prompt + """ + service = PromptEnhancerService() + return service.enhance_scene_prompt(current_scene, story_context, all_scenes, user_id) + diff --git a/backend/services/story_writer/video_generation_service.py b/backend/services/story_writer/video_generation_service.py index 09f6b599..dab46b2a 100644 --- a/backend/services/story_writer/video_generation_service.py +++ b/backend/services/story_writer/video_generation_service.py @@ -41,6 +41,47 @@ class StoryVideoGenerationService: unique_id = str(uuid.uuid4())[:8] return f"story_{clean_title}_{unique_id}.mp4" + def save_scene_video(self, video_bytes: bytes, scene_number: int, user_id: str) -> Dict[str, str]: + """ + Save individual scene video bytes to file. + + Parameters: + video_bytes: Raw video file bytes (mp4/webm format) + scene_number: Scene number for naming + user_id: Clerk user ID for naming + + Returns: + Dict[str, str]: Video metadata with video_url and video_filename + """ + try: + # Generate filename with scene number and user ID + clean_user_id = "".join(c if c.isalnum() or c in ('-', '_') else '_' for c in user_id[:16]) + timestamp = str(uuid.uuid4())[:8] + filename = f"scene_{scene_number}_{clean_user_id}_{timestamp}.mp4" + + video_path = self.output_dir / filename + + # Write video bytes to file + with open(video_path, 'wb') as f: + f.write(video_bytes) + + file_size = video_path.stat().st_size + logger.info(f"[StoryVideoGeneration] Saved scene {scene_number} video: {filename} ({file_size} bytes)") + + # Generate URL path (relative to /api/story/videos/) + video_url = f"/api/story/videos/{filename}" + + return { + "video_filename": filename, + "video_url": video_url, + "video_path": str(video_path), + "file_size": file_size + } + + except Exception as e: + logger.error(f"[StoryVideoGeneration] Error saving scene video: {e}", exc_info=True) + raise RuntimeError(f"Failed to save scene video: {str(e)}") from e + def generate_scene_video( self, scene: Dict[str, Any], @@ -125,12 +166,12 @@ class StoryVideoGenerationService: # Use provided duration or audio duration video_duration = duration if duration is not None else audio_duration - # Create image clip - image_clip = ImageClip(str(image_file)).set_duration(video_duration) - image_clip = image_clip.set_fps(fps) + # Create image clip (MoviePy v2: use with_* API) + image_clip = ImageClip(str(image_file)).with_duration(video_duration) + image_clip = image_clip.with_fps(fps) # Set audio to image clip - video_clip = image_clip.set_audio(audio_clip) + video_clip = image_clip.with_audio(audio_clip) # Generate video filename video_filename = f"scene_{scene_number}_{scene_title.replace(' ', '_').replace('/', '_')[:50]}_{uuid.uuid4().hex[:8]}.mp4" @@ -274,12 +315,12 @@ class StoryVideoGenerationService: audio_clip = AudioFileClip(str(audio_file)) audio_duration = audio_clip.duration - # Create image clip - image_clip = ImageClip(str(image_file)).set_duration(audio_duration) - image_clip = image_clip.set_fps(fps) + # Create image clip (MoviePy v2: use with_* API) + image_clip = ImageClip(str(image_file)).with_duration(audio_duration) + image_clip = image_clip.with_fps(fps) # Set audio to image clip - video_clip = image_clip.set_audio(audio_clip) + video_clip = image_clip.with_audio(audio_clip) scene_clips.append(video_clip) total_duration += audio_duration diff --git a/backend/services/story_writer/video_preflight.py b/backend/services/story_writer/video_preflight.py new file mode 100644 index 00000000..41870d34 --- /dev/null +++ b/backend/services/story_writer/video_preflight.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from loguru import logger + + +def log_video_stack_diagnostics() -> None: + try: + import sys + import platform + import importlib + + mv = importlib.import_module("moviepy") + im = importlib.import_module("imageio") + try: + import imageio_ffmpeg as iff + ff = iff.get_ffmpeg_exe() + except Exception: + ff = "unresolved" + logger.info( + "[VideoStack] py={} plat={} moviepy={} imageio={} ffmpeg={}", + sys.executable, + platform.platform(), + getattr(mv, "__version__", "NA"), + getattr(im, "__version__", "NA"), + ff, + ) + except Exception as e: + logger.error("[VideoStack] diagnostics failed: {}", e) + + +def assert_supported_moviepy() -> None: + """Fail fast if MoviePy isn't version 2.x.""" + try: + import pkg_resources as pr + mv = pr.get_distribution("moviepy").version + if not mv.startswith("2."): + raise RuntimeError( + f"Unsupported MoviePy version {mv}. Expected 2.x. " + "Please install with: pip install moviepy==2.1.2" + ) + except Exception as e: + # Log and re-raise so startup fails clearly + logger.error("[VideoStack] version check failed: {}", e) + raise + + diff --git a/backend/services/subscription/limit_validation.py b/backend/services/subscription/limit_validation.py index b50b93aa..ac45be5e 100644 --- a/backend/services/subscription/limit_validation.py +++ b/backend/services/subscription/limit_validation.py @@ -694,6 +694,44 @@ class LimitValidator: total_images = projected_images + # Check video generation limits + elif provider == APIProvider.VIDEO: + video_limit = limits.get('video_calls', 0) or 0 + total_video_calls = usage.video_calls or 0 + projected_video_calls = total_video_calls + 1 + + if video_limit > 0 and projected_video_calls > video_limit: + error_info = { + 'current_calls': total_video_calls, + 'limit': video_limit, + 'provider': 'video', + 'operation_type': operation_type, + 'operation_index': op_idx + } + return False, f"Video generation limit would be exceeded. Would use {projected_video_calls} of {video_limit} videos this billing period.", { + 'error_type': 'video_limit', + 'usage_info': error_info + } + + # Check image editing limits + elif provider == APIProvider.IMAGE_EDIT: + image_edit_limit = limits.get('image_edit_calls', 0) or 0 + total_image_edit_calls = getattr(usage, 'image_edit_calls', 0) or 0 + projected_image_edit_calls = total_image_edit_calls + 1 + + if image_edit_limit > 0 and projected_image_edit_calls > image_edit_limit: + error_info = { + 'current_calls': total_image_edit_calls, + 'limit': image_edit_limit, + 'provider': 'image_edit', + 'operation_type': operation_type, + 'operation_index': op_idx + } + return False, f"Image editing limit would be exceeded. Would use {projected_image_edit_calls} of {image_edit_limit} image edits this billing period.", { + 'error_type': 'image_edit_limit', + 'usage_info': error_info + } + # Check other provider-specific limits else: provider_calls_key = f"{provider_name}_calls" diff --git a/backend/services/subscription/preflight_validator.py b/backend/services/subscription/preflight_validator.py index 4df873bb..7b341c81 100644 --- a/backend/services/subscription/preflight_validator.py +++ b/backend/services/subscription/preflight_validator.py @@ -299,3 +299,124 @@ def validate_image_generation_operations( } ) + +def validate_image_editing_operations( + pricing_service: PricingService, + user_id: str +) -> None: + """ + Validate image editing operation before making API calls. + + Args: + pricing_service: PricingService instance + user_id: User ID for subscription checking + + Returns: + None - raises HTTPException with 429 status if validation fails + """ + try: + operations_to_validate = [ + { + 'provider': APIProvider.IMAGE_EDIT, + 'tokens_requested': 0, + 'actual_provider_name': 'image_edit', + 'operation_type': 'image_editing' + } + ] + + can_proceed, message, error_details = pricing_service.check_comprehensive_limits( + user_id=user_id, + operations=operations_to_validate + ) + + if not can_proceed: + logger.error(f"[Pre-flight Validator] Image editing blocked for user {user_id}: {message}") + + usage_info = error_details.get('usage_info', {}) if error_details else {} + provider = usage_info.get('provider', 'image_edit') if usage_info else 'image_edit' + + raise HTTPException( + status_code=429, + detail={ + 'error': message, + 'message': message, + 'provider': provider, + 'usage_info': usage_info if usage_info else error_details + } + ) + + logger.info(f"[Pre-flight Validator] ✅ Image editing validated for user {user_id}") + # Validation passed - no return needed (function raises HTTPException if validation fails) + + except HTTPException: + raise + except Exception as e: + logger.error(f"[Pre-flight Validator] Error validating image editing: {e}", exc_info=True) + raise HTTPException( + status_code=500, + detail={ + 'error': f"Failed to validate image editing: {str(e)}", + 'message': f"Failed to validate image editing: {str(e)}" + } + ) + + +def validate_video_generation_operations( + pricing_service: PricingService, + user_id: str +) -> None: + """ + Validate video generation operation before making API calls. + + Args: + pricing_service: PricingService instance + user_id: User ID for subscription checking + + Returns: + None - raises HTTPException with 429 status if validation fails + """ + try: + operations_to_validate = [ + { + 'provider': APIProvider.VIDEO, + 'tokens_requested': 0, + 'actual_provider_name': 'video', + 'operation_type': 'video_generation' + } + ] + + can_proceed, message, error_details = pricing_service.check_comprehensive_limits( + user_id=user_id, + operations=operations_to_validate + ) + + if not can_proceed: + logger.error(f"[Pre-flight Validator] Video generation blocked for user {user_id}: {message}") + + usage_info = error_details.get('usage_info', {}) if error_details else {} + provider = usage_info.get('provider', 'video') if usage_info else 'video' + + raise HTTPException( + status_code=429, + detail={ + 'error': message, + 'message': message, + 'provider': provider, + 'usage_info': usage_info if usage_info else error_details + } + ) + + logger.info(f"[Pre-flight Validator] ✅ Video generation validated for user {user_id}") + # Validation passed - no return needed (function raises HTTPException if validation fails) + + except HTTPException: + raise + except Exception as e: + logger.error(f"[Pre-flight Validator] Error validating video generation: {e}", exc_info=True) + raise HTTPException( + status_code=500, + detail={ + 'error': f"Failed to validate video generation: {str(e)}", + 'message': f"Failed to validate video generation: {str(e)}" + } + ) diff --git a/backend/services/subscription/pricing_service.py b/backend/services/subscription/pricing_service.py index bce611ad..aca345fa 100644 --- a/backend/services/subscription/pricing_service.py +++ b/backend/services/subscription/pricing_service.py @@ -295,10 +295,22 @@ class PricingService: "model_name": "exa-search", "cost_per_request": 0.005, # $0.005 per search (1-25 results) "description": "Exa Neural Search API" + }, + { + "provider": APIProvider.VIDEO, + "model_name": "tencent/HunyuanVideo", + "cost_per_request": 0.10, # $0.10 per video generation (estimated) + "description": "HuggingFace AI Video Generation (HunyuanVideo)" + }, + { + "provider": APIProvider.VIDEO, + "model_name": "default", + "cost_per_request": 0.10, # $0.10 per video generation (estimated) + "description": "AI Video Generation default pricing" } ] - # Combine all pricing data + # Combine all pricing data (include video pricing in search_pricing list) all_pricing = gemini_pricing + openai_pricing + anthropic_pricing + mistral_pricing + search_pricing # Insert or update pricing data @@ -344,6 +356,8 @@ class PricingService: "firecrawl_calls_limit": 10, "stability_calls_limit": 5, "exa_calls_limit": 100, + "video_calls_limit": 0, # No video generation for free tier + "image_edit_calls_limit": 10, # 10 AI image editing calls/month "gemini_tokens_limit": 100000, "monthly_cost_limit": 0.0, "features": ["basic_content_generation", "limited_research"], @@ -365,6 +379,8 @@ class PricingService: "firecrawl_calls_limit": 100, "stability_calls_limit": 5, "exa_calls_limit": 500, + "video_calls_limit": 20, # 20 videos/month for basic plan + "image_edit_calls_limit": 30, # 30 AI image editing calls/month "gemini_tokens_limit": 20000, # Increased from 5000 for better stability "openai_tokens_limit": 20000, # Increased from 5000 for better stability "anthropic_tokens_limit": 20000, # Increased from 5000 for better stability @@ -388,6 +404,8 @@ class PricingService: "firecrawl_calls_limit": 500, "stability_calls_limit": 200, "exa_calls_limit": 2000, + "video_calls_limit": 50, # 50 videos/month for pro plan + "image_edit_calls_limit": 100, # 100 AI image editing calls/month "gemini_tokens_limit": 5000000, "openai_tokens_limit": 2500000, "anthropic_tokens_limit": 1000000, @@ -411,6 +429,8 @@ class PricingService: "firecrawl_calls_limit": 0, "stability_calls_limit": 0, "exa_calls_limit": 0, # Unlimited + "video_calls_limit": 0, # Unlimited for enterprise + "image_edit_calls_limit": 0, # Unlimited image editing for enterprise "gemini_tokens_limit": 0, "openai_tokens_limit": 0, "anthropic_tokens_limit": 0, @@ -429,6 +449,20 @@ class PricingService: if not existing: plan = SubscriptionPlan(**plan_data) self.db.add(plan) + else: + # Update existing plan with new limits (e.g., image_edit_calls_limit) + # This ensures existing plans get new columns like image_edit_calls_limit + for key, value in plan_data.items(): + if key not in ["name", "tier"]: # Don't overwrite name/tier + try: + # Try to set the attribute (works even if column was just added) + setattr(existing, key, value) + except (AttributeError, Exception) as e: + # If attribute doesn't exist yet (column not migrated), skip it + # Schema migration will add it, then this will update it on next run + logger.debug(f"Could not set {key} on plan {existing.name}: {e}") + existing.updated_at = datetime.utcnow() + logger.debug(f"Updated existing plan: {existing.name}") self.db.commit() logger.debug("Default subscription plans initialized") @@ -615,6 +649,8 @@ class PricingService: 'metaphor_calls': plan.metaphor_calls_limit, 'firecrawl_calls': plan.firecrawl_calls_limit, 'stability_calls': plan.stability_calls_limit, + 'video_calls': getattr(plan, 'video_calls_limit', 0), # Support missing column + 'image_edit_calls': getattr(plan, 'image_edit_calls_limit', 0), # Support missing column # Token limits 'gemini_tokens': plan.gemini_tokens_limit, 'openai_tokens': plan.openai_tokens_limit, diff --git a/backend/services/subscription/schema_utils.py b/backend/services/subscription/schema_utils.py index cca9faf1..1dd65935 100644 --- a/backend/services/subscription/schema_utils.py +++ b/backend/services/subscription/schema_utils.py @@ -29,6 +29,8 @@ def ensure_subscription_plan_columns(db: Session) -> None: # Columns we may reference in models but might be missing in older DBs required_columns = { "exa_calls_limit": "INTEGER DEFAULT 0", + "video_calls_limit": "INTEGER DEFAULT 0", + "image_edit_calls_limit": "INTEGER DEFAULT 0", } for col_name, ddl in required_columns.items(): @@ -78,6 +80,10 @@ def ensure_usage_summaries_columns(db: Session) -> None: required_columns = { "exa_calls": "INTEGER DEFAULT 0", "exa_cost": "REAL DEFAULT 0.0", + "video_calls": "INTEGER DEFAULT 0", + "video_cost": "REAL DEFAULT 0.0", + "image_edit_calls": "INTEGER DEFAULT 0", + "image_edit_cost": "REAL DEFAULT 0.0", } for col_name, ddl in required_columns.items(): diff --git a/backend/services/subscription/usage_tracking_service.py b/backend/services/subscription/usage_tracking_service.py index 98d54e56..5b38cb6c 100644 --- a/backend/services/subscription/usage_tracking_service.py +++ b/backend/services/subscription/usage_tracking_service.py @@ -608,6 +608,12 @@ class UsageTrackingService: # Reset image generation counters summary.stability_calls = 0 + # Reset video generation counters + summary.video_calls = 0 + + # Reset image editing counters + summary.image_edit_calls = 0 + # Reset cost counters summary.gemini_cost = 0.0 summary.openai_cost = 0.0 @@ -618,6 +624,9 @@ class UsageTrackingService: summary.metaphor_cost = 0.0 summary.firecrawl_cost = 0.0 summary.stability_cost = 0.0 + summary.exa_cost = 0.0 + summary.video_cost = 0.0 + summary.image_edit_cost = 0.0 # Reset totals summary.total_calls = 0 diff --git a/backend/start_alwrity_backend.py b/backend/start_alwrity_backend.py index 1078d0a4..6fc90700 100644 --- a/backend/start_alwrity_backend.py +++ b/backend/start_alwrity_backend.py @@ -161,9 +161,29 @@ def start_backend(enable_reload=False, production_mode=False): # Set up clean logging for end users from logging_config import setup_clean_logging, get_uvicorn_log_level + # Video stack preflight (diagnostics + version assert) + try: + from services.story_writer.video_preflight import ( + log_video_stack_diagnostics, + assert_supported_moviepy, + ) + except Exception: + # Preflight is optional; continue if module missing + log_video_stack_diagnostics = None + assert_supported_moviepy = None verbose_mode = setup_clean_logging() uvicorn_log_level = get_uvicorn_log_level() + + # Log diagnostics and assert versions (fail fast if misconfigured) + try: + if log_video_stack_diagnostics: + log_video_stack_diagnostics() + if assert_supported_moviepy: + assert_supported_moviepy() + except Exception as _video_stack_err: + print(f"[ERROR] Video stack preflight failed: {_video_stack_err}") + return False uvicorn.run( "app:app", diff --git a/docs-site/docs/features/ai/assistive-writing.md b/docs-site/docs/features/ai/assistive-writing.md index 24505635..d58e8414 100644 --- a/docs-site/docs/features/ai/assistive-writing.md +++ b/docs-site/docs/features/ai/assistive-writing.md @@ -5,10 +5,38 @@ ALwrity's Assistive Writing feature revolutionizes content creation by providing ## Visuals

- Assistive writing selection tools - Inline fact checking and quick edits + Assistive writing selection tools + Inline fact checking and quick edits

+## Quick Reference + +1. Enable: Toggle “Assistive Writing” in the LinkedIn Writer header +2. Write: Type at least 5 words +3. Wait: 5 seconds for the first automatic suggestion +4. Accept/Dismiss: Use buttons in the suggestion card + +### How It Works +- First suggestion: Automatic (5 words + 5 seconds) +- More suggestions: Click “Continue writing” +- Daily limit: 50 suggestions (resets every 24 hours) + +### Best Practices +- Write specific, clear content +- Review source links before accepting +- Use manual “Continue writing” for additional suggestions +- Don’t expect suggestions for very short text +- Don’t ignore source verification + +### Common Issues (Quick Table) + +| Problem | Solution | +| --- | --- | +| No suggestions | Write 5+ words, then wait 5 seconds | +| “API quota exceeded” | Wait 24 hours or upgrade plan | +| “No relevant sources” | Be more specific in your writing | +| Suggestions not relevant | Try different wording or topics | + ## What is Assistive Writing? Assistive Writing is an AI-powered feature that provides real-time writing assistance, suggestions, and enhancements to help you create compelling content. It combines advanced natural language processing with contextual understanding to offer intelligent recommendations that improve your writing quality and efficiency. @@ -160,6 +188,67 @@ Assistive Writing is an AI-powered feature that provides real-time writing assis - **Message Alignment**: Align with brand messaging - **Value Integration**: Incorporate brand values +## Workflow + +```text +1. ENABLE ASSISTIVE WRITING + ┌─────────────────────────┐ + │ Toggle "Assistive │ + │ Writing" ON (blue) │ + └─────────────────────────┘ + │ + ▼ + +2. START WRITING + ┌─────────────────────────┐ + │ Type at least 5 words │ + │ in the text area │ + └─────────────────────────┘ + │ + ▼ + +3. WAIT FOR AI ANALYSIS + ┌─────────────────────────┐ + │ Wait 5 seconds │ + │ AI analyzes your text │ + └─────────────────────────┘ + │ + ▼ + +4. RECEIVE FIRST SUGGESTION + ┌─────────────────────────┐ + │ Suggestion card appears │ + │ near your cursor │ + │ │ + │ [Accept] [Dismiss] │ + └─────────────────────────┘ + │ + ▼ + +5. AFTER FIRST SUGGESTION + ┌─────────────────────────┐ + │ "Continue writing" │ + │ prompt appears │ + │ │ + │ [Continue writing] │ + │ [Dismiss] │ + └─────────────────────────┘ + │ + ▼ + +6. MANUAL SUGGESTIONS + ┌─────────────────────────┐ + │ Click "Continue writing"│ + │ to get more suggestions │ + │ (saves costs) │ + └─────────────────────────┘ +``` + +### Step-by-Step +- Enable → Start writing (5+ words) → Wait 5s +- First suggestion shows: suggested text, confidence score, source links, Accept/Dismiss +- After first suggestion, trigger more via “Continue writing” + ## Integration with Other Features ### Blog Writer Integration diff --git a/docs-site/docs/features/ai/grounding-ui.md b/docs-site/docs/features/ai/grounding-ui.md index 19e0747c..eeab31f7 100644 --- a/docs-site/docs/features/ai/grounding-ui.md +++ b/docs-site/docs/features/ai/grounding-ui.md @@ -5,13 +5,47 @@ ALwrity's Grounding UI feature provides AI-powered content verification and fact ## Visuals

- Inline fact checking with citations and claim statuses + Inline fact checking with citations and claim statuses

## What is Grounding UI? Grounding UI is an intelligent content verification system that connects AI-generated content with real-world data sources, ensuring accuracy and reliability. It provides visual indicators, source citations, and verification status to help you create trustworthy, fact-checked content. +## Implementation Overview (Concise) + +- Backend service: `backend/services/hallucination_detector.py` (claim extraction → evidence search → verification) +- Models: `backend/models/hallucination_models.py` +- API router: `backend/api/hallucination_detector.py` (registered in `backend/app.py`) +- Frontend service: `frontend/src/services/hallucinationDetectorService.ts` +- UI: LinkedIn Writer selection menu + FactCheckResults modal + +### API Endpoints (Summary) +- `POST /api/hallucination-detector/detect` – main fact-checking +- `POST /api/hallucination-detector/extract-claims` – claims only +- `POST /api/hallucination-detector/verify-claim` – single claim +- `GET /api/hallucination-detector/health` – health check + +### Minimal Setup +- Backend env: + - `EXA_API_KEY=...` (evidence search) + - `OPENAI_API_KEY=...` (claim extraction + verification) +- Frontend env: + - `REACT_APP_API_URL=http://localhost:8000` + +### Quick Usage (UI) +1) In LinkedIn Writer, select a passage (10+ chars) +2) Click “Check Facts” in the selection menu +3) Review claims, assessments (supported/refuted/insufficient), confidence, and sources in the results modal + +### Quick Usage (API) + +```bash +curl -X POST "$API_URL/api/hallucination-detector/detect" \ + -H "Content-Type: application/json" \ + -d '{"text":"The Eiffel Tower is in Paris and built in 1889.","include_sources":true,"max_claims":5}' +``` + ### Key Benefits - **Content Verification**: Verify facts and claims in real-time @@ -274,6 +308,11 @@ Grounding UI is an intelligent content verification system that connects AI-gene - **Display Problems**: Fix visual indicator issues - **Integration Errors**: Resolve integration problems +### Environment & Health +- “EXA_API_KEY not found” → add key to backend `.env`, restart server +- “OpenAI API key not found” → add `OPENAI_API_KEY`, verify credits +- Health check: `GET /api/hallucination-detector/health` + ### Getting Help #### Support Resources diff --git a/docs-site/docs/features/integrations/wix/api.md b/docs-site/docs/features/integrations/wix/api.md new file mode 100644 index 00000000..0355ef1e --- /dev/null +++ b/docs-site/docs/features/integrations/wix/api.md @@ -0,0 +1,58 @@ +# API (Summary) + +Short reference of Wix integration endpoints exposed by ALwrity’s backend. + +## Authentication +### Get Authorization URL +```http +GET /api/wix/auth/url?state=optional_state +``` + +### OAuth Callback +```http +POST /api/wix/auth/callback +Content-Type: application/json +{ + "code": "authorization_code", + "state": "optional_state" +} +``` + +## Connection +### Status +```http +GET /api/wix/connection/status +``` + +### Disconnect +```http +POST /api/wix/disconnect +``` + +## Publishing +### Publish blog post +```http +POST /api/wix/publish +Content-Type: application/json +{ + "title": "Blog Post Title", + "content": "Markdown", + "cover_image_url": "https://example.com/image.jpg", + "category_ids": ["category_id"], + "tag_ids": ["tag_id_1", "tag_id_2"], + "publish": true +} +``` + +## Content Management +### Categories +```http +GET /api/wix/categories +``` + +### Tags +```http +GET /api/wix/tags +``` + + diff --git a/docs-site/docs/features/integrations/wix/overview.md b/docs-site/docs/features/integrations/wix/overview.md new file mode 100644 index 00000000..077998c5 --- /dev/null +++ b/docs-site/docs/features/integrations/wix/overview.md @@ -0,0 +1,33 @@ +# Wix Integration (Overview) + +ALwrity’s Wix integration lets you publish AI‑generated blogs directly to your Wix site, including categories/tags and SEO metadata. + +## What’s Included +- OAuth connection (Headless OAuth – Client ID only) +- Markdown → Wix Ricos JSON conversion +- Image import to Wix Media Manager +- Blog creation and publish (draft or published) +- Categories/tags lookup + auto-create +- SEO metadata posting (keywords, meta, OG, Twitter, canonical) + +## High-level Flow +1. Connect your Wix account (OAuth) +2. Convert blog content to Ricos JSON +3. Import images +4. Create blog post +5. Publish and return URL + +## Benefits +- One-click publishing from ALwrity +- Preserves formatting and images +- Posts complete SEO metadata +- Clear error handling and feedback + +See also: +- Setup: setup.md +- Publishing: publishing.md +- API: api.md +- SEO Metadata: seo-metadata.md +- Testing (Bypass): testing-bypass.md + + diff --git a/docs-site/docs/features/integrations/wix/publishing.md b/docs-site/docs/features/integrations/wix/publishing.md new file mode 100644 index 00000000..5b914932 --- /dev/null +++ b/docs-site/docs/features/integrations/wix/publishing.md @@ -0,0 +1,28 @@ +# Publishing Flow + +End‑to‑end flow for publishing a blog post to Wix. + +## Steps +1. Check connection (tokens + `memberId`) +2. Convert markdown → Ricos JSON +3. Import images to Wix Media Manager +4. Create blog post via Wix Blog API +5. Publish (or save draft) +6. Return URL + +## From the Blog Writer +- Generate content in ALwrity +- Use “Publish to Wix” action +- The publisher will: + - Verify connection + - Convert content + - Import images + - Create & publish + - Return published URL + +## Notes +- Categories and tags are looked up/created automatically +- SEO metadata is posted with the blog (see SEO Metadata) +- Errors are reported with actionable messages + + diff --git a/docs-site/docs/features/integrations/wix/seo-metadata.md b/docs-site/docs/features/integrations/wix/seo-metadata.md new file mode 100644 index 00000000..d14dc6e3 --- /dev/null +++ b/docs-site/docs/features/integrations/wix/seo-metadata.md @@ -0,0 +1,52 @@ +# SEO Metadata (Wix) + +This page summarizes what ALwrity posts to Wix and what remains out of scope. + +## Posted to Wix +- Keywords (seoData.settings.keywords) + - Main keyword: `focus_keyword` → `isMain: true` + - Additional: `blog_tags`, `social_hashtags` → `isMain: false` +- Meta Tags (seoData.tags) + - `` from `meta_description` + - `` from `seo_title` +- Open Graph (seoData.tags) + - `og:title`, `og:description`, `og:image`, `og:type=article`, `og:url` +- Twitter Card (seoData.tags) + - `twitter:title`, `twitter:description`, `twitter:image`, `twitter:card` +- Canonical URL (seoData.tags) + - `` +- Categories & Tags + - Auto‑lookup/create and post as `categoryIds` and `tagIds` + +## Not Posted (Limitations) +- JSON‑LD structured data + - Reason: Requires Wix site frontend (`@wix/site-seo`) +- URL slug customization + - Wix auto‑generates from title +- Reading time / optimization score + - Internal metadata, not part of Wix post + +## Conversion +- Markdown → Ricos JSON via official API (with custom parser fallback) +- Supports headings, paragraphs, lists, images, basic formatting + +## Example (structure excerpt) +```json +{ + "draftPost": { + "title": "SEO optimized title", + "memberId": "author-member-id", + "richContent": { /* Ricos JSON */ }, + "excerpt": "First 200 chars...", + "categoryIds": ["uuid1"], + "tagIds": ["uuid1","uuid2"], + "seoData": { + "settings": { "keywords": [ { "term": "main", "isMain": true } ] }, + "tags": [ { "type": "meta", "props": { "name": "description", "content": "..." } } ] + } + }, + "publish": true +} +``` + + diff --git a/docs-site/docs/features/integrations/wix/setup.md b/docs-site/docs/features/integrations/wix/setup.md new file mode 100644 index 00000000..41e69a98 --- /dev/null +++ b/docs-site/docs/features/integrations/wix/setup.md @@ -0,0 +1,40 @@ +# Wix Integration Setup + +## Wix App Configuration +1. Go to Wix Developers and create an app +2. Set redirect URI: `http://localhost:3000/wix/callback` (dev) +3. Scopes: `BLOG.CREATE-DRAFT`, `BLOG.PUBLISH`, `MEDIA.MANAGE` +4. Note your Client ID (Headless OAuth uses Client ID only) + +## Environment +```bash +# .env +WIX_CLIENT_ID=your_wix_client_id_here +WIX_REDIRECT_URI=http://localhost:3000/wix/callback +``` + +## Database (tokens) +Store tokens per user: +```sql +CREATE TABLE wix_tokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + access_token TEXT NOT NULL, + refresh_token TEXT, + expires_at TIMESTAMP, + member_id TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +## Third‑Party App Requirement +`memberId` is mandatory for third‑party blog creation. The OAuth flow retrieves and stores it and it is used when creating posts. + +## Key Files +- Backend service: `backend/services/wix_service.py` +- API routes: `backend/api/wix_routes.py` +- Test page: `frontend/src/components/WixTestPage/WixTestPage.tsx` +- Blog publisher: `frontend/src/components/BlogWriter/Publisher.tsx` + + diff --git a/docs-site/docs/features/integrations/wix/testing-bypass.md b/docs-site/docs/features/integrations/wix/testing-bypass.md new file mode 100644 index 00000000..57777c24 --- /dev/null +++ b/docs-site/docs/features/integrations/wix/testing-bypass.md @@ -0,0 +1,34 @@ +# Testing (Bypass Guide) + +Local testing options to exercise Wix integration without onboarding blockers. + +## Routes +| Option | URL | Purpose | +| --- | --- | --- | +| Primary | `http://localhost:3000/wix-test` | Main Wix test page | +| Backup | `http://localhost:3000/wix-test-direct` | Direct route (no protections) | +| Backend | `http://localhost:8000/api/wix/auth/url` | Direct API testing | + +## How to Test +1. Start backend: `python start_alwrity_backend.py` +2. Start frontend: `npm start` +3. Navigate to `/wix-test`, connect account, publish a test post + +## Env (backend) +```bash +WIX_CLIENT_ID=your_wix_client_id_here +WIX_REDIRECT_URI=http://localhost:3000/wix/callback +``` + +## Restore After Testing +- Re‑enable monitoring middleware in `backend/app.py` if disabled +- Remove any temporary onboarding mocks +- Restore `ProtectedRoute` for `/wix-test` if removed + +## Expected Results +- No onboarding redirect +- Wix OAuth works +- Blog posting works +- No rate‑limit errors + + diff --git a/docs-site/docs/features/subscription/api-reference.md b/docs-site/docs/features/subscription/api-reference.md new file mode 100644 index 00000000..b46dabb1 --- /dev/null +++ b/docs-site/docs/features/subscription/api-reference.md @@ -0,0 +1,445 @@ +# Subscription API Reference + +Complete API endpoint documentation for the ALwrity subscription system. + +## Base URL + +All endpoints are prefixed with `/api/subscription` + +## Authentication + +All endpoints require user authentication. Include the user ID in the request path or headers as appropriate. + +## Subscription Management Endpoints + +### Get All Subscription Plans + +Get a list of all available subscription plans. + +```http +GET /api/subscription/plans +``` + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "id": 1, + "name": "Free", + "price": 0.0, + "billing_cycle": "monthly", + "limits": { + "gemini_calls": 100, + "tokens": 100000 + } + }, + { + "id": 2, + "name": "Basic", + "price": 29.0, + "billing_cycle": "monthly", + "limits": { + "gemini_calls": 1000, + "openai_calls": 500, + "tokens": 1500000, + "monthly_cost": 50.0 + } + } + ] +} +``` + +### Get User Subscription + +Get the current subscription details for a specific user. + +```http +GET /api/subscription/user/{user_id}/subscription +``` + +**Parameters:** +- `user_id` (path): The user's unique identifier + +**Response:** + +```json +{ + "success": true, + "data": { + "user_id": "user123", + "plan_name": "Pro", + "plan_id": 3, + "status": "active", + "started_at": "2025-01-01T00:00:00Z", + "expires_at": "2025-02-01T00:00:00Z", + "limits": { + "gemini_calls": 5000, + "openai_calls": 2500, + "monthly_cost": 150.0 + } + } +} +``` + +### Get API Pricing Information + +Get current pricing configuration for all API providers. + +```http +GET /api/subscription/pricing +``` + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "provider": "gemini", + "model": "gemini-2.5-flash", + "input_cost_per_1m": 0.125, + "output_cost_per_1m": 0.375 + }, + { + "provider": "tavily", + "cost_per_request": 0.001 + } + ] +} +``` + +## Usage Tracking Endpoints + +### Get Current Usage Statistics + +Get current usage statistics for a user. + +```http +GET /api/subscription/usage/{user_id} +``` + +**Parameters:** +- `user_id` (path): The user's unique identifier + +**Response:** + +```json +{ + "success": true, + "data": { + "billing_period": "2025-01", + "total_calls": 1250, + "total_tokens": 210000, + "total_cost": 15.75, + "usage_status": "active", + "limits": { + "gemini_calls": 5000, + "monthly_cost": 150.0 + }, + "provider_breakdown": { + "gemini": { + "calls": 800, + "tokens": 125000, + "cost": 10.50 + }, + "openai": { + "calls": 450, + "tokens": 85000, + "cost": 5.25 + } + }, + "usage_percentages": { + "gemini_calls": 16.0, + "cost": 10.5 + } + } +} +``` + +### Get Usage Trends + +Get historical usage trends over time. + +```http +GET /api/subscription/usage/{user_id}/trends?months=6 +``` + +**Parameters:** +- `user_id` (path): The user's unique identifier +- `months` (query, optional): Number of months to retrieve (default: 6) + +**Response:** + +```json +{ + "success": true, + "data": { + "periods": [ + { + "period": "2024-07", + "total_calls": 850, + "total_cost": 12.50, + "provider_breakdown": { + "gemini": {"calls": 600, "cost": 8.00}, + "openai": {"calls": 250, "cost": 4.50} + } + } + ], + "trends": { + "calls_trend": "increasing", + "cost_trend": "stable" + } + } +} +``` + +### Get Dashboard Data + +Get comprehensive dashboard data including usage, limits, projections, and alerts. + +```http +GET /api/subscription/dashboard/{user_id} +``` + +**Parameters:** +- `user_id` (path): The user's unique identifier + +**Response:** + +```json +{ + "success": true, + "data": { + "summary": { + "total_api_calls_this_month": 1250, + "total_cost_this_month": 15.75, + "usage_status": "active", + "unread_alerts": 2 + }, + "current_usage": { + "billing_period": "2025-01", + "total_calls": 1250, + "total_cost": 15.75, + "usage_status": "active", + "provider_breakdown": { + "gemini": {"calls": 800, "cost": 10.50, "tokens": 125000}, + "openai": {"calls": 450, "cost": 5.25, "tokens": 85000} + }, + "usage_percentages": { + "gemini_calls": 16.0, + "openai_calls": 18.0, + "cost": 10.5 + } + }, + "limits": { + "plan_name": "Pro", + "limits": { + "gemini_calls": 5000, + "openai_calls": 2500, + "monthly_cost": 150.0 + } + }, + "projections": { + "projected_monthly_cost": 47.25, + "projected_usage_percentage": 31.5 + }, + "alerts": [ + { + "id": 1, + "title": "API Usage Notice - Gemini", + "message": "You have used 800 of 5,000 Gemini API calls", + "severity": "info", + "created_at": "2025-01-15T10:30:00Z", + "read": false + } + ] + } +} +``` + +## Alerts & Notifications Endpoints + +### Get Usage Alerts + +Get usage alerts and notifications for a user. + +```http +GET /api/subscription/alerts/{user_id}?unread_only=false +``` + +**Parameters:** +- `user_id` (path): The user's unique identifier +- `unread_only` (query, optional): Filter to only unread alerts (default: false) + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "id": 1, + "user_id": "user123", + "title": "API Usage Notice - Gemini", + "message": "You have used 800 of 5,000 Gemini API calls", + "severity": "info", + "alert_type": "usage_threshold", + "created_at": "2025-01-15T10:30:00Z", + "read": false + }, + { + "id": 2, + "user_id": "user123", + "title": "Usage Warning", + "message": "You have reached 90% of your monthly cost limit", + "severity": "warning", + "alert_type": "cost_threshold", + "created_at": "2025-01-20T14:15:00Z", + "read": false + } + ] +} +``` + +### Mark Alert as Read + +Mark a specific alert as read. + +```http +POST /api/subscription/alerts/{alert_id}/mark-read +``` + +**Parameters:** +- `alert_id` (path): The alert's unique identifier + +**Response:** + +```json +{ + "success": true, + "message": "Alert marked as read" +} +``` + +## Usage Examples + +### Get User Usage (cURL) + +```bash +curl -X GET "http://localhost:8000/api/subscription/usage/user123" \ + -H "Content-Type: application/json" +``` + +### Get Dashboard Data (cURL) + +```bash +curl -X GET "http://localhost:8000/api/subscription/dashboard/user123" \ + -H "Content-Type: application/json" +``` + +### Get Usage Trends (cURL) + +```bash +curl -X GET "http://localhost:8000/api/subscription/usage/user123/trends?months=6" \ + -H "Content-Type: application/json" +``` + +### JavaScript Example + +```javascript +// Get comprehensive usage data +const response = await fetch(`/api/subscription/dashboard/${userId}`); +const data = await response.json(); + +console.log(data.data.summary); +// { +// total_api_calls_this_month: 1250, +// total_cost_this_month: 15.75, +// usage_status: "active", +// unread_alerts: 2 +// } + +// Get current usage percentages +const usage = data.data.current_usage; +console.log(usage.usage_percentages); +// { +// gemini_calls: 16.0, +// openai_calls: 18.0, +// cost: 10.5 +// } +``` + +### Python Example + +```python +import requests + +# Get user usage statistics +response = requests.get( + f"http://localhost:8000/api/subscription/usage/{user_id}", + headers={"Content-Type": "application/json"} +) + +data = response.json() +usage = data["data"] + +print(f"Total calls: {usage['total_calls']}") +print(f"Total cost: ${usage['total_cost']}") +print(f"Status: {usage['usage_status']}") +``` + +## Error Responses + +All endpoints return consistent error responses: + +```json +{ + "success": false, + "error": "error_type", + "message": "Human-readable error message", + "details": "Additional error details", + "user_id": "user123", + "timestamp": "2025-01-15T10:30:00Z" +} +``` + +### Common Error Codes + +- `400 Bad Request`: Invalid request parameters +- `401 Unauthorized`: Authentication required +- `404 Not Found`: Resource not found +- `429 Too Many Requests`: Usage limit exceeded +- `500 Internal Server Error`: Server error + +## Rate Limiting + +Usage limits are enforced automatically. When a limit is exceeded, the API returns a `429 Too Many Requests` response: + +```json +{ + "success": false, + "error": "UsageLimitExceededException", + "message": "Usage limit exceeded for Gemini API calls", + "details": { + "provider": "gemini", + "limit_type": "api_calls", + "current_usage": 1000, + "limit_value": 1000 + } +} +``` + +## Next Steps + +- [Overview](overview.md) - System architecture and features +- [Setup](setup.md) - Installation and configuration +- [Pricing](pricing.md) - Subscription plans and API pricing + +--- + +**Last Updated**: January 2025 + diff --git a/docs-site/docs/features/subscription/frontend-integration.md b/docs-site/docs/features/subscription/frontend-integration.md new file mode 100644 index 00000000..51913fe7 --- /dev/null +++ b/docs-site/docs/features/subscription/frontend-integration.md @@ -0,0 +1,339 @@ +# Frontend Integration Guide + +Technical specifications and integration guide for implementing the billing dashboard in the ALwrity frontend. + +## Overview + +The billing dashboard provides enterprise-grade insights and cost transparency for all external API usage. It integrates with the subscription system's backend APIs to display real-time usage, costs, and system health. + +## Architecture + +### Main Dashboard Integration Points + +``` +Main Dashboard +├── Header Section +│ ├── System Health Indicator +│ ├── Real-time Usage Summary +│ └── Alert Notifications +├── Billing Overview Section +│ ├── Current Usage vs Limits +│ ├── Cost Breakdown by Provider +│ └── Monthly Projections +├── API Monitoring Section +│ ├── External API Performance +│ ├── Cost per API Call +│ └── Usage Trends +└── Subscription Management + ├── Plan Comparison + ├── Usage Optimization Tips + └── Upgrade/Downgrade Options +``` + +## Service Layer + +### Billing Service (`frontend/src/services/billingService.ts`) + +Core functions to implement: + +```typescript +export const billingService = { + // Get comprehensive dashboard data + getDashboardData: (userId: string) => Promise + + // Get current usage statistics + getUsageStats: (userId: string, period?: string) => Promise + + // Get usage trends over time + getUsageTrends: (userId: string, months?: number) => Promise + + // Get subscription plans + getSubscriptionPlans: () => Promise + + // Get API pricing information + getAPIPricing: (provider?: string) => Promise + + // Get usage alerts + getUsageAlerts: (userId: string, unreadOnly?: boolean) => Promise + + // Mark alert as read + markAlertRead: (alertId: number) => Promise +} +``` + +### Monitoring Service (`frontend/src/services/monitoringService.ts`) + +Core functions to implement: + +```typescript +export const monitoringService = { + // Get system health status + getSystemHealth: () => Promise + + // Get API performance statistics + getAPIStats: (minutes?: number) => Promise + + // Get lightweight monitoring stats + getLightweightStats: () => Promise + + // Get cache performance metrics + getCacheStats: () => Promise +} +``` + +## Type Definitions + +### Core Data Structures (`frontend/src/types/billing.ts`) + +```typescript +interface DashboardData { + current_usage: UsageStats + trends: UsageTrends + limits: SubscriptionLimits + alerts: UsageAlert[] + projections: CostProjections + summary: UsageSummary +} + +interface UsageStats { + billing_period: string + usage_status: 'active' | 'warning' | 'limit_reached' + total_calls: number + total_tokens: number + total_cost: number + avg_response_time: number + error_rate: number + limits: SubscriptionLimits + provider_breakdown: ProviderBreakdown + alerts: UsageAlert[] + usage_percentages: UsagePercentages + last_updated: string +} + +interface ProviderBreakdown { + gemini: ProviderUsage + openai: ProviderUsage + anthropic: ProviderUsage + mistral: ProviderUsage + tavily: ProviderUsage + serper: ProviderUsage + metaphor: ProviderUsage + firecrawl: ProviderUsage + stability: ProviderUsage +} + +interface ProviderUsage { + calls: number + tokens: number + cost: number +} +``` + +## Component Architecture + +### BillingOverview Component + +**File**: `frontend/src/components/billing/BillingOverview.tsx` + +**Key Features**: +- Real-time usage display with animated counters +- Progress bars for usage limits +- Cost breakdown with interactive tooltips +- Quick action buttons for plan management + +**State Management**: +```typescript +const [usageData, setUsageData] = useState(null) +const [loading, setLoading] = useState(true) +const [error, setError] = useState(null) +``` + +### CostBreakdown Component + +**File**: `frontend/src/components/billing/CostBreakdown.tsx` + +**Key Features**: +- Interactive pie chart with provider breakdown +- Hover effects showing detailed costs +- Click to drill down into provider details +- Cost per token calculations + +### UsageTrends Component + +**File**: `frontend/src/components/billing/UsageTrends.tsx` + +**Key Features**: +- Multi-line chart showing usage over time +- Toggle between cost, calls, and tokens +- Trend analysis with projections +- Peak usage identification + +### SystemHealthIndicator Component + +**File**: `frontend/src/components/monitoring/SystemHealthIndicator.tsx` + +**Key Features**: +- Color-coded health status +- Real-time performance metrics +- Error rate monitoring +- Response time tracking + +## Design System + +### Color Palette + +```typescript +const colors = { + primary: { + 50: '#eff6ff', + 500: '#3b82f6', + 900: '#1e3a8a' + }, + success: { + 50: '#f0fdf4', + 500: '#22c55e', + 900: '#14532d' + }, + warning: { + 50: '#fffbeb', + 500: '#f59e0b', + 900: '#78350f' + }, + danger: { + 50: '#fef2f2', + 500: '#ef4444', + 900: '#7f1d1d' + } +} +``` + +### Responsive Design + +**Breakpoints**: +- Mobile: `640px` +- Tablet: `768px` +- Desktop: `1024px` +- Large: `1280px` + +## Real-Time Updates + +### Polling Strategy + +Intelligent polling based on user activity: + +```typescript +const useIntelligentPolling = (userId: string) => { + const [isActive, setIsActive] = useState(true) + + useEffect(() => { + const interval = setInterval(() => { + if (isActive) { + fetchUsageData(userId) + } + }, isActive ? 30000 : 300000) // 30s when active, 5m when inactive + + return () => clearInterval(interval) + }, [isActive, userId]) +} +``` + +## Chart Configuration + +### Recharts Theme + +```typescript +const chartTheme = { + colors: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'], + grid: { + stroke: '#e5e7eb', + strokeWidth: 1, + strokeDasharray: '3 3' + } +} +``` + +## Security Implementation + +### API Security + +Secure API calls with authentication: + +```typescript +const secureApiCall = async (endpoint: string, options: RequestInit = {}) => { + const token = await getAuthToken() + + return fetch(endpoint, { + ...options, + headers: { + ...options.headers, + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }) +} +``` + +## Performance Optimization + +### Code Splitting + +Lazy load heavy components: + +```typescript +const BillingDashboard = lazy(() => import('./BillingDashboard')) +const UsageTrends = lazy(() => import('./UsageTrends')) +``` + +### Memoization + +Memoize expensive calculations: + +```typescript +const MemoizedCostBreakdown = memo(({ data }: { data: ProviderData[] }) => { + const processedData = useMemo(() => + data.map(item => ({ + ...item, + percentage: (item.cost / totalCost) * 100 + })) + , [data, totalCost]) + + return +}) +``` + +## Dependencies + +Required packages: + +```bash +npm install recharts framer-motion lucide-react +npm install @tanstack/react-query axios +npm install zod +``` + +## Integration Status + +### ✅ Completed +- Type definitions and validation schemas +- Service layer with all API functions +- Core components (BillingOverview, CostBreakdown, UsageTrends, UsageAlerts) +- SystemHealthIndicator component +- Main dashboard integration +- Real-time data fetching with auto-refresh + +### 🔄 Ready for Enhancement +- WebSocket integration for instant updates +- Advanced analytics and optimization suggestions +- Export functionality for reports +- Mobile app optimization + +## Next Steps + +- [API Reference](api-reference.md) - Backend endpoint documentation +- [Implementation Status](implementation-status.md) - Current features and metrics +- [Roadmap](roadmap.md) - Future enhancements + +--- + +**Last Updated**: January 2025 + diff --git a/docs/Billing_Subscription/BILLING_IMPLEMENTATION_STATUS.md b/docs-site/docs/features/subscription/implementation-status.md similarity index 80% rename from docs/Billing_Subscription/BILLING_IMPLEMENTATION_STATUS.md rename to docs-site/docs/features/subscription/implementation-status.md index 19d70a6f..7a9894a6 100644 --- a/docs/Billing_Subscription/BILLING_IMPLEMENTATION_STATUS.md +++ b/docs-site/docs/features/subscription/implementation-status.md @@ -1,21 +1,25 @@ -# Billing & Subscription Implementation Status Report +# Implementation Status -## 📊 Current Implementation Status +Current status of the ALwrity usage-based subscription system implementation. -**Overall Progress**: ✅ **Phase 1 Complete** - Core billing dashboard integrated and functional +## Overall Progress -### ✅ Completed Components +**Phase 1 Complete** - Core billing dashboard integrated and functional + +## Completed Components + +### Backend Integration (100% Complete) -#### 1. Backend Integration (100% Complete) - **Database Setup**: ✅ All subscription tables created and initialized - **API Integration**: ✅ All subscription routes integrated in `app.py` - **Middleware Integration**: ✅ Enhanced monitoring middleware with usage tracking -- **Critical Issues Fixed**: ✅ All 3 identified issues resolved: +- **Critical Issues Fixed**: ✅ All identified issues resolved: - Fixed `billing_history` table detection in test suite - Resolved `NoneType + int` error in usage tracking service - Fixed middleware double request body consumption -#### 2. Frontend Foundation (100% Complete) +### Frontend Foundation (100% Complete) + - **Dependencies**: ✅ All required packages installed - `recharts` - Data visualization - `framer-motion` - Animations @@ -24,7 +28,8 @@ - `axios` - HTTP client - `zod` - Type validation -#### 3. Type System (100% Complete) +### Type System (100% Complete) + - **File**: `frontend/src/types/billing.ts` - **Interfaces**: ✅ All core interfaces defined - `DashboardData`, `UsageStats`, `ProviderBreakdown` @@ -33,7 +38,8 @@ - **Zod Schemas**: ✅ All validation schemas implemented - **Type Safety**: ✅ Full TypeScript coverage with runtime validation -#### 4. Service Layer (100% Complete) +### Service Layer (100% Complete) + - **File**: `frontend/src/services/billingService.ts` - **API Functions**: ✅ All core functions implemented - `getDashboardData()`, `getUsageStats()`, `getUsageTrends()` @@ -46,7 +52,8 @@ - **Monitoring Functions**: ✅ All monitoring APIs integrated - `getSystemHealth()`, `getAPIStats()`, `getLightweightStats()`, `getCacheStats()` -#### 5. Core Components (100% Complete) +### Core Components (100% Complete) + - **File**: `frontend/src/components/billing/BillingDashboard.tsx` - ✅ Main container component with real-time data fetching - ✅ Loading states and error handling @@ -83,19 +90,21 @@ - ✅ Performance metrics (response time, error rate, uptime) - ✅ Auto-refresh capabilities -#### 6. Main Dashboard Integration (100% Complete) +### Main Dashboard Integration (100% Complete) + - **File**: `frontend/src/components/MainDashboard/MainDashboard.tsx` - ✅ `BillingDashboard` component integrated - ✅ Positioned after `AnalyticsInsights` as requested - ✅ Seamless integration with existing dashboard layout -#### 7. Build System (100% Complete) +### Build System (100% Complete) + - **TypeScript Compilation**: ✅ All type errors resolved - **Schema Validation**: ✅ Zod schemas properly ordered and validated - **Import Resolution**: ✅ All module imports working correctly - **Production Build**: ✅ Successful build with optimized bundle -## 🎯 Current Features +## Current Features ### Real-Time Monitoring - ✅ Live usage tracking with 30-second refresh @@ -123,7 +132,7 @@ - ✅ Progress bars for usage limits - ✅ Status indicators with color coding -## 📈 Implementation Metrics +## Implementation Metrics ### Code Quality - **TypeScript Coverage**: 100% - All components fully typed @@ -143,59 +152,32 @@ - **Data Validation**: Runtime validation with Zod schemas - **Caching**: React Query for intelligent data caching -## 🚀 Next Phase Recommendations +## Delivered Components -### Phase 2: Advanced Features (Optional) -1. **Real-Time WebSocket Integration** - - WebSocket connection for instant updates - - Push notifications for usage alerts - - Live cost tracking during API calls +### Database Models +- **SubscriptionPlan**: Defines subscription tiers (Free, Basic, Pro, Enterprise) +- **UserSubscription**: Tracks user subscription details and billing +- **APIUsageLog**: Detailed logging of every API call with cost tracking +- **UsageSummary**: Aggregated usage statistics per user per billing period +- **APIProviderPricing**: Configurable pricing for all API providers +- **UsageAlert**: Automated alerts for usage thresholds +- **BillingHistory**: Historical billing records -2. **Advanced Analytics** - - Cost optimization suggestions - - Usage pattern analysis - - Predictive cost modeling - - Provider performance comparison +### Core Services +- **Pricing Service**: Real-time cost calculation for all API providers +- **Usage Tracking Service**: Comprehensive API usage tracking +- **Exception Handler**: Robust error handling with detailed logging +- **Enhanced Middleware**: Automatic API provider detection and usage tracking -3. **Enhanced User Experience** - - Interactive tooltips with detailed explanations - - Advanced filtering and sorting options - - Export functionality for reports - - Mobile app optimization +### API Endpoints +- `GET /api/subscription/plans` - Available subscription plans +- `GET /api/subscription/usage/{user_id}` - Current usage statistics +- `GET /api/subscription/usage/{user_id}/trends` - Usage trends over time +- `GET /api/subscription/dashboard/{user_id}` - Comprehensive dashboard data +- `GET /api/subscription/pricing` - API pricing information +- `GET /api/subscription/alerts/{user_id}` - Usage alerts and notifications -4. **Subscription Management** - - Plan comparison and upgrade flows - - Billing history and invoice management - - Payment method management - - Usage-based plan recommendations - -## 🔧 Technical Debt & Optimizations - -### Minor Issues (Non-Critical) -- **Unused Imports**: Some components have unused imports (linting warnings) -- **Bundle Size**: Could be optimized with code splitting for large components -- **Error Boundaries**: Could add React error boundaries for better error handling - -### Performance Optimizations -- **Memoization**: Could add React.memo for expensive components -- **Lazy Loading**: Could implement lazy loading for chart components -- **Data Pagination**: Could add pagination for large datasets - -## 📋 Testing Status - -### Current Testing -- ✅ Backend API testing (comprehensive test suite) -- ✅ Database integration testing -- ✅ Type validation testing -- ✅ Build system testing - -### Recommended Testing -- **Component Testing**: Unit tests for React components -- **Integration Testing**: End-to-end billing flow testing -- **Visual Regression**: Screenshot testing for UI consistency -- **Performance Testing**: Load testing for real-time updates - -## 🎉 Success Criteria Met +## Success Criteria Met ### ✅ Functional Requirements - [x] Real-time usage monitoring @@ -221,7 +203,7 @@ - [x] Professional design - [x] Smooth animations -## 📊 Business Impact +## Business Impact ### Cost Transparency - **Before**: Users had no visibility into API costs @@ -238,7 +220,33 @@ - **After**: Enterprise-grade billing dashboard with advanced analytics - **Impact**: Professional appearance, increased user confidence -## 🎯 Conclusion +## Next Steps + +### Phase 2: Advanced Features (Optional) +1. **Real-Time WebSocket Integration** + - WebSocket connection for instant updates + - Push notifications for usage alerts + - Live cost tracking during API calls + +2. **Advanced Analytics** + - Cost optimization suggestions + - Usage pattern analysis + - Predictive cost modeling + - Provider performance comparison + +3. **Enhanced User Experience** + - Interactive tooltips with detailed explanations + - Advanced filtering and sorting options + - Export functionality for reports + - Mobile app optimization + +4. **Subscription Management** + - Plan comparison and upgrade flows + - Billing history and invoice management + - Payment method management + - Usage-based plan recommendations + +## Conclusion The billing and subscription implementation is **100% complete** for Phase 1, successfully delivering: @@ -249,10 +257,11 @@ The billing and subscription implementation is **100% complete** for Phase 1, su 5. **Cost Transparency** - Detailed breakdowns and trend analysis 6. **Production Ready** - Successful build with no critical issues -The system is now ready for production deployment and provides users with comprehensive visibility into their API usage, costs, and system performance. The implementation follows enterprise-grade standards with proper error handling, type safety, and responsive design. +The system is now ready for production deployment and provides users with comprehensive visibility into their API usage, costs, and system performance. --- -**Last Updated**: December 2024 +**Last Updated**: January 2025 **Status**: ✅ Production Ready **Next Review**: Optional Phase 2 enhancements + diff --git a/docs-site/docs/features/subscription/overview.md b/docs-site/docs/features/subscription/overview.md new file mode 100644 index 00000000..1bc11f37 --- /dev/null +++ b/docs-site/docs/features/subscription/overview.md @@ -0,0 +1,157 @@ +# Subscription System Overview + +ALwrity's usage-based subscription system provides comprehensive API cost tracking, usage limits, and real-time monitoring for all external API providers. + +## Features + +### Core Functionality +- **Usage-Based Billing**: Track API calls, tokens, and costs across all providers +- **Subscription Tiers**: Free, Basic, Pro, and Enterprise plans with different limits +- **Real-Time Monitoring**: Live usage tracking and limit enforcement +- **Cost Calculation**: Accurate pricing for Gemini, OpenAI, Anthropic, and other APIs +- **Usage Alerts**: Automatic notifications at 80%, 90%, and 100% usage thresholds +- **Robust Error Handling**: Comprehensive logging and exception management + +### Supported API Providers +- **Gemini API**: Google's AI models with latest pricing +- **OpenAI**: GPT models and embeddings +- **Anthropic**: Claude models +- **Mistral AI**: Mistral models +- **Tavily**: AI-powered search +- **Serper**: Google search API +- **Metaphor/Exa**: Advanced search +- **Firecrawl**: Web content extraction +- **Stability AI**: Image generation +- **Hugging Face**: GPT-OSS-120B via Groq + +## Architecture + +### Database Schema + +The system uses the following core tables: + +- `subscription_plans`: Available subscription tiers and limits +- `user_subscriptions`: User subscription information +- `api_usage_logs`: Detailed log of every API call +- `usage_summaries`: Aggregated usage per user per billing period +- `api_provider_pricing`: Pricing configuration for all providers +- `usage_alerts`: Usage notifications and warnings +- `billing_history`: Historical billing records + +### Service Structure + +``` +backend/services/subscription/ +├── __init__.py # Package exports +├── pricing_service.py # API pricing and cost calculations +├── usage_tracking_service.py # Usage tracking and limits +├── exception_handler.py # Exception handling +└── monitoring_middleware.py # API monitoring middleware +``` + +### Core Services + +#### Pricing Service +- Real-time cost calculation for all API providers +- Subscription limit management +- Usage validation and enforcement +- Support for Gemini, OpenAI, Anthropic, Mistral, and search APIs + +#### Usage Tracking Service +- Comprehensive API usage tracking +- Real-time usage statistics +- Trend analysis and projections +- Automatic alert generation at 80%, 90%, and 100% thresholds + +#### Exception Handler +- Robust error handling with detailed logging +- Structured exception types for different scenarios +- Automatic alert creation for critical errors +- User-friendly error messages + +### Enhanced Middleware + +The system automatically tracks API usage through enhanced middleware: + +- **Automatic API Provider Detection**: Identifies Gemini, OpenAI, Anthropic, etc. +- **Token Estimation**: Estimates usage from request/response content +- **Pre-Request Validation**: Enforces usage limits before processing +- **Cost Tracking**: Real-time cost calculation and logging +- **Usage Limit Enforcement**: Returns 429 errors when limits exceeded + +## Key Capabilities + +### Usage-Based Billing +- ✅ **Real-time cost tracking** for all API providers +- ✅ **Token-level precision** for LLM APIs (Gemini, OpenAI, Anthropic) +- ✅ **Request-based pricing** for search APIs (Tavily, Serper, Metaphor) +- ✅ **Automatic cost calculation** with configurable pricing + +### Subscription Management +- ✅ **4 Subscription Tiers**: Free, Basic ($29/mo), Pro ($79/mo), Enterprise ($199/mo) +- ✅ **Flexible limits**: API calls, tokens, and monthly cost caps +- ✅ **Usage enforcement**: Pre-request validation and blocking +- ✅ **Billing cycle support**: Monthly and yearly options + +### Monitoring & Analytics +- ✅ **Real-time dashboard** with usage statistics +- ✅ **Usage trends** and projections +- ✅ **Provider-specific breakdowns** (Gemini, OpenAI, etc.) +- ✅ **Performance metrics** (response times, error rates) + +### Alert System +- ✅ **Automatic notifications** at 80%, 90%, and 100% usage +- ✅ **Multi-channel alerts** (database, logs, future email integration) +- ✅ **Alert management** (mark as read, severity levels) +- ✅ **Usage recommendations** and upgrade prompts + +## Security & Privacy + +### Data Protection +- User usage data is encrypted at rest +- API keys are never logged in usage tracking +- Sensitive information is excluded from error logs +- GDPR-compliant data handling + +### Rate Limiting +- Pre-request usage validation +- Automatic limit enforcement +- Graceful degradation when limits are reached +- User-friendly error messages + +## Exception Types + +The system uses structured exception types: + +- `UsageLimitExceededException`: When usage limits are reached +- `PricingException`: Pricing calculation errors +- `TrackingException`: Usage tracking failures +- `SubscriptionException`: General subscription errors + +## Customization + +### Adding New API Providers +1. Add provider to `APIProvider` enum +2. Configure pricing in `api_provider_pricing` table +3. Update detection patterns in middleware +4. Add usage tracking logic + +### Modifying Subscription Plans +1. Update plans in database or via API +2. Modify limits and pricing +3. Add/remove features +4. Update billing integration + +## Next Steps + +- [Setup Guide](setup.md) - Installation and configuration +- [API Reference](api-reference.md) - Endpoint documentation +- [Pricing](pricing.md) - Subscription plans and API pricing +- [Frontend Integration](frontend-integration.md) - Technical specifications +- [Implementation Status](implementation-status.md) - Current features and metrics + +--- + +**Version**: 1.0.0 +**Last Updated**: January 2025 + diff --git a/docs-site/docs/features/subscription/pricing.md b/docs-site/docs/features/subscription/pricing.md new file mode 100644 index 00000000..e9e56a6a --- /dev/null +++ b/docs-site/docs/features/subscription/pricing.md @@ -0,0 +1,98 @@ +# Subscription Plans & API Pricing + +End-to-end reference for ALwrity's usage-based subscription tiers, API cost configuration, and plan-specific limits. All data is sourced from `backend/services/subscription/pricing_service.py`. + +## Subscription Plans + +> **Legend**: `∞` = Unlimited. Limits reset at the start of each billing cycle. + +| Plan | Price (Monthly / Yearly) | AI Text Generation Calls* | Token Limits (per provider) | Key API Limits | Video Generation | Monthly Cost Cap | Highlights | +| --- | --- | --- | --- | --- | --- | --- | --- | +| **Free** | `$0 / $0` | 100 Gemini • 50 Mistral (legacy enforcement) | 100K Gemini tokens | 20 Tavily • 20 Serper • 10 Metaphor • 10 Firecrawl • 5 Stability • 100 Exa | Not included | `$0` | Basic content generation & limited research | +| **Basic** | `$29 / $290` | **10 unified LLM calls** (Gemini + OpenAI + Anthropic + Mistral combined) | 20K tokens each (Gemini, OpenAI, Anthropic, Mistral) | 200 Tavily • 200 Serper • 100 Metaphor • 100 Firecrawl • 5 Stability • 500 Exa | 20 videos/mo | `$50` | Full content generation, advanced research, basic analytics | +| **Pro** | `$79 / $790` | 5K Gemini • 2.5K OpenAI • 1K Anthropic • 2.5K Mistral | 5M Gemini • 2.5M OpenAI • 1M Anthropic • 2.5M Mistral | 1K Tavily • 1K Serper • 500 Metaphor • 500 Firecrawl • 200 Stability • 2K Exa | 50 videos/mo | `$150` | Premium research, advanced analytics, priority support | +| **Enterprise** | `$199 / $1,990` | ∞ across all LLM providers | ∞ | ∞ across every research/media API | ∞ | `$500` | White-label, dedicated support, custom integrations | + +\*The Basic plan now enforces a **unified** `ai_text_generation_calls_limit` of 10 requests across all LLM providers. Legacy per-provider columns remain for analytics dashboards but do not control enforcement. + +### Plan Feature Notes +- **Video Generation**: Powered by Hugging Face `tencent/HunyuanVideo` ($0.10 per request). Plan limits are shown above. +- **Image Generation**: Stability AI billed at $0.04/image. Limits shown under “Key API Limits”. +- **Research APIs**: Tavily, Serper, Metaphor, Exa, and Firecrawl are individually rate-limited per plan. +- **Cost Caps**: `monthly_cost_limit` hard stops spend at $50 / $150 / $500 for paid tiers. Enterprise caps are adjustable via support. + +## Provider Pricing Matrix + +### Gemini 2.5 & 1.5 (Google) +- `gemini-2.5-pro` — $0.00000125 input / $0.00001 output per token ($1.25 / $10 per 1M tokens) +- `gemini-2.5-pro-large` — $0.0000025 / $0.000015 per token (large context) +- `gemini-2.5-flash` — $0.0000003 / $0.0000025 per token +- `gemini-2.5-flash-audio` — $0.000001 / $0.0000025 per token +- `gemini-2.5-flash-lite` — $0.0000001 / $0.0000004 per token +- `gemini-2.5-flash-lite-audio` — $0.0000003 / $0.0000004 per token +- `gemini-1.5-flash` — $0.000000075 / $0.0000003 per token +- `gemini-1.5-flash-8b` — $0.0000000375 / $0.00000015 per token +- `gemini-1.5-pro` — $0.00000125 / $0.000005 per token +- `gemini-1.5-pro-large` — $0.0000025 / $0.00001 per token +- `gemini-embedding` — $0.00000015 per input token +- `gemini-grounding-search` — $35 per 1,000 requests after the free tier + +### OpenAI (estimates — update when official pricing changes) +- `gpt-4o` — $0.0000025 input / $0.00001 output per token +- `gpt-4o-mini` — $0.00000015 input / $0.0000006 output per token + +### Anthropic +- `claude-3.5-sonnet` — $0.000003 input / $0.000015 output per token + +### Hugging Face / Mistral (GPT-OSS-120B via Groq) +Pricing is configurable through environment variables: +``` +HUGGINGFACE_INPUT_TOKEN_COST=0.000001 # $1 per 1M tokens +HUGGINGFACE_OUTPUT_TOKEN_COST=0.000003 # $3 per 1M tokens +``` +Models covered: `openai/gpt-oss-120b:groq`, `gpt-oss-120b`, and `default` (fallback). + +### Search, Image, and Video APIs +- Tavily — $0.001 per search +- Serper — $0.001 per search +- Metaphor — $0.003 per search +- Exa — $0.005 per search (1–25 results) +- Firecrawl — $0.002 per crawled page +- Stability AI — $0.04 per image +- Video Generation (HunyuanVideo) — $0.10 per video request + +## Updating Pricing & Plans + +1. **Initial Seed** — `python backend/scripts/create_subscription_tables.py` creates plans and pricing. +2. **Env Overrides** — Hugging Face pricing refreshes from `HUGGINGFACE_*` vars every boot. +3. **Scripts & Maintenance** — Use `backend/scripts/` utilities (e.g., `update_basic_plan_limits.py`, `cap_basic_plan_usage.py`) to roll forward changes. +4. **Direct DB Edits** — Modify `subscription_plans` or `api_provider_pricing` tables for emergency adjustments. + +## Cost Examples + +| Scenario | Calculation | Cost | +| --- | --- | --- | +| Gemini 2.5 Flash (1K input / 500 output tokens) | (1,000 × 0.0000003) + (500 × 0.0000025) | **$0.00155** | +| Tavily Search | 1 request × $0.001 | **$0.001** | +| Hugging Face GPT-OSS-120B (2K in / 1K out) | (2,000 × 0.000001) + (1,000 × 0.000003) | **$0.005** | +| Video Generation (Basic plan) | 1 request × $0.10 | **$0.10** (counts toward 20-video quota) | + +## Enforcement & Monitoring + +1. Middleware estimates usage and calls `UsageTrackingService.track_api_usage`. +2. `UsageService.enforce_usage_limits` validates the request before the downstream provider call. +3. When a limit would be exceeded, the API returns `429` with upgrade guidance. +4. The Billing Dashboard (`/billing`) shows real-time usage, cost projections, provider breakdowns, renewal history, and usage logs. + +## Additional Resources + +- [Billing Dashboard](billing-dashboard.md) +- [API Reference](api-reference.md) +- [Setup Guide](setup.md) +- [Gemini Pricing](https://ai.google.dev/gemini-api/docs/pricing) +- [OpenAI Pricing](https://openai.com/pricing) + +--- + +**Last Updated**: November 2025 + diff --git a/docs-site/docs/features/subscription/roadmap.md b/docs-site/docs/features/subscription/roadmap.md new file mode 100644 index 00000000..e9089565 --- /dev/null +++ b/docs-site/docs/features/subscription/roadmap.md @@ -0,0 +1,232 @@ +# Subscription System Roadmap + +Implementation phases and future enhancements for the ALwrity subscription system. + +## Phase 1: Foundation & Core Components ✅ Complete + +**Status**: ✅ **100% Complete** + +### Completed Deliverables + +#### Project Setup & Dependencies +- ✅ Installed required packages (recharts, framer-motion, lucide-react, react-query, axios, zod) +- ✅ Created folder structure for billing and monitoring components + +#### Type Definitions +- ✅ Defined core interfaces (DashboardData, UsageStats, ProviderBreakdown, etc.) +- ✅ Created validation schemas with Zod +- ✅ Exported all type definitions + +#### Service Layer +- ✅ Implemented billing service with all API client functions +- ✅ Implemented monitoring service with monitoring API functions +- ✅ Added error handling and retry logic +- ✅ Implemented request/response interceptors + +#### Core Components +- ✅ BillingOverview component with usage metrics +- ✅ SystemHealthIndicator component with health status +- ✅ CostBreakdown component with interactive charts +- ✅ UsageTrends component with time range selection +- ✅ UsageAlerts component with alert management +- ✅ BillingDashboard main container component + +#### Dashboard Integration +- ✅ Integrated BillingDashboard into MainDashboard +- ✅ Added responsive grid layout +- ✅ Implemented section navigation + +## Phase 2: Data Visualization & Charts ✅ Complete + +**Status**: ✅ **100% Complete** + +### Completed Deliverables + +#### Chart Components +- ✅ Implemented pie chart with Recharts for cost breakdown +- ✅ Added interactive tooltips and provider legend +- ✅ Created line chart for usage trends +- ✅ Implemented metric toggle (cost/calls/tokens) +- ✅ Added trend analysis display + +#### Dashboard Integration +- ✅ Enhanced dashboard header with system health indicator +- ✅ Added usage summary and alert notification badge +- ✅ Created billing section wrapper +- ✅ Implemented responsive grid layout + +## Phase 3: Real-Time Updates & Animations ✅ Complete + +**Status**: ✅ **100% Complete** + +### Completed Deliverables + +#### Real-Time Updates +- ✅ Implemented intelligent polling (30s when active, 5m when inactive) +- ✅ Added auto-refresh capabilities +- ✅ Implemented loading states and error handling + +#### Animations +- ✅ Added Framer Motion animations for page transitions +- ✅ Implemented card hover effects +- ✅ Added number animations for metrics +- ✅ Created skeleton loaders for loading states + +#### Responsive Design +- ✅ Implemented mobile-first responsive design +- ✅ Added breakpoint-specific layouts +- ✅ Optimized chart sizing for different screen sizes + +## Phase 4: Advanced Features & Optimization 🔄 Optional + +**Status**: 🔄 **Future Enhancements** + +### Planned Features + +#### Real-Time WebSocket Integration +- [ ] WebSocket connection for instant updates +- [ ] Push notifications for usage alerts +- [ ] Live cost tracking during API calls +- [ ] Real-time dashboard updates without polling + +#### Advanced Analytics +- [ ] Cost optimization suggestions +- [ ] Usage pattern analysis +- [ ] Predictive cost modeling +- [ ] Provider performance comparison +- [ ] Anomaly detection for unusual usage patterns + +#### Enhanced User Experience +- [ ] Interactive tooltips with detailed explanations +- [ ] Advanced filtering and sorting options +- [ ] Export functionality for reports (PDF, CSV) +- [ ] Mobile app optimization +- [ ] Dark mode support +- [ ] Customizable dashboard layouts + +#### Subscription Management +- [ ] Plan comparison interface +- [ ] Upgrade/downgrade flows +- [ ] Billing history and invoice management +- [ ] Payment method management +- [ ] Usage-based plan recommendations +- [ ] Automatic plan suggestions based on usage + +#### Performance Optimizations +- [ ] Code splitting for large components +- [ ] Lazy loading for chart components +- [ ] Data pagination for large datasets +- [ ] Memoization for expensive calculations +- [ ] Virtual scrolling for long lists + +## Phase 5: Enterprise Features 🚀 Future + +**Status**: 🚀 **Long-Term Roadmap** + +### Planned Enterprise Features + +#### Multi-Tenant Support +- [ ] Organization-level usage tracking +- [ ] Team usage allocation and limits +- [ ] Department-level cost allocation +- [ ] Budget management per team/department + +#### Advanced Reporting +- [ ] Custom report builder +- [ ] Scheduled report generation +- [ ] Email report delivery +- [ ] Executive dashboards +- [ ] Cost forecasting and budgeting + +#### Integration Enhancements +- [ ] Slack/Teams notifications +- [ ] Webhook support for external integrations +- [ ] API for third-party billing systems +- [ ] SSO integration for enterprise customers +- [ ] Audit log and compliance reporting + +#### Advanced Monitoring +- [ ] Custom alert rules +- [ ] Alert escalation policies +- [ ] Performance SLA tracking +- [ ] Provider health monitoring +- [ ] Cost anomaly detection + +## Technical Debt & Optimizations + +### Minor Issues (Non-Critical) +- [ ] Remove unused imports (linting warnings) +- [ ] Optimize bundle size with code splitting +- [ ] Add React error boundaries for better error handling +- [ ] Improve TypeScript strict mode compliance + +### Performance Optimizations +- [ ] Add React.memo for expensive components +- [ ] Implement lazy loading for chart components +- [ ] Add data pagination for large datasets +- [ ] Optimize API response caching + +## Testing Enhancements + +### Recommended Testing +- [ ] Component unit tests for React components +- [ ] Integration testing for end-to-end billing flow +- [ ] Visual regression testing for UI consistency +- [ ] Performance testing for real-time updates +- [ ] Load testing for high-traffic scenarios + +## Documentation Updates + +### Planned Documentation +- [ ] Video tutorials for billing dashboard +- [ ] Interactive API documentation +- [ ] Best practices guide for cost optimization +- [ ] Troubleshooting guide with common issues +- [ ] Migration guide for existing users + +## Timeline Estimates + +### Phase 4 (Advanced Features) +- **Estimated Duration**: 4-6 weeks +- **Priority**: Medium +- **Dependencies**: Phase 1-3 completion ✅ + +### Phase 5 (Enterprise Features) +- **Estimated Duration**: 8-12 weeks +- **Priority**: Low +- **Dependencies**: Phase 4 completion, enterprise customer demand + +## Success Metrics + +### Phase 4 Success Criteria +- [ ] WebSocket integration reduces polling overhead by 80% +- [ ] Cost optimization suggestions reduce user costs by 15% +- [ ] Export functionality used by 30% of users +- [ ] Mobile app optimization increases mobile usage by 50% + +### Phase 5 Success Criteria +- [ ] Multi-tenant support enables 10+ enterprise customers +- [ ] Advanced reporting reduces support tickets by 40% +- [ ] Integration enhancements increase customer retention by 25% + +## Contributing + +We welcome contributions to the subscription system roadmap. Please: + +1. Review existing issues and feature requests +2. Discuss major features before implementation +3. Follow the established code standards +4. Add comprehensive tests for new features +5. Update documentation with changes + +## Next Steps + +- [Implementation Status](implementation-status.md) - Current features and metrics +- [Frontend Integration](frontend-integration.md) - Technical specifications +- [API Reference](api-reference.md) - Endpoint documentation + +--- + +**Last Updated**: January 2025 +**Current Phase**: Phase 3 Complete, Phase 4 Planning + diff --git a/docs-site/docs/features/subscription/setup.md b/docs-site/docs/features/subscription/setup.md new file mode 100644 index 00000000..bb86b5a9 --- /dev/null +++ b/docs-site/docs/features/subscription/setup.md @@ -0,0 +1,241 @@ +# Subscription System Setup + +Complete guide for installing and configuring the ALwrity usage-based subscription system. + +## Prerequisites + +- Python 3.8+ +- PostgreSQL database (or SQLite for development) +- FastAPI backend environment +- Required Python packages: `sqlalchemy`, `loguru`, `fastapi` + +## Installation + +### 1. Database Migration + +Run the database setup script to create all subscription tables: + +```bash +cd backend +python scripts/create_subscription_tables.py +``` + +This script will: +- Create all subscription-related database tables +- Initialize default subscription plans (Free, Basic, Pro, Enterprise) +- Configure API pricing for all providers +- Verify the setup + +### 2. Verify Installation + +Test the subscription system: + +```bash +python test_subscription_system.py +``` + +This will verify: +- Database table creation +- Pricing calculations +- Usage tracking +- Limit enforcement +- Error handling +- API endpoints + +### 3. Start the Server + +```bash +python start_alwrity_backend.py +``` + +## Configuration + +### Environment Variables + +Create or update your `.env` file with the following: + +```env +# Database Configuration +DATABASE_URL=postgresql://user:password@localhost/alwrity +# For development, you can use SQLite: +# DATABASE_URL=sqlite:///./alwrity.db + +# API Keys (required for usage tracking) +GEMINI_API_KEY=your_gemini_key +OPENAI_API_KEY=your_openai_key +ANTHROPIC_API_KEY=your_anthropic_key +# ... other API keys as needed + +# HuggingFace Pricing (optional, for GPT-OSS-120B via Groq) +HUGGINGFACE_INPUT_TOKEN_COST=0.000001 +HUGGINGFACE_OUTPUT_TOKEN_COST=0.000003 +``` + +### HuggingFace Pricing Configuration + +HuggingFace API calls (specifically for GPT-OSS-120B model via Groq) are tracked and billed using configurable pricing. + +#### Environment Variables + +- `HUGGINGFACE_INPUT_TOKEN_COST`: Cost per input token (default: `0.000001` = $1 per 1M tokens) +- `HUGGINGFACE_OUTPUT_TOKEN_COST`: Cost per output token (default: `0.000003` = $3 per 1M tokens) + +#### Updating Pricing + +The pricing is automatically initialized when the database is set up. To update pricing after changing environment variables: + +1. **Option 1**: Restart the backend server (pricing will be updated on next initialization) +2. **Option 2**: Run the database setup script again: + ```bash + python backend/scripts/create_subscription_tables.py + ``` + +#### Verify Pricing + +Check that pricing is correctly configured by: +1. Checking the database `api_provider_pricing` table +2. Making a test API call and checking the cost in usage logs +3. Viewing the billing dashboard to see cost calculations + +## Production Setup + +### 1. Database + +Use PostgreSQL for production: + +```env +DATABASE_URL=postgresql://user:password@host:5432/alwrity_prod +``` + +### 2. Caching + +Set up Redis for caching (optional but recommended): + +```env +REDIS_URL=redis://localhost:6379/0 +``` + +### 3. Email Notifications + +Configure email service for usage alerts: + +```env +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USER=alerts@alwrity.com +SMTP_PASSWORD=your_password +``` + +### 4. Monitoring and Alerting + +Set up monitoring and alerting systems: +- Configure log aggregation +- Set up performance monitoring +- Configure alert thresholds + +### 5. Payment Processing + +Implement payment processing integration: +- Stripe integration +- Payment gateway setup +- Billing cycle management + +## Middleware Integration + +The subscription system automatically tracks API usage through enhanced middleware. The middleware: + +- Detects API provider from request patterns +- Estimates token usage from request/response content +- Validates usage limits before processing +- Calculates costs in real-time +- Logs all API calls for tracking + +No additional configuration is required - the middleware is automatically active once the subscription system is installed. + +## Usage Limit Enforcement + +The system enforces usage limits automatically: + +```python +# Usage limits are checked before processing requests +can_proceed, message, usage_info = await usage_service.enforce_usage_limits( + user_id=user_id, + provider=APIProvider.GEMINI, + tokens_requested=1000 +) + +if not can_proceed: + return JSONResponse( + status_code=429, + content={"error": "Usage limit exceeded", "message": message} + ) +``` + +## Testing + +### Run Tests + +```bash +python test_subscription_system.py +``` + +### Test Coverage + +The test suite covers: +- Database table creation +- Pricing calculations +- Usage tracking +- Limit enforcement +- Error handling +- API endpoints + +## Troubleshooting + +### Common Issues + +1. **Database Connection Errors** + - Check `DATABASE_URL` configuration + - Verify database is running + - Check network connectivity + +2. **Missing API Keys** + - Verify all required keys are set in `.env` + - Check environment variable names match exactly + +3. **Usage Not Tracking** + - Verify middleware is integrated + - Check database connection + - Review logs for errors + +4. **Pricing Errors** + - Verify provider pricing configuration in database + - Check `api_provider_pricing` table + - Review pricing initialization logs + +### Debug Mode + +Enable debug logging: + +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +### Support + +For issues and questions: +1. Check the logs in `logs/subscription_errors.log` +2. Run the test suite to identify problems +3. Review the error handling documentation +4. Contact the development team + +## Next Steps + +- [API Reference](api-reference.md) - Endpoint documentation and examples +- [Pricing](pricing.md) - Subscription plans and API pricing details +- [Frontend Integration](frontend-integration.md) - Technical specifications for frontend + +--- + +**Last Updated**: January 2025 + diff --git a/docs-site/mkdocs.yml b/docs-site/mkdocs.yml index 7b144239..e6b765d1 100644 --- a/docs-site/mkdocs.yml +++ b/docs-site/mkdocs.yml @@ -236,6 +236,22 @@ nav: - Grounding UI: features/ai/grounding-ui.md - LinkedIn Writer: - Overview: features/linkedin-writer/overview.md + - Integrations: + - Wix: + - Overview: features/integrations/wix/overview.md + - Setup: features/integrations/wix/setup.md + - Publishing: features/integrations/wix/publishing.md + - API: features/integrations/wix/api.md + - SEO Metadata: features/integrations/wix/seo-metadata.md + - Testing (Bypass): features/integrations/wix/testing-bypass.md + - Subscription: + - Overview: features/subscription/overview.md + - Setup: features/subscription/setup.md + - API Reference: features/subscription/api-reference.md + - Pricing: features/subscription/pricing.md + - Frontend Integration: features/subscription/frontend-integration.md + - Implementation Status: features/subscription/implementation-status.md + - Roadmap: features/subscription/roadmap.md - API Reference: - Overview: api/overview.md - Authentication: api/authentication.md diff --git a/docs/AI_BLOG_WRITER_IMPLEMENTATION_SPEC.md b/docs/AI_BLOG_WRITER_IMPLEMENTATION_SPEC.md deleted file mode 100644 index 817f3381..00000000 --- a/docs/AI_BLOG_WRITER_IMPLEMENTATION_SPEC.md +++ /dev/null @@ -1,427 +0,0 @@ -## AI Blog Writer — Implementation Specification (Copilot-first, Research-led) - -### Overview -- **Goal**: Build a SOTA AI blog writer that guides non-technical users end-to-end: research → outline → section generation → quality/SEO → publishing. -- **Approach**: Copilot-first UX using CopilotKit. Reuse LinkedIn assistive writing patterns: Google Search grounding, Exa research, hallucination detector, quality analysis, citations. -- **User Interaction Model**: The user only talks to the Copilot; the editor reflects all state and changes via generative UI and HITL confirmations. - -### 🚀 **Current Implementation Status** (Updated: December 2024) - -**✅ COMPLETED PHASES:** -- **Stage 1: Research & Strategy** - ✅ FULLY IMPLEMENTED -- **Stage 2: Content Planning (Outline)** - ✅ FULLY IMPLEMENTED -- **Backend Architecture** - ✅ MODULAR & PRODUCTION-READY -- **Frontend UI Components** - ✅ COMPREHENSIVE EDITOR -- **CopilotKit Integration** - ✅ FULLY FUNCTIONAL - -**🔄 IN PROGRESS:** -- **Stage 3: Content Generation** - 🔄 PARTIALLY IMPLEMENTED -- **Stage 4: SEO & Publishing** - 🔄 PARTIALLY IMPLEMENTED - -**📋 TODO:** -- Section-by-section content generation -- Full SEO optimization pipeline -- Publishing integrations (Wix/WordPress) -- Advanced quality checks - -### Key Principles -- **AI-first, HITL**: The assistant leads with intelligent suggestions; the user approves via render-and-wait HITL components where appropriate. -- **Research fidelity**: Google grounding + Exa researcher; hallucination detection with claim verification; pervasive citations. -- **Persona-aware**: Import blog writing persona from DB and apply it across planning/generation/optimizations. -- **SEO-excellent**: Real-time SEO analysis, metadata generation, schema, and image alt handling. -- **Publish-ready**: Smooth handoff to Wix/WordPress; preview and scheduling. - ---- - -## 1) Workflow (4 Stages) - -### Stage 1: Research & Strategy (AI Orchestration) ✅ **FULLY IMPLEMENTED** - -**✅ IMPLEMENTED FEATURES:** -- **Google Search Grounding**: Single Gemini API call with native Google Search integration -- **Intelligent Caching**: Exact keyword match caching to reduce API costs -- **AI-Powered Analysis**: Keyword analysis, competitor analysis, content angle generation -- **Robust Error Handling**: No fallback data - only real AI-generated insights or graceful failures -- **Progress Tracking**: Real-time progress messages during research operations - -**✅ IMPLEMENTED INPUTS:** -- `keywords: string[]`, `industry: string`, `targetAudience: string`, `wordCountTarget: number` -- Persona support (basic implementation) - -**✅ IMPLEMENTED BACKEND/SERVICES:** -- **Modular Architecture**: `ResearchService`, `KeywordAnalyzer`, `CompetitorAnalyzer`, `ContentAngleGenerator` -- **Google Grounding**: Native Gemini Google Search integration (no Exa dependency) -- **Caching System**: Intelligent research result caching with TTL and LRU eviction -- **Error Handling**: Graceful failure with specific error messages - -**✅ IMPLEMENTED COPILOTKIT ACTIONS:** -- `researchTopic(keywords, industry, target_audience, blogLength)` → comprehensive research with sources -- `chatWithResearchData(question)` → interactive research data exploration -- `getResearchKeywords()` → HITL keyword collection form -- `performResearch(formData)` → research execution with form data - -**✅ IMPLEMENTED GENERATIVE UI:** -- **ResearchResults Component**: Sources, credibility scores, keyword analysis, content angles -- **KeywordInputForm**: HITL form for keyword collection with blog length selection -- **Progress Messages**: Real-time loading states with CopilotKit status system - -**✅ IMPLEMENTED SUGGESTIONS:** -- "I want to research a topic for my blog" (initial) -- "Let's proceed to create an Outline" (post-research) -- "Chat with Research Data" (exploration) -- "Create outline with custom inputs" (advanced) - ---- - -### Stage 2: Content Planning (AI + Human) ✅ **FULLY IMPLEMENTED** - -**✅ IMPLEMENTED DELIVERABLES:** -- **Structured Outline**: H1/H2/H3 hierarchy with per-section key points and target word counts -- **AI-Generated Titles**: Multiple title options with SEO optimization -- **Research Integration**: Outline sections linked to research sources and keywords -- **Word Count Distribution**: Intelligent word allocation across sections - -**✅ IMPLEMENTED COPILOTKIT ACTIONS:** -- `generateOutline()` → AI-powered outline generation from research data -- `createOutlineWithCustomInputs(customInstructions)` → custom outline with user instructions -- `refineOutline(operation, sectionId, payload)` → add/remove/move/merge/rename sections -- `enhanceSection(sectionId, focus)` → AI enhancement of individual sections -- `optimizeOutline(focus)` → AI optimization of entire outline -- `rebalanceOutline(targetWords)` → word count rebalancing across sections - -**✅ IMPLEMENTED GENERATIVE UI:** -- **EnhancedOutlineEditor**: Interactive outline editor with expandable sections -- **TitleSelector**: AI-generated title options with custom title creation -- **CustomOutlineForm**: HITL form for custom outline instructions -- **Section Management**: Add, edit, reorder, merge sections with visual feedback -- **Research Integration**: Source references and keyword suggestions per section - -**✅ IMPLEMENTED SUGGESTIONS:** -- "Generate outline" (standard) -- "Create outline with custom inputs" (advanced) -- "Enhance section [X]" (section-specific) -- "Optimize entire outline" (global) -- "Rebalance word counts" (distribution) - ---- - -### Stage 3: Content Generation (CopilotKit-only, no multi-agent) 🔄 **PARTIALLY IMPLEMENTED** - -**🔄 PARTIALLY IMPLEMENTED DELIVERABLES:** -- **Section Generation**: Basic section generation with markdown output -- **Content Structure**: Sectioned markdown with inline citations support -- **Quality Checks**: Hallucination detection integration - -**✅ IMPLEMENTED COPILOTKIT ACTIONS:** -- `generateSection(sectionId)` → generates content for specific section -- `generateAllSections()` → placeholder for bulk generation -- `runHallucinationCheck()` → integrates with hallucination detector service - -**🔄 PARTIALLY IMPLEMENTED UI:** -- **Section Editors**: Basic markdown editing per section -- **DiffPreview Component**: Exists but needs integration -- **Citation System**: Basic structure in place - -**📋 TODO:** -- Full section-by-section content generation -- Advanced content optimization -- Inline citation management -- Content quality improvements -- Progress tracking for bulk generation - ---- - -### Stage 4: Optimization & Publishing (AI + Human) 🔄 **PARTIALLY IMPLEMENTED** - -**🔄 PARTIALLY IMPLEMENTED SEO OPTIMIZATION:** -- **SEO Analysis**: Basic SEO analysis with keyword density and structure -- **Metadata Generation**: Title options and meta description generation -- **SEO Integration**: Wraps existing SEO tools services - -**✅ IMPLEMENTED COPILOTKIT ACTIONS:** -- `runSEOAnalyze(keywords)` → SEO analysis with scores and recommendations -- `generateSEOMetadata(title)` → metadata generation for titles and descriptions -- `publishToPlatform(platform, schedule)` → placeholder for publishing - -**🔄 PARTIALLY IMPLEMENTED UI:** -- **SEOMiniPanel**: Basic SEO analysis display -- **Metadata Management**: Title and description editing - -**📋 TODO:** -- Full SEO optimization pipeline -- Advanced SEO recommendations -- Publishing integrations (Wix/WordPress) -- Content optimization with diff preview -- Image alt text and media management -- Schema markup generation - ---- - -## 2) SEO Tools Integration & Metadata - -Existing Services to Wrap -- Meta Description, OpenGraph, Image Alt, On-Page SEO, Technical SEO, Content Strategy (see `backend/services/seo_tools/*` and docs). - -Unified Endpoints -- `POST /api/blog/seo/analyze` → { seoScore, density, structure, readability, link suggestions, image alt status, recs } -- `POST /api/blog/seo/metadata` → { titleOptions, metaDescriptionOptions, openGraph, twitterCard, schema: { Article, FAQ?, Breadcrumb, Org/Person } } - -Editor SEO Panel -- Live density and distribution, readability (Flesch-Kincaid), heading hierarchy, internal/external link suggestions. -- One-click “Apply Fix” with diff preview. - -Schema -- Default Article schema; optional FAQ when Q&A snippets exist; Breadcrumb, Organization/Person as applicable. - ---- - -## 3) Dedicated Blog Editor Design (Copilot-first) - -Layout -- Left: Markdown Editor (per-section tabs), word count, persona cues, inline citation chips. -- Right: Live Preview (desktop/mobile), SEO SERP snippet preview, social preview (OG/Twitter). -- Sidebar Panels: Research (sources, claims), SEO (scores/fixes), Media (AI images + alt text), History (versions). - -Core Components -- `BlogResearchCard` (render-only): sources, credibility scores, add-to-outline. -- `OutlineEditor` (HITL): drag-drop H2/H3, per-section refs and target words. -- `SectionEditor`: markdown area with persona/tone badges; per-section SEO mini-score. -- `DiffPreview` (HITL): apply/reject AI edits. -- `SEOPanel`: density/structure/readability + apply fix. -- `MediaPanel`: AI images, compression, automatic alt-text. - -CopilotKit Integrations -- Suggestions: set programmatically (`useCopilotChatHeadless_c`) or via `CopilotSidebar` props. -- Generative UI: `useCopilotAction({ render })` for research cards, outline editor, diff preview, publish dialog. -- HITL: `renderAndWaitForResponse` for approvals at outline, diff apply, and publish steps. -- References: CopilotKit docs — Frontend Actions, Generative UI, Suggestions, HITL. - -Persistence -- Persist outline, per-section content, references, persona snapshot, SEO state, metadata drafts. -- Auto-save every 30s; version history for undo. - ---- - -## 4) Backend APIs ✅ **FULLY IMPLEMENTED** - -**✅ IMPLEMENTED BLOG ENDPOINTS:** -- `POST /api/blog/research/start` → async research with progress tracking -- `GET /api/blog/research/status/{task_id}` → research progress status -- `POST /api/blog/outline/start` → async outline generation with progress -- `GET /api/blog/outline/status/{task_id}` → outline progress status -- `POST /api/blog/outline/refine` → outline refinement operations -- `POST /api/blog/outline/rebalance` → word count rebalancing -- `POST /api/blog/section/generate` → section content generation -- `POST /api/blog/section/optimize` → content optimization -- `POST /api/blog/quality/hallucination-check` → hallucination detection -- `POST /api/blog/seo/analyze` → SEO analysis and recommendations -- `POST /api/blog/seo/metadata` → metadata generation -- `POST /api/blog/publish` → publishing to platforms -- `GET /api/blog/health` → service health check - -**✅ IMPLEMENTED MODULAR ARCHITECTURE:** -- **Core Service**: `BlogWriterService` - main orchestrator -- **Research Module**: `ResearchService`, `KeywordAnalyzer`, `CompetitorAnalyzer`, `ContentAngleGenerator` -- **Outline Module**: `OutlineService`, `OutlineGenerator`, `OutlineOptimizer`, `SectionEnhancer` -- **Caching System**: Intelligent research result caching with TTL and LRU eviction -- **Error Handling**: Graceful failure with specific error messages - -**✅ IMPLEMENTED MODELS:** -- `BlogResearchRequest`, `BlogResearchResponse` -- `BlogOutlineRequest`, `BlogOutlineResponse`, `BlogOutlineRefineRequest` -- `BlogSectionRequest`, `BlogSectionResponse` -- `BlogOptimizeRequest`, `BlogOptimizeResponse` -- `BlogSEOAnalyzeRequest`, `BlogSEOAnalyzeResponse` -- `BlogSEOMetadataRequest`, `BlogSEOMetadataResponse` -- `BlogPublishRequest`, `BlogPublishResponse` -- `HallucinationCheckRequest`, `HallucinationCheckResponse` - -**✅ REUSED SERVICES:** -- `/api/hallucination-detector/*` - hallucination detection integration -- SEO tools services - wrapped for blog-specific analysis - ---- - -## 5) CopilotKit Action Inventory ✅ **COMPREHENSIVE IMPLEMENTATION** - -**✅ RESEARCH ACTIONS (FULLY IMPLEMENTED):** -- `researchTopic(keywords, industry, target_audience, blogLength)` → comprehensive research -- `chatWithResearchData(question)` → interactive research exploration -- `getResearchKeywords()` → HITL keyword collection form -- `performResearch(formData)` → research execution with form data - -**✅ PLANNING ACTIONS (FULLY IMPLEMENTED):** -- `generateOutline()` → AI-powered outline generation -- `createOutlineWithCustomInputs(customInstructions)` → custom outline creation -- `refineOutline(operation, sectionId, payload)` → outline refinement operations -- `enhanceSection(sectionId, focus)` → section enhancement -- `optimizeOutline(focus)` → outline optimization -- `rebalanceOutline(targetWords)` → word count rebalancing - -**🔄 GENERATION ACTIONS (PARTIALLY IMPLEMENTED):** -- `generateSection(sectionId)` → section content generation ✅ -- `generateAllSections()` → bulk generation (placeholder) 🔄 -- `runHallucinationCheck()` → hallucination detection ✅ - -**🔄 SEO ACTIONS (PARTIALLY IMPLEMENTED):** -- `runSEOAnalyze(keywords)` → SEO analysis ✅ -- `generateSEOMetadata(title)` → metadata generation ✅ - -**🔄 PUBLISHING ACTIONS (PARTIALLY IMPLEMENTED):** -- `publishToPlatform(platform, schedule)` → publishing (placeholder) 🔄 - -**✅ UX/RENDER-ONLY/HITL (FULLY IMPLEMENTED):** -- `ResearchResults` → research data visualization -- `EnhancedOutlineEditor` → interactive outline management -- `KeywordInputForm` → HITL keyword collection -- `CustomOutlineForm` → HITL custom outline creation -- `TitleSelector` → title selection and creation -- `DiffPreview` → content diff visualization -- `SEOMiniPanel` → SEO analysis display - ---- - -## 6) Intelligent Suggestions (states) - -Before research -- “Load persona”, “Analyze keywords”, “Research topic” - -After research -- “Generate outline”, “Add competitor H2s”, “Attach sources” - -Outline ready -- “Generate [Section 1]”, “…”, “Generate all sections” - -Draft ready -- “Run fact-check”, “Run SEO analysis”, “Generate metadata” - -Final -- “Publish to WordPress”, “Schedule on Wix” - ---- - -## 7) Delivery Plan / Milestones ✅ **UPDATED STATUS** - -**✅ MILESTONE 1: Research + Outline (COMPLETED)** -- ✅ Actions: research topic, generate outline, outline editor (HITL) -- ✅ Google Search grounding integration -- ✅ AI-powered keyword and competitor analysis -- ✅ Interactive outline editor with refinement capabilities -- ✅ Research data visualization and exploration - -**🔄 MILESTONE 2: Section Generation + Quality (IN PROGRESS)** -- ✅ generateSection (basic implementation) -- 🔄 generateAllSections (needs full implementation) -- 🔄 optimizeSection with diff preview (needs integration) -- ✅ hallucination check integration -- 📋 Content quality improvements and optimization - -**🔄 MILESTONE 3: SEO & Metadata (IN PROGRESS)** -- ✅ analyzeSEO panel (basic implementation) -- ✅ generateSEOMetadata (title/meta generation) -- 📋 Advanced SEO recommendations and fixes -- 📋 Schema markup and social media optimization - -**📋 MILESTONE 4: Publishing (TODO)** -- 📋 prepareForPublish functionality -- 📋 publishToPlatform (Wix/WordPress integration) -- 📋 Scheduling and publishing workflow -- 📋 Success URL and status tracking - -**📋 MILESTONE 5: Polish (TODO)** -- 📋 Advanced readability aids -- 📋 Version history and auto-save -- 📋 Performance optimization -- 📋 Accessibility improvements - ---- - -## 8) Current Architecture & Implementation Details - -### 🏗️ **Backend Architecture (Modular & Production-Ready)** - -**Core Service Structure:** -``` -backend/services/blog_writer/ -├── core/ -│ └── blog_writer_service.py # Main orchestrator -├── research/ -│ ├── research_service.py # Research orchestration -│ ├── keyword_analyzer.py # AI keyword analysis -│ ├── competitor_analyzer.py # Competitor intelligence -│ └── content_angle_generator.py # Content angle discovery -├── outline/ -│ ├── outline_service.py # Outline orchestration -│ ├── outline_generator.py # AI outline generation -│ ├── outline_optimizer.py # Outline optimization -│ └── section_enhancer.py # Section enhancement -└── blog_service.py # Entry point (thin wrapper) -``` - -**Key Features:** -- **No Fallback Data**: Only real AI-generated insights or graceful failures -- **Intelligent Caching**: Research result caching with TTL and LRU eviction -- **Error Handling**: Specific error messages and retry logic -- **Progress Tracking**: Real-time progress updates for long-running operations - -### 🎨 **Frontend Architecture (CopilotKit-First)** - -**Component Structure:** -``` -frontend/src/components/BlogWriter/ -├── BlogWriter.tsx # Main orchestrator component -├── ResearchAction.tsx # Research CopilotKit actions -├── ResearchResults.tsx # Research data visualization -├── KeywordInputForm.tsx # HITL keyword collection -├── EnhancedOutlineEditor.tsx # Interactive outline editor -├── TitleSelector.tsx # Title selection and creation -├── CustomOutlineForm.tsx # HITL custom outline creation -├── ResearchDataActions.tsx # Research data interaction -├── EnhancedOutlineActions.tsx # Outline management actions -├── DiffPreview.tsx # Content diff visualization -└── SEOMiniPanel.tsx # SEO analysis display -``` - -**Key Features:** -- **CopilotKit Integration**: Full action system with HITL components -- **Real-time Updates**: Progress messages and status tracking -- **Interactive UI**: Drag-and-drop, expandable sections, visual feedback -- **Error Handling**: User-friendly error messages and recovery - -### 🔧 **Technical Implementation Highlights** - -**Research Phase:** -- Single Gemini API call with Google Search grounding -- AI-powered analysis of keywords, competitors, and content angles -- Intelligent caching to reduce API costs -- No fallback data - only real AI insights - -**Outline Phase:** -- Research-driven outline generation -- Interactive outline editor with full CRUD operations -- AI-powered section enhancement and optimization -- Word count rebalancing and distribution - -**Quality Assurance:** -- Robust error handling with specific messages -- Progress tracking for long-running operations -- Graceful failure without misleading data -- Real-time user feedback and guidance - ---- - -## 9) References -- CopilotKit Quickstart, Frontend Actions, Generative UI, HITL, Suggestions - - Quickstart: https://docs.copilotkit.ai/direct-to-llm/guides/quickstart - - Frontend Actions: https://docs.copilotkit.ai/frontend-actions - - Generative UI: https://docs.copilotkit.ai/direct-to-llm/guides/generative-ui - - Headless + Suggestions + HITL: https://docs.copilotkit.ai/premium/headless-ui - ---- - -## 9) Notes on Reuse from LinkedIn Writer -- Research handler; Gemini grounded provider; citation manager; quality analyzer. -- Hallucination detector + Exa verification endpoints. -- CopilotKit integration patterns: actions, suggestions, render/HITL, state persistence. - - diff --git a/docs/ALPHA_SUBSCRIPTION_IMPLEMENTATION_PLAN.md b/docs/ALPHA_SUBSCRIPTION_IMPLEMENTATION_PLAN.md deleted file mode 100644 index 3fdb38f5..00000000 --- a/docs/ALPHA_SUBSCRIPTION_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,207 +0,0 @@ -# Alpha Subscription System Implementation Plan - -## 🎯 **Your Unique Situation Analysis** - -### **Why BUILD is Perfect for You:** - -1. **80% Already Built** - You have comprehensive subscription models, usage tracking, and billing infrastructure -2. **Unique Business Model** - Outcome-based billing doesn't exist in external solutions -3. **Cost Control Critical** - Need real-time protection from API bleeding -4. **Alpha Testing Perfect** - Simple limits, easy to modify based on feedback - -### **Cost Comparison:** -- **External Solutions**: $7,500+ annually (Stripe, Chargebee, Recurly) -- **Your Build**: $0 (you're doing it) + 1-2 weeks development -- **ROI**: Immediate cost savings + perfect fit for your needs - -## 🚀 **Implementation Phases** - -### **Phase 1: Fix Current System (2-3 hours)** - -#### **1.1 Fix Monitoring Middleware Integration** ✅ COMPLETED -- ✅ Updated API provider detection patterns -- ✅ Enhanced user ID extraction -- ✅ Fixed request body reading issues -- ✅ Added comprehensive logging - -#### **1.2 Test Billing System** -```bash -# Start backend -python backend/start_alwrity_backend.py - -# Test endpoints -python backend/quick_billing_test.py -``` - -### **Phase 2: Alpha Subscription Tiers (1 week)** - -#### **2.1 Alpha Subscription Plans** ✅ COMPLETED -```python -ALPHA_TIERS = { - "Free Alpha": { - "daily_tokens": 1000, # ~$0.10/day - "daily_images": 5, # ~$0.25/day - "monthly_cost_limit": 10.00, - "features": ["blog_writer", "basic_seo"] - }, - "Basic Alpha": { - "daily_tokens": 10000, # ~$1.00/day - "daily_images": 50, # ~$2.50/day - "monthly_cost_limit": 100.00, - "features": ["blog_writer", "seo_analysis", "content_planning"] - }, - "Pro Alpha": { - "daily_tokens": 50000, # ~$5.00/day - "daily_images": 200, # ~$10.00/day - "monthly_cost_limit": 500.00, - "features": ["all_features", "advanced_analytics"] - } -} -``` - -#### **2.2 Cost Control Implementation** -```python -# Emergency stops to prevent bleeding: -EMERGENCY_LIMITS = { - "daily_token_limit": 1000, # Hard stop - "daily_cost_limit": 5.00, # Hard stop - "warning_threshold": 0.80, # 80% usage warning - "block_threshold": 0.95, # 95% usage block -} -``` - -### **Phase 3: Real-Time Usage Monitoring (3-5 days)** - -#### **3.1 Usage Tracking Dashboard** -- Real-time token usage display -- Cost tracking per user -- Usage warnings at 80% limit -- Automatic blocking at 95% limit - -#### **3.2 Admin Controls** -- Override user limits for testing -- Emergency stop all API calls -- Real-time cost monitoring -- User usage analytics - -### **Phase 4: Future Outcome-Based Billing (Future)** - -#### **4.1 Goal-Based Billing Architecture** -```python -class OutcomeBasedBilling: - def __init__(self): - self.goals = [ - "traffic_increase", - "conversion_rate", - "engagement_rate", - "lead_generation" - ] - self.milestones = [25%, 50%, 75%, 100%] - - def calculate_billing(self, goal_achievement): - # Pay only when goals are achieved - if goal_achievement >= 100: - return full_payment - elif goal_achievement >= 75: - return partial_payment * 0.75 - # etc. -``` - -## 🛡️ **Cost Control Strategy** - -### **Immediate Protection (Alpha Phase)** -1. **Daily Token Limits**: Hard stops at conservative limits -2. **Real-Time Monitoring**: Track every API call -3. **Automatic Blocking**: Stop requests at 95% usage -4. **Emergency Override**: Admin can stop all API calls -5. **User Notifications**: Warn at 80% usage - -### **Alpha Tester Onboarding** -1. **Start Conservative**: All testers start with Free Alpha (1000 tokens/day) -2. **Monitor Usage**: Track actual usage patterns -3. **Adjust Limits**: Increase limits based on real data -4. **Promote Active Users**: Move to Basic/Pro Alpha as needed - -## 📊 **Expected Alpha Usage Patterns** - -### **Conservative Estimates** -```python -ALPHA_USAGE_ESTIMATES = { - "casual_tester": { - "daily_tokens": 500, # Light usage - "daily_images": 2, # Occasional images - "monthly_cost": 15.00 - }, - "active_tester": { - "daily_tokens": 2000, # Regular usage - "daily_images": 10, # Regular images - "monthly_cost": 60.00 - }, - "power_tester": { - "daily_tokens": 5000, # Heavy usage - "daily_images": 25, # Many images - "monthly_cost": 150.00 - } -} -``` - -### **Cost Protection** -- **Free Alpha**: Max $10/month per user -- **Basic Alpha**: Max $100/month per user -- **Pro Alpha**: Max $500/month per user -- **Emergency Stop**: Admin can stop all API calls instantly - -## 🎯 **Implementation Timeline** - -### **Week 1: Core System** -- ✅ Fix monitoring middleware -- ✅ Create alpha subscription tiers -- ✅ Test billing system -- ✅ Implement basic cost control - -### **Week 2: Alpha Launch** -- Deploy alpha subscription system -- Onboard first 10 alpha testers -- Monitor usage patterns -- Adjust limits based on real data - -### **Week 3-4: Refinement** -- Add usage warnings/alerts -- Implement admin controls -- Create usage analytics -- Prepare for beta launch - -## 🚀 **Next Steps** - -### **Immediate (Today)** -1. **Test Current System**: Run `python backend/quick_billing_test.py` -2. **Verify Monitoring**: Check logs for API call tracking -3. **Deploy Alpha Tiers**: System is ready for alpha testers - -### **This Week** -1. **Onboard Alpha Testers**: Start with Free Alpha tier -2. **Monitor Usage**: Track real usage patterns -3. **Adjust Limits**: Based on actual data - -### **Next Week** -1. **Add Warnings**: 80% usage notifications -2. **Admin Controls**: Emergency stop capabilities -3. **Usage Analytics**: Dashboard for monitoring - -## 💡 **Key Success Factors** - -1. **Start Conservative**: Better to have limits too low than too high -2. **Monitor Closely**: Track every API call and cost -3. **Iterate Quickly**: Adjust limits based on real usage data -4. **Communicate Clearly**: Alpha testers understand the limits -5. **Have Emergency Plans**: Admin override and emergency stops - -## 🎉 **Why This Will Work** - -1. **You're 80% There**: Just need integration fixes -2. **Perfect for Alpha**: Simple limits, easy to modify -3. **Cost Protected**: Real-time monitoring and blocking -4. **Future Ready**: Foundation for outcome-based billing -5. **You Control It**: No external dependencies or fees - -**Bottom Line**: You have a sophisticated subscription system that just needs integration fixes. Perfect for alpha testing and future outcome-based billing! diff --git a/docs/ALPHA_TESTING_SETUP_COMPLETE.md b/docs/ALPHA_TESTING_SETUP_COMPLETE.md deleted file mode 100644 index 7a0470a7..00000000 --- a/docs/ALPHA_TESTING_SETUP_COMPLETE.md +++ /dev/null @@ -1,291 +0,0 @@ -# Alpha Testing Setup - Complete Implementation Summary - -## 🎉 **Overview** - -ALwrity is now ready for alpha testing with 5 testers! This document summarizes all changes made to support subscription management, billing enforcement, and a streamlined user onboarding flow. - ---- - -## ✅ **Phase 1: Emergency Subscription Enforcement - COMPLETE** - -### **Backend Changes** - -1. **✅ Enabled Monitoring Middleware** (`backend/app.py`) - - Uncommented `app.middleware("http")(monitoring_middleware)` - - Real-time API usage tracking and enforcement - - Returns 429 errors when limits exceeded - -2. **✅ Added Subscription Status Endpoint** (`backend/api/subscription_api.py`) - - New endpoint: `GET /api/subscription/status/{user_id}` - - Returns active subscription status with limits - - Supports Free, Basic, Pro, Enterprise tiers - -3. **✅ Added Subscription Management Endpoint** (`backend/api/subscription_api.py`) - - New endpoint: `POST /api/subscription/subscribe/{user_id}` - - Creates/updates user subscriptions - - Handles billing cycle (monthly/yearly) - -### **Frontend Changes** - -1. **✅ Subscription Context & Provider** (`frontend/src/contexts/SubscriptionContext.tsx`) - - Global subscription state management - - Auto-refresh every 5 minutes - - Listens for subscription updates - -2. **✅ Subscription Guard Component** (`frontend/src/components/SubscriptionGuard.tsx`) - - Protects features when subscription inactive - - Shows upgrade prompts - - Redirects to `/pricing` page - -3. **✅ Subscription Hook** (`frontend/src/hooks/useSubscriptionGuard.ts`) - - Check feature access - - Get remaining usage - - Validate subscription status - -4. **✅ Protected Dashboard** (`frontend/src/components/MainDashboard/MainDashboard.tsx`) - - Wrapped main content with `SubscriptionGuard` - - Shows upgrade prompts for inactive subscriptions - ---- - -## ✅ **Phase 2: Pricing Page & User Flow - COMPLETE** - -### **Subscription Tiers** - -| Plan | Status | Price | Platforms | AI Content | Limits | -|------|--------|-------|-----------|------------|--------| -| **Free** | ✅ Enabled | $0/mo | Blog, LinkedIn, Facebook | Text + Image | 100 AI calls | -| **Basic** | ✅ Enabled | $29/mo | Blog, LinkedIn, Facebook | Text + Image | 500 AI calls | -| **Pro** | 🔒 Coming Soon | $79/mo | 6 Social Platforms | Text + Image + Audio + Video | 2000 AI calls | -| **Enterprise** | 🔒 Contact Sales | $199/mo | 6 Social Platforms | All AI + Custom | Unlimited | - -### **Pricing Page Features** (`frontend/src/components/Pricing/PricingPage.tsx`) - -1. **✅ Comprehensive Feature Showcase** - - Platform access details (Blog, LinkedIn, Facebook writers) - - Platform integrations (Wix, WordPress, GSC) - - AI content creation capabilities - - Interactive tooltips with info icons - - "Know More" modals with detailed explanations - -2. **✅ Alpha Testing Configuration** - - Free & Basic plans: Selectable - - Pro plan: Disabled ("Coming Soon") - - Enterprise plan: Disabled ("Contact Sales") - -3. **✅ Mock Payment Flow** - - Shows payment modal for Basic plan - - "Alpha testing credit: $29" message - - Auto-redirects to onboarding/dashboard after subscription - -### **Updated User Flow** (`frontend/src/App.tsx`) - -**New Authentication Flow:** -``` -Landing Page (with pricing link) - ↓ Sign In (Clerk) -Check Subscription Status - ├─ No Subscription? → Pricing Page - └─ Has Subscription? - ├─ Onboarding Complete? → Dashboard - └─ Onboarding Incomplete? → Onboarding -``` - -**First-Time User Journey:** -1. View landing page with features/pricing -2. Sign in via Clerk -3. **Redirected to `/pricing`** (no subscription) -4. Select Free or Basic plan -5. **Redirected to `/onboarding`** (if incomplete) -6. Complete 6-step onboarding -7. **Redirected to `/dashboard`** - -### **Landing Page Integration** (`frontend/src/components/Landing/Landing.tsx`) - -- ✅ Added pricing section to landing page -- ✅ "View All Plans & Features" button → navigates to `/pricing` -- ✅ Positioned after feature showcase, before final CTA - ---- - -## ✅ **Database Setup** - -### **Created Subscription Tables** - -1. **`subscription_plans`**: Plan definitions (Free, Basic, Pro, Enterprise) -2. **`user_subscriptions`**: User subscription records -3. **`api_usage_logs`**: Detailed API call tracking -4. **`usage_summaries`**: Aggregated usage statistics -5. **`api_provider_pricing`**: API cost configuration -6. **`usage_alerts`**: Usage threshold alerts -7. **`billing_history`**: Historical billing records - -### **Migration Scripts** - -1. **`backend/scripts/create_subscription_tables.py`** - Creates all subscription tables -2. **`backend/scripts/cleanup_alpha_plans.py`** - Updates plan limits and removes alpha plans - -**Executed Successfully:** -```bash -✅ 6 tables created -✅ 22 API pricing entries configured -✅ 4 subscription plans initialized -✅ Plan limits updated for alpha testing -``` - ---- - -## ✅ **Documentation & Setup** - -### **Created Files** - -1. **`setup_alwrity.sh`** - Automated setup for macOS/Linux -2. **`setup_alwrity.bat`** - Automated setup for Windows -3. **`.github/INSTALLATION.md`** - Complete manual setup guide -4. **`.github/TROUBLESHOOTING.md`** - Fix for GitHub Issue #291 -5. **`README.md`** - Concise root README (GitHub best practices) - -### **Documentation Structure (GitHub Best Practices)** - -``` -ALwrity/ -├── README.md # Concise overview & quick start -├── setup_alwrity.sh # Automated setup (Unix) -├── setup_alwrity.bat # Automated setup (Windows) -├── .github/ -│ ├── README.md # Detailed features & roadmap -│ ├── INSTALLATION.md # Complete setup guide -│ ├── TROUBLESHOOTING.md # Common issues & fixes -│ ├── CONTRIBUTING.md # Contribution guidelines -│ ├── SUPPORT.md # Support resources -│ └── SECURITY.md # Security policies -└── docs/ # Technical documentation - ├── API_KEY_MANAGEMENT_ARCHITECTURE.md - ├── Billing_Subscription/ - └── ... (internal docs) -``` - ---- - -## 🐛 **GitHub Issue #291 - Resolution** - -### **Issue**: `'CopilotSidebar' is not exported from '@copilotkit/react-ui'` - -### **Root Cause** -User skipped `npm install` step after cloning repository. - -### **Solution** -1. Created comprehensive troubleshooting guide: `.github/TROUBLESHOOTING.md` -2. Added automated setup scripts: `setup_alwrity.sh`, `setup_alwrity.bat` -3. Updated root README with common error fixes - -### **User Response** -```bash -cd frontend -rm -rf node_modules package-lock.json -npm install -npm run build -npm start -``` - ---- - -## 🎯 **Alpha Testing Readiness** - -### **What's Ready** - -- ✅ **Subscription Enforcement**: Real-time API usage limits -- ✅ **4 Subscription Tiers**: Free, Basic, Pro, Enterprise -- ✅ **Pricing Page**: Beautiful UI with feature details -- ✅ **User Flow**: Sign In → Pricing → Onboarding → Dashboard -- ✅ **Mock Payment**: Alpha testing credit system -- ✅ **Database Persistence**: All subscription data stored -- ✅ **Real-time Updates**: Subscription status refreshes automatically - -### **Testing Instructions for 5 Alpha Testers** - -1. **Clone repository**: `git clone https://github.com/AJaySi/ALwrity.git` -2. **Run setup**: `./setup_alwrity.bat` (Windows) or `./setup_alwrity.sh` (Unix) -3. **Configure .env files**: Add Clerk keys -4. **Start application**: Backend + Frontend -5. **Test flow**: - - Sign in - - Select Free or Basic plan - - Complete onboarding - - Use features until limits reached - - Test upgrade prompts - -### **What to Test** - -- [ ] Fresh installation process -- [ ] Sign in with Clerk -- [ ] Subscription selection (Free/Basic) -- [ ] Onboarding completion (6 steps) -- [ ] API usage tracking -- [ ] Limit enforcement (try to exceed limits) -- [ ] Upgrade prompts -- [ ] Platform integrations (Wix, WordPress, GSC) - ---- - -## 📋 **Next Phase: Clerk B2C Integration** - -**Future Work (Post-Alpha):** -1. Integrate Stripe/Paddle for real payments -2. Migrate to Clerk B2C billing system -3. Enable Pro plan features (6 social platforms, audio/video) -4. Add webhook handling for subscription updates -5. Implement usage analytics dashboard - ---- - -## 🎯 **Success Metrics** - -- ✅ **No Code Bugs**: All TypeScript errors resolved -- ✅ **Complete Documentation**: Setup, troubleshooting, and user guides -- ✅ **Automated Setup**: One-command installation -- ✅ **Subscription Enforcement**: API limits working -- ✅ **User Flow**: Seamless sign-in to dashboard experience - -**ALwrity is production-ready for alpha testing!** 🚀 - ---- - -**Created:** October 13, 2025 -**Status:** ✅ Ready for Alpha Testing -**Testers:** 5 users -**Plans Available:** Free, Basic - ---- - -## 🔧 **Bug Fixes Applied** - -### **Issue #291: CopilotSidebar Import Error** -- **Cause**: User didn't run `npm install` -- **Fix**: Created automated setup scripts + troubleshooting guide -- **Documentation**: `.github/TROUBLESHOOTING.md` - -### **Subscription 500 Error** -- **Cause**: Missing `UsageStatus` import in `subscription_api.py` -- **Fix**: Added `UsageStatus` to imports (line 18) -- **Status**: ✅ Verified working - -### **Anonymous User Subscription** -- **Cause**: Users not signed in trying to subscribe -- **Fix**: Added sign-in prompt modal -- **Behavior**: Shows "Sign In Required" dialog before subscription - ---- - -## 📝 **Documentation Updates** - -**GitHub Best Practices Applied:** -- Root `README.md`: Concise overview only -- `.github/INSTALLATION.md`: Complete setup guide -- `.github/TROUBLESHOOTING.md`: Common issues & fixes -- `.github/README.md`: Full features & roadmap - -**Setup Automation:** -- `setup_alwrity.sh`: Unix systems -- `setup_alwrity.bat`: Windows systems - diff --git a/docs/API_KEY_FLOW_DIAGRAM.md b/docs/API_KEY_FLOW_DIAGRAM.md deleted file mode 100644 index d2acc73d..00000000 --- a/docs/API_KEY_FLOW_DIAGRAM.md +++ /dev/null @@ -1,370 +0,0 @@ -# API Key Management Flow Diagrams - -## 🏠 Local Development Mode - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ LOCAL DEVELOPMENT │ -│ (DEBUG=true) │ -└─────────────────────────────────────────────────────────────────────┘ - -Developer completes onboarding - │ - ├─> Frontend: Save API keys - │ └─> POST /api/onboarding/api-keys (gemini, exa, copilotkit) - │ - ├─> Backend: Process API keys - │ │ - │ ├─> Save to PostgreSQL database - │ │ └─> onboarding_sessions (user_id) - │ │ └─> api_keys (provider, key) - │ │ - │ └─> Save to backend/.env file [DEV MODE ONLY] - │ ├─> GEMINI_API_KEY=xxx - │ ├─> EXA_API_KEY=xxx - │ └─> COPILOTKIT_API_KEY=xxx - │ - └─> Frontend: Save CopilotKit to frontend/.env - └─> REACT_APP_COPILOTKIT_API_KEY=xxx - - -Developer generates content - │ - ├─> Service calls user_api_keys(user_id=None) - │ │ - │ └─> Detects DEV mode (DEBUG=true) - │ └─> Reads from backend/.env file - │ └─> Returns all keys - │ - └─> Content generated using developer's keys - └─> All costs → Developer's API account - - -✅ Advantages: - • Quick setup (keys persist in .env) - • No database required for basic dev - • Single developer = single set of keys - • Keys survive server restarts -``` - ---- - -## 🌐 Production Mode (Multi-User) - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ PRODUCTION (VERCEL + RENDER) │ -│ (DEBUG=false, DEPLOY_ENV=render) │ -└─────────────────────────────────────────────────────────────────────┘ - -Alpha Tester A visits https://alwrity-ai.vercel.app - │ - ├─> Completes onboarding - │ └─> Enters API keys: - │ ├─> GEMINI_API_KEY=tester_a_key - │ ├─> EXA_API_KEY=tester_a_exa - │ └─> COPILOTKIT_API_KEY=tester_a_copilot - │ - ├─> Frontend: Save API keys - │ ├─> POST /api/onboarding/api-keys (gemini, exa, copilotkit) - │ └─> Save to localStorage (CopilotKit) - │ - └─> Backend: Process API keys - ├─> Save to PostgreSQL database ONLY [PROD MODE] - │ └─> onboarding_sessions - │ ├─> user_id = "user_clerk_tester_a" - │ └─> api_keys - │ ├─> (session_id, "gemini", "tester_a_key") - │ ├─> (session_id, "exa", "tester_a_exa") - │ └─> (session_id, "copilotkit", "tester_a_copilot") - │ - └─> [SKIP] ❌ Do NOT save to .env file (multi-user conflict!) - - -Alpha Tester A generates blog content - │ - ├─> Request to /api/blog/generate - │ └─> Headers: Authorization: Bearer - │ - ├─> Auth Middleware extracts user_id = "user_clerk_tester_a" - │ - ├─> BlogService calls user_api_keys("user_clerk_tester_a") - │ │ - │ ├─> Detects PROD mode (DEPLOY_ENV=render) - │ │ - │ └─> Query database: - │ SELECT key FROM api_keys - │ WHERE session_id = ( - │ SELECT id FROM onboarding_sessions - │ WHERE user_id = 'user_clerk_tester_a' - │ ) - │ └─> Returns: {"gemini": "tester_a_key", "exa": "tester_a_exa"} - │ - └─> Content generated using Tester A's Gemini key - └─> All costs → Tester A's Gemini account - - -──────────────────────────────────────────────────────────────────────── - -SIMULTANEOUSLY... - -Alpha Tester B visits https://alwrity-ai.vercel.app - │ - ├─> Completes onboarding - │ └─> Enters API keys: - │ ├─> GEMINI_API_KEY=tester_b_key - │ ├─> EXA_API_KEY=tester_b_exa - │ └─> COPILOTKIT_API_KEY=tester_b_copilot - │ - └─> Backend: Save to database - └─> onboarding_sessions - ├─> user_id = "user_clerk_tester_b" - └─> api_keys - ├─> (session_id, "gemini", "tester_b_key") [SEPARATE!] - ├─> (session_id, "exa", "tester_b_exa") - └─> (session_id, "copilotkit", "tester_b_copilot") - - -Alpha Tester B generates blog content - │ - ├─> Request to /api/blog/generate - │ └─> Headers: Authorization: Bearer - │ - ├─> Auth Middleware extracts user_id = "user_clerk_tester_b" - │ - ├─> BlogService calls user_api_keys("user_clerk_tester_b") - │ │ - │ └─> Query database: - │ WHERE user_id = 'user_clerk_tester_b' [DIFFERENT!] - │ └─> Returns: {"gemini": "tester_b_key", "exa": "tester_b_exa"} - │ - └─> Content generated using Tester B's Gemini key - └─> All costs → Tester B's Gemini account - - -✅ User Isolation: - • Tester A's keys ≠ Tester B's keys - • Tester A's costs ≠ Tester B's costs - • Completely isolated in database - • You (owner) pay nothing! 🎉 -``` - ---- - -## 🔄 Environment Detection Logic - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ ENVIRONMENT DETECTION │ -└─────────────────────────────────────────────────────────────────────┘ - -When user_api_keys(user_id) is called: - - ┌──────────────────────────────────┐ - │ Check environment variables │ - └──────────────────────────────────┘ - │ - ├─> DEBUG=true OR DEPLOY_ENV=None - │ │ - │ ├─> DEVELOPMENT MODE - │ │ └─> Read from backend/.env file - │ │ └─> os.getenv('GEMINI_API_KEY') - │ │ - │ └─> Log: "[DEV MODE] Using .env file" - │ - └─> DEBUG=false AND DEPLOY_ENV=render - │ - ├─> PRODUCTION MODE - │ └─> Read from database - │ └─> SELECT key FROM api_keys WHERE user_id=? - │ - └─> Log: "[PROD MODE] Using database for user {user_id}" - - -Example configurations: - - Local Development: - ┌─────────────────────────────┐ - │ backend/.env │ - ├─────────────────────────────┤ - │ DEBUG=true │ - │ GEMINI_API_KEY=dev_key │ - │ EXA_API_KEY=dev_exa │ - └─────────────────────────────┘ - - Render Production: - ┌─────────────────────────────┐ - │ Environment Variables │ - ├─────────────────────────────┤ - │ DEBUG=false │ - │ DEPLOY_ENV=render │ - │ DATABASE_URL=postgresql:// │ - └─────────────────────────────┘ -``` - ---- - -## 📊 Database Schema Visualization - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ DATABASE SCHEMA │ -└─────────────────────────────────────────────────────────────────────┘ - -onboarding_sessions -┌────────────┬──────────────────────────┬─────────────┬──────────┐ -│ id (PK) │ user_id (UNIQUE) │ current_step│ progress │ -├────────────┼──────────────────────────┼─────────────┼──────────┤ -│ 1 │ user_clerk_tester_a │ 6 │ 100.0 │ -│ 2 │ user_clerk_tester_b │ 6 │ 100.0 │ -│ 3 │ user_clerk_tester_c │ 3 │ 50.0 │ -└────────────┴──────────────────────────┴─────────────┴──────────┘ - -api_keys -┌────────────┬────────────┬──────────────┬────────────────────────┐ -│ id (PK) │ session_id │ provider │ key │ -│ │ (FK) │ │ │ -├────────────┼────────────┼──────────────┼────────────────────────┤ -│ 1 │ 1 │ gemini │ tester_a_gemini_key │ ← Tester A -│ 2 │ 1 │ exa │ tester_a_exa_key │ ← Tester A -│ 3 │ 1 │ copilotkit │ tester_a_copilot_key │ ← Tester A -├────────────┼────────────┼──────────────┼────────────────────────┤ -│ 4 │ 2 │ gemini │ tester_b_gemini_key │ ← Tester B -│ 5 │ 2 │ exa │ tester_b_exa_key │ ← Tester B -│ 6 │ 2 │ copilotkit │ tester_b_copilot_key │ ← Tester B -├────────────┼────────────┼──────────────┼────────────────────────┤ -│ 7 │ 3 │ gemini │ tester_c_gemini_key │ ← Tester C -│ 8 │ 3 │ exa │ tester_c_exa_key │ ← Tester C -└────────────┴────────────┴──────────────┴────────────────────────┘ - -Query to get Tester A's Gemini key: - - SELECT k.key - FROM api_keys k - JOIN onboarding_sessions s ON k.session_id = s.id - WHERE s.user_id = 'user_clerk_tester_a' - AND k.provider = 'gemini' - - Result: 'tester_a_gemini_key' - - -Query to get Tester B's Gemini key: - - SELECT k.key - FROM api_keys k - JOIN onboarding_sessions s ON k.session_id = s.id - WHERE s.user_id = 'user_clerk_tester_b' - AND k.provider = 'gemini' - - Result: 'tester_b_gemini_key' [DIFFERENT!] -``` - ---- - -## 🔐 Security & Isolation - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ USER ISOLATION GUARANTEE │ -└─────────────────────────────────────────────────────────────────────┘ - -Scenario: Both Tester A and Tester B generate content simultaneously - - Tester A's Request Thread: - ┌────────────────────────────────────────────┐ - │ 1. Auth: user_id = "user_clerk_tester_a" │ - │ 2. Fetch keys: WHERE user_id = tester_a │ - │ 3. Get: gemini_key = "tester_a_key" │ - │ 4. Generate with tester_a_key │ - │ 5. Response to Tester A │ - └────────────────────────────────────────────┘ - ↓ - [Database] - ↑ - ┌────────────────────────────────────────────┐ - │ 1. Auth: user_id = "user_clerk_tester_b" │ - │ 2. Fetch keys: WHERE user_id = tester_b │ - │ 3. Get: gemini_key = "tester_b_key" │ - │ 4. Generate with tester_b_key │ - │ 5. Response to Tester B │ - └────────────────────────────────────────────┘ - Tester B's Request Thread: - - -✅ Guarantees: - • Tester A NEVER sees Tester B's keys - • Tester B NEVER sees Tester A's keys - • Tester A's costs charged to Tester A - • Tester B's costs charged to Tester B - • Database enforces isolation via user_id - • Clerk auth ensures correct user_id -``` - ---- - -## 💰 Cost Distribution - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ WHO PAYS FOR WHAT? │ -└─────────────────────────────────────────────────────────────────────┘ - -Local Development (You): - Your API Keys → Your Costs - ┌─────────────────────────────────────────────┐ - │ Developer generates 100 blog posts │ - │ Uses: GEMINI_API_KEY from .env │ - │ Cost: $5.00 → Charged to developer's │ - │ Google Cloud account │ - └─────────────────────────────────────────────┘ - - -Production (Alpha Testers): - Their API Keys → Their Costs - ┌─────────────────────────────────────────────┐ - │ Tester A generates 50 blog posts │ - │ Uses: tester_a_gemini_key from database │ - │ Cost: $2.50 → Charged to Tester A's │ - │ Google Cloud account │ - └─────────────────────────────────────────────┘ - - ┌─────────────────────────────────────────────┐ - │ Tester B generates 200 blog posts │ - │ Uses: tester_b_gemini_key from database │ - │ Cost: $10.00 → Charged to Tester B's │ - │ Google Cloud account │ - └─────────────────────────────────────────────┘ - - ┌─────────────────────────────────────────────┐ - │ You (owner) host infrastructure │ - │ Render: Free tier / $7/month │ - │ Vercel: Free tier │ - │ Database: Render free tier │ - │ Cost: $0 - $7/month (infrastructure only) │ - └─────────────────────────────────────────────┘ - - -Total monthly cost for you with 100 alpha testers: - Infrastructure: $0 - $7 - API usage: $0 (testers pay their own!) - ──────────────────────────── - Total: $0 - $7/month 🎉 -``` - ---- - -## 🎯 Summary - -| Aspect | Local Dev | Production | -|--------|-----------|------------| -| **Environment** | `DEBUG=true` | `DEPLOY_ENV=render` | -| **Key Storage** | `.env` file + DB | Database only | -| **Key Retrieval** | `os.getenv()` | Database query | -| **User Isolation** | Not needed | Full isolation | -| **Cost Bearer** | You (developer) | Each tester | -| **Scalability** | 1 developer | Unlimited users | -| **Setup Effort** | Low (persist .env) | Low (onboard once) | - -**Architecture Principle:** -> Development convenience with `.env` files, production isolation with database. Best of both worlds! 🚀 - diff --git a/docs/API_KEY_INJECTION_EXPLAINED.md b/docs/API_KEY_INJECTION_EXPLAINED.md deleted file mode 100644 index 560679d4..00000000 --- a/docs/API_KEY_INJECTION_EXPLAINED.md +++ /dev/null @@ -1,326 +0,0 @@ -# API Key Injection - How It Works in Production - -## 🎯 The Problem You Identified - -**Question:** "For production, when we read APIs from database, how will they be exported to the environment?" - -**Answer:** They are **temporarily injected** into `os.environ` for each request, then immediately cleaned up. - ---- - -## 🔍 The Challenge - -### **Existing Code Pattern:** - -Most of your codebase uses this pattern: - -```python -import os -import google.generativeai as genai - -def generate_content(prompt: str): - # Expects GEMINI_API_KEY in environment - gemini_key = os.getenv('GEMINI_API_KEY') - genai.configure(api_key=gemini_key) - # ... -``` - -### **Production Problem:** - -``` -User A's request: - ↓ - os.getenv('GEMINI_API_KEY') → ??? (User A's key in database, not in os.environ) - -User B's request (simultaneous): - ↓ - os.getenv('GEMINI_API_KEY') → ??? (User B's key in database, not in os.environ) -``` - -**Issue:** `os.environ` is global, but we need user-specific keys! - ---- - -## ✅ The Solution: Request-Scoped Injection - -### **How It Works:** - -``` -1. Request arrives with Authorization: Bearer - ↓ -2. API Key Injection Middleware extracts user_id from token - ↓ -3. Fetch User A's keys from database - ↓ -4. Temporarily inject into os.environ: - - GEMINI_API_KEY = user_a_gemini_key - - EXA_API_KEY = user_a_exa_key - ↓ -5. Process request (all os.getenv() calls get User A's keys) - ↓ -6. Request completes - ↓ -7. IMMEDIATELY clean up os.environ (remove User A's keys) -``` - -### **Key Insight:** - -**The injection is request-scoped, not global:** -- User A's keys exist in `os.environ` ONLY during User A's request -- Immediately removed after response sent -- User B's request gets User B's keys injected -- No overlap, no conflict! - ---- - -## 🏗️ Architecture - -### **Middleware Flow:** - -``` -FastAPI Request Pipeline: - -┌─────────────────────────────────────────────────────────────┐ -│ 1. Rate Limit Middleware │ -│ └─> Check rate limits │ -└─────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────┐ -│ 2. API Key Injection Middleware (NEW!) │ -│ ├─> Extract user_id from Authorization header │ -│ ├─> Fetch user's API keys from database │ -│ ├─> Inject into os.environ (temporarily) │ -│ │ ├─> GEMINI_API_KEY = user_specific_key │ -│ │ ├─> EXA_API_KEY = user_specific_key │ -│ │ └─> COPILOTKIT_API_KEY = user_specific_key │ -│ └─> [Request processed with user-specific keys] │ -│ ↓ │ -│ ├─> [Response generated] │ -│ └─> CLEANUP: Remove injected keys from os.environ │ -└─────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────┐ -│ 3. Your Endpoint (e.g., /api/blog/generate) │ -│ └─> Calls service that uses os.getenv('GEMINI_API_KEY') │ -│ └─> Gets user-specific key! ✅ │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## 💻 Code Example - -### **The Middleware:** - -```python -async def __call__(self, request: Request, call_next): - # 1. Extract user_id from token - user_id = extract_user_from_token(request) - - if not user_id or DEPLOY_ENV == 'local': - return await call_next(request) # Skip in local mode - - # 2. Get user-specific keys from database - with user_api_keys(user_id) as user_keys: - # 3. Save original environment (if any) - original_gemini = os.environ.get('GEMINI_API_KEY') - original_exa = os.environ.get('EXA_API_KEY') - - # 4. Inject user-specific keys - os.environ['GEMINI_API_KEY'] = user_keys['gemini'] - os.environ['EXA_API_KEY'] = user_keys['exa'] - - try: - # 5. Process request with user-specific keys - response = await call_next(request) - return response - finally: - # 6. CRITICAL: Restore original environment - if original_gemini is None: - del os.environ['GEMINI_API_KEY'] - else: - os.environ['GEMINI_API_KEY'] = original_gemini - - if original_exa is None: - del os.environ['EXA_API_KEY'] - else: - os.environ['EXA_API_KEY'] = original_exa -``` - ---- - -## 📊 Concurrent Requests Example - -### **Scenario: Two Users Generate Content Simultaneously** - -``` -TIME: 00:00:000 -User A request arrives -├─> Extract user_id = "user_a" -├─> Fetch keys from DB: gemini_key = "key_a_123" -├─> os.environ['GEMINI_API_KEY'] = "key_a_123" -│ -├─> TIME: 00:00:050 (50ms later) -│ User B request arrives -│ ├─> Extract user_id = "user_b" -│ ├─> Fetch keys from DB: gemini_key = "key_b_456" -│ ├─> os.environ['GEMINI_API_KEY'] = "key_b_456" ← Overwrites! -│ │ -│ ├─> User B's request processes -│ │ os.getenv('GEMINI_API_KEY') → "key_b_456" ✅ -│ │ -│ └─> TIME: 00:00:100 -│ User B response sent -│ os.environ['GEMINI_API_KEY'] restored -│ -└─> TIME: 00:00:120 - User A's request processes - os.getenv('GEMINI_API_KEY') → ??? (Could be wrong!) -``` - -**⚠️ PROBLEM: Race condition!** - ---- - -## 🔒 Thread Safety Solution - -Python's asyncio in FastAPI handles this correctly: - -```python -# FastAPI uses asyncio, which is single-threaded -# Each request is processed in sequence (no parallel execution) -# So the injection is safe! - -User A request: - ├─> Inject A's keys - ├─> await generate_content() ← Async, but single-threaded - └─> Cleanup A's keys - -User B request (after A): - ├─> Inject B's keys - ├─> await generate_content() - └─> Cleanup B's keys -``` - -**BUT:** If your code uses threading or multiprocessing, this approach WON'T work safely. - ---- - -## 🎛️ Modes Compared - -### **Local Mode (DEPLOY_ENV=local):** - -``` -Request arrives - ↓ -Middleware detects DEPLOY_ENV=local - ↓ -SKIP injection (keys already in .env) - ↓ -os.getenv('GEMINI_API_KEY') → reads from .env file - ↓ -Works! ✅ -``` - -### **Production Mode (DEPLOY_ENV=production):** - -``` -Request arrives with user_id=user_123 - ↓ -Middleware detects DEPLOY_ENV=production - ↓ -Fetch user_123's keys from database - ↓ -Inject into os.environ (temporarily) - ↓ -os.getenv('GEMINI_API_KEY') → gets user_123's key - ↓ -Process request - ↓ -Clean up os.environ - ↓ -Works! ✅ -``` - ---- - -## 🚨 Important Caveats - -### **1. Async-Only Safety** - -This approach is safe ONLY because FastAPI uses asyncio (single-threaded event loop). - -**If you use:** -- `concurrent.futures.ThreadPoolExecutor` -- `multiprocessing.Pool` -- `threading.Thread` - -Then environment injection is **NOT SAFE** and will cause race conditions! - -### **2. Better Long-Term Approach** - -For critical services, refactor to pass `user_id` explicitly: - -```python -# Instead of: -def generate(prompt: str): - key = os.getenv('GEMINI_API_KEY') # Fragile! - -# Do this: -def generate(user_id: str, prompt: str): - with user_api_keys(user_id) as keys: - key = keys['gemini'] # Explicit and safe! -``` - ---- - -## 📝 Summary - -### **The Magic:** - -1. **Request arrives** → Middleware extracts `user_id` -2. **Fetch from DB** → Get user's keys -3. **Inject temporarily** → `os.environ['GEMINI_API_KEY'] = user_key` -4. **Process request** → All `os.getenv()` calls get user's key -5. **Cleanup** → Remove from `os.environ` -6. **Next request** → Different user, different keys - -### **Why It Works:** - -- ✅ FastAPI is async + single-threaded -- ✅ Injection is request-scoped -- ✅ Cleanup is guaranteed (finally block) -- ✅ Existing code works without changes -- ✅ Each user gets their own keys - -### **Limitations:** - -- ⚠️ Not safe with threading/multiprocessing -- ⚠️ Slightly slower (DB query per request) -- ⚠️ Better to refactor critical services - -### **Bottom Line:** - -> **It works!** Your existing code that uses `os.getenv()` will get user-specific keys in production, with zero code changes. The middleware handles everything automatically. - ---- - -## 🔄 Migration Path - -### **Phase 1: Now (Compatibility Layer)** -- ✅ Middleware injects keys for ALL services -- ✅ No code changes needed -- ✅ Works immediately - -### **Phase 2: Later (Gradual Refactor)** -- Refactor critical services to use `UserAPIKeyContext` directly -- Remove dependency on `os.getenv()` -- More explicit, safer - -### **Phase 3: Future (Full Migration)** -- All services use `user_api_keys(user_id)` -- Remove injection middleware -- Clean, explicit architecture - -**For now:** Middleware lets you deploy immediately without touching 100+ files! 🎉 - diff --git a/docs/ASSISTIVE_WRITING_QUICK_REFERENCE.md b/docs/ASSISTIVE_WRITING_QUICK_REFERENCE.md deleted file mode 100644 index f5b0911a..00000000 --- a/docs/ASSISTIVE_WRITING_QUICK_REFERENCE.md +++ /dev/null @@ -1,42 +0,0 @@ -# Assistive Writing - Quick Reference - -## 🚀 Getting Started -1. **Enable:** Toggle "Assistive Writing" in LinkedIn Writer header -2. **Write:** Type at least 5 words -3. **Wait:** 5 seconds for first automatic suggestion -4. **Accept/Dismiss:** Use buttons in suggestion card - -## 📝 How It Works -- **First suggestion:** Automatic (5 words + 5 seconds) -- **More suggestions:** Click "Continue writing" button -- **Daily limit:** 50 suggestions (resets every 24 hours) - -## 🎯 Best Practices -- ✅ Write specific, clear content -- ✅ Review source links before accepting -- ✅ Use manual "Continue writing" for additional suggestions -- ❌ Don't expect suggestions for very short text -- ❌ Don't ignore source verification - -## 🔧 Common Issues -| Problem | Solution | -|---------|----------| -| No suggestions | Write 5+ words, wait 5 seconds | -| "API quota exceeded" | Wait 24 hours or upgrade plan | -| "No relevant sources" | Be more specific in your writing | -| Suggestions not relevant | Try different wording or topics | - -## 💡 Pro Tips -- Use business terminology for better results -- Write complete thoughts, not fragments -- Check source links for accuracy -- Edit suggestions to match your voice -- Use manual triggering to control costs - -## 📞 Need Help? -- Check the full user guide: `ASSISTIVE_WRITING_USER_GUIDE.md` -- Contact support for technical issues -- Try refreshing the page if problems persist - ---- -*Quick reference for ALwrity's Assistive Writing feature* diff --git a/docs/ASSISTIVE_WRITING_USER_GUIDE.md b/docs/ASSISTIVE_WRITING_USER_GUIDE.md deleted file mode 100644 index 2f6d01c3..00000000 --- a/docs/ASSISTIVE_WRITING_USER_GUIDE.md +++ /dev/null @@ -1,151 +0,0 @@ -# Assistive Writing User Guide - -## What is Assistive Writing? - -Assistive Writing is an AI-powered feature in ALwrity that helps you continue your LinkedIn posts with contextually relevant suggestions. It uses advanced AI to understand what you're writing and provides intelligent continuations based on real-time web research. - -## How to Use Assistive Writing - -### 1. Enable Assistive Writing - -1. Open the LinkedIn Writer in ALwrity -2. Look for the **"Assistive Writing"** toggle switch in the header -3. Click the toggle to enable the feature (it will turn blue when active) - -### 2. Start Writing - -1. Begin typing your LinkedIn post in the text area -2. Write at least **5 words** to give the AI enough context -3. Wait **5 seconds** after typing - the AI will automatically analyze your content - -### 3. Receive Your First Suggestion - -- After 5 words and 5 seconds, you'll see an **"Assistive Writing Suggestion"** card appear near your cursor -- The suggestion includes: - - **Confidence score** (how certain the AI is about the suggestion) - - **Suggested text** to continue your post - - **Source links** for verification and further reading - -### 4. Accept or Dismiss Suggestions - -**To Accept a Suggestion:** -- Click the **"Accept"** button -- The suggested text will be inserted at your cursor position -- You can continue editing from there - -**To Dismiss a Suggestion:** -- Click the **"Dismiss"** button -- The suggestion will disappear - -### 5. Request More Suggestions - -After your first automatic suggestion, the system becomes more conservative to save costs: - -- You'll see a **"Continue writing"** prompt: *"ALwrity can contextually continue writing. Click Continue writing."* -- Click **"Continue writing"** to get another AI-powered suggestion -- This manual approach ensures you only get suggestions when you actually want them - -## Understanding the Suggestions - -### What Makes a Good Suggestion? - -- **Contextually relevant** to your topic -- **Professionally written** in LinkedIn style -- **Based on real sources** from the web -- **Confidence score** of 70% or higher - -### Source Information - -Each suggestion includes: -- **Article titles** from reputable sources -- **Clickable links** to read the full articles -- **Author information** when available -- **Publication dates** for recency - -## Best Practices - -### ✅ Do This: -- Write at least 5 words before expecting suggestions -- Use specific, clear language in your posts -- Review source links to verify information -- Accept suggestions that align with your message -- Use the manual "Continue writing" button for additional suggestions - -### ❌ Avoid This: -- Expecting suggestions for very short text (under 5 words) -- Accepting suggestions without reviewing them -- Ignoring source links for fact-checking -- Making rapid changes that might confuse the AI - -## Troubleshooting - -### "No suggestions appearing" -- **Check:** Have you written at least 5 words? -- **Check:** Have you waited 5 seconds after typing? -- **Check:** Is Assistive Writing enabled (toggle should be blue)? - -### "API quota exceeded" error -- This means the daily limit for AI suggestions has been reached -- Wait 24 hours for the quota to reset, or upgrade your plan -- The feature will automatically resume when quota is available - -### "No relevant sources found" -- The AI couldn't find good sources for your specific topic -- Try being more specific in your writing -- Consider rephrasing to use more common business terms - -### "Search service not configured" -- This is a technical configuration issue -- Contact support for assistance - -## Cost and Usage - -### How It Works: -- **First suggestion:** Automatic after 5 words + 5 seconds -- **Additional suggestions:** Manual only (click "Continue writing") -- **Daily limit:** 50 suggestions per day on free tier -- **Cost control:** Manual triggering prevents excessive API usage - -### Why Manual After First Suggestion? -- Saves costs by not making unnecessary API calls -- Gives you control over when to get suggestions -- Prevents overwhelming you with too many options -- Ensures suggestions are relevant to your current writing - -## Tips for Better Results - -### 1. Be Specific -Instead of: "AI is changing business" -Try: "AI is transforming customer service with chatbots and predictive analytics" - -### 2. Use Industry Terms -The AI understands business terminology better than casual language - -### 3. Write Complete Thoughts -Instead of: "Marketing is" -Try: "Marketing is evolving rapidly with new digital tools" - -### 4. Review Sources -Always check the provided source links to ensure accuracy - -### 5. Edit as Needed -Accept suggestions as starting points, then edit to match your voice - -## Privacy and Data - -- Your writing content is processed securely -- No personal data is stored permanently -- Suggestions are generated in real-time -- Source links are from publicly available web content - -## Support - -If you encounter issues: -1. Check this guide first -2. Try disabling and re-enabling Assistive Writing -3. Refresh the page and try again -4. Contact support with specific error messages - ---- - -*Assistive Writing is designed to enhance your LinkedIn content creation experience while maintaining cost efficiency and user control.* diff --git a/docs/ASSISTIVE_WRITING_WORKFLOW.md b/docs/ASSISTIVE_WRITING_WORKFLOW.md deleted file mode 100644 index eecb4b89..00000000 --- a/docs/ASSISTIVE_WRITING_WORKFLOW.md +++ /dev/null @@ -1,131 +0,0 @@ -# Assistive Writing Workflow - -## Visual Workflow - -``` -1. ENABLE ASSISTIVE WRITING - ┌─────────────────────────┐ - │ Toggle "Assistive │ - │ Writing" ON (blue) │ - └─────────────────────────┘ - │ - ▼ - -2. START WRITING - ┌─────────────────────────┐ - │ Type at least 5 words │ - │ in the text area │ - └─────────────────────────┘ - │ - ▼ - -3. WAIT FOR AI ANALYSIS - ┌─────────────────────────┐ - │ Wait 5 seconds │ - │ AI analyzes your text │ - └─────────────────────────┘ - │ - ▼ - -4. RECEIVE FIRST SUGGESTION - ┌─────────────────────────┐ - │ Suggestion card appears │ - │ near your cursor │ - │ │ - │ [Accept] [Dismiss] │ - └─────────────────────────┘ - │ - ▼ - -5. AFTER FIRST SUGGESTION - ┌─────────────────────────┐ - │ "Continue writing" │ - │ prompt appears │ - │ │ - │ [Continue writing] │ - │ [Dismiss] │ - └─────────────────────────┘ - │ - ▼ - -6. MANUAL SUGGESTIONS - ┌─────────────────────────┐ - │ Click "Continue writing"│ - │ to get more suggestions │ - │ (saves costs) │ - └─────────────────────────┘ -``` - -## Step-by-Step Process - -### Phase 1: Initial Setup -1. **Enable Feature** → Toggle switch turns blue -2. **Start Writing** → Type 5+ words -3. **Wait** → 5-second delay for AI processing - -### Phase 2: First Suggestion -4. **Receive Suggestion** → Card appears with: - - Suggested text - - Confidence score - - Source links - - Accept/Dismiss buttons - -### Phase 3: Ongoing Usage -5. **Accept or Dismiss** → Choose your action -6. **Continue Writing** → Manual trigger for more suggestions -7. **Repeat** → Use "Continue writing" as needed - -## Key Points - -### Automatic vs Manual -- **Automatic:** Only the first suggestion (after 5 words + 5 seconds) -- **Manual:** All subsequent suggestions (click "Continue writing") - -### Cost Control -- Prevents excessive API calls -- Gives you control over when to get suggestions -- Respects daily limits (50 suggestions/day) - -### User Experience -- Suggestions appear near your cursor -- Clear accept/dismiss options -- Source verification available -- Professional LinkedIn-style content - -## Error Handling - -``` -If you see an error: -┌─────────────────────────┐ -│ Check the error message │ -│ │ -│ Common errors: │ -│ • "API quota exceeded" │ -│ • "No relevant sources" │ -│ • "Service not available"│ -└─────────────────────────┘ - │ - ▼ -┌─────────────────────────┐ -│ Follow troubleshooting │ -│ steps in user guide │ -└─────────────────────────┘ -``` - -## Success Indicators - -✅ **Working Correctly:** -- Toggle is blue when enabled -- Suggestions appear after 5 words + 5 seconds -- Source links are clickable -- "Continue writing" button appears after first suggestion - -❌ **Needs Attention:** -- No suggestions after 10+ words -- Error messages in suggestion cards -- Toggle not staying blue -- Suggestions not appearing near cursor - ---- - -*This workflow ensures cost-effective, user-controlled AI assistance for LinkedIn content creation.* diff --git a/docs/Billing_Subscription/BILLING_FRONTEND_INTEGRATION_PLAN.md b/docs/Billing_Subscription/BILLING_FRONTEND_INTEGRATION_PLAN.md deleted file mode 100644 index 478c6fe2..00000000 --- a/docs/Billing_Subscription/BILLING_FRONTEND_INTEGRATION_PLAN.md +++ /dev/null @@ -1,347 +0,0 @@ -# ALwrity Billing Frontend Integration Plan - -## 🎯 Overview -This document outlines the integration of usage-based billing and monitoring into ALwrity's main dashboard, providing enterprise-grade insights and cost transparency for all external API usage. - -## 📊 Current System Analysis - -### Existing Monitoring APIs -- **System Health**: `/api/content-planning/monitoring/health` -- **API Stats**: `/api/content-planning/monitoring/api-stats` -- **Lightweight Stats**: `/api/content-planning/monitoring/lightweight-stats` -- **Cache Performance**: `/api/content-planning/monitoring/cache-stats` - -### New Subscription APIs -- **Usage Dashboard**: `/api/subscription/dashboard/{user_id}` -- **Usage Stats**: `/api/subscription/usage/{user_id}` -- **Usage Trends**: `/api/subscription/usage/{user_id}/trends` -- **Subscription Plans**: `/api/subscription/plans` -- **API Pricing**: `/api/subscription/pricing` -- **Usage Alerts**: `/api/subscription/alerts/{user_id}` - -## 🏗️ Architecture Overview - -### Main Dashboard Integration Points -``` -Main Dashboard -├── Header Section -│ ├── System Health Indicator -│ ├── Real-time Usage Summary -│ └── Alert Notifications -├── Billing Overview Section -│ ├── Current Usage vs Limits -│ ├── Cost Breakdown by Provider -│ └── Monthly Projections -├── API Monitoring Section -│ ├── External API Performance -│ ├── Cost per API Call -│ └── Usage Trends -└── Subscription Management - ├── Plan Comparison - ├── Usage Optimization Tips - └── Upgrade/Downgrade Options -``` - -## 🎨 Design System & Components - -### Design Principles -- **Enterprise-Grade**: Professional, clean, trustworthy -- **Cost Transparency**: Clear breakdown of all charges -- **Real-Time**: Live updates and monitoring -- **Actionable Insights**: Recommendations and optimizations -- **Mobile Responsive**: Works across all devices - -### Technology Stack -- **Styling**: Tailwind CSS with custom enterprise theme -- **Animations**: Framer Motion for smooth transitions -- **Charts**: Recharts for data visualization -- **Icons**: Lucide React for consistent iconography -- **State Management**: React Query for API caching - -## 📁 File Structure - -### New Components to Create -``` -frontend/src/components/ -├── billing/ -│ ├── BillingOverview.tsx -│ ├── UsageDashboard.tsx -│ ├── CostBreakdown.tsx -│ ├── UsageTrends.tsx -│ ├── SubscriptionPlans.tsx -│ ├── UsageAlerts.tsx -│ └── CostOptimization.tsx -├── monitoring/ -│ ├── SystemHealthIndicator.tsx -│ ├── APIPerformanceMetrics.tsx -│ ├── RealTimeUsageMonitor.tsx -│ └── ExternalAPICosts.tsx -└── dashboard/ - ├── BillingSection.tsx - ├── MonitoringSection.tsx - └── DashboardHeader.tsx -``` - -### Services to Create -``` -frontend/src/services/ -├── billingService.ts -├── monitoringService.ts -└── subscriptionService.ts -``` - -### Types to Create -``` -frontend/src/types/ -├── billing.ts -├── monitoring.ts -└── subscription.ts -``` - -## 🔧 Component Specifications - -### 1. Dashboard Header Enhancement -**File**: `frontend/src/components/dashboard/DashboardHeader.tsx` - -**Features**: -- System health indicator with color-coded status -- Real-time usage summary (calls, cost, tokens) -- Alert notification badge -- Quick access to billing details - -**API Integration**: -- `GET /api/content-planning/monitoring/lightweight-stats` -- `GET /api/subscription/dashboard/{user_id}` - -### 2. Billing Overview Section -**File**: `frontend/src/components/billing/BillingOverview.tsx` - -**Features**: -- Current month usage vs limits -- Cost breakdown by API provider -- Monthly cost projection -- Usage percentage indicators - -**API Integration**: -- `GET /api/subscription/dashboard/{user_id}` -- `GET /api/subscription/usage/{user_id}` - -### 3. Cost Breakdown Component -**File**: `frontend/src/components/billing/CostBreakdown.tsx` - -**Features**: -- Interactive pie chart of API costs -- Provider-specific cost details -- Token usage visualization -- Cost per request analysis - -**API Integration**: -- `GET /api/subscription/usage/{user_id}` -- `GET /api/subscription/pricing` - -### 4. Usage Trends Component -**File**: `frontend/src/components/billing/UsageTrends.tsx` - -**Features**: -- 6-month usage trend charts -- Cost projection graphs -- Peak usage identification -- Seasonal pattern analysis - -**API Integration**: -- `GET /api/subscription/usage/{user_id}/trends` - -### 5. System Health Indicator -**File**: `frontend/src/components/monitoring/SystemHealthIndicator.tsx` - -**Features**: -- Real-time system status -- API response time monitoring -- Error rate tracking -- Performance metrics - -**API Integration**: -- `GET /api/content-planning/monitoring/health` -- `GET /api/content-planning/monitoring/api-stats` - -### 6. External API Costs Monitor -**File**: `frontend/src/components/monitoring/ExternalAPICosts.tsx` - -**Features**: -- Real-time cost tracking -- API call frequency monitoring -- Cost per provider breakdown -- Usage optimization suggestions - -**API Integration**: -- `GET /api/subscription/usage/{user_id}` -- `GET /api/content-planning/monitoring/api-stats` - -## 🎨 Design Elements & Styling - -### Color Scheme -```css -/* Enterprise Theme */ ---primary: #1e40af (Blue) ---secondary: #059669 (Green) ---warning: #d97706 (Orange) ---danger: #dc2626 (Red) ---success: #16a34a (Green) ---neutral: #6b7280 (Gray) -``` - -### Key Design Elements -- **Gradient Cards**: Subtle gradients for depth -- **Glass Morphism**: Frosted glass effects for modern look -- **Micro Animations**: Smooth hover states and transitions -- **Data Visualization**: Clean, professional charts -- **Status Indicators**: Color-coded health and usage status -- **Progress Bars**: Animated usage progress indicators - -### Framer Motion Animations -- **Page Transitions**: Smooth slide-in effects -- **Card Hover**: Subtle lift and shadow effects -- **Loading States**: Skeleton loaders and spinners -- **Data Updates**: Smooth number transitions -- **Chart Animations**: Progressive data reveal - -## 📊 Data Visualization Strategy - -### Chart Types & Usage -- **Line Charts**: Usage trends over time -- **Pie Charts**: Cost breakdown by provider -- **Bar Charts**: Monthly usage comparisons -- **Area Charts**: Cumulative cost tracking -- **Gauge Charts**: Usage percentage indicators -- **Heatmaps**: Peak usage patterns - -### Recharts Configuration -```typescript -// Chart theme configuration -const chartTheme = { - colors: ['#1e40af', '#059669', '#d97706', '#dc2626', '#16a34a'], - grid: { stroke: '#e5e7eb', strokeWidth: 1 }, - axis: { stroke: '#6b7280', fontSize: 12 }, - tooltip: { backgroundColor: 'rgba(0,0,0,0.8)', border: 'none' } -} -``` - -## 💬 User Messaging Strategy - -### Cost Transparency Messages -- **"This month you've used $X.XX across Y API calls"** -- **"Your Gemini usage costs $X.XX per 1M tokens"** -- **"You're on track to spend $X.XX this month"** -- **"Upgrading to Pro could save you $X.XX/month"** - -### Usage Optimization Tips -- **"Consider using Gemini 2.0 Flash Lite for 40% cost savings"** -- **"Your search API usage is 3x higher than average"** -- **"Batch similar requests to reduce API call costs"** -- **"Enable caching to reduce redundant API calls"** - -### Alert Messages -- **"⚠️ You've used 80% of your monthly limit"** -- **"🚨 API limit reached - upgrade to continue"** -- **"💡 Cost optimization opportunity detected"** -- **"✅ Usage within normal range"** - -## 🔄 Real-Time Updates - -### WebSocket Integration -- **Usage Updates**: Real-time cost and usage tracking -- **System Health**: Live performance monitoring -- **Alert Notifications**: Instant usage warnings -- **Cost Projections**: Dynamic monthly estimates - -### Polling Strategy -- **High Frequency**: Every 30 seconds for critical metrics -- **Medium Frequency**: Every 5 minutes for usage stats -- **Low Frequency**: Every 15 minutes for trends - -## 📱 Responsive Design - -### Breakpoint Strategy -- **Mobile**: < 768px - Stacked layout, simplified charts -- **Tablet**: 768px - 1024px - Two-column layout -- **Desktop**: > 1024px - Full dashboard layout - -### Mobile Optimizations -- **Touch-Friendly**: Large tap targets -- **Simplified Charts**: Essential data only -- **Swipe Navigation**: Between dashboard sections -- **Collapsible Sections**: Space-efficient design - -## 🚀 Implementation Phases - -### Phase 1: Core Integration (Week 1) -1. **Dashboard Header Enhancement** - - System health indicator - - Basic usage summary - - Alert notifications - -2. **Billing Overview Section** - - Current usage display - - Cost breakdown - - Usage limits - -### Phase 2: Advanced Features (Week 2) -1. **Cost Visualization** - - Interactive charts - - Provider breakdown - - Usage trends - -2. **Monitoring Integration** - - API performance metrics - - Real-time cost tracking - - System health monitoring - -### Phase 3: Optimization (Week 3) -1. **User Experience** - - Animations and transitions - - Mobile responsiveness - - Performance optimization - -2. **Advanced Analytics** - - Cost optimization suggestions - - Usage pattern analysis - - Predictive insights - -## 🔒 Security & Privacy - -### Data Protection -- **Cost Data**: Encrypted in transit and at rest -- **Usage Patterns**: Anonymized for analytics -- **User Privacy**: No sensitive data in logs -- **API Keys**: Secure storage and rotation - -### Access Control -- **Role-Based**: Different views for different user types -- **Audit Logging**: Track all billing-related actions -- **Rate Limiting**: Prevent abuse of monitoring APIs -- **Data Retention**: Configurable data retention policies - -## 📈 Success Metrics - -### User Engagement -- **Dashboard Usage**: Time spent on billing section -- **Feature Adoption**: Usage of cost optimization features -- **User Satisfaction**: Feedback on cost transparency - -### Business Impact -- **Cost Awareness**: Reduction in unexpected overages -- **Plan Optimization**: Appropriate plan selection -- **User Retention**: Reduced churn due to cost surprises - -## 🎯 Next Steps - -1. **Review and Approve**: This integration plan -2. **Create Component Library**: Build reusable billing components -3. **API Integration**: Connect to subscription and monitoring APIs -4. **Design System**: Implement enterprise-grade styling -5. **Testing**: Comprehensive testing across devices and scenarios -6. **Deployment**: Gradual rollout with monitoring - ---- - -**Note**: This plan prioritizes cost transparency, user experience, and enterprise-grade quality while maintaining the existing system's functionality and performance. diff --git a/docs/Billing_Subscription/BILLING_IMPLEMENTATION_ROADMAP.md b/docs/Billing_Subscription/BILLING_IMPLEMENTATION_ROADMAP.md deleted file mode 100644 index aad7c6a3..00000000 --- a/docs/Billing_Subscription/BILLING_IMPLEMENTATION_ROADMAP.md +++ /dev/null @@ -1,374 +0,0 @@ -# Billing Frontend Implementation Roadmap - -## 🎯 Project Overview -Implement enterprise-grade billing and monitoring dashboard for ALwrity, integrating usage-based subscription system with real-time cost tracking and system health monitoring. - -## 📋 Implementation Phases - -### Phase 1: Foundation & Core Components (Week 1) -**Priority: HIGH** | **Effort: 40 hours** - -#### 1.1 Project Setup & Dependencies -- [ ] Install required packages: - ```bash - npm install recharts framer-motion lucide-react - npm install @tanstack/react-query axios - npm install zod (for type validation) - ``` -- [ ] Create folder structure: - ``` - src/ - ├── components/billing/ - ├── components/monitoring/ - ├── services/ - ├── types/ - └── hooks/ - ``` - -#### 1.2 Type Definitions -**File**: `src/types/billing.ts` -- [ ] Define core interfaces: - - `DashboardData` - - `UsageStats` - - `ProviderBreakdown` - - `SubscriptionLimits` - - `UsageAlert` -- [ ] Create validation schemas with Zod -- [ ] Export type definitions - -#### 1.3 Service Layer -**File**: `src/services/billingService.ts` -- [ ] Implement API client functions: - - `getDashboardData(userId)` - - `getUsageStats(userId, period?)` - - `getUsageTrends(userId, months?)` - - `getSubscriptionPlans()` - - `getAPIPricing(provider?)` -- [ ] Add error handling and retry logic -- [ ] Implement request/response interceptors - -**File**: `src/services/monitoringService.ts` -- [ ] Implement monitoring API functions: - - `getSystemHealth()` - - `getAPIStats(minutes?)` - - `getLightweightStats()` - - `getCacheStats()` -- [ ] Add real-time update capabilities - -#### 1.4 Core Components -**File**: `src/components/billing/BillingOverview.tsx` -- [ ] Create basic layout structure -- [ ] Implement usage metrics display -- [ ] Add loading and error states -- [ ] Integrate with billing service - -**File**: `src/components/monitoring/SystemHealthIndicator.tsx` -- [ ] Create health status display -- [ ] Implement color-coded indicators -- [ ] Add performance metrics -- [ ] Connect to monitoring service - -### Phase 2: Data Visualization & Charts (Week 2) -**Priority: HIGH** | **Effort: 35 hours** - -#### 2.1 Chart Components -**File**: `src/components/billing/CostBreakdown.tsx` -- [ ] Implement pie chart with Recharts -- [ ] Add interactive tooltips -- [ ] Create provider legend -- [ ] Add click-to-drill-down functionality - -**File**: `src/components/billing/UsageTrends.tsx` -- [ ] Create line chart for trends -- [ ] Add time range selector -- [ ] Implement metric toggle (cost/calls/tokens) -- [ ] Add trend analysis display - -#### 2.2 Dashboard Integration -**File**: `src/components/dashboard/DashboardHeader.tsx` -- [ ] Enhance existing header -- [ ] Add system health indicator -- [ ] Implement usage summary -- [ ] Add alert notification badge - -**File**: `src/components/dashboard/BillingSection.tsx` -- [ ] Create billing section wrapper -- [ ] Integrate billing components -- [ ] Add responsive grid layout -- [ ] Implement section navigation - -### Phase 3: Real-Time Updates & Animations (Week 3) -**Priority: MEDIUM** | **Effort: 30 hours** - -#### 3.1 Real-Time Features -**File**: `src/hooks/useRealtimeUpdates.ts` -- [ ] Implement WebSocket connection -- [ ] Add intelligent polling strategy -- [ ] Create data synchronization -- [ ] Handle connection errors - -**File**: `src/hooks/useIntelligentPolling.ts` -- [ ] Implement activity-based polling -- [ ] Add background/foreground detection -- [ ] Create polling optimization -- [ ] Handle network conditions - -#### 3.2 Animations & Transitions -**File**: `src/components/common/AnimatedCounter.tsx` -- [ ] Create number animation component -- [ ] Implement smooth transitions -- [ ] Add easing functions -- [ ] Handle large number changes - -**File**: `src/components/common/ProgressBar.tsx` -- [ ] Create animated progress bars -- [ ] Add color transitions -- [ ] Implement smooth filling -- [ ] Add percentage labels - -#### 3.3 Framer Motion Integration -- [ ] Add page transition animations -- [ ] Implement card hover effects -- [ ] Create loading state animations -- [ ] Add micro-interactions - -### Phase 4: Advanced Features & Optimization (Week 4) -**Priority: MEDIUM** | **Effort: 25 hours** - -#### 4.1 Advanced Components -**File**: `src/components/billing/SubscriptionPlans.tsx` -- [ ] Create plan comparison table -- [ ] Add upgrade/downgrade options -- [ ] Implement plan recommendation -- [ ] Add pricing calculator - -**File**: `src/components/billing/UsageAlerts.tsx` -- [ ] Create alert management interface -- [ ] Add alert filtering and sorting -- [ ] Implement alert actions -- [ ] Add alert history - -**File**: `src/components/billing/CostOptimization.tsx` -- [ ] Create optimization suggestions -- [ ] Add cost-saving tips -- [ ] Implement usage recommendations -- [ ] Add provider comparison - -#### 4.2 Performance Optimization -- [ ] Implement code splitting -- [ ] Add component memoization -- [ ] Optimize chart rendering -- [ ] Add virtual scrolling for large datasets - -#### 4.3 Error Handling & Edge Cases -- [ ] Add comprehensive error boundaries -- [ ] Implement fallback UI components -- [ ] Add offline support -- [ ] Handle API rate limiting - -### Phase 5: Testing & Polish (Week 5) -**Priority: HIGH** | **Effort: 20 hours** - -#### 5.1 Testing Implementation -**File**: `__tests__/components/billing/` -- [ ] Unit tests for all components -- [ ] Integration tests for services -- [ ] Visual regression tests -- [ ] Performance tests - -**File**: `__tests__/services/` -- [ ] API service tests -- [ ] Error handling tests -- [ ] Mock data tests -- [ ] Network failure tests - -#### 5.2 User Experience Polish -- [ ] Accessibility improvements (ARIA labels, keyboard navigation) -- [ ] Mobile responsiveness testing -- [ ] Cross-browser compatibility -- [ ] Performance optimization - -#### 5.3 Documentation & Deployment -- [ ] Component documentation -- [ ] API integration guide -- [ ] Deployment checklist -- [ ] User guide creation - -## 🎨 Design Implementation Tasks - -### Design System Setup -- [ ] Create Tailwind CSS custom theme -- [ ] Define color palette and typography -- [ ] Create component style guide -- [ ] Implement responsive breakpoints - -### Visual Components -- [ ] Design card layouts and spacing -- [ ] Create icon library integration -- [ ] Implement glass morphism effects -- [ ] Add gradient and shadow effects - -### Chart Styling -- [ ] Customize Recharts theme -- [ ] Implement consistent color scheme -- [ ] Add chart animations -- [ ] Create responsive chart sizing - -## 🔧 Technical Implementation Tasks - -### State Management -- [ ] Set up React Query for API caching -- [ ] Implement global state for user preferences -- [ ] Add local storage for settings -- [ ] Create state persistence - -### API Integration -- [ ] Implement authentication headers -- [ ] Add request/response logging -- [ ] Create API error handling -- [ ] Add retry mechanisms - -### Performance -- [ ] Implement lazy loading -- [ ] Add image optimization -- [ ] Create bundle splitting -- [ ] Optimize re-renders - -## 📱 Responsive Design Tasks - -### Mobile Optimization -- [ ] Create mobile-first layouts -- [ ] Implement touch-friendly interactions -- [ ] Add swipe gestures -- [ ] Optimize chart sizing for mobile - -### Tablet Optimization -- [ ] Create tablet-specific layouts -- [ ] Implement two-column grids -- [ ] Add tablet navigation -- [ ] Optimize touch targets - -### Desktop Enhancement -- [ ] Create desktop-specific features -- [ ] Implement keyboard shortcuts -- [ ] Add advanced interactions -- [ ] Create multi-panel layouts - -## 🔒 Security & Privacy Tasks - -### Data Protection -- [ ] Implement secure API calls -- [ ] Add data encryption -- [ ] Create privacy controls -- [ ] Add audit logging - -### Access Control -- [ ] Implement role-based access -- [ ] Add permission checks -- [ ] Create user session management -- [ ] Add activity tracking - -## 📊 Analytics & Monitoring Tasks - -### Usage Analytics -- [ ] Implement user interaction tracking -- [ ] Add feature usage metrics -- [ ] Create performance monitoring -- [ ] Add error tracking - -### Business Metrics -- [ ] Track billing feature adoption -- [ ] Monitor cost optimization usage -- [ ] Add subscription conversion tracking -- [ ] Create user satisfaction metrics - -## 🚀 Deployment & Rollout Tasks - -### Environment Setup -- [ ] Configure development environment -- [ ] Set up staging environment -- [ ] Create production deployment -- [ ] Add environment-specific configs - -### Feature Flags -- [ ] Implement feature flag system -- [ ] Create gradual rollout plan -- [ ] Add A/B testing capability -- [ ] Create rollback procedures - -### Monitoring & Alerts -- [ ] Set up application monitoring -- [ ] Add performance alerts -- [ ] Create error notifications -- [ ] Implement health checks - -## 📋 Quality Assurance Checklist - -### Functionality -- [ ] All API endpoints working correctly -- [ ] Real-time updates functioning -- [ ] Charts rendering properly -- [ ] Animations smooth and performant - -### User Experience -- [ ] Intuitive navigation -- [ ] Clear cost explanations -- [ ] Helpful error messages -- [ ] Responsive design working - -### Performance -- [ ] Fast loading times -- [ ] Smooth animations -- [ ] Efficient data updates -- [ ] Minimal memory usage - -### Security -- [ ] Secure API communications -- [ ] Proper data validation -- [ ] Access control working -- [ ] Privacy protection in place - -## 🎯 Success Metrics - -### Technical Metrics -- [ ] Page load time < 2 seconds -- [ ] API response time < 500ms -- [ ] 99.9% uptime -- [ ] Zero critical bugs - -### User Experience Metrics -- [ ] User engagement increase -- [ ] Cost transparency satisfaction -- [ ] Feature adoption rate -- [ ] User retention improvement - -### Business Metrics -- [ ] Reduced support tickets -- [ ] Increased plan upgrades -- [ ] Improved cost awareness -- [ ] Higher user satisfaction - -## 📅 Timeline Summary - -| Week | Phase | Key Deliverables | Effort | -|------|-------|------------------|--------| -| 1 | Foundation | Core components, services, types | 40h | -| 2 | Visualization | Charts, dashboard integration | 35h | -| 3 | Real-time | WebSocket, animations | 30h | -| 4 | Advanced | Optimization, alerts, plans | 25h | -| 5 | Polish | Testing, documentation | 20h | -| **Total** | | **Complete billing dashboard** | **150h** | - -## 🎉 Final Deliverables - -1. **Complete billing dashboard** with real-time monitoring -2. **Enterprise-grade design** with smooth animations -3. **Comprehensive testing suite** with 90%+ coverage -4. **Detailed documentation** for maintenance and updates -5. **Performance optimization** for production deployment -6. **Mobile-responsive design** across all devices -7. **Accessibility compliance** for inclusive user experience - ---- - -This roadmap provides a structured approach to implementing the billing frontend integration, ensuring enterprise-grade quality, excellent user experience, and seamless integration with the existing ALwrity system. diff --git a/docs/Billing_Subscription/BILLING_TECHNICAL_SPECIFICATION.md b/docs/Billing_Subscription/BILLING_TECHNICAL_SPECIFICATION.md deleted file mode 100644 index 731c98b6..00000000 --- a/docs/Billing_Subscription/BILLING_TECHNICAL_SPECIFICATION.md +++ /dev/null @@ -1,515 +0,0 @@ -# Billing Frontend Technical Specification - -## 🔧 API Integration Specifications - -### 1. Billing Service (`frontend/src/services/billingService.ts`) - -```typescript -// Core functions to implement -export const billingService = { - // Get comprehensive dashboard data - getDashboardData: (userId: string) => Promise - - // Get current usage statistics - getUsageStats: (userId: string, period?: string) => Promise - - // Get usage trends over time - getUsageTrends: (userId: string, months?: number) => Promise - - // Get subscription plans - getSubscriptionPlans: () => Promise - - // Get API pricing information - getAPIPricing: (provider?: string) => Promise - - // Get usage alerts - getUsageAlerts: (userId: string, unreadOnly?: boolean) => Promise - - // Mark alert as read - markAlertRead: (alertId: number) => Promise -} -``` - -### 2. Monitoring Service (`frontend/src/services/monitoringService.ts`) - -```typescript -// Core functions to implement -export const monitoringService = { - // Get system health status - getSystemHealth: () => Promise - - // Get API performance statistics - getAPIStats: (minutes?: number) => Promise - - // Get lightweight monitoring stats - getLightweightStats: () => Promise - - // Get cache performance metrics - getCacheStats: () => Promise -} -``` - -## 📊 Type Definitions (`frontend/src/types/billing.ts`) - -```typescript -// Core data structures -interface DashboardData { - current_usage: UsageStats - trends: UsageTrends - limits: SubscriptionLimits - alerts: UsageAlert[] - projections: CostProjections - summary: UsageSummary -} - -interface UsageStats { - billing_period: string - usage_status: 'active' | 'warning' | 'limit_reached' - total_calls: number - total_tokens: number - total_cost: number - avg_response_time: number - error_rate: number - limits: SubscriptionLimits - provider_breakdown: ProviderBreakdown - alerts: UsageAlert[] - usage_percentages: UsagePercentages - last_updated: string -} - -interface ProviderBreakdown { - gemini: ProviderUsage - openai: ProviderUsage - anthropic: ProviderUsage - mistral: ProviderUsage - tavily: ProviderUsage - serper: ProviderUsage - metaphor: ProviderUsage - firecrawl: ProviderUsage - stability: ProviderUsage -} - -interface ProviderUsage { - calls: number - tokens: number - cost: number -} -``` - -## 🎨 Component Architecture - -### 1. BillingOverview Component -**File**: `frontend/src/components/billing/BillingOverview.tsx` - -**Props Interface**: -```typescript -interface BillingOverviewProps { - userId: string - onUpgrade?: () => void - onViewDetails?: () => void -} -``` - -**Key Features**: -- Real-time usage display with animated counters -- Progress bars for usage limits -- Cost breakdown with interactive tooltips -- Quick action buttons for plan management - -**State Management**: -```typescript -const [usageData, setUsageData] = useState(null) -const [loading, setLoading] = useState(true) -const [error, setError] = useState(null) -``` - -### 2. CostBreakdown Component -**File**: `frontend/src/components/billing/CostBreakdown.tsx` - -**Props Interface**: -```typescript -interface CostBreakdownProps { - providerBreakdown: ProviderBreakdown - totalCost: number - onProviderClick?: (provider: string) => void -} -``` - -**Key Features**: -- Interactive pie chart with provider breakdown -- Hover effects showing detailed costs -- Click to drill down into provider details -- Cost per token calculations - -### 3. UsageTrends Component -**File**: `frontend/src/components/billing/UsageTrends.tsx` - -**Props Interface**: -```typescript -interface UsageTrendsProps { - trends: UsageTrends - timeRange: '3m' | '6m' | '12m' - onTimeRangeChange: (range: string) => void -} -``` - -**Key Features**: -- Multi-line chart showing usage over time -- Toggle between cost, calls, and tokens -- Trend analysis with projections -- Peak usage identification - -### 4. SystemHealthIndicator Component -**File**: `frontend/src/components/monitoring/SystemHealthIndicator.tsx` - -**Props Interface**: -```typescript -interface SystemHealthIndicatorProps { - health: SystemHealth - onRefresh?: () => void -} -``` - -**Key Features**: -- Color-coded health status -- Real-time performance metrics -- Error rate monitoring -- Response time tracking - -## 🎭 Animation Specifications - -### Framer Motion Variants -```typescript -// Page transitions -const pageVariants = { - initial: { opacity: 0, y: 20 }, - animate: { opacity: 1, y: 0 }, - exit: { opacity: 0, y: -20 } -} - -// Card hover effects -const cardVariants = { - rest: { scale: 1, boxShadow: '0 4px 6px rgba(0,0,0,0.1)' }, - hover: { - scale: 1.02, - boxShadow: '0 8px 25px rgba(0,0,0,0.15)', - transition: { duration: 0.2 } - } -} - -// Number animations -const numberVariants = { - animate: { - scale: [1, 1.1, 1], - transition: { duration: 0.3 } - } -} -``` - -### Loading States -```typescript -// Skeleton loaders -const SkeletonCard = () => ( -
-) - -// Shimmer effects -const ShimmerEffect = () => ( -
-) -``` - -## 📱 Responsive Design Specifications - -### Tailwind CSS Breakpoints -```css -/* Mobile First Approach */ -.sm: '640px' /* Small devices */ -.md: '768px' /* Medium devices */ -.lg: '1024px' /* Large devices */ -.xl: '1280px' /* Extra large devices */ -.2xl: '1536px' /* 2X large devices */ -``` - -### Component Responsive Behavior -```typescript -// Responsive grid layout -const gridClasses = { - mobile: 'grid-cols-1 gap-4', - tablet: 'md:grid-cols-2 md:gap-6', - desktop: 'lg:grid-cols-3 lg:gap-8' -} - -// Responsive chart sizing -const chartDimensions = { - mobile: { width: 300, height: 200 }, - tablet: { width: 500, height: 300 }, - desktop: { width: 800, height: 400 } -} -``` - -## 🔄 Real-Time Updates Implementation - -### WebSocket Integration -```typescript -// WebSocket connection for real-time updates -const useRealtimeUpdates = (userId: string) => { - const [socket, setSocket] = useState(null) - - useEffect(() => { - const ws = new WebSocket(`ws://localhost:8000/ws/billing/${userId}`) - - ws.onmessage = (event) => { - const data = JSON.parse(event.data) - // Update local state with real-time data - updateUsageData(data) - } - - setSocket(ws) - return () => ws.close() - }, [userId]) -} -``` - -### Polling Strategy -```typescript -// Intelligent polling based on user activity -const useIntelligentPolling = (userId: string) => { - const [isActive, setIsActive] = useState(true) - - useEffect(() => { - const interval = setInterval(() => { - if (isActive) { - fetchUsageData(userId) - } - }, isActive ? 30000 : 300000) // 30s when active, 5m when inactive - - return () => clearInterval(interval) - }, [isActive, userId]) -} -``` - -## 🎨 Design System Implementation - -### Color Palette -```typescript -const colors = { - primary: { - 50: '#eff6ff', - 500: '#3b82f6', - 900: '#1e3a8a' - }, - success: { - 50: '#f0fdf4', - 500: '#22c55e', - 900: '#14532d' - }, - warning: { - 50: '#fffbeb', - 500: '#f59e0b', - 900: '#78350f' - }, - danger: { - 50: '#fef2f2', - 500: '#ef4444', - 900: '#7f1d1d' - } -} -``` - -### Typography Scale -```typescript -const typography = { - heading: 'text-2xl font-bold text-gray-900', - subheading: 'text-lg font-semibold text-gray-800', - body: 'text-base text-gray-700', - caption: 'text-sm text-gray-500', - metric: 'text-3xl font-bold text-blue-600' -} -``` - -## 📊 Chart Configuration - -### Recharts Theme -```typescript -const chartTheme = { - colors: ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6'], - grid: { - stroke: '#e5e7eb', - strokeWidth: 1, - strokeDasharray: '3 3' - }, - axis: { - stroke: '#6b7280', - fontSize: 12, - fontWeight: 500 - }, - tooltip: { - backgroundColor: 'rgba(0, 0, 0, 0.8)', - border: 'none', - borderRadius: 8, - color: 'white' - } -} -``` - -### Chart Components -```typescript -// Usage trend chart -const UsageTrendChart = ({ data, type }: { data: TrendData[], type: 'cost' | 'calls' | 'tokens' }) => ( - - - - - } /> - - - -) - -// Cost breakdown pie chart -const CostBreakdownChart = ({ data }: { data: ProviderData[] }) => ( - - - `${name} ${(percent * 100).toFixed(0)}%`} - > - {data.map((entry, index) => ( - - ))} - - [`$${value.toFixed(2)}`, 'Cost']} /> - - -) -``` - -## 🔒 Security Implementation - -### API Security -```typescript -// Secure API calls with authentication -const secureApiCall = async (endpoint: string, options: RequestInit = {}) => { - const token = await getAuthToken() - - return fetch(endpoint, { - ...options, - headers: { - ...options.headers, - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }) -} -``` - -### Data Validation -```typescript -// Runtime type checking for API responses -const validateUsageStats = (data: unknown): UsageStats => { - const schema = z.object({ - billing_period: z.string(), - total_calls: z.number(), - total_cost: z.number(), - // ... other fields - }) - - return schema.parse(data) -} -``` - -## 🧪 Testing Strategy - -### Component Testing -```typescript -// Test file structure -__tests__/ -├── components/ -│ ├── BillingOverview.test.tsx -│ ├── CostBreakdown.test.tsx -│ └── UsageTrends.test.tsx -├── services/ -│ ├── billingService.test.ts -│ └── monitoringService.test.ts -└── integration/ - └── billing-dashboard.test.tsx -``` - -### Test Scenarios -- **Loading States**: Test skeleton loaders and spinners -- **Error Handling**: Test API failure scenarios -- **Responsive Design**: Test across different screen sizes -- **Real-time Updates**: Test WebSocket connections -- **User Interactions**: Test hover effects and animations - -## 📈 Performance Optimization - -### Code Splitting -```typescript -// Lazy load heavy components -const BillingDashboard = lazy(() => import('./BillingDashboard')) -const UsageTrends = lazy(() => import('./UsageTrends')) - -// Route-based code splitting -const BillingRoutes = () => ( - }> - - } /> - } /> - - -) -``` - -### Memoization -```typescript -// Memoize expensive calculations -const MemoizedCostBreakdown = memo(({ data }: { data: ProviderData[] }) => { - const processedData = useMemo(() => - data.map(item => ({ - ...item, - percentage: (item.cost / totalCost) * 100 - })) - , [data, totalCost]) - - return -}) -``` - -## 🚀 Deployment Considerations - -### Environment Configuration -```typescript -// Environment-specific API endpoints -const API_ENDPOINTS = { - development: 'http://localhost:8000/api', - staging: 'https://staging-api.alwrity.com/api', - production: 'https://api.alwrity.com/api' -} -``` - -### Feature Flags -```typescript -// Feature flag for gradual rollout -const useFeatureFlag = (flag: string) => { - const [enabled, setEnabled] = useState(false) - - useEffect(() => { - fetchFeatureFlags().then(flags => { - setEnabled(flags[flag] || false) - }) - }, [flag]) - - return enabled -} -``` - ---- - -This technical specification provides the foundation for implementing enterprise-grade billing and monitoring features in the ALwrity dashboard, ensuring cost transparency, real-time monitoring, and excellent user experience. diff --git a/docs/Billing_Subscription/HUGGINGFACE_PRICING.md b/docs/Billing_Subscription/HUGGINGFACE_PRICING.md deleted file mode 100644 index 67952af9..00000000 --- a/docs/Billing_Subscription/HUGGINGFACE_PRICING.md +++ /dev/null @@ -1,103 +0,0 @@ -# HuggingFace Pricing Configuration - -## Overview - -HuggingFace API calls (specifically for GPT-OSS-120B model via Groq) are tracked and billed using configurable pricing. The pricing can be set via environment variables in your `.env` file. - -## Environment Variables - -### `HUGGINGFACE_INPUT_TOKEN_COST` -- **Description**: Cost per input token for HuggingFace API calls -- **Format**: Float (decimal number) -- **Default**: `0.000001` ($1 per 1M input tokens) -- **Example**: `HUGGINGFACE_INPUT_TOKEN_COST=0.000001` - -### `HUGGINGFACE_OUTPUT_TOKEN_COST` -- **Description**: Cost per output token for HuggingFace API calls -- **Format**: Float (decimal number) -- **Default**: `0.000003` ($3 per 1M output tokens) -- **Example**: `HUGGINGFACE_OUTPUT_TOKEN_COST=0.000003` - -## Configuration - -### Step 1: Add to .env File - -Add the following lines to your `.env` file: - -```bash -# HuggingFace Pricing (for GPT-OSS-120B via Groq) -# Pricing is per token (e.g., 0.000001 = $1 per 1M tokens) -HUGGINGFACE_INPUT_TOKEN_COST=0.000001 -HUGGINGFACE_OUTPUT_TOKEN_COST=0.000003 -``` - -### Step 2: Initialize/Update Pricing - -The pricing is automatically initialized when the database is set up. To update pricing after changing environment variables: - -1. **Option 1**: Restart the backend server (pricing will be updated on next initialization) -2. **Option 2**: Run the database setup script to update pricing: - ```bash - python backend/scripts/create_subscription_tables.py - ``` - -### Step 3: Verify Pricing - -Check that pricing is correctly configured by: -1. Checking the database `api_provider_pricing` table -2. Making a test API call and checking the cost in usage logs -3. Viewing the billing dashboard to see cost calculations - -## Pricing Calculation - -The cost calculation works as follows: - -1. **Database Lookup**: The system first tries to find pricing in the database for the specific model -2. **Model Matching**: It tries multiple model name variations: - - Exact model name (e.g., "openai/gpt-oss-120b:groq") - - Short model name (e.g., "gpt-oss-120b") - - Default model name ("default") -3. **Environment Variable Fallback**: If no pricing is found in the database, it uses environment variables for HuggingFace/Mistral provider -4. **Default Estimates**: As a last resort, it uses default estimates ($1 per 1M tokens for both input and output) - -## Cost Calculation Formula - -``` -cost_input = tokens_input * HUGGINGFACE_INPUT_TOKEN_COST -cost_output = tokens_output * HUGGINGFACE_OUTPUT_TOKEN_COST -cost_total = cost_input + cost_output -``` - -## Example - -For a HuggingFace API call with: -- Input tokens: 1000 -- Output tokens: 500 -- HUGGINGFACE_INPUT_TOKEN_COST: 0.000001 ($1 per 1M tokens) -- HUGGINGFACE_OUTPUT_TOKEN_COST: 0.000003 ($3 per 1M tokens) - -Calculation: -``` -cost_input = 1000 * 0.000001 = 0.001 ($0.001) -cost_output = 500 * 0.000003 = 0.0015 ($0.0015) -cost_total = 0.001 + 0.0015 = 0.0025 ($0.0025) -``` - -## Testing - -To test the pricing configuration: - -1. Set environment variables in `.env` -2. Restart the backend server -3. Make a HuggingFace API call -4. Check the usage logs in the billing dashboard -5. Verify the cost is calculated correctly - -## Notes - -- Pricing is stored in the `api_provider_pricing` table -- Pricing is updated automatically when `initialize_default_pricing()` is called -- Environment variables take precedence over database values if pricing is not found in DB -- The pricing applies to all HuggingFace models that map to the MISTRAL provider enum -- Default pricing is based on Groq's estimated pricing for GPT-OSS-120B model - diff --git a/docs/Billing_Subscription/SUBSCRIPTION_IMPLEMENTATION_SUMMARY.md b/docs/Billing_Subscription/SUBSCRIPTION_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index ab122a18..00000000 --- a/docs/Billing_Subscription/SUBSCRIPTION_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,268 +0,0 @@ -# ALwrity Usage-Based Subscription System Implementation Summary - -## 🎉 Implementation Complete! - -I have successfully implemented a comprehensive usage-based subscription system for ALwrity with robust monitoring, cost tracking, and usage limits. Here's what has been delivered: - -## 📦 Delivered Components - -### 1. Database Models (`backend/models/subscription_models.py`) -- **SubscriptionPlan**: Defines subscription tiers (Free, Basic, Pro, Enterprise) -- **UserSubscription**: Tracks user subscription details and billing -- **APIUsageLog**: Detailed logging of every API call with cost tracking -- **UsageSummary**: Aggregated usage statistics per user per billing period -- **APIProviderPricing**: Configurable pricing for all API providers -- **UsageAlert**: Automated alerts for usage thresholds -- **BillingHistory**: Historical billing records - -### 2. Core Services - -#### Pricing Service (`backend/services/pricing_service.py`) -- Real-time cost calculation for all API providers -- Subscription limit management -- Usage validation and enforcement -- Support for Gemini, OpenAI, Anthropic, Mistral, and search APIs - -#### Usage Tracking Service (`backend/services/usage_tracking_service.py`) -- Comprehensive API usage tracking -- Real-time usage statistics -- Trend analysis and projections -- Automatic alert generation at 80%, 90%, and 100% thresholds - -#### Exception Handler (`backend/services/subscription_exception_handler.py`) -- Robust error handling with detailed logging -- Structured exception types for different scenarios -- Automatic alert creation for critical errors -- User-friendly error messages - -### 3. Enhanced Middleware (`backend/middleware/monitoring_middleware.py`) -- **Automatic API Provider Detection**: Identifies Gemini, OpenAI, Anthropic, etc. -- **Token Estimation**: Estimates usage from request/response content -- **Pre-Request Validation**: Enforces usage limits before processing -- **Cost Tracking**: Real-time cost calculation and logging -- **Usage Limit Enforcement**: Returns 429 errors when limits exceeded - -### 4. API Endpoints (`backend/api/subscription_api.py`) -- `GET /api/subscription/plans` - Available subscription plans -- `GET /api/subscription/usage/{user_id}` - Current usage statistics -- `GET /api/subscription/usage/{user_id}/trends` - Usage trends over time -- `GET /api/subscription/dashboard/{user_id}` - Comprehensive dashboard data -- `GET /api/subscription/pricing` - API pricing information -- `GET /api/subscription/alerts/{user_id}` - Usage alerts and notifications - -### 5. Database Migration (`backend/scripts/create_subscription_tables.py`) -- Automated table creation for all subscription components -- Default subscription plan initialization -- API pricing configuration with current Gemini rates -- Comprehensive setup verification - -## 🔧 Key Features Implemented - -### Usage-Based Billing -- ✅ **Real-time cost tracking** for all API providers -- ✅ **Token-level precision** for LLM APIs (Gemini, OpenAI, Anthropic) -- ✅ **Request-based pricing** for search APIs (Tavily, Serper, Metaphor) -- ✅ **Automatic cost calculation** with configurable pricing - -### Subscription Management -- ✅ **4 Subscription Tiers**: Free, Basic ($29/mo), Pro ($79/mo), Enterprise ($199/mo) -- ✅ **Flexible limits**: API calls, tokens, and monthly cost caps -- ✅ **Usage enforcement**: Pre-request validation and blocking -- ✅ **Billing cycle support**: Monthly and yearly options - -### Monitoring & Analytics -- ✅ **Real-time dashboard** with usage statistics -- ✅ **Usage trends** and projections -- ✅ **Provider-specific breakdowns** (Gemini, OpenAI, etc.) -- ✅ **Performance metrics** (response times, error rates) - -### Alert System -- ✅ **Automatic notifications** at 80%, 90%, and 100% usage -- ✅ **Multi-channel alerts** (database, logs, future email integration) -- ✅ **Alert management** (mark as read, severity levels) -- ✅ **Usage recommendations** and upgrade prompts - -## 📊 Current API Pricing Configuration - -### Gemini API (Google) -- **Gemini 2.0 Flash Lite**: $0.075 input / $0.30 output per 1M tokens -- **Gemini 2.5 Flash**: $0.125 input / $0.375 output per 1M tokens -- **Gemini 2.5 Pro**: $1.25 input / $10.00 output per 1M tokens - -### Search APIs -- **Tavily Search**: $0.001 per search -- **Serper Google Search**: $0.001 per search -- **Metaphor/Exa Search**: $0.003 per search -- **Firecrawl Web Extraction**: $0.002 per page - -### Placeholder Pricing -- **OpenAI**: Estimated pricing (to be updated with actual rates) -- **Anthropic**: Estimated pricing (to be updated with actual rates) -- **Stability AI**: $0.04 per image generation - -## 🚀 Integration Status - -### ✅ Completed Integrations -- **FastAPI App**: Subscription routes added to main application -- **Database Service**: Subscription models integrated -- **Monitoring Middleware**: Enhanced with usage tracking -- **Exception Handling**: Comprehensive error management -- **API Documentation**: Complete endpoint documentation - -### 🔄 Ready for Integration -- **Frontend Dashboard**: API endpoints ready for UI integration -- **Payment Processing**: Stripe/payment gateway integration points prepared -- **Email Notifications**: Alert system ready for email service integration -- **User Authentication**: User ID extraction points identified - -## 📈 Dashboard Data Structure - -The system provides comprehensive dashboard data including: - -```json -{ - "current_usage": { - "total_calls": 1250, - "total_cost": 15.75, - "usage_status": "active", - "provider_breakdown": { - "gemini": {"calls": 800, "cost": 10.50, "tokens": 125000}, - "openai": {"calls": 450, "cost": 5.25, "tokens": 85000} - } - }, - "limits": { - "plan_name": "Pro", - "limits": { - "gemini_calls": 5000, - "monthly_cost": 150.0 - } - }, - "usage_percentages": { - "gemini_calls": 16.0, - "cost": 10.5 - }, - "projections": { - "projected_monthly_cost": 47.25, - "projected_usage_percentage": 31.5 - }, - "alerts": [ - { - "title": "API Usage Notice - Gemini", - "message": "You have used 800 of 5,000 Gemini API calls", - "severity": "info" - } - ] -} -``` - -## 🔍 Monitoring Capabilities - -### Real-Time Tracking -- **Every API call** is logged with full context -- **Token usage** tracked for accurate billing -- **Response times** and error rates monitored -- **Cost accumulation** in real-time - -### Usage Analytics -- **Historical trends** over 6+ months -- **Provider comparisons** and optimization insights -- **Cost projections** based on current usage -- **Performance benchmarks** and SLA tracking - -## 🛡️ Security & Reliability - -### Error Handling -- **Graceful degradation** when limits are reached -- **User-friendly error messages** with upgrade suggestions -- **Comprehensive logging** for debugging and auditing -- **Automatic retry logic** for transient failures - -### Data Protection -- **No sensitive data** in logs or error messages -- **Encrypted storage** for usage statistics -- **GDPR-compliant** data handling -- **Secure API key management** - -## 🎯 Next Steps for Production - -### 1. Environment Setup -```bash -# Install dependencies (when environment allows) -pip install sqlalchemy loguru fastapi - -# Run database migration -python backend/scripts/create_subscription_tables.py - -# Verify setup -python backend/verify_subscription_setup.py -``` - -### 2. Configuration Updates -- Update API pricing with actual current rates -- Configure email notification service -- Set up payment processing (Stripe, etc.) -- Configure production database (PostgreSQL) - -### 3. Frontend Integration -- Integrate dashboard API endpoints -- Add usage monitoring components -- Implement subscription management UI -- Add billing and payment interfaces - -### 4. User Management -- Implement user authentication -- Add user ID extraction to middleware -- Set up user onboarding flow -- Configure subscription upgrade/downgrade flows - -## 📚 Documentation & Testing - -### Comprehensive Documentation -- **README**: Complete setup and usage guide -- **API Documentation**: All endpoints with examples -- **Architecture Guide**: System design and components -- **Troubleshooting**: Common issues and solutions - -### Testing Suite -- **Unit Tests**: Core functionality testing -- **Integration Tests**: End-to-end workflow testing -- **Performance Tests**: Load and stress testing -- **Verification Scripts**: Setup validation - -## 🎉 Implementation Highlights - -### Robust Architecture -- **Modular design** with clear separation of concerns -- **Scalable database schema** supporting millions of API calls -- **Efficient middleware** with minimal performance impact -- **Comprehensive error handling** with automatic recovery - -### Production-Ready Features -- **Real-time usage enforcement** prevents overage -- **Accurate cost tracking** down to individual tokens -- **Automated alerting** keeps users informed -- **Detailed analytics** for business insights - -### Developer-Friendly -- **Clean API design** with consistent responses -- **Comprehensive logging** for debugging -- **Extensive documentation** with examples -- **Easy configuration** and customization - ---- - -## 🚀 Ready for Deployment! - -The usage-based subscription system is **fully implemented and ready for production use**. All core components are in place, tested, and integrated with the existing ALwrity infrastructure. - -The system provides: -- ✅ **Complete usage tracking** for all API providers -- ✅ **Real-time cost monitoring** and billing -- ✅ **Automated usage limits** and enforcement -- ✅ **Comprehensive dashboard** integration -- ✅ **Robust error handling** and logging -- ✅ **Scalable architecture** for growth - -**Total Implementation**: 7 major components, 8 files, 2000+ lines of production-ready code with comprehensive error handling, logging, and documentation. - -The system is ready to handle your usage-based subscription needs and can be easily extended with additional API providers or billing features as needed. \ No newline at end of file diff --git a/docs/CONTENT_GENERATOR_REFACTORING.md b/docs/CONTENT_GENERATOR_REFACTORING.md deleted file mode 100644 index 9a7f3414..00000000 --- a/docs/CONTENT_GENERATOR_REFACTORING.md +++ /dev/null @@ -1,399 +0,0 @@ -# Content Generator Refactoring - Prompt Extraction & Method Extraction - -## Overview - -The `ContentGenerator` class has been refactored to improve maintainability and organization by: -1. **Extracting all prompt templates** into separate, dedicated modules -2. **Extracting complex generation methods** (`generate_carousel` and `generate_video_script`) into specialized generator classes -3. **Removing all fallback methods** to ensure only AI-generated content is used - -This refactoring eliminates large inline prompt methods, complex generation logic, and mock fallback content, creating a cleaner, more modular architecture that strictly enforces AI-generated content quality. - -## What Was Refactored - -### **Before: Inline Methods and Complex Logic** -The original `ContentGenerator` class contained: -- **5 large inline prompt methods** (150+ lines) -- **2 complex generation methods** with extensive processing logic: - - `generate_carousel()` - 80+ lines of carousel generation logic - - `generate_video_script()` - 70+ lines of video script generation logic -- **5 fallback methods** that returned low-quality mock content: - - `generate_fallback_post_content()` - Mock post content - - `generate_fallback_article_content()` - Mock article content - - `generate_fallback_carousel_content()` - Mock carousel content - - `generate_fallback_video_script_content()` - Mock video script content - - `generate_fallback_comment_response()` - Mock comment response content -- All logic mixed together in one large class - -### **After: Modular Architecture with Strict AI Content** -All functionality has been extracted into dedicated modules within the `content_generator_prompts` directory: - -``` -backend/services/linkedin/content_generator_prompts/ -├── __init__.py # Package exports (updated) -├── post_prompts.py # LinkedIn post prompts -├── article_prompts.py # LinkedIn article prompts -├── carousel_prompts.py # LinkedIn carousel prompts -├── video_script_prompts.py # LinkedIn video script prompts -├── comment_response_prompts.py # LinkedIn comment response prompts -├── carousel_generator.py # LinkedIn carousel generation logic -└── video_script_generator.py # LinkedIn video script generation logic -``` - -## New Module Structure - -### 1. **`__init__.py`** -Package initialization file that exports all prompt builders and generators: -```python -from .post_prompts import PostPromptBuilder -from .article_prompts import ArticlePromptBuilder -from .carousel_prompts import CarouselPromptBuilder -from .video_script_prompts import VideoScriptPromptBuilder -from .comment_response_prompts import CommentResponsePromptBuilder -from .carousel_generator import CarouselGenerator -from .video_script_generator import VideoScriptGenerator - -__all__ = [ - 'PostPromptBuilder', - 'ArticlePromptBuilder', - 'CarouselPromptBuilder', - 'VideoScriptPromptBuilder', - 'CommentResponsePromptBuilder', - 'CarouselGenerator', - 'VideoScriptGenerator' -] -``` - -### 2. **Prompt Builder Modules** (Existing) -- **`post_prompts.py`** - LinkedIn post generation prompts -- **`article_prompts.py`** - LinkedIn article generation prompts -- **`carousel_prompts.py`** - LinkedIn carousel generation prompts -- **`video_script_prompts.py`** - LinkedIn video script prompts -- **`comment_response_prompts.py`** - LinkedIn comment response prompts - -### 3. **Generator Modules** (New) -- **`carousel_generator.py`** - Complete carousel generation logic with citations, quality analysis, and response building -- **`video_script_generator.py`** - Complete video script generation logic with citations, quality analysis, and response building - -## Generator Classes - -### **CarouselGenerator Class** -```python -class CarouselGenerator: - """Handles LinkedIn carousel generation with all processing steps.""" - - def __init__(self, citation_manager=None, quality_analyzer=None): - self.citation_manager = citation_manager - self.quality_analyzer = quality_analyzer - - async def generate_carousel(self, request, research_sources, research_time, content_result, grounding_enabled): - """Generate LinkedIn carousel with all processing steps.""" - # Complete carousel generation logic including: - # - Citation processing - # - Quality analysis - # - Response building - # - Grounding status -``` - -### **VideoScriptGenerator Class** -```python -class VideoScriptGenerator: - """Handles LinkedIn video script generation with all processing steps.""" - - def __init__(self, citation_manager=None, quality_analyzer=None): - self.citation_manager = citation_manager - self.quality_analyzer = quality_analyzer - - async def generate_video_script(self, request, research_sources, research_time, content_result, grounding_enabled): - """Generate LinkedIn video script with all processing steps.""" - # Complete video script generation logic including: - # - Citation processing - # - Quality analysis - # - Response building - # - Grounding status -``` - -## Changes Made to ContentGenerator - -### **1. Import Statements Added** -```python -from services.linkedin.content_generator_prompts import ( - PostPromptBuilder, - ArticlePromptBuilder, - CarouselPromptBuilder, - VideoScriptPromptBuilder, - CommentResponsePromptBuilder, - CarouselGenerator, - VideoScriptGenerator -) -``` - -### **2. Generator Initialization** -```python -def __init__(self, citation_manager=None, quality_analyzer=None, gemini_grounded=None, fallback_provider=None): - self.citation_manager = citation_manager - self.quality_analyzer = quality_analyzer - self.gemini_grounded = gemini_grounded - self.fallback_provider = fallback_provider - - # Initialize specialized generators - self.carousel_generator = CarouselGenerator(citation_manager, quality_analyzer) - self.video_script_generator = VideoScriptGenerator(citation_manager, quality_analyzer) -``` - -### **3. Method Delegation** -The main `ContentGenerator` class now delegates to specialized generators: - -```python -async def generate_carousel(self, request, research_sources, research_time, content_result, grounding_enabled): - """Generate LinkedIn carousel using the specialized CarouselGenerator.""" - return await self.carousel_generator.generate_carousel( - request, research_sources, research_time, content_result, grounding_enabled - ) - -async def generate_video_script(self, request, research_sources, research_time, content_result, grounding_enabled): - """Generate LinkedIn video script using the specialized VideoScriptGenerator.""" - return await self.video_script_generator.generate_video_script( - request, research_sources, research_time, content_result, grounding_enabled - ) -``` - -### **4. Methods Removed** -- **`generate_carousel()`** - 80+ lines of complex logic extracted to `CarouselGenerator` -- **`generate_video_script()`** - 70+ lines of complex logic extracted to `VideoScriptGenerator` -- **All fallback methods** - 5 methods that returned mock content completely removed - -### **5. Strict AI Content Enforcement** -All grounded content generation methods now fail gracefully instead of falling back to mock content: - -```python -async def generate_grounded_post_content(self, request, research_sources: List) -> Dict[str, Any]: - """Generate grounded post content using the enhanced Gemini provider with native grounding.""" - try: - if not self.gemini_grounded: - logger.error("Gemini Grounded Provider not available - cannot generate content without AI provider") - raise Exception("Gemini Grounded Provider not available - cannot generate content without AI provider") - - # ... AI content generation logic ... - - except Exception as e: - logger.error(f"Error generating grounded post content: {str(e)}") - raise Exception(f"Failed to generate grounded post content: {str(e)}") -``` - -## Benefits of Additional Refactoring - -### **1. Enhanced Separation of Concerns** -- **Prompt logic**: Handled by prompt builder classes -- **Generation logic**: Handled by specialized generator classes -- **Main coordination**: Handled by ContentGenerator class - -### **2. Improved Testability** -- **Individual generators** can be unit tested in isolation -- **Mock dependencies** can be easily injected for testing -- **Smaller, focused classes** are easier to test comprehensively - -### **3. Better Code Organization** -- **Related functionality** is grouped together -- **Easier to locate** specific generation logic -- **Clearer responsibilities** for each class - -### **4. Enhanced Maintainability** -- **Modify carousel logic** without affecting other content types -- **Update video script processing** independently -- **Add new features** to specific generators without cluttering main class - -### **5. Improved Reusability** -- **CarouselGenerator** can be used independently of ContentGenerator -- **VideoScriptGenerator** can be imported and used in other contexts -- **Cleaner dependencies** between different components - -### **6. Strict Content Quality Enforcement** -- **No mock content** - only AI-generated real content is allowed -- **Fail-fast approach** - errors are raised immediately instead of degraded content -- **Consistent quality** - all content meets the same high standards -- **Professional output** - no placeholder or template content - -## Functionality Preserved - -### **✅ All Existing Features Maintained** -- **Post generation**: LinkedIn posts with citations and quality analysis -- **Article generation**: Comprehensive articles with SEO optimization -- **Carousel generation**: Visual content with multiple slides (now via CarouselGenerator) -- **Video script generation**: Engaging video content with timing (now via VideoScriptGenerator) -- **Comment response generation**: Professional engagement responses -- **Grounded content generation**: AI-powered content with research sources -- **Quality analysis**: Content quality metrics and scoring -- **Citation management**: Source tracking and reference generation - -### **✅ No Breaking Changes** -- **Same method signatures**: All public methods remain unchanged -- **Same return types**: All responses maintain their original structure -- **Same error handling**: Exception handling and fallback logic preserved -- **Same configuration**: All initialization parameters remain the same - -### **✅ Enhanced Quality Assurance** -- **AI-only content**: No fallback to mock or template content -- **Immediate failure**: Clear error messages when AI providers are unavailable -- **Consistent standards**: All content meets professional quality requirements - -## Usage Examples - -### **Using the Refactored ContentGenerator** -```python -# Initialize the content generator (same as before) -content_generator = ContentGenerator( - citation_manager=citation_mgr, - quality_analyzer=quality_analyzer, - gemini_grounded=gemini_provider, - fallback_provider=fallback_provider -) - -# Generate carousel (now uses CarouselGenerator internally) -carousel_content = await content_generator.generate_carousel( - request=carousel_request, - research_sources=research_sources, - research_time=research_time, - content_result=content_result, - grounding_enabled=True -) - -# Generate video script (now uses VideoScriptGenerator internally) -video_script = await content_generator.generate_video_script( - request=video_request, - research_sources=research_sources, - research_time=research_time, - content_result=content_result, - grounding_enabled=True -) -``` - -### **Using Generators Directly** -```python -from services.linkedin.content_generator_prompts import CarouselGenerator, VideoScriptGenerator - -# Use carousel generator directly -carousel_gen = CarouselGenerator(citation_manager, quality_analyzer) -carousel_result = await carousel_gen.generate_carousel( - request, research_sources, research_time, content_result, grounding_enabled -) - -# Use video script generator directly -video_gen = VideoScriptGenerator(citation_manager, quality_analyzer) -video_result = await video_gen.generate_video_script( - request, research_sources, research_time, content_result, grounding_enabled -) -``` - -## Testing Considerations - -### **Unit Testing Individual Generators** -```python -def test_carousel_generator(): - """Test that carousel generation works correctly.""" - generator = CarouselGenerator(mock_citation_manager, mock_quality_analyzer) - - result = await generator.generate_carousel( - mock_request, mock_sources, 10.5, mock_content, True - ) - - assert result['success'] is True - assert 'slides' in result['data'] - assert len(result['data']['slides']) > 0 - -def test_video_script_generator(): - """Test that video script generation works correctly.""" - generator = VideoScriptGenerator(mock_citation_manager, mock_quality_analyzer) - - result = await generator.generate_video_script( - mock_request, mock_sources, 8.2, mock_content, True - ) - - assert result['success'] is True - assert 'hook' in result['data'] - assert 'main_content' in result['data'] - assert 'conclusion' in result['data'] -``` - -### **Integration Testing** -```python -def test_content_generator_with_extracted_generators(): - """Test that ContentGenerator works with extracted generators.""" - generator = ContentGenerator( - citation_manager=mock_citation_manager, - quality_analyzer=mock_quality_analyzer - ) - - # These should work exactly as before - carousel_result = await generator.generate_carousel(request, sources, time, content, True) - video_result = await generator.generate_video_script(request, sources, time, content, True) - - assert carousel_result['success'] is True - assert video_result['success'] is True -``` - -### **Error Handling Testing** -```python -def test_no_fallback_content(): - """Test that no fallback/mock content is generated.""" - generator = ContentGenerator( - citation_manager=mock_citation_manager, - quality_analyzer=mock_quality_analyzer, - gemini_grounded=None # No AI provider - ) - - with pytest.raises(Exception) as exc_info: - await generator.generate_grounded_post_content(request, sources) - - assert "cannot generate content without AI provider" in str(exc_info.value) -``` - -## Migration Guide - -### **For Existing Code** -No changes are required in existing code that uses the `ContentGenerator` class. All public methods and their behavior remain identical. - -### **For New Development** -When creating new content types or modifying existing generation logic: - -1. **Create a new generator module** in `content_generator_prompts/` -2. **Add the generator class** to the package `__init__.py` -3. **Initialize the generator** in ContentGenerator's `__init__` method -4. **Delegate method calls** to the specialized generator -5. **Update tests** to cover the new generator functionality -6. **Ensure no mock content** - only AI-generated content is allowed - -## Future Enhancements - -### **1. Additional Generator Types** -- **PostGenerator**: Extract post generation logic -- **ArticleGenerator**: Extract article generation logic -- **CommentResponseGenerator**: Extract comment response logic - -### **2. Generator Composition** -- **Shared base class**: Common functionality across generators -- **Mixin classes**: Reusable generation patterns -- **Strategy pattern**: Different generation strategies - -### **3. Advanced Generator Features** -- **Async processing**: Parallel content generation -- **Caching**: Cache generated content for reuse -- **Validation**: Content validation and quality checks -- **Quality gates**: Ensure all content meets minimum standards - -## Conclusion - -The additional refactoring of the `ContentGenerator` class successfully extracts complex generation methods into specialized, focused classes while maintaining 100% of existing functionality. Most importantly, **all fallback methods have been removed** to ensure only AI-generated real content is used. - -### **Key Benefits Achieved:** -- ✅ **Improved maintainability** through better code organization and separation of concerns -- ✅ **Enhanced reusability** of both prompt templates and generation logic -- ✅ **Cleaner architecture** with clear responsibilities for each class -- ✅ **Easier testing** of individual components and generators -- ✅ **Future extensibility** for new content types and generation strategies -- ✅ **Zero breaking changes** to existing functionality -- ✅ **Better code organization** with logical grouping of related functionality -- ✅ **Strict content quality enforcement** with no mock or fallback content -- ✅ **Professional output standards** maintained across all content types - -The refactored code maintains all sophisticated content generation capabilities while providing a much cleaner, more modular, and maintainable structure for developers. The separation of prompts, generation logic, and coordination creates a robust foundation for future enhancements and new content types. **Most importantly, the system now strictly enforces AI-generated content only, eliminating any possibility of low-quality mock or template content.** diff --git a/docs/CRITICAL_ONBOARDING_DATABASE_MIGRATION.md b/docs/CRITICAL_ONBOARDING_DATABASE_MIGRATION.md deleted file mode 100644 index d41bacf1..00000000 --- a/docs/CRITICAL_ONBOARDING_DATABASE_MIGRATION.md +++ /dev/null @@ -1,264 +0,0 @@ -# 🚨 CRITICAL: Onboarding Data Must Use Database - -## Issue Summary - -**Severity:** 🔴 CRITICAL -**Impact:** User isolation, data persistence, security -**Status:** ⚠️ NEEDS IMMEDIATE FIX AFTER DEPLOYMENT STABILIZES - -## Problem Description - -The onboarding system currently saves all user data to a JSON file (`.onboarding_progress.json`) instead of using the database. This causes multiple critical issues: - -### 1. **No User Isolation** 🔴 -- All users share the same JSON file -- User data can be overwritten by other users -- Privacy violation - users can see each other's data -- **Line:** `backend/services/api_key_manager.py:45` -- **Code:** `self.progress_file = progress_file or ".onboarding_progress.json"` - -### 2. **Data Loss on Deployment** 🔴 -- Render uses ephemeral filesystem -- File is deleted on every deployment/restart -- Users lose all onboarding progress -- Have to restart onboarding after each deployment - -### 3. **No Scalability** 🔴 -- Won't work with multiple backend instances -- File locking issues -- Race conditions -- Performance bottleneck - -### 4. **Security Risk** 🔴 -- API keys stored in plain text JSON file -- No encryption -- File accessible with filesystem access -- Should be in database with proper security - -## Current Architecture - -``` -User completes step → OnboardingProgress.mark_step_completed() - → save_progress() (line 214) - → json.dump(progress_data, ".onboarding_progress.json") -``` - -**File Location:** `backend/.onboarding_progress.json` -**Affected Code:** -- `backend/services/api_key_manager.py` (OnboardingProgress class) -- `backend/api/onboarding_utils/endpoints_core.py` -- `backend/api/onboarding_utils/step_management_service.py` - -## Database Models Available - -✅ **Good News:** Proper database models already exist! - -**File:** `backend/models/onboarding.py` - -```python -- OnboardingSession (user_id, current_step, progress, started_at, updated_at) -- APIKey (session_id, provider, key, created_at, updated_at) -- WebsiteAnalysis (session_id, website_url, analysis_date, writing_style, etc.) -- ResearchPreferences (session_id, research_depth, content_types, etc.) -``` - -**Database Schema:** -- ✅ User isolation via `user_id` and `session_id` -- ✅ Proper relationships and foreign keys -- ✅ Timestamps for audit trail -- ✅ JSON fields for complex data -- ✅ Cascade deletes for cleanup - -## Required Changes - -### Phase 1: Database Layer (Priority 1) - -**File:** `backend/services/onboarding_database_service.py` (NEW) - -```python -class OnboardingDatabaseService: - """Database-backed onboarding service replacing JSON file storage.""" - - def get_or_create_session(self, user_id: str) -> OnboardingSession: - """Get existing session or create new one.""" - - def get_progress(self, user_id: str) -> OnboardingProgress: - """Load progress from database.""" - - def save_step_data(self, user_id: str, step_number: int, data: Dict): - """Save step data to database.""" - - def mark_step_completed(self, user_id: str, step_number: int): - """Mark step as completed in database.""" - - def get_step_data(self, user_id: str, step_number: int) -> Dict: - """Retrieve step data from database.""" -``` - -### Phase 2: Refactor API Key Manager (Priority 1) - -**File:** `backend/services/api_key_manager.py` - -**Changes:** -1. Remove JSON file operations (lines 214-242) -2. Add database dependency injection -3. Replace `save_progress()` with database calls -4. Replace `load_progress()` with database queries -5. Add user_id parameter to all methods - -**Before:** -```python -def mark_step_completed(self, step_number: int, data: Optional[Dict] = None): - # ... update in-memory state ... - self.save_progress() # Saves to JSON file -``` - -**After:** -```python -def mark_step_completed(self, user_id: str, step_number: int, data: Optional[Dict] = None): - # ... update database ... - db_service.save_step_data(user_id, step_number, data) - db_service.mark_step_completed(user_id, step_number) -``` - -### Phase 3: Update Endpoints (Priority 2) - -**Files to Update:** -- `backend/api/onboarding_utils/endpoints_core.py` -- `backend/api/onboarding_utils/step_management_service.py` -- `backend/api/onboarding_utils/step3_routes.py` -- `backend/api/onboarding_utils/step4_persona_routes.py` - -**Changes:** -1. Pass `user_id` from `get_current_user` to all service calls -2. Remove file-based caching -3. Use database queries for progress retrieval - -### Phase 4: Migration Script (Priority 3) - -**File:** `backend/scripts/migrate_onboarding_to_database.py` (NEW) - -```python -def migrate_json_to_database(): - """ - Migrate existing .onboarding_progress.json to database. - Only needed if production has existing data in JSON files. - """ - # Read JSON file - # Create database records - # Backup JSON file - # Delete JSON file -``` - -## Implementation Plan - -### Step 1: Create Database Service (1-2 hours) -- [ ] Create `onboarding_database_service.py` -- [ ] Implement CRUD operations -- [ ] Add user isolation checks -- [ ] Write unit tests - -### Step 2: Refactor API Key Manager (2-3 hours) -- [ ] Remove JSON file operations -- [ ] Add database calls -- [ ] Update method signatures with user_id -- [ ] Test with database - -### Step 3: Update Endpoints (1-2 hours) -- [ ] Pass user_id to service calls -- [ ] Remove file-based logic -- [ ] Test each endpoint - -### Step 4: Testing (2-3 hours) -- [ ] Test user isolation -- [ ] Test data persistence across deployments -- [ ] Test concurrent users -- [ ] Test error handling - -### Step 5: Deployment (1 hour) -- [ ] Deploy to staging -- [ ] Run migration script if needed -- [ ] Deploy to production -- [ ] Monitor for issues - -**Total Estimated Time:** 8-12 hours - -## Temporary Mitigation - -Until this is fixed, we must: - -1. ✅ Add `.onboarding_progress.json` to `.gitignore` -2. ✅ Document that onboarding data will be lost on deployment -3. ⚠️ Warn users that onboarding must be completed in one session -4. ⚠️ Consider using Render's persistent disk (expensive workaround) - -## Testing Checklist - -After migration: - -- [ ] User A completes onboarding -- [ ] User B completes onboarding -- [ ] Verify User A and User B data are separate -- [ ] Redeploy backend -- [ ] Verify both users' data persists -- [ ] User C starts onboarding -- [ ] Verify User C doesn't see User A or B data -- [ ] Test concurrent onboarding (multiple users at once) -- [ ] Verify API keys are stored securely -- [ ] Test onboarding restart (partial completion) - -## Security Considerations - -### Current (Insecure): -```json -{ - "steps": [ - { - "step_number": 1, - "data": { - "api_keys": { - "gemini": "ACTUAL_API_KEY_HERE", - "exa": "ACTUAL_API_KEY_HERE" - } - } - } - ] -} -``` - -### After Migration (Secure): -- API keys in database with user isolation -- Encrypted at rest (if database supports it) -- Access controlled by user_id -- Audit trail via timestamps - -## References - -- Database Models: `backend/models/onboarding.py` -- Current Implementation: `backend/services/api_key_manager.py` -- Endpoints: `backend/api/onboarding_utils/` -- Issue tracking: GitHub Issue #XXX (to be created) - -## Priority - -**This must be fixed before:** -- ❌ Going to production with real users -- ❌ Accepting paying customers -- ❌ Handling sensitive data -- ❌ Scaling to multiple instances - -**Acceptable to delay if:** -- ✅ Still in alpha/beta with limited users -- ✅ Users aware of data loss on deployment -- ✅ Not handling production workloads yet - -## Conclusion - -This is a critical architectural flaw that violates basic principles: -- User data isolation -- Data persistence -- Security best practices -- Scalability - -**Must be fixed immediately after current deployment stabilizes.** - diff --git a/docs/EXAMPLES_USER_API_KEYS.md b/docs/EXAMPLES_USER_API_KEYS.md deleted file mode 100644 index acf81d96..00000000 --- a/docs/EXAMPLES_USER_API_KEYS.md +++ /dev/null @@ -1,489 +0,0 @@ -# User API Key Context - Usage Examples - -This document shows how to use the `UserAPIKeyContext` in your backend services to ensure user-specific API keys are used. - -## Quick Start - -### **1. Basic Usage in FastAPI Endpoint** - -```python -from fastapi import APIRouter, Depends -from middleware.auth_middleware import get_current_user -from services.user_api_key_context import user_api_keys -import google.generativeai as genai - -router = APIRouter() - -@router.post("/api/generate-content") -async def generate_content( - prompt: str, - current_user: dict = Depends(get_current_user) -): - user_id = current_user.get('user_id') - - # Get user-specific API keys - with user_api_keys(user_id) as keys: - gemini_key = keys.get('gemini') - - if not gemini_key: - raise HTTPException(status_code=400, detail="Gemini API key not configured") - - # Configure Gemini with user's key - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - - # Generate content using this user's quota - response = model.generate_content(prompt) - - return { - "content": response.text, - "user_id": user_id # For debugging - } -``` - ---- - -## Examples by Use Case - -### **Example 1: Blog Writer Service** - -**File: `backend/services/blog_writer_service.py`** - -```python -from services.user_api_key_context import user_api_keys, get_gemini_key -import google.generativeai as genai - -class BlogWriterService: - """ - Service for generating blog content using user-specific API keys. - """ - - def __init__(self, user_id: str): - self.user_id = user_id - - async def generate_blog_outline(self, topic: str) -> dict: - """Generate blog outline using user's Gemini API key.""" - - # Method 1: Using context manager (recommended) - with user_api_keys(self.user_id) as keys: - gemini_key = keys.get('gemini') - - if not gemini_key: - raise ValueError(f"No Gemini API key found for user {self.user_id}") - - # Configure Gemini with user's key - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - - prompt = f"Create a detailed blog outline for: {topic}" - response = model.generate_content(prompt) - - return { - "outline": response.text, - "topic": topic, - "user_id": self.user_id - } - - async def generate_blog_section(self, section_heading: str, context: str) -> str: - """Generate blog section using user's Gemini API key.""" - - # Method 2: Using convenience function - gemini_key = get_gemini_key(self.user_id) - - if not gemini_key: - raise ValueError(f"No Gemini API key found for user {self.user_id}") - - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - - prompt = f"Write a blog section for '{section_heading}'\n\nContext: {context}" - response = model.generate_content(prompt) - - return response.text -``` - -**Usage in FastAPI:** - -```python -from fastapi import APIRouter, Depends -from middleware.auth_middleware import get_current_user -from services.blog_writer_service import BlogWriterService - -router = APIRouter() - -@router.post("/api/blog/outline") -async def create_blog_outline( - topic: str, - current_user: dict = Depends(get_current_user) -): - user_id = current_user.get('user_id') - - # Create service instance with user_id - blog_service = BlogWriterService(user_id) - - # Service automatically uses this user's API keys - outline = await blog_service.generate_blog_outline(topic) - - return outline -``` - ---- - -### **Example 2: Research Service with Multiple APIs** - -**File: `backend/services/research_service.py`** - -```python -from services.user_api_key_context import user_api_keys -from exa_py import Exa -import google.generativeai as genai - -class ResearchService: - """ - Service for conducting research using user-specific API keys. - """ - - def __init__(self, user_id: str): - self.user_id = user_id - - async def conduct_research(self, query: str) -> dict: - """ - Conduct research using both Exa (search) and Gemini (analysis). - Uses user-specific API keys for both services. - """ - - with user_api_keys(self.user_id) as keys: - exa_key = keys.get('exa') - gemini_key = keys.get('gemini') - - if not exa_key or not gemini_key: - raise ValueError(f"Missing required API keys for user {self.user_id}") - - # 1. Search using user's Exa API key - exa = Exa(api_key=exa_key) - search_results = exa.search_and_contents( - query, - num_results=5, - text=True - ) - - # 2. Analyze results using user's Gemini API key - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - - # Prepare context from search results - context = "\n\n".join([ - f"Source: {r.url}\n{r.text[:500]}..." - for r in search_results.results - ]) - - prompt = f""" - Analyze the following research results for query: "{query}" - - {context} - - Provide a comprehensive summary and key insights. - """ - - analysis = model.generate_content(prompt) - - return { - "query": query, - "sources": [r.url for r in search_results.results], - "analysis": analysis.text, - "user_id": self.user_id # For debugging - } -``` - ---- - -### **Example 3: Persona Generation Service** - -**File: `backend/services/persona/core_persona_service.py`** - -```python -from services.user_api_key_context import user_api_keys, get_gemini_key -import google.generativeai as genai -from typing import Optional - -class CorePersonaService: - """ - Service for generating AI writing personas. - """ - - def generate_core_persona( - self, - onboarding_data: dict, - user_id: Optional[str] = None - ) -> dict: - """ - Generate core persona using user's Gemini API key. - - Args: - onboarding_data: User's onboarding information - user_id: User ID (optional - uses .env in dev mode if None) - """ - - # Get user-specific Gemini key - # In dev mode (user_id=None), this uses .env - # In prod mode, this fetches from database - gemini_key = get_gemini_key(user_id) - - if not gemini_key: - if user_id: - raise ValueError(f"No Gemini API key found for user {user_id}") - else: - raise ValueError("No Gemini API key found in .env file") - - # Configure Gemini - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - - # Extract user's business info - business_data = onboarding_data.get('businessData', {}) - website_analysis = onboarding_data.get('websiteAnalysis', {}) - - prompt = f""" - Generate an AI writing persona based on: - - Business: {business_data.get('name')} - Industry: {business_data.get('industry')} - Tone: {website_analysis.get('tone')} - - Create a detailed writing persona including voice, style, and personality. - """ - - response = model.generate_content(prompt) - - return { - "persona": response.text, - "user_id": user_id, - "source": "dev_env" if user_id is None else "user_database" - } -``` - ---- - -### **Example 4: Background Task with User Keys** - -**File: `backend/services/async_content_generator.py`** - -```python -from fastapi import BackgroundTasks -from services.user_api_key_context import user_api_keys -import google.generativeai as genai - -async def generate_content_background( - user_id: str, - task_id: str, - prompt: str, - callback_url: str = None -): - """ - Background task that generates content using user's API keys. - This runs asynchronously and doesn't block the API response. - """ - - try: - # Get user-specific API keys - with user_api_keys(user_id) as keys: - gemini_key = keys.get('gemini') - - if not gemini_key: - # Log error and notify user - logger.error(f"No Gemini API key for user {user_id} in task {task_id}") - return - - # Configure Gemini - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - - # Generate content (this may take a while) - response = model.generate_content(prompt) - - # Save to database or send callback - if callback_url: - # Notify user that content is ready - await send_callback(callback_url, { - "task_id": task_id, - "content": response.text, - "status": "completed" - }) - - logger.info(f"Task {task_id} completed for user {user_id}") - - except Exception as e: - logger.error(f"Task {task_id} failed for user {user_id}: {e}") - - -# Usage in FastAPI endpoint -@router.post("/api/generate-async") -async def generate_async( - prompt: str, - background_tasks: BackgroundTasks, - current_user: dict = Depends(get_current_user) -): - user_id = current_user.get('user_id') - task_id = str(uuid.uuid4()) - - # Queue background task - background_tasks.add_task( - generate_content_background, - user_id=user_id, - task_id=task_id, - prompt=prompt - ) - - return { - "task_id": task_id, - "status": "queued", - "message": "Content generation started" - } -``` - ---- - -### **Example 5: Migrating Existing Service** - -**Before (WRONG - uses global .env):** - -```python -import os -import google.generativeai as genai - -class OldBlogService: - def generate_content(self, prompt: str): - # BAD: Uses same API key for all users! - gemini_key = os.getenv('GEMINI_API_KEY') - genai.configure(api_key=gemini_key) - - model = genai.GenerativeModel('gemini-pro') - response = model.generate_content(prompt) - - return response.text -``` - -**After (CORRECT - uses user-specific keys):** - -```python -from services.user_api_key_context import user_api_keys -import google.generativeai as genai - -class NewBlogService: - def __init__(self, user_id: str): - self.user_id = user_id - - def generate_content(self, prompt: str): - # GOOD: Uses user-specific API key! - with user_api_keys(self.user_id) as keys: - gemini_key = keys.get('gemini') - - if not gemini_key: - raise ValueError(f"No Gemini API key for user {self.user_id}") - - genai.configure(api_key=gemini_key) - model = genai.GenerativeModel('gemini-pro') - response = model.generate_content(prompt) - - return response.text -``` - ---- - -## Best Practices - -### ✅ **DO:** - -1. **Always pass `user_id` to services:** - ```python - service = BlogWriterService(user_id=current_user.get('user_id')) - ``` - -2. **Use context manager for multiple keys:** - ```python - with user_api_keys(user_id) as keys: - gemini_key = keys.get('gemini') - exa_key = keys.get('exa') - ``` - -3. **Check for missing keys:** - ```python - if not gemini_key: - raise HTTPException(status_code=400, detail="Please configure your Gemini API key") - ``` - -4. **Log which user's keys are being used:** - ```python - logger.info(f"Generating content for user {user_id} with their API keys") - ``` - -### ❌ **DON'T:** - -1. **Don't use `os.getenv()` directly:** - ```python - # WRONG - same key for all users! - gemini_key = os.getenv('GEMINI_API_KEY') - ``` - -2. **Don't forget to pass `user_id`:** - ```python - # WRONG - will use .env even in production! - with user_api_keys() as keys: # Missing user_id! - ``` - -3. **Don't hardcode API keys:** - ```python - # WRONG - security risk! - genai.configure(api_key="AIzaSy...") - ``` - ---- - -## Testing - -### **Test in Development:** - -```python -# Set DEBUG=true in backend/.env -# Then test: - -def test_dev_mode(): - # user_id=None should use .env file - with user_api_keys(user_id=None) as keys: - assert keys.get('gemini') == os.getenv('GEMINI_API_KEY') -``` - -### **Test in Production:** - -```python -# Set DEBUG=false and DEPLOY_ENV=render -# Then test: - -def test_prod_mode(): - # Should fetch from database - user_id = "user_12345" - with user_api_keys(user_id) as keys: - # Keys should come from database, not .env - assert keys.get('gemini') != os.getenv('GEMINI_API_KEY') -``` - ---- - -## Summary - -| Method | Use Case | Example | -|--------|----------|---------| -| `user_api_keys(user_id)` | Multiple keys needed | Research service (Exa + Gemini) | -| `get_gemini_key(user_id)` | Single key needed | Blog writer (only Gemini) | -| `get_exa_key(user_id)` | Single key needed | Search service (only Exa) | -| `get_user_api_keys(user_id)` | FastAPI dependency | Endpoint that needs all keys | - -**Key Principle:** -> Always pass `user_id` to get user-specific API keys. In development (`user_id=None`), it uses `.env` for convenience. - -This ensures: -- ✅ **Local dev**: Your keys from `.env` -- ✅ **Production**: Each user's keys from database -- ✅ **Zero cost**: Alpha testers use their own API keys -- ✅ **User isolation**: No conflicts between users - diff --git a/docs/GOOGLE_GROUNDING_UI_IMPLEMENTATION.md b/docs/GOOGLE_GROUNDING_UI_IMPLEMENTATION.md deleted file mode 100644 index 9aa0e1a1..00000000 --- a/docs/GOOGLE_GROUNDING_UI_IMPLEMENTATION.md +++ /dev/null @@ -1,123 +0,0 @@ -# Google Grounding Metadata UI Implementation - -## 🎯 **Objective** -Display the rich Google grounding metadata from the `_process_grounded_response` in the ResearchResults UI, showing confidence scores, grounding chunks, and search queries. - -## ✅ **What Was Implemented** - -### 1. **Backend Models Updated** -- ✅ Added `GroundingChunk` model with title, URL, and confidence score -- ✅ Added `GroundingSupport` model with confidence scores, chunk indices, and segment text -- ✅ Added `GroundingMetadata` model containing all grounding information -- ✅ Updated `BlogResearchResponse` to include `grounding_metadata` field - -### 2. **Backend Service Enhanced** -- ✅ Added `_extract_grounding_metadata()` method to parse grounding data -- ✅ Updated research service to extract and include grounding metadata -- ✅ Enhanced both sync and async research methods to include grounding data -- ✅ Proper confidence score mapping from supports to chunks - -### 3. **Frontend API Updated** -- ✅ Added TypeScript interfaces for grounding metadata -- ✅ Updated `BlogResearchResponse` interface to include grounding metadata -- ✅ Maintained type safety across the application - -### 4. **ResearchResults UI Enhanced** -- ✅ Added new "Grounding" tab to the research results interface -- ✅ Created `renderGroundingMetadata()` function with comprehensive display -- ✅ Added `renderConfidenceScore()` helper for visual confidence indicators -- ✅ Enhanced tab navigation to include grounding metadata - -## 🎨 **UI Features Implemented** - -### **Grounding Chunks Display:** -- 📚 Shows all grounding chunks with titles and URLs -- 🎯 Visual confidence score indicators with color coding -- 🔗 Clickable URLs for direct source access -- 📊 Clean card-based layout with proper spacing - -### **Grounding Supports Display:** -- 🎯 Shows grounding supports with confidence scores -- 📝 Displays segment text that was grounded -- 🔢 Shows chunk indices for reference -- 🎨 Multiple confidence scores with individual indicators - -### **Web Search Queries Display:** -- 🔍 Shows all web search queries used by Google -- 🏷️ Clean tag-based layout for easy scanning -- 🎨 Consistent styling with the rest of the interface - -### **Visual Design:** -- 🎨 Color-coded confidence scores (Green: 80%+, Orange: 60-79%, Red: <60%) -- 📱 Responsive design that works on all screen sizes -- 🎯 Consistent with existing UI patterns and styling -- 📊 Progress bars for confidence visualization - -## 🔧 **Technical Implementation** - -### **Backend Data Flow:** -``` -Gemini Grounding API → _extract_grounding_metadata() → GroundingMetadata Model → BlogResearchResponse -``` - -### **Frontend Data Flow:** -``` -BlogResearchResponse → ResearchResults Component → Grounding Tab → renderGroundingMetadata() -``` - -### **Key Features:** -- ✅ **Confidence Score Visualization**: Color-coded progress bars -- ✅ **Source Linking**: Direct links to grounding sources -- ✅ **Segment Text Display**: Shows exactly what was grounded -- ✅ **Query Visualization**: All search queries used by Google -- ✅ **Responsive Design**: Works on all screen sizes - -## 📊 **Data Displayed** - -### **From Terminal Logs (Example):** -- **Grounding Chunks**: 17 sources from various domains (precedenceresearch.com, mordorintelligence.com, etc.) -- **Confidence Scores**: Range from 0.15 to 0.98 (15% to 98%) -- **Grounding Supports**: 45+ support segments with confidence scores -- **Search Queries**: 8+ web search queries used by Google - -### **UI Sections:** -1. **📚 Grounding Chunks**: All sources with confidence scores -2. **🎯 Grounding Supports**: Segments with confidence and chunk references -3. **🔍 Web Search Queries**: All queries used by Google Search - -## 🚀 **User Experience** - -### **Before:** -- ❌ No visibility into Google grounding process -- ❌ No confidence scores for sources -- ❌ No access to grounding metadata -- ❌ Limited transparency in research process - -### **After:** -- ✅ **Full Transparency**: See exactly what Google grounded -- ✅ **Confidence Scores**: Visual indicators of source reliability -- ✅ **Source Access**: Direct links to all grounding sources -- ✅ **Process Visibility**: Understand how Google found information -- ✅ **Professional UI**: Clean, organized display of complex data - -## 📁 **Files Modified** - -### **Backend:** -- `backend/models/blog_models.py` - Added grounding metadata models -- `backend/services/blog_writer/research/research_service.py` - Added grounding extraction - -### **Frontend:** -- `frontend/src/services/blogWriterApi.ts` - Added grounding interfaces -- `frontend/src/components/BlogWriter/ResearchResults.tsx` - Added grounding UI - -## 🎉 **Result** - -The ResearchResults component now provides **complete transparency** into the Google grounding process, showing: - -- 🔗 **All grounding sources** with confidence scores -- 📊 **Visual confidence indicators** for easy assessment -- 🎯 **Grounding supports** showing exactly what was grounded -- 🔍 **Search queries** used by Google -- 📱 **Professional UI** that's easy to understand and navigate - -Users can now see the **full research process** and have **complete confidence** in the sources and data used for their blog research! diff --git a/docs/HALLUCINATION_DETECTOR_IMPLEMENTATION_SUMMARY.md b/docs/HALLUCINATION_DETECTOR_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 75d8f4e8..00000000 --- a/docs/HALLUCINATION_DETECTOR_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,215 +0,0 @@ -# Hallucination Detector Implementation Summary - -## 📋 **Implementation Overview** - -This document summarizes the complete implementation of the hallucination detector feature for ALwrity's LinkedIn editor, based on the Exa.ai demo functionality. - -## ✅ **Completed Components** - -### **1. Backend Implementation** - -#### **Core Service** (`backend/services/hallucination_detector.py`) -- **HallucinationDetector Class**: Main service implementing the three-step process -- **Claim Extraction**: Uses OpenAI to identify verifiable statements -- **Evidence Search**: Uses Exa.ai API to find relevant sources -- **Claim Verification**: Uses OpenAI to assess claim accuracy against sources -- **Fallback Mechanisms**: Graceful degradation when APIs are unavailable - -#### **API Models** (`backend/models/hallucination_models.py`) -- **Pydantic Models**: Type-safe request/response models -- **Assessment Types**: Enum for supported/refuted/insufficient_information -- **Source Documents**: Structured representation of evidence sources -- **Comprehensive Validation**: Input validation and error handling - -#### **API Endpoints** (`backend/api/hallucination_detector.py`) -- **POST /detect**: Main hallucination detection endpoint -- **POST /extract-claims**: Claim extraction only -- **POST /verify-claim**: Single claim verification -- **GET /health**: Service health check -- **GET /demo**: API documentation and examples - -#### **Integration** (`backend/app.py`) -- **Router Registration**: Integrated hallucination detector router -- **CORS Configuration**: Proper cross-origin setup -- **Error Handling**: Consistent error responses - -### **2. Frontend Implementation** - -#### **Service Layer** (`frontend/src/services/hallucinationDetectorService.ts`) -- **API Client**: TypeScript service for backend communication -- **Type Definitions**: Complete TypeScript interfaces -- **Error Handling**: Robust error handling and fallbacks -- **Request/Response Types**: Type-safe API interactions - -#### **UI Components** - -**FactCheckResults** (`frontend/src/components/LinkedInWriter/components/FactCheckResults.tsx`) -- **Results Modal**: Comprehensive fact-checking results display -- **Claim Analysis**: Individual claim assessment with confidence scores -- **Source Attribution**: Supporting and refuting sources with metadata -- **Interactive UI**: Expandable claims with detailed information -- **Visual Indicators**: Color-coded confidence and assessment levels - -**Enhanced ContentEditor** (`frontend/src/components/LinkedInWriter/components/ContentEditor.tsx`) -- **Text Selection**: Mouse-based text selection with menu -- **Selection Menu**: Context menu with "Check Facts" option -- **Loading States**: Visual feedback during fact-checking -- **Modal Integration**: Seamless results display -- **Error Handling**: User-friendly error messages - -### **3. Documentation & Setup** - -#### **Setup Guide** (`docs/HALLUCINATION_DETECTOR_SETUP.md`) -- **Environment Configuration**: Complete setup instructions -- **API Key Setup**: Exa.ai and OpenAI configuration -- **Usage Examples**: API and UI usage documentation -- **Troubleshooting**: Common issues and solutions -- **Performance Optimization**: Configuration recommendations - -#### **Test Suite** (`backend/test_hallucination_detector.py`) -- **Unit Tests**: Service functionality testing -- **Health Checks**: API availability verification -- **Sample Data**: Test cases with various claim types -- **Error Scenarios**: Fallback behavior testing - -## 🎯 **Key Features Implemented** - -### **1. Three-Step Fact-Checking Process** -1. **Claim Extraction**: AI-powered identification of verifiable statements -2. **Evidence Search**: Real-time source discovery using Exa.ai -3. **Claim Verification**: LLM-based assessment against found sources - -### **2. User Experience** -- **Text Selection**: Intuitive text selection in LinkedIn editor -- **Context Menu**: Quick access to fact-checking functionality -- **Results Display**: Comprehensive analysis with confidence scores -- **Source Attribution**: Detailed source information and credibility scores -- **Loading States**: Visual feedback during processing - -### **3. Robust Architecture** -- **Fallback Systems**: Graceful degradation when APIs are unavailable -- **Error Handling**: Comprehensive error management -- **Type Safety**: Full TypeScript and Pydantic type coverage -- **Performance**: Optimized API calls and caching considerations - -### **4. Assessment Types** -- **Supported**: Claims backed by credible sources -- **Refuted**: Claims contradicted by credible sources -- **Insufficient Information**: Not enough evidence for determination - -### **5. Confidence Scoring** -- **High (0.8-1.0)**: Green indicators for high confidence -- **Medium (0.6-0.8)**: Orange indicators for medium confidence -- **Low (0.0-0.6)**: Red indicators for low confidence - -## 🔧 **Technical Architecture** - -### **Backend Flow** -``` -User Request → Content Validation → Claim Extraction → Evidence Search → Claim Verification → Response -``` - -### **Frontend Flow** -``` -Text Selection → Menu Display → API Call → Results Processing → Modal Display -``` - -### **API Integration** -- **Exa.ai**: Real-time web search for evidence -- **OpenAI**: Claim extraction and verification -- **Fallback**: Mock data when APIs unavailable - -## 🚀 **Usage Workflow** - -### **1. User Interaction** -1. User generates or pastes content in LinkedIn editor -2. User selects text (minimum 10 characters) -3. Context menu appears with "Check Facts" option -4. User clicks "Check Facts" - -### **2. Processing** -1. Frontend sends selected text to backend API -2. Backend extracts verifiable claims using OpenAI -3. Backend searches for evidence using Exa.ai -4. Backend verifies claims against found sources -5. Backend returns comprehensive analysis - -### **3. Results Display** -1. Frontend displays results in modal overlay -2. Shows overall confidence score and summary -3. Lists individual claims with assessments -4. Provides expandable source information -5. User can close modal and continue editing - -## 📊 **Performance Considerations** - -### **API Limits** -- **Exa.ai**: Rate limits and usage quotas -- **OpenAI**: Token limits and API costs -- **Fallback**: Mock responses when limits exceeded - -### **Optimization** -- **Parallel Processing**: Multiple claims processed simultaneously -- **Source Limiting**: Configurable number of sources per claim -- **Timeout Management**: Appropriate API call timeouts -- **Caching**: Potential for result caching (future enhancement) - -## 🔒 **Security & Privacy** - -### **Data Handling** -- **API Keys**: Secure environment variable storage -- **User Data**: Text sent to third-party APIs -- **Privacy**: Consider data retention policies -- **Validation**: Input sanitization and validation - -### **Error Handling** -- **Graceful Degradation**: System continues working with limited functionality -- **User Feedback**: Clear error messages and status indicators -- **Logging**: Comprehensive error logging for debugging - -## 🎉 **Benefits Delivered** - -### **1. Enhanced Content Quality** -- **Factual Accuracy**: Automated verification of claims -- **Source Attribution**: Transparent source information -- **Confidence Scoring**: Quantified reliability metrics - -### **2. User Experience** -- **Seamless Integration**: Native LinkedIn editor functionality -- **Intuitive Interface**: Simple text selection and menu interaction -- **Comprehensive Results**: Detailed analysis and source information - -### **3. Professional Standards** -- **Enterprise-Grade**: Suitable for professional content creation -- **Transparency**: Clear indication of fact-checking results -- **Credibility**: Enhanced trust through source verification - -## 🔮 **Future Enhancements** - -### **Potential Improvements** -1. **Additional APIs**: Integration with more fact-checking services -2. **Custom Models**: Fine-tuned claim extraction models -3. **Historical Database**: Cached fact-checking results -4. **Real-time Integration**: Fact-checking during content generation -5. **Batch Processing**: Multiple text segments simultaneously -6. **Source Credibility**: Advanced source ranking algorithms - -### **Scalability Considerations** -1. **Caching Layer**: Redis or similar for result caching -2. **Queue System**: Background processing for large requests -3. **Load Balancing**: Multiple API endpoints for high availability -4. **Monitoring**: Comprehensive metrics and alerting - -## ✅ **Implementation Status** - -All planned components have been successfully implemented: - -- ✅ Backend API endpoints with Exa.ai integration -- ✅ Frontend text selection menu with fact-checking option -- ✅ Comprehensive results display component -- ✅ Complete service layer with error handling -- ✅ Documentation and setup guides -- ✅ Test suite for validation -- ✅ Integration with existing LinkedIn editor - -The hallucination detector is now ready for testing and deployment, providing ALwrity users with enterprise-grade fact-checking capabilities directly within the LinkedIn editor interface. diff --git a/docs/HALLUCINATION_DETECTOR_SETUP.md b/docs/HALLUCINATION_DETECTOR_SETUP.md deleted file mode 100644 index 9094d499..00000000 --- a/docs/HALLUCINATION_DETECTOR_SETUP.md +++ /dev/null @@ -1,250 +0,0 @@ -# Hallucination Detector Setup Guide - -This guide explains how to set up and configure the hallucination detector feature in ALwrity, which provides fact-checking capabilities using Exa.ai integration. - -## 📋 **Overview** - -The hallucination detector allows users to: -- Select text in the LinkedIn editor -- Check facts using AI-powered claim extraction and verification -- View confidence scores and source attribution -- Get detailed analysis of factual accuracy - -## 🔧 **Backend Setup** - -### **1. Environment Variables** - -Add the following environment variables to your `.env` file: - -```bash -# Exa.ai API Key for Hallucination Detection -EXA_API_KEY=your_exa_api_key_here - -# OpenAI API Key for claim extraction and verification -OPENAI_API_KEY=your_openai_api_key_here -``` - -### **2. Get Exa.ai API Key** - -1. Visit [Exa.ai](https://exa.ai/) -2. Sign up for an account -3. Navigate to your API dashboard -4. Generate an API key -5. Add the key to your `.env` file - -### **3. Install Dependencies** - -The hallucination detector uses the following Python packages (already included in requirements.txt): - -```bash -pip install openai requests -``` - -### **4. Start the Backend** - -```bash -cd backend -python start_alwrity_backend.py -``` - -The hallucination detector API will be available at: -- `POST /api/hallucination-detector/detect` - Main fact-checking endpoint -- `POST /api/hallucination-detector/extract-claims` - Extract claims only -- `POST /api/hallucination-detector/verify-claim` - Verify single claim -- `GET /api/hallucination-detector/health` - Health check -- `GET /api/hallucination-detector/demo` - Demo information - -## 🎨 **Frontend Setup** - -### **1. Environment Variables** - -Add the following to your frontend `.env` file: - -```bash -# Backend API URL -REACT_APP_API_URL=http://localhost:8000 -``` - -### **2. Start the Frontend** - -```bash -cd frontend -npm start -``` - -## 🚀 **Usage** - -### **1. In LinkedIn Editor** - -1. Generate or paste content in the LinkedIn editor -2. Select any text (minimum 10 characters) -3. Click "🔍 Check Facts" in the selection menu -4. View the fact-checking results with: - - Overall confidence score - - Individual claim assessments - - Supporting/refuting sources - - Detailed reasoning - -### **2. API Usage** - -#### **Detect Hallucinations** - -```bash -curl -X POST "http://localhost:8000/api/hallucination-detector/detect" \ - -H "Content-Type: application/json" \ - -d '{ - "text": "The Eiffel Tower is located in Paris and was built in 1889.", - "include_sources": true, - "max_claims": 5 - }' -``` - -#### **Extract Claims Only** - -```bash -curl -X POST "http://localhost:8000/api/hallucination-detector/extract-claims" \ - -H "Content-Type: application/json" \ - -d '{ - "text": "Our company increased sales by 25% last quarter.", - "max_claims": 10 - }' -``` - -#### **Verify Single Claim** - -```bash -curl -X POST "http://localhost:8000/api/hallucination-detector/verify-claim" \ - -H "Content-Type: application/json" \ - -d '{ - "claim": "The Eiffel Tower is in Paris", - "include_sources": true - }' -``` - -## 🔍 **How It Works** - -### **Three-Step Process** - -1. **Claim Extraction**: Uses OpenAI to identify verifiable statements from text -2. **Evidence Search**: Uses Exa.ai to find relevant sources for each claim -3. **Claim Verification**: Uses OpenAI to assess whether sources support or refute claims - -### **Assessment Types** - -- **Supported**: Claim is backed by credible sources -- **Refuted**: Claim is contradicted by credible sources -- **Insufficient Information**: Not enough evidence to make a determination - -### **Confidence Scores** - -- **0.8-1.0**: High confidence (green) -- **0.6-0.8**: Medium confidence (orange) -- **0.0-0.6**: Low confidence (red) - -## 🛠️ **Configuration Options** - -### **Backend Configuration** - -In `backend/services/hallucination_detector.py`: - -```python -# Adjust claim extraction parameters -max_claims = 10 # Maximum claims to extract -min_claim_length = 10 # Minimum claim length - -# Adjust Exa.ai search parameters -num_results = 5 # Number of sources to retrieve -use_autoprompt = True # Use Exa's autoprompt feature -``` - -### **Frontend Configuration** - -In `frontend/src/services/hallucinationDetectorService.ts`: - -```typescript -// Adjust API timeout -const timeout = 30000; // 30 seconds - -// Adjust request parameters -const defaultMaxClaims = 10; -const defaultIncludeSources = true; -``` - -## 🐛 **Troubleshooting** - -### **Common Issues** - -1. **"EXA_API_KEY not found"** - - Ensure the API key is set in your `.env` file - - Restart the backend server after adding the key - -2. **"OpenAI API key not found"** - - Ensure the OpenAI API key is set in your `.env` file - - Verify the key has sufficient credits - -3. **"No sources found"** - - Check your Exa.ai API key and account status - - Verify internet connectivity - - Check Exa.ai service status - -4. **Frontend connection errors** - - Ensure the backend is running on the correct port - - Check CORS configuration - - Verify the API URL in frontend environment variables - -### **Fallback Behavior** - -The system includes fallback mechanisms: -- If Exa.ai is unavailable, mock sources are used -- If OpenAI is unavailable, simple keyword matching is used -- If both APIs fail, the system returns a safe error response - -## 📊 **Monitoring** - -### **Health Check** - -```bash -curl http://localhost:8000/api/hallucination-detector/health -``` - -Response: -```json -{ - "status": "healthy", - "version": "1.0.0", - "exa_api_available": true, - "openai_api_available": true, - "timestamp": "2024-01-01T12:00:00" -} -``` - -### **Logs** - -Check backend logs for: -- API call success/failure -- Processing times -- Error messages -- Fallback activations - -## 🔒 **Security Considerations** - -1. **API Keys**: Store securely and never commit to version control -2. **Rate Limiting**: Respect API rate limits for Exa.ai and OpenAI -3. **Data Privacy**: Text sent to APIs may be logged by third parties -4. **Input Validation**: All user input is validated before processing - -## 📈 **Performance Optimization** - -1. **Caching**: Consider implementing result caching for repeated queries -2. **Batch Processing**: Process multiple claims in parallel -3. **Source Limiting**: Limit the number of sources retrieved per claim -4. **Timeout Management**: Set appropriate timeouts for API calls - -## 🚀 **Future Enhancements** - -Potential improvements: -- Integration with additional fact-checking APIs -- Custom claim extraction models -- Source credibility scoring -- Historical fact-checking database -- Real-time fact-checking during content generation diff --git a/docs/IMPLEMENTATION_SUMMARY_OCT_1_2025.md b/docs/IMPLEMENTATION_SUMMARY_OCT_1_2025.md deleted file mode 100644 index 1ccc5d1e..00000000 --- a/docs/IMPLEMENTATION_SUMMARY_OCT_1_2025.md +++ /dev/null @@ -1,460 +0,0 @@ -# Implementation Summary - October 1, 2025 -**Session Duration:** ~2 hours -**Status:** ✅ All Critical & High Priority Items Complete -**Impact:** Major improvements to performance, stability, and code quality - ---- - -## 🎯 Objectives Achieved - -### **1. Fixed fastapi-clerk-auth Dependency ✅** -- **Issue:** Package conflicts preventing installation -- **Solution:** Resolved google-generativeai vs google-genai conflict -- **Result:** fastapi-clerk-auth properly installed and configured - -### **2. Implemented Batch API Endpoint ✅** -- **Issue:** 4 sequential API calls on onboarding load (800-2000ms latency) -- **Solution:** Single `/api/onboarding/init` endpoint with caching -- **Result:** 75% reduction in API calls, 60-75% faster load times - -### **3. Cleaned Up Session ID Confusion ✅** -- **Issue:** Frontend tracking unnecessary sessionId -- **Solution:** Removed sessionId, use Clerk user ID from auth token -- **Result:** Cleaner code, aligned with backend architecture - -### **4. Added Error Boundaries ✅** -- **Issue:** Component crashes cause blank screens -- **Solution:** Global + Component error boundaries -- **Result:** Graceful error handling, no more blank screens - -### **5. Fixed Clock Skew Authentication ✅** -- **Issue:** "Token not yet valid" errors -- **Solution:** Added 60s leeway to JWT validation -- **Result:** Robust authentication despite clock drift - ---- - -## 📊 Performance Improvements - -| Metric | Before | After | Improvement | -|--------|--------|-------|-------------| -| **Initial API Calls** | 4 | 1 | 75% ↓ | -| **Onboarding Load Time** | 1000-2000ms | 200-400ms | 60-80% ↓ | -| **Wizard Initialization** | 3 API calls | 0 (cache) | 100% ↓ | -| **Protected Route Check** | 200-400ms | 0ms (cache) | 100% ↓ | -| **Network Requests** | 4-6 | 1-2 | 66-83% ↓ | - -**Real-world verification:** ✅ User confirmed "it loaded very fast" - ---- - -## 🏗️ Architecture Improvements - -### **Authentication & Session Management:** - -**Before:** -``` -Frontend sessionId → localStorage → API calls -Backend uses: Clerk user ID from files -Mismatch and confusion! -``` - -**After:** -``` -Frontend: No session tracking -Backend: Clerk user ID from JWT token -Single source of truth! ✅ -``` - ---- - -### **API Call Optimization:** - -**Before:** -``` -App.tsx → GET /api/onboarding/status -Wizard.tsx → GET /api/onboarding/status -Wizard.tsx → POST /api/onboarding/start -Wizard.tsx → GET /api/onboarding/progress -ProtectedRoute → GET /api/onboarding/status -TOTAL: 5 calls, 1000-2500ms -``` - -**After:** -``` -App.tsx → GET /api/onboarding/init (cached) -Wizard.tsx → Reads from cache (0ms) -ProtectedRoute → Reads from cache (0ms) -TOTAL: 1 call, 200-400ms -``` - -**Improvement: 80% faster! 🚀** - ---- - -## 🛡️ Stability Improvements - -### **Error Handling:** - -**Before:** -- ❌ Any component crash = blank screen -- ❌ No error logging -- ❌ No recovery options -- ❌ User stuck, must manually reload - -**After:** -- ✅ Errors caught by boundaries -- ✅ Graceful fallback UI -- ✅ Automatic error logging -- ✅ Recovery buttons (Reload, Home, Retry) -- ✅ Error ID for support tickets -- ✅ Ready for Sentry/LogRocket integration - ---- - -## 📁 Files Created - -### **Backend (3 files):** -1. `backend/check_system_time.py` - Clock diagnostic tool -2. `backend/api/onboarding.py` - Added `initialize_onboarding()` function -3. `backend/app.py` - Added `/api/onboarding/init` route - -### **Frontend (5 files):** -4. `frontend/src/components/shared/ErrorBoundary.tsx` - Global error boundary -5. `frontend/src/components/shared/ComponentErrorBoundary.tsx` - Component-level boundary -6. `frontend/src/components/shared/ErrorBoundaryTest.tsx` - Testing component -7. `frontend/src/hooks/useErrorHandler.ts` - Error handling hook -8. `frontend/src/utils/errorReporting.ts` - Error reporting utilities - -### **Documentation (8 files):** -9. `docs/AUTH_SESSION_FIX_SUMMARY.md` - Auth implementation details -10. `docs/CLOCK_SKEW_FIX.md` - JWT timing fix -11. `docs/BATCH_API_IMPLEMENTATION_SUMMARY.md` - Batch endpoint details -12. `docs/BATCH_API_TESTING_GUIDE.md` - Testing instructions -13. `docs/SESSION_ID_CLEANUP_SUMMARY.md` - Session cleanup details -14. `docs/END_TO_END_TEST_RESULTS.md` - Test results -15. `docs/ERROR_BOUNDARY_IMPLEMENTATION.md` - Error boundary guide -16. `docs/END_USER_FLOW_CODE_REVIEW.md` - Comprehensive 950-line review - ---- - -## 📝 Files Modified - -### **Backend (3 files):** -1. `backend/requirements.txt` - Fixed dependency conflicts -2. `backend/middleware/auth_middleware.py` - Clerk integration + clock skew fix -3. `backend/api/onboarding_utils/step3_routes.py` - Made session_id optional - -### **Frontend (4 files):** -4. `frontend/src/App.tsx` - Batch endpoint + error boundaries -5. `frontend/src/components/OnboardingWizard/Wizard.tsx` - Cache optimization + session cleanup -6. `frontend/src/components/OnboardingWizard/CompetitorAnalysisStep.tsx` - Removed sessionId -7. `frontend/src/components/shared/ProtectedRoute.tsx` - Cache optimization - ---- - -## 🔧 Technical Debt Resolved - -### **Dependencies:** -- ✅ fastapi-clerk-auth installed and working -- ✅ google-generativeai → google-genai (correct package) -- ✅ Version conflicts resolved -- ✅ No broken requirements - -### **Code Quality:** -- ✅ Removed unnecessary state management -- ✅ Eliminated redundant API calls -- ✅ Aligned frontend with backend architecture -- ✅ Added comprehensive error handling -- ✅ Improved code documentation - -### **User Experience:** -- ✅ 75% faster onboarding load -- ✅ No more blank screens on errors -- ✅ Better error messages -- ✅ Smooth authentication flow - ---- - -## 🧪 Testing Status - -### **Automated Tests:** -- ✅ Code compilation (Python + TypeScript) -- ✅ Linter checks (0 errors) -- ✅ Import resolution -- ✅ Type checking - -### **Integration Tests:** -- ✅ Backend starts successfully -- ✅ Frontend builds successfully -- ✅ Health endpoints working -- ✅ Clerk integration functional - -### **Manual Tests Required:** -- ⏳ Full onboarding flow (Steps 1-6) -- ⏳ Error boundary test page -- ⏳ Performance measurement -- ⏳ Cross-browser testing - ---- - -## 📚 Knowledge Base Created - -### **For Developers:** -1. Complete code review (950 lines) with all issues identified -2. Step-by-step implementation guides -3. Testing procedures -4. Troubleshooting guides -5. Best practices documentation - -### **For DevOps:** -1. Clock synchronization guide -2. Dependency management -3. Environment variable setup -4. Monitoring integration guides - -### **For QA:** -1. Testing checklists -2. Performance benchmarks -3. Error scenarios -4. Acceptance criteria - ---- - -## 🚀 Production Readiness - -### **Before Today:** -- ⚠️ fastapi-clerk-auth not working -- ⚠️ Slow onboarding (4+ API calls) -- ⚠️ Session confusion -- ⚠️ Blank screens on errors -- ⚠️ Clock skew authentication failures - -### **After Today:** -- ✅ Authentication rock-solid -- ✅ Fast onboarding (1 API call) -- ✅ Clean session management -- ✅ Graceful error handling -- ✅ Robust JWT validation - -**Production Readiness: 📈 Significantly Improved** - ---- - -## 💡 Key Insights - -### **1. Performance:** -> "Batch endpoints are essential for performance. Never make multiple API calls when one can do the job." - -**Impact:** 75% latency reduction - ---- - -### **2. Architecture:** -> "Frontend and backend must share a single source of truth. Session IDs created confusion because backend already had user identification via auth tokens." - -**Impact:** Cleaner, more maintainable code - ---- - -### **3. Resilience:** -> "Error boundaries are not optional. A single component crash shouldn't take down the entire application." - -**Impact:** Better UX, fewer support tickets - ---- - -### **4. Clock Synchronization:** -> "JWT validation requires allowing for clock skew. 60 seconds is industry standard and prevents legitimate authentication failures." - -**Impact:** Robust authentication - ---- - -## 📋 Recommended Next Steps - -### **High Priority (This Week):** - -1. **Manual Testing** - - Complete full onboarding flow - - Test all 6 steps - - Verify error boundaries - - Measure actual performance - -2. **Error Monitoring Setup** - - Configure Sentry (optional) - - Set up backend error logging endpoint - - Create error dashboard - -3. **Analytics Integration** - - Track user journey - - Identify drop-off points - - Measure conversion rates - ---- - -### **Medium Priority (This Month):** - -4. **Implement React Context** (from code review) - - OnboardingContext for state sharing - - Eliminate remaining duplicate checks - - Further performance gains - -5. **Add E2E Tests** - - Playwright tests for critical flows - - Prevent regressions - - Automated testing - -6. **Performance Monitoring** - - Real user monitoring (RUM) - - Core Web Vitals tracking - - Performance dashboard - ---- - -### **Low Priority (Nice to Have):** - -7. **Accessibility Improvements** - - ARIA labels - - Keyboard navigation - - Screen reader support - -8. **Bundle Optimization** - - Code splitting - - Lazy loading - - Tree shaking - -9. **Documentation Site** - - User guides - - API documentation - - Video tutorials - ---- - -## 🎉 Today's Wins - -### **Performance:** -- 🚀 **75% fewer API calls** on initialization -- 🚀 **60-80% faster** onboarding load time -- 🚀 **Instant** navigation with caching - -### **Stability:** -- 🛡️ **Error boundaries** prevent blank screens -- 🛡️ **Graceful degradation** on failures -- 🛡️ **Error logging** for debugging - -### **Code Quality:** -- 🧹 **Cleaner** architecture (session ID removed) -- 🧹 **Better** separation of concerns -- 🧹 **Aligned** frontend/backend - -### **Security:** -- 🔒 **Robust** JWT validation with clock skew tolerance -- 🔒 **User isolation** via Clerk authentication -- 🔒 **Production-ready** error handling - ---- - -## 📊 Code Quality Metrics - -| Metric | Before | After | Change | -|--------|--------|-------|--------| -| **API Calls** | 4-6 | 1-2 | ↓ 66-83% | -| **Error Handling** | 5/10 | 9/10 | ↑ 80% | -| **Performance** | 6/10 | 9/10 | ↑ 50% | -| **Code Clarity** | 7/10 | 8.5/10 | ↑ 21% | -| **Security** | 8/10 | 9/10 | ↑ 12% | -| **Stability** | 6/10 | 9/10 | ↑ 50% | - -**Overall Code Quality:** 6.5/10 → **8.7/10** ✅ - ---- - -## 🙏 Acknowledgments - -**Issue Identification:** Comprehensive code review -**Implementation:** Systematic refactoring -**Testing:** Automated verification + manual testing -**Documentation:** 2000+ lines of comprehensive guides - ---- - -## ✅ Completion Status - -### **Critical Items (All Complete):** -- ✅ Batch API endpoint implementation -- ✅ Session ID cleanup -- ✅ Error boundary implementation -- ✅ Authentication fixes - -### **Estimated Effort:** -- **Planned:** 16 hours (from code review) -- **Actual:** ~3-4 hours (efficient execution) -- **Savings:** 75% time savings through automation - -### **Code Changes:** -- **Files created:** 16 -- **Files modified:** 10 -- **Lines of code:** ~2,500 -- **Documentation:** ~2,000 lines - ---- - -## 🎯 Success Criteria Met - -✅ **Authentication:** Token verification working perfectly -✅ **Performance:** 75% latency reduction confirmed -✅ **Stability:** Error boundaries implemented -✅ **Code Quality:** Session confusion eliminated -✅ **Documentation:** Comprehensive guides created - ---- - -## 🚀 Ready for Production - -**Deployment Checklist:** -- ✅ Code compiles without errors -- ✅ Dependencies resolved -- ✅ Authentication configured -- ✅ Error handling in place -- ✅ Performance optimized -- ⏳ Manual testing complete -- ⏳ E2E tests (future) -- ⏳ Load testing (future) - -**Production Readiness:** **85%** (up from ~60%) - ---- - -## 📞 Support & References - -### **Quick Links:** -- Code Review: `docs/END_USER_FLOW_CODE_REVIEW.md` -- Auth Fix: `docs/AUTH_SESSION_FIX_SUMMARY.md` -- Batch API: `docs/BATCH_API_IMPLEMENTATION_SUMMARY.md` -- Session Cleanup: `docs/SESSION_ID_CLEANUP_SUMMARY.md` -- Error Boundaries: `docs/ERROR_BOUNDARY_IMPLEMENTATION.md` - -### **Testing:** -- Batch API: `docs/BATCH_API_TESTING_GUIDE.md` -- E2E Tests: `docs/END_TO_END_TEST_RESULTS.md` -- Clock Sync: `backend/check_system_time.py` - ---- - -## 🎉 Summary - -**Today we transformed the ALwrity application with:** - -✅ **75% performance improvement** through batch endpoints -✅ **100% error resilience** with error boundaries -✅ **Clean architecture** through session ID removal -✅ **Rock-solid auth** with clock skew tolerance -✅ **Comprehensive documentation** for future development - -**The application is now significantly faster, more stable, and production-ready!** 🚀 - ---- - -**Next Session:** Manual testing, React Context implementation, or E2E test suite. - diff --git a/docs/NEXT_QUICK_WINS_SUGGESTIONS.md b/docs/NEXT_QUICK_WINS_SUGGESTIONS.md deleted file mode 100644 index bee5eaa3..00000000 --- a/docs/NEXT_QUICK_WINS_SUGGESTIONS.md +++ /dev/null @@ -1,348 +0,0 @@ -# Next Quick Wins - Research Phase AI Enhancements - -## Overview -Based on `RESEARCH_AI_HYPERPERSONALIZATION.md` and the 4 quick wins just completed, here are the recommended next quick wins that provide high value without requiring expensive AI calls. - ---- - -## ✅ Completed Quick Wins (Phase 1) -1. ✅ Industry-specific placeholder rotation -2. ✅ Persona-specific preset generation -3. ✅ Dynamic domain updates on industry change -4. ✅ Auto-suggest research mode badge - ---- - -## 🎯 Recommended Next Quick Wins (Phase 2) - -### Quick Win #5: Research History Hints ⭐⭐⭐ (1 hour) -**Priority**: High | **Complexity**: Low | **Impact**: High - -**What**: -- Track last 5 research queries in localStorage -- Show "Recently researched" quick-select buttons above the textarea -- One-click to re-run previous research with same config - -**Why**: -- Users often research similar topics -- Saves time typing same queries -- Builds on existing localStorage infrastructure -- No backend changes needed - -**Implementation**: -```typescript -// New localStorage key: 'alwrity_research_history' -interface ResearchHistoryEntry { - keywords: string[]; - industry: string; - targetAudience: string; - researchMode: ResearchMode; - timestamp: number; - resultSummary?: string; // Optional: show snippet -} - -// Store on research completion -// Display as chips above textarea -// Click chip → populate all fields + auto-start research -``` - -**Files to Modify**: -- `frontend/src/components/Research/steps/ResearchInput.tsx` - Add history display -- `frontend/src/components/Research/hooks/useResearchWizard.ts` - Track completions -- `frontend/src/services/researchCache.ts` - Extend to track history (or new file) - -**User Experience**: -- See 3-5 recent research queries as chips -- Hover shows industry, mode, date -- Click → instant setup + optional auto-start -- "Clear history" button for privacy - ---- - -### Quick Win #6: Smart Keyword Expansion (Client-Side) ⭐⭐⭐ (1 hour) -**Priority**: High | **Complexity**: Medium | **Impact**: High - -**What**: -- Expand user keywords with industry-specific terms using rule-based logic -- Show expanded keywords as suggestions below textarea -- User can accept/reject individual suggestions -- Example: "AI tools" + Healthcare → ["AI tools", "medical AI", "healthcare automation", "clinical decision support"] - -**Why**: -- Users often enter vague queries -- Industry context already available -- Rule-based = no API cost -- Can be AI-enhanced later (Phase 3) - -**Implementation**: -```typescript -// Rule-based keyword expansion maps -const industryKeywordExpansions: Record> = { - Healthcare: { - 'AI': ['medical AI', 'healthcare AI', 'clinical AI', 'diagnostic AI'], - 'tools': ['medical devices', 'clinical tools', 'diagnostic systems'], - 'automation': ['healthcare automation', 'clinical automation', 'patient care automation'] - }, - Technology: { - 'AI': ['machine learning', 'deep learning', 'neural networks'], - 'cloud': ['AWS', 'Azure', 'GCP', 'cloud infrastructure'], - 'security': ['cybersecurity', 'data protection', 'privacy compliance'] - }, - // ... 13 industries -}; - -// Function to expand keywords -function expandKeywords(keywords: string[], industry: string): string[] { - // Match user keywords against expansion maps - // Return expanded list with originals + suggestions -} -``` - -**Files to Modify**: -- `frontend/src/components/Research/steps/ResearchInput.tsx` - Add expansion UI -- New: `frontend/src/utils/keywordExpansion.ts` - Expansion logic - -**User Experience**: -- User types: "AI automation" -- System shows: "Suggested: AI automation, healthcare automation, clinical automation" -- Click to add/remove suggestions -- Visual distinction: original vs. suggested - ---- - -### Quick Win #7: Alternative Research Angles ⭐⭐ (45 min) -**Priority**: Medium | **Complexity**: Low | **Impact**: Medium - -**What**: -- Show 3-5 related research angles based on user input -- Display as clickable cards below the textarea -- Each angle suggests a different research focus -- Example: "AI tools" → ["Compare AI tools", "AI tool ROI", "Best practices", "Implementation guides"] - -**Why**: -- Helps users discover research directions -- Rule-based patterns (can be AI-enhanced later) -- Increases research value for users -- Encourages exploration - -**Implementation**: -```typescript -// Pattern-based angle generation -const anglePatterns = { - tools: ['Compare {topic}', '{topic} ROI analysis', 'Best {topic} for {industry}'], - trends: ['Latest {topic} trends', '{topic} market analysis', '{topic} future predictions'], - strategies: ['{topic} implementation guide', '{topic} best practices', '{topic} case studies'], - // ... more patterns -}; - -function generateAngles(query: string, industry: string): string[] { - // Detect query intent (tools, trends, strategies, etc.) - // Generate 3-5 relevant angles using patterns - // Return formatted angle suggestions -} -``` - -**Files to Modify**: -- `frontend/src/components/Research/steps/ResearchInput.tsx` - Add angles display -- New: `frontend/src/utils/researchAngles.ts` - Angle generation - -**User Experience**: -- User types query -- System shows 3-5 angle cards below -- Each card: Title + brief description -- Click card → replaces textarea content -- "Use this angle" button - ---- - -### Quick Win #8: Smart Query Rewriting (Rule-Based) ⭐⭐ (1 hour) -**Priority**: Medium | **Complexity**: Medium | **Impact**: Medium - -**What**: -- Improve vague inputs with industry context and persona data -- Show "Enhanced query" suggestion above/below textarea -- User can accept enhanced version -- Example: "write something about AI" → "Research: AI-powered diagnostic tools in healthcare for medical professionals" - -**Why**: -- Many users enter very vague queries -- Industry + persona context already available -- Rule-based templates (no AI cost) -- Foundation for future AI enhancement - -**Implementation**: -```typescript -// Query enhancement templates -const enhancementTemplates = { - vague_ai: (industry: string, audience: string) => - `Research: AI applications in ${industry} for ${audience}`, - vague_tools: (industry: string) => - `Compare top ${industry} tools and platforms`, - vague_trends: (industry: string) => - `Latest trends and innovations in ${industry}`, - // ... more templates -}; - -function enhanceQuery( - query: string, - industry: string, - audience: string -): string | null { - // Detect vague patterns ("write about", "something", "best", etc.) - // Match to template + apply industry/audience context - // Return enhanced query or null if already specific -} -``` - -**Files to Modify**: -- `frontend/src/components/Research/steps/ResearchInput.tsx` - Add enhancement UI -- New: `frontend/src/utils/queryEnhancement.ts` - Enhancement logic - -**User Experience**: -- User types: "something about AI" -- System shows: "💡 Enhanced: Research AI applications in Healthcare for medical professionals" -- "Use enhanced query" button -- Can still use original if preferred - ---- - -## Priority Ranking - -### Immediate Impact (Week 1) -1. **#5: Research History** - Highest ROI, lowest effort -2. **#6: Keyword Expansion** - High value, uses existing context - -### High Value (Week 2) -3. **#7: Alternative Angles** - Encourages exploration -4. **#8: Query Rewriting** - Improves vague inputs - ---- - -## Implementation Strategy - -### Phase 2A: Week 1 (2 hours) -- Implement Quick Win #5 (Research History) -- Implement Quick Win #6 (Keyword Expansion) -- **Total**: 2 hours, high impact - -### Phase 2B: Week 2 (1.75 hours) -- Implement Quick Win #7 (Alternative Angles) -- Implement Quick Win #8 (Query Rewriting) -- **Total**: 1.75 hours, medium-high impact - ---- - -## Technical Considerations - -### No Backend Changes Required -All quick wins are client-side using: -- Existing localStorage infrastructure -- Existing persona/industry data from APIs -- Rule-based logic (no AI calls) - -### Future AI Enhancement Path -All quick wins designed to be AI-enhanced later: -- History → AI-powered "similar research" suggestions -- Keyword Expansion → AI semantic expansion -- Angles → AI-generated angles from user intent -- Query Rewriting → AI understanding of user goals - -### Performance -- All operations <10ms (local computation) -- Minimal memory footprint -- No API calls = instant feedback - ---- - -## Success Metrics - -### Track -1. **History Usage**: % of users clicking recent research -2. **Expansion Acceptance**: % of expanded keywords accepted -3. **Angle Clicks**: % of users clicking alternative angles -4. **Enhancement Acceptance**: % of enhanced queries used - -### Goals (30 days) -- 40% of users use research history at least once -- 30% of users accept keyword expansions -- 25% of users explore alternative angles -- 20% of users accept query enhancements - ---- - -## Comparison with Document - -### From `RESEARCH_AI_HYPERPERSONALIZATION.md`: - -**Phase 2: Persona-Aware Defaults** ✅ (Completed in Quick Wins 1-4) -- ✅ Auto-fill industry from persona -- ✅ Auto-fill target audience from persona -- ✅ Suggest research mode based on topic complexity -- ✅ Suggest provider based on topic type -- ✅ Suggest Exa category based on industry -- ✅ Suggest domains based on industry - -**Phase 3: AI Query Enhancement** (Future - but rule-based foundation here) -- 🔄 Generate optimal search queries ← Quick Win #8 (rule-based) -- 🔄 Expand keywords semantically ← Quick Win #6 (rule-based) -- 🔄 Suggest related research angles ← Quick Win #7 (rule-based) -- 🔮 Predict best configuration (still future - needs AI) - -**Additional Value**: -- 🔄 Research history tracking (not in doc, but high value) - ---- - -## Recommended Next Steps - -1. **Start with Quick Win #5** (Research History) - 1 hour, instant value -2. **Then Quick Win #6** (Keyword Expansion) - 1 hour, uses persona data -3. **Evaluate user feedback** before implementing #7 and #8 -4. **Plan Phase 3** AI enhancements based on usage data - ---- - -## Code Reuse Opportunities - -### Existing Patterns to Leverage -- **localStorage**: Already used in `researchCache.ts`, `useResearchWizard.ts` -- **Persona Data**: Already fetched in `ResearchInput.tsx` via `getResearchConfig()` -- **Industry Maps**: Already exist for domains/categories in `ResearchInput.tsx` -- **State Management**: Can follow `useResearchWizard` patterns - -### New Utilities Needed -- `frontend/src/utils/researchHistory.ts` - History management -- `frontend/src/utils/keywordExpansion.ts` - Expansion logic -- `frontend/src/utils/researchAngles.ts` - Angle generation -- `frontend/src/utils/queryEnhancement.ts` - Query improvement - ---- - -## Risk Assessment - -### Low Risk ✅ -- All client-side (no backend impact) -- Graceful fallbacks (works without persona data) -- Progressive enhancement (can disable if issues) -- No breaking changes - -### Potential Issues -- **localStorage size**: History limited to 5 entries -- **Privacy**: History stored locally (user-controlled) -- **Performance**: All operations synchronous (should be fast) - ---- - -## Conclusion - -These 4 quick wins build on the foundation laid in Phase 1 and provide immediate value without AI costs. They can all be AI-enhanced later (Phase 3) once we validate user behavior and have usage data to guide the AI prompts. - -**Recommended Order**: -1. Research History (highest ROI) -2. Keyword Expansion (high value, uses persona) -3. Alternative Angles (encourages exploration) -4. Query Rewriting (improves vague inputs) - -**Total Time**: ~3.75 hours for all 4 features -**Impact**: High (40% time savings, better research quality) -**Risk**: Low (client-side only, graceful fallbacks) diff --git a/docs/ONBOARDING_CONTEXT_IMPLEMENTATION.md b/docs/ONBOARDING_CONTEXT_IMPLEMENTATION.md deleted file mode 100644 index ec45e306..00000000 --- a/docs/ONBOARDING_CONTEXT_IMPLEMENTATION.md +++ /dev/null @@ -1,912 +0,0 @@ -# Onboarding Context Implementation -**Date:** October 1, 2025 -**Feature:** Centralized Onboarding State Management -**Status:** ✅ Implemented - ---- - -## Overview - -**Problem:** Multiple components making duplicate API calls for onboarding status -**Solution:** React Context to share state across entire application -**Result:** Single source of truth, zero redundant API calls, better state sync - ---- - -## Architecture - -### **Context Structure:** - -``` -ErrorBoundary (App Root) -└─ ClerkProvider (Authentication) - └─ OnboardingProvider ← SINGLE DATA FETCH - └─ CopilotKit - └─ Router - ├─ InitialRouteHandler ← Uses context - ├─ ProtectedRoute ← Uses context - ├─ Wizard ← Uses context - └─ Other Routes -``` - -**Key Benefit:** OnboardingProvider fetches data ONCE, all children use it! - ---- - -## Implementation Details - -### **1. OnboardingContext** (`frontend/src/contexts/OnboardingContext.tsx`) - -**Features:** -- ✅ Centralized state management -- ✅ Single API call on mount -- ✅ Automatic caching in sessionStorage -- ✅ Manual refresh capability -- ✅ Optimistic updates -- ✅ Loading and error states -- ✅ TypeScript type safety - -**State:** -```typescript -interface OnboardingContextValue { - // State - data: OnboardingData | null; - loading: boolean; - error: string | null; - - // Computed properties - isOnboardingComplete: boolean; - currentStep: number; - completionPercentage: number; - - // Actions - refresh: () => Promise; - markStepComplete: (stepNumber: number) => void; - clearError: () => void; -} -``` - ---- - -### **2. Provider Integration** (`App.tsx`) - -**Before:** -```typescript - - - - {/* Each component makes own API calls */} - - - -``` - -**After:** -```typescript - - ← Fetches data once - - - {/* All components use context */} - - - - -``` - ---- - -### **3. InitialRouteHandler Simplified** - -**Before (62 lines with API call):** -```typescript -const InitialRouteHandler = () => { - const [loading, setLoading] = useState(true); - const [onboardingComplete, setOnboardingComplete] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchData = async () => { - const response = await apiClient.get('/api/onboarding/init'); - // ... process response - setOnboardingComplete(response.data.onboarding.is_completed); - setLoading(false); - }; - fetchData(); - }, []); - - // ... loading/error UI ... - - if (onboardingComplete) { - return ; - } - return ; -}; -``` - -**After (30 lines, no API call):** -```typescript -const InitialRouteHandler = () => { - const { loading, error, isOnboardingComplete } = useOnboarding(); - - if (loading) return ; - if (error) return ; - - if (isOnboardingComplete) { - return ; - } - return ; -}; -``` - -**Reduction:** 50% less code, 0 API calls! - ---- - -### **4. ProtectedRoute Simplified** - -**Before (120 lines with caching logic):** -```typescript -const ProtectedRoute = ({ children }) => { - const [loading, setLoading] = useState(true); - const [onboardingComplete, setOnboardingComplete] = useState(false); - - useEffect(() => { - const checkStatus = async () => { - // Check cache - const cached = sessionStorage.getItem('onboarding_init'); - if (cached) { - // Use cache - } else { - // Make API call - const response = await apiClient.get('/api/onboarding/init'); - // ... cache and process - } - }; - checkStatus(); - }, [isSignedIn]); - - // ... complex logic ... -}; -``` - -**After (60 lines, no API call, no caching):** -```typescript -const ProtectedRoute = ({ children }) => { - const { loading, error, isOnboardingComplete, refresh } = useOnboarding(); - - if (loading) return ; - if (error) return ; - if (!isOnboardingComplete) return ; - - return <>{children}; -}; -``` - -**Reduction:** 50% less code, simpler logic! - ---- - -## Usage - -### **Basic Usage:** - -```typescript -import { useOnboarding } from '../contexts/OnboardingContext'; - -const MyComponent = () => { - const { - data, - loading, - error, - isOnboardingComplete, - currentStep, - completionPercentage, - refresh - } = useOnboarding(); - - if (loading) return ; - if (error) return {error}; - - return ( -
-

Current Step: {currentStep}

-

Progress: {completionPercentage}%

-

Complete: {isOnboardingComplete ? 'Yes' : 'No'}

- -
- ); -}; -``` - ---- - -### **Refresh After Step Completion:** - -```typescript -const StepComponent = () => { - const { refresh, markStepComplete } = useOnboarding(); - - const handleComplete = async () => { - // Complete step via API - await apiClient.post('/api/onboarding/step/1/complete', data); - - // Option 1: Manual refresh - await refresh(); - - // Option 2: Optimistic update + background refresh - markStepComplete(1); // Updates UI immediately, then refreshes - }; -}; -``` - ---- - -### **Optional Usage (Components Outside Provider):** - -```typescript -import { useOnboardingOptional } from '../contexts/OnboardingContext'; - -const OptionalComponent = () => { - const onboarding = useOnboardingOptional(); - - if (!onboarding) { - // Not in OnboardingProvider, handle gracefully - return
Onboarding not available
; - } - - return
Step: {onboarding.currentStep}
; -}; -``` - ---- - -## Benefits - -### **Performance:** - -**Before Context:** -``` -App loads → InitialRouteHandler API call -Navigate to /dashboard → ProtectedRoute API call -Navigate to /onboarding → Wizard uses cache -Navigate back to /dashboard → ProtectedRoute API call again -TOTAL: 3+ API calls -``` - -**After Context:** -``` -App loads → OnboardingProvider API call -All components → Use context (0 additional calls) -TOTAL: 1 API call (shared across all components) -``` - -**Improvement:** 66-75% reduction in API calls - ---- - -### **Code Quality:** - -| Metric | Before | After | Improvement | -|--------|--------|-------|-------------| -| **Lines of code** | 250 | 120 | 52% reduction | -| **API calls** | 3-5 | 1 | 70-80% reduction | -| **State management** | Duplicated | Centralized | 100% better | -| **Complexity** | High | Low | Simpler | - ---- - -### **Developer Experience:** - -✅ **Single hook** for all onboarding data -✅ **No caching logic** needed in components -✅ **Automatic synchronization** across app -✅ **Type-safe** with TypeScript -✅ **Easy to use** - just call `useOnboarding()` - ---- - -## Data Flow - -``` -1. User signs in - ↓ -2. ClerkProvider authenticates - ↓ -3. OnboardingProvider initializes - ↓ -4. Calls GET /api/onboarding/init - ↓ -5. Stores data in context state - ↓ -6. All components access via useOnboarding() - ↓ -7. Step completed → refresh() → Updates all components -``` - ---- - -## State Updates - -### **Automatic Updates:** - -```typescript -// OnboardingProvider watches for changes -useEffect(() => { - fetchOnboardingData(); // Fetches on mount -}, []); - -// Components get updates automatically -const Component = () => { - const { currentStep } = useOnboarding(); // Auto-updates when context changes - return
Step: {currentStep}
; -}; -``` - ---- - -### **Manual Refresh:** - -```typescript -// After completing a step -const { refresh } = useOnboarding(); - -await completeStep(2); -await refresh(); // All components update! -``` - ---- - -### **Optimistic Updates:** - -```typescript -// Immediate UI update, background sync -const { markStepComplete } = useOnboarding(); - -markStepComplete(2); -// UI updates immediately -// Background: fetches from backend -// If mismatch: shows backend state -``` - ---- - -## Context Provider Placement - -### **✅ Correct Placement:** - -```typescript - - ← Auth must wrap provider - ← Can access Clerk token - {/* All components can use useOnboarding() */} - - - -``` - -**Why?** -- OnboardingProvider calls API with auth token -- Must be inside ClerkProvider to access getToken() -- ErrorBoundary catches any provider errors - ---- - -### **❌ Wrong Placement:** - -```typescript - ← Won't have auth token! - - {/* API calls will fail - no token */} - - -``` - ---- - -## Error Handling - -### **Provider Level:** - -```typescript -// OnboardingProvider catches fetch errors -try { - const response = await apiClient.get('/api/onboarding/init'); - setData(response.data); -} catch (err) { - setError(err.message); // All components see error -} -``` - ---- - -### **Component Level:** - -```typescript -const Component = () => { - const { error, clearError, refresh } = useOnboarding(); - - if (error) { - return ( - { clearError(); refresh(); }}> - Retry - - } - > - {error} - - ); - } - - // Normal render -}; -``` - ---- - -## Testing - -### **Test 1: Context Initialization** - -```javascript -// In browser console -// After signing in -console.log('Context test started'); - -// Should see in console: -// "OnboardingContext: Provider mounted, fetching data..." -// "OnboardingContext: Data fetched successfully" -``` - ---- - -### **Test 2: Shared State** - -**Steps:** -1. Sign in → Navigate to /onboarding -2. Open DevTools → React DevTools -3. Find OnboardingProvider in component tree -4. Check state is populated -5. Navigate to /dashboard -6. Check network tab - should be 0 new API calls -7. State shared across routes! - ---- - -### **Test 3: Refresh Functionality** - -```javascript -// In browser console (when onboarding context available) -// Get the context value -const onboardingCtx = /* access via React DevTools */; - -// Trigger refresh -await onboardingCtx.refresh(); - -// Should see new data loaded -``` - ---- - -## Performance Impact - -### **API Call Reduction:** - -| Scenario | Before | After | Saved | -|----------|--------|-------|-------| -| Initial load | 1 | 1 | 0 | -| InitialRouteHandler | 0 (uses cache) | 0 (uses context) | 0 | -| ProtectedRoute #1 | 0 (uses cache) | 0 (uses context) | 0 | -| ProtectedRoute #2 | 1 (cache expired) | 0 (uses context) | 1 | -| ProtectedRoute #3 | 1 (cache expired) | 0 (uses context) | 1 | -| **Total** | **3** | **1** | **66%** | - ---- - -### **Memory Impact:** - -- Context state: ~5KB (user + onboarding data) -- Provider overhead: ~2KB -- Hooks overhead: ~1KB -- **Total: ~8KB** (negligible) - -**Trade-off:** 8KB memory for 66% fewer API calls = Excellent! - ---- - -## Migration Guide - -### **Before (Component makes API call):** - -```typescript -const Component = () => { - const [loading, setLoading] = useState(true); - const [complete, setComplete] = useState(false); - - useEffect(() => { - apiClient.get('/api/onboarding/status') - .then(res => setComplete(res.data.is_completed)) - .finally(() => setLoading(false)); - }, []); - - if (loading) return ; - if (!complete) return ; - return ; -}; -``` - ---- - -### **After (Component uses context):** - -```typescript -const Component = () => { - const { loading, isOnboardingComplete } = useOnboarding(); - - if (loading) return ; - if (!isOnboardingComplete) return ; - return ; -}; -``` - -**Simplified:** 12 lines → 6 lines! - ---- - -## Advanced Usage - -### **Selective Rendering Based on Step:** - -```typescript -const DashboardWidget = () => { - const { currentStep, data } = useOnboarding(); - - if (currentStep < 3) { - return - - ; - } - - return ; -}; -``` - ---- - -### **Progress Tracking:** - -```typescript -const ProgressIndicator = () => { - const { completionPercentage, currentStep, data } = useOnboarding(); - - return ( - - - - Step {currentStep} of {data?.onboarding?.steps.length} - - - {completionPercentage.toFixed(0)}% Complete - - - ); -}; -``` - ---- - -### **Step-Specific Data Access:** - -```typescript -const APIKeyStatus = () => { - const { data } = useOnboarding(); - - const step1 = data?.onboarding?.steps.find(s => s.step_number === 1); - - if (step1?.status === 'completed') { - return ; - } - - return ; -}; -``` - ---- - -## Context Methods - -### **refresh()** - -Manually refresh onboarding data from backend: - -```typescript -const { refresh } = useOnboarding(); - -// After completing a step -await apiClient.post('/api/onboarding/step/2/complete', data); -await refresh(); // All components update! -``` - -**Use cases:** -- After completing onboarding steps -- After user updates profile -- When data becomes stale -- Manual user refresh - ---- - -### **markStepComplete(stepNumber)** - -Optimistic update with background refresh: - -```typescript -const { markStepComplete } = useOnboarding(); - -// Complete step -await apiClient.post('/api/onboarding/step/3/complete', data); - -// Optimistic update -markStepComplete(3); -// ↑ UI updates immediately -// ↓ Background: fetches from backend for consistency -``` - -**Benefits:** -- Instant UI feedback -- Background consistency check -- Best of both worlds - ---- - -### **clearError()** - -Reset error state: - -```typescript -const { error, clearError, refresh } = useOnboarding(); - -if (error) { - return ( - { clearError(); refresh(); }}> - Retry - - } - > - {error} - - ); -} -``` - ---- - -## Comparison: Before vs After - -### **Before (Without Context):** - -**InitialRouteHandler.tsx:** -- ❌ Makes own API call -- ❌ Manages own state -- ❌ 62 lines of code - -**ProtectedRoute.tsx:** -- ❌ Checks cache -- ❌ Makes fallback API call -- ❌ 120 lines of code - -**Wizard.tsx:** -- ❌ Checks cache -- ❌ Makes fallback API call -- ❌ Complex initialization - -**Total:** 200+ lines, 1-3 API calls - ---- - -### **After (With Context):** - -**InitialRouteHandler.tsx:** -- ✅ Uses context -- ✅ No API calls -- ✅ 30 lines of code - -**ProtectedRoute.tsx:** -- ✅ Uses context -- ✅ No caching logic -- ✅ 60 lines of code - -**Wizard.tsx:** -- ✅ Uses context (optional) -- ✅ Can still use cache for backwards compat -- ✅ Simpler initialization - -**Total:** 90 lines, 1 API call (in provider) - -**Improvement:** 55% less code, 66% fewer API calls! - ---- - -## Cache Strategy - -### **Dual Strategy (Best of Both Worlds):** - -1. **Context (Primary)** - - In-memory state - - Shared across components - - Automatic updates - -2. **sessionStorage (Fallback)** - - Persists across page refreshes - - Backwards compatibility - - Emergency fallback - -**Why both?** -- Context faster (in-memory) -- sessionStorage survives refresh -- Redundancy ensures stability - ---- - -## Error Recovery - -### **Automatic Retry:** - -```typescript -const OnboardingProvider = ({ children }) => { - const [retryCount, setRetryCount] = useState(0); - - const fetchWithRetry = async () => { - try { - await fetchOnboardingData(); - } catch (err) { - if (retryCount < MAX_RETRIES) { - setRetryCount(c => c + 1); - setTimeout(fetchWithRetry, 2000); // Retry after 2s - } else { - setError(err.message); - } - } - }; -}; -``` - ---- - -## Future Enhancements - -### **Phase 2 (Optional):** - -1. **Subscription to Backend Events** - ```typescript - // Real-time updates via WebSocket - useEffect(() => { - const ws = new WebSocket('ws://localhost:8000/onboarding-updates'); - ws.onmessage = (event) => { - setData(JSON.parse(event.data)); - }; - }, []); - ``` - -2. **Persistence Strategies** - ```typescript - // Save to localStorage for offline support - useEffect(() => { - localStorage.setItem('onboarding_backup', JSON.stringify(data)); - }, [data]); - ``` - -3. **Multi-Tab Synchronization** - ```typescript - // Listen for changes in other tabs - useEffect(() => { - window.addEventListener('storage', (e) => { - if (e.key === 'onboarding_init') { - refresh(); - } - }); - }, []); - ``` - ---- - -## Testing Checklist - -- [x] Context provider created -- [x] Integrated into App.tsx -- [x] InitialRouteHandler uses context -- [x] ProtectedRoute uses context -- [x] Loading states work -- [x] Error states work -- [ ] Manual testing: Sign in and navigate -- [ ] Verify single API call in Network tab -- [ ] Test refresh() functionality -- [ ] Test error recovery - ---- - -## Troubleshooting - -### **Issue: "useOnboarding must be used within OnboardingProvider"** - -**Cause:** Component trying to use context outside provider - -**Solution:** -```typescript -// Make sure component is inside OnboardingProvider - - ← Can use useOnboarding() - - - ← Cannot use useOnboarding() - will throw error -``` - ---- - -### **Issue: Context not updating** - -**Cause:** Not calling refresh() after data changes - -**Solution:** -```typescript -// After any API call that changes onboarding state -await apiClient.post('/api/onboarding/step/1/complete', data); -await refresh(); // ← Don't forget this! -``` - ---- - -### **Issue: Stale data** - -**Cause:** Context doesn't auto-refresh - -**Solution:** -```typescript -// Add auto-refresh interval (optional) -useEffect(() => { - const interval = setInterval(() => { - refresh(); - }, 60000); // Refresh every minute - return () => clearInterval(interval); -}, []); -``` - ---- - -## Files Modified - -### **New Files:** -1. `frontend/src/contexts/OnboardingContext.tsx` - Context implementation - -### **Modified Files:** -2. `frontend/src/App.tsx` - Added OnboardingProvider -3. `frontend/src/components/shared/ProtectedRoute.tsx` - Uses context -4. (Optional) `frontend/src/components/OnboardingWizard/Wizard.tsx` - Can use context - ---- - -## Summary - -✅ **Context implemented** - Centralized state management -✅ **Provider integrated** - Wraps entire app -✅ **Components simplified** - Use context hook -✅ **Performance improved** - 66% fewer API calls -✅ **Code reduced** - 55% less duplicate code -✅ **Type-safe** - Full TypeScript support - -**The onboarding state is now managed efficiently with a single source of truth!** 🎯 - ---- - -## Related Documentation - -- **Code Review:** `END_USER_FLOW_CODE_REVIEW.md` (Issue #4) -- **Batch API:** `BATCH_API_IMPLEMENTATION_SUMMARY.md` -- **Session Cleanup:** `SESSION_ID_CLEANUP_SUMMARY.md` -- **Error Boundaries:** `ERROR_BOUNDARY_IMPLEMENTATION.md` - diff --git a/docs/ONBOARDING_DATA_PERSISTENCE_FIX.md b/docs/ONBOARDING_DATA_PERSISTENCE_FIX.md deleted file mode 100644 index 80f95a07..00000000 --- a/docs/ONBOARDING_DATA_PERSISTENCE_FIX.md +++ /dev/null @@ -1,318 +0,0 @@ -# ✅ Onboarding Data Persistence Fix - COMPLETE - -## Summary - -Successfully implemented comprehensive fixes to ensure that data from Step 2 (Website Analysis) and Step 3 (Competitor Analysis) is properly saved to the database and available for Step 4 (Persona Generation) and Step 5 (Integrations). - -## 🔍 Issues Identified - -### **Critical Data Loss Problems:** - -#### **Problem 1: Step 2 Data Not Persisted** -- **Issue:** Website analysis data was only saved to localStorage, not to database -- **Impact:** Data lost on page refresh, not available for persona generation - -#### **Problem 2: Step 3 Data Not Saved** -- **Issue:** Research preferences data was never saved to database -- **Impact:** Competitor analysis results lost, not available for AI personalization - -#### **Problem 3: Wizard Initialization Incomplete** -- **Issue:** Wizard initialization didn't load step data from database -- **Impact:** Previous step data not available when navigating back/forward - -#### **Problem 4: Step Completion Missing Validation** -- **Issue:** No backend validation for step completion data -- **Impact:** Steps could complete without proper data validation - -## 🚀 Solutions Implemented - -### **1. Enhanced Step 2 Data Persistence** ✅ - -#### **Frontend:** WebsiteStep Component -- **File:** Already properly saves to backend via `/api/onboarding/style-detection/complete` -- **Database:** Data stored in `website_analyses` table via `WebsiteAnalysis` model -- **Service:** `WebsiteAnalysisService.save_analysis()` handles database operations - -#### **Backend:** Style Detection Endpoint -```python -# /api/onboarding/style-detection/complete -@router.post("/style-detection/complete", response_model=StyleDetectionResponse) -async def complete_style_detection(request: StyleDetectionRequest, current_user: Dict[str, Any]): - # Saves to database via WebsiteAnalysisService - analysis_service = WebsiteAnalysisService(db_session) - analysis_id = analysis_service.save_analysis(user_id_int, request.url, analysis_data) -``` - -### **2. Added Step 3 Data Persistence** ✅ - -#### **Frontend:** CompetitorAnalysisStep Component -**File:** `frontend/src/components/OnboardingWizard/CompetitorAnalysisStep.tsx` - -**Added Backend Save Call:** -```typescript -const handleContinue = async () => { - // Save research preferences to backend before continuing - try { - const researchData = getResearchData(); - - // Extract research preferences for saving (use defaults if not available) - const researchPreferences = { - research_depth: 'Comprehensive', - content_types: ['blog_posts', 'social_media'], - auto_research: true, - factual_content: true - }; - - // Save research preferences to backend - await aiApiClient.post('/api/ai-research/configure-preferences', { - research_depth: researchPreferences.research_depth, - content_types: researchPreferences.content_types, - auto_research: researchPreferences.auto_research, - factual_content: researchPreferences.factual_content - }); - - console.log('Research preferences saved to backend'); - } catch (error) { - console.error('Error saving research preferences:', error); - // Continue anyway - don't block user progress for save errors - } - - // Continue with wizard navigation - onContinue(getResearchData()); -}; -``` - -#### **Backend:** Research Preferences Endpoint -**File:** `backend/api/component_logic.py` - -```python -@router.post("/ai-research/configure-preferences", response_model=ResearchPreferencesResponse) -async def configure_research_preferences(request: ResearchPreferencesRequest, db: Session, current_user: Dict[str, Any]): - # Saves to database via ResearchPreferencesService - preferences_service = ResearchPreferencesService(db) - preferences_id = preferences_service.save_preferences_with_style_data(user_id_int, preferences) -``` - -**Database:** Data stored in `research_preferences` table via `ResearchPreferences` model - -### **3. Enhanced Wizard Data Handling** ✅ - -#### **Frontend:** Wizard Component -**File:** `frontend/src/components/OnboardingWizard/Wizard.tsx` - -**Added Special Handling for Step 2 (Research):** -```typescript -// Special handling for CompetitorAnalysisStep (step 2) -if (activeStep === 2) { - console.log('Wizard: Handling CompetitorAnalysisStep data...'); - - // Merge research data with existing step data - const currentData = stepDataRef.current || {}; - const researchData = currentStepData || {}; - - // Ensure we have research data - if (researchData.competitors || researchData.researchSummary || researchData.sitemapAnalysis) { - currentStepData = { - ...currentData, // Preserve existing data (website, etc.) - ...researchData, // Add/update research data - // Ensure all required research fields are present - competitors: researchData.competitors || currentData.competitors, - researchSummary: researchData.researchSummary || currentData.researchSummary, - sitemapAnalysis: researchData.sitemapAnalysis || currentData.sitemapAnalysis, - // Mark this as the research step - stepType: 'research', - completedAt: new Date().toISOString() - }; - - console.log('Wizard: Merged research data:', currentStepData); - } else { - console.warn('Wizard: No research data provided, using existing step data'); - currentStepData = currentData; - } -} -``` - -**Added Special Handling for Step 3 (Persona):** -```typescript -// Special handling for PersonaStep (step 3) -if (activeStep === 3) { - // Enhanced persona data merging with existing step data - // Preserves website and research data while adding persona data -} -``` - -### **4. Enhanced Backend Initialization** ✅ - -#### **Backend:** Onboarding Initialization -**File:** `backend/api/onboarding_utils/endpoints_core.py` - -**Modified to Include Step Data:** -```python -# Include step data for completed steps, especially research data (step 3) and persona data (step 4) -if step.data: - if step.step_number == 4: # Personalization step with persona data - step_data = step.data - logger.info(f"Including persona data for step 4: {len(str(step_data))} chars") - elif step.step_number == 3: # Research step with research preferences - step_data = step.data - logger.info(f"Including research data for step 3: {len(str(step_data))} chars") -``` - -#### **Frontend:** Wizard Initialization -**File:** `frontend/src/components/OnboardingWizard/Wizard.tsx` - -**Modified to Load Step Data:** -```typescript -// Load step data, especially research data from step 3 and persona data from step 4 -if (onboarding.steps && Array.isArray(onboarding.steps)) { - // Load research preferences from step 3 - const step3Data = onboarding.steps.find((step: any) => step.step_number === 3); - if (step3Data && step3Data.data) { - console.log('Wizard: Loading research data from step 3:', Object.keys(step3Data.data)); - setStepData((prevData: any) => ({ ...prevData, ...step3Data.data })); - } - - // Load persona data from step 4 - const step4Data = onboarding.steps.find((step: any) => step.step_number === 4); - if (step4Data && step4Data.data) { - console.log('Wizard: Loading persona data from step 4:', Object.keys(step4Data.data)); - setStepData((prevData: any) => ({ ...prevData, ...step4Data.data })); - } -} -``` - -### **5. Enhanced Backend Validation** ✅ - -#### **Backend:** Step Validation -**File:** `backend/services/validation.py` - -**Added Research Preferences Validation:** -```python -elif step_number == 4: # Personalization - # Validate that persona data is present - if not data: - errors.append("Persona data is required for step 4 completion") - else: - # Check for required persona fields - required_persona_fields = ['corePersona', 'platformPersonas'] - missing_fields = [] - - for field in required_persona_fields: - if field not in data or not data[field]: - missing_fields.append(field) - - if missing_fields: - errors.append(f"Missing required persona data: {', '.join(missing_fields)}") -``` - -## 🔄 Complete Data Flow Architecture - -### **Step 2 (Website Analysis) Flow:** -``` -User Input → WebsiteStep → /api/onboarding/style-detection/complete → -WebsiteAnalysisService.save_analysis() → Database (website_analyses table) → -OnboardingSummaryService.get_website_analysis_data() → Available for Step 4 -``` - -### **Step 3 (Competitor Analysis) Flow:** -``` -User Input → CompetitorAnalysisStep → /api/ai-research/configure-preferences → -ResearchPreferencesService.save_preferences_with_style_data() → -Database (research_preferences table) → Available for Step 4 -``` - -### **Step 4 (Persona Generation) Flow:** -``` -Website Data + Research Data → PersonaStep → /api/onboarding/step4/persona-save → -Cache Storage → Wizard Merge → Backend Validation → Step Completion → -Available for Step 5 -``` - -### **Wizard Navigation Flow:** -``` -Wizard Init → Load from Cache/API → Include Step 3 & 4 Data → -Step Navigation → Data Available → Session Persistence -``` - -## 🛡️ Data Persistence Layers - -### **1. Immediate Persistence:** -- **Step 2:** Database (`website_analyses` table) -- **Step 3:** Database (`research_preferences` table) -- **Step 4:** Cache (`persona_latest_cache`) - -### **2. Session Persistence:** -- **Browser Storage:** `sessionStorage` for wizard state -- **Cache Storage:** `localStorage` for step data -- **Database:** Long-term persistence across sessions - -### **3. Cross-Step Availability:** -- **Wizard State:** Maintains data during navigation -- **Backend APIs:** Serve data for each step -- **Initialization:** Loads data on wizard startup - -## 🎯 Validation & Error Handling - -### **Frontend Validation:** -- ✅ **Required Data Checks:** Ensures essential data is present -- ✅ **Type Validation:** Validates data structure and types -- ✅ **User Feedback:** Clear error messages for missing data - -### **Backend Validation:** -- ✅ **Step Completion:** Validates before marking steps complete -- ✅ **Data Integrity:** Ensures proper data structure -- ✅ **Error Recovery:** Graceful handling of validation failures - -### **Error Recovery:** -- ✅ **Fallback Mechanisms:** Uses existing data if new data fails -- ✅ **User Guidance:** Clear messages for data requirements -- ✅ **Retry Logic:** Allows users to fix and retry - -## 📊 Testing Checklist - -### **Data Persistence Tests:** -- ✅ **Step 2 → Database:** Website analysis data saved and retrievable -- ✅ **Step 3 → Database:** Research preferences data saved and retrievable -- ✅ **Step 4 → Cache:** Persona data cached and available -- ✅ **Cross-Step Access:** Data available in subsequent steps - -### **Wizard Navigation Tests:** -- ✅ **Back/Forward:** Data persists during step navigation -- ✅ **Page Refresh:** Data restored after browser refresh -- ✅ **Session Recovery:** Data available in new browser sessions -- ✅ **Step Completion:** Proper validation before step completion - -### **Integration Tests:** -- ✅ **End-to-End Flow:** Complete Step 2 → 3 → 4 → 5 flow -- ✅ **Data Integrity:** Data unchanged during transitions -- ✅ **Performance:** No significant impact on navigation speed - -## 🚀 Production Readiness - -### **Technical Quality:** -- ✅ **No Linter Errors:** All code changes pass linting -- ✅ **TypeScript Compliance:** Proper type definitions maintained -- ✅ **API Compatibility:** No breaking changes to existing APIs -- ✅ **Performance Impact:** Minimal overhead for data persistence - -### **Data Safety:** -- ✅ **Multiple Storage Layers:** Database + cache + session storage -- ✅ **Validation Safety:** Data integrity checks before persistence -- ✅ **Error Recovery:** Graceful handling of persistence failures -- ✅ **User Experience:** Non-blocking error handling - -## 🎉 Conclusion - -**Onboarding data persistence is now 100% secure and reliable!** The comprehensive solution ensures that: - -- ✨ **No Data Loss:** All step data properly saved to database/cache -- 🔄 **Seamless Navigation:** Data persists across step transitions -- 🛡️ **Data Validation:** Ensures data integrity before step completion -- 📱 **Session Persistence:** Data survives browser refreshes and sessions -- 🚀 **Production Ready:** Robust, tested, and maintainable solution - -**All onboarding steps now have proper data persistence, ensuring no data loss during the comprehensive onboarding flow!** 🎯✨ - ---- - -**Status:** ✅ **DATA PERSISTENCE FIX COMPLETE - READY FOR PRODUCTION** 🔒📊 diff --git a/docs/ONBOARDING_STEP_4_IMPLEMENTATION_PLAN.md b/docs/ONBOARDING_STEP_4_IMPLEMENTATION_PLAN.md deleted file mode 100644 index eb0cad32..00000000 --- a/docs/ONBOARDING_STEP_4_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,373 +0,0 @@ -# Onboarding Step 4: Competitive Analysis Implementation Plan - -## Overview - -Step 4 of the onboarding process will provide comprehensive competitive analysis including competitor analysis, content gap analysis, sitemap analysis, and social media discovery. This step serves as a foundation for persona generation and content strategy creation. - -## Strategic Objectives - -### Primary Goals -- **Comprehensive Market Analysis**: Understand user's competitive landscape -- **Content Strategy Foundation**: Provide data-driven insights for content planning -- **Persona Generation Input**: Feed rich analysis data into Step 5 persona creation -- **API Efficiency**: Reuse existing services without duplication - -### Business Impact -- **User Onboarding Value**: Users gain immediate competitive insights -- **Content Strategy Acceleration**: Faster, data-driven strategy generation -- **Market Positioning**: Clear understanding of competitive advantages -- **Content Gap Identification**: Actionable opportunities for content expansion - -## Architecture Overview - -### Data Flow Strategy -``` -Onboarding Step 4 → Store Analysis Results → Content Strategy Generation - ↓ ↓ ↓ -API Orchestration → Onboarding Database → Reuse Without Re-running -``` - -### Database Schema Enhancement -```sql --- Add to onboarding_sessions table -ALTER TABLE onboarding_sessions ADD COLUMN competitor_analysis_data JSON; -ALTER TABLE onboarding_sessions ADD COLUMN sitemap_analysis_data JSON; -ALTER TABLE onboarding_sessions ADD COLUMN content_gap_analysis_data JSON; -ALTER TABLE onboarding_sessions ADD COLUMN social_media_discovery_data JSON; -ALTER TABLE onboarding_sessions ADD COLUMN analysis_completed_at TIMESTAMP; -``` - -## Feature Specifications - -### 1. Competitor Analysis -**Purpose**: Market positioning and competitive benchmarking -**API Reuse**: `POST /api/content-planning/gap-analysis/analyze` -**Key Insights**: -- Market position assessment -- Content strategy comparison -- Competitive advantage identification -- Performance benchmarking - -### 2. Sitemap Analysis -**Purpose**: Content structure and publishing pattern analysis -**API Reuse**: `POST /api/seo/sitemap-analysis` -**Key Insights**: -- Content organization patterns -- Publishing frequency analysis -- SEO structure optimization -- Content distribution insights - -### 3. Content Gap Analysis -**Purpose**: Missing content opportunity identification -**API Reuse**: `POST /api/content-planning/gap-analysis/analyze` -**Key Insights**: -- Content gaps vs competitors -- Topic coverage analysis -- Content expansion opportunities -- Strategic content recommendations - -### 4. Social Media Discovery -**Purpose**: Cross-platform presence analysis -**New Implementation**: Enhanced social media discovery -**Key Insights**: -- Social media account discovery -- Platform presence analysis -- Content strategy insights -- Engagement opportunities - -## Implementation Phases - -### Phase 1: Sitemap Analysis Enhancement (Week 1) -**Priority**: High -**Duration**: 5-7 days -**Objectives**: -- Enhance existing sitemap service for onboarding context -- Add competitive benchmarking capabilities -- Create onboarding-specific AI insights -- Implement data storage in onboarding database - -#### 1.1 Sitemap Service Enhancement -**File**: `backend/services/seo_tools/sitemap_service.py` -**Modifications**: -- Add onboarding-specific analysis prompts -- Integrate competitive benchmarking -- Enhance AI insights for strategic recommendations -- Add data export capabilities for onboarding storage - -#### 1.2 Onboarding Integration -**File**: `backend/api/onboarding.py` -**New Endpoint**: `POST /api/onboarding/step4/sitemap-analysis` -**Features**: -- Orchestrate sitemap analysis -- Store results in onboarding database -- Provide progress tracking -- Handle analysis errors gracefully - -#### 1.3 Database Integration -**File**: `backend/models/onboarding.py` -**Modifications**: -- Add sitemap analysis storage fields -- Create data serialization methods -- Add data freshness validation -- Implement data migration for existing users - -### Phase 2: Unified Step 4 Orchestration (Week 2) -**Priority**: High -**Duration**: 7-10 days -**Objectives**: -- Create unified Step 4 endpoint -- Implement sequential analysis workflow -- Add comprehensive error handling -- Create progress tracking system - -#### 2.1 Orchestration Service -**New File**: `backend/api/onboarding_utils/competitive_analysis_service.py` -**Responsibilities**: -- Coordinate all four analysis types -- Manage analysis dependencies -- Handle partial failures -- Provide unified response format - -#### 2.2 Progress Tracking -**Implementation**: -- Real-time progress updates -- Partial completion handling -- Error recovery mechanisms -- User feedback system - -#### 2.3 Error Handling Strategy -**Approach**: -- Graceful degradation on API failures -- Retry mechanisms for transient errors -- User-friendly error messages -- Fallback analysis options - -### Phase 3: Frontend Integration (Week 3) -**Priority**: Medium -**Duration**: 7-10 days -**Objectives**: -- Create Step 4 UI components -- Implement progress visualization -- Add results display sections -- Create data export capabilities - -#### 3.1 UI Components -**New Files**: -- `frontend/src/components/OnboardingWizard/CompetitiveAnalysisStep.tsx` -- `frontend/src/components/OnboardingWizard/CompetitiveAnalysis/` -- `frontend/src/components/OnboardingWizard/CompetitiveAnalysis/ProgressDisplay.tsx` -- `frontend/src/components/OnboardingWizard/CompetitiveAnalysis/ResultsDisplay.tsx` - -#### 3.2 Progress Visualization -**Features**: -- Real-time progress bars -- Analysis status indicators -- Error state handling -- Completion celebrations - -#### 3.3 Results Display -**Sections**: -- Competitor Analysis Results -- Sitemap Analysis Insights -- Content Gap Opportunities -- Social Media Discovery - -### Phase 4: Content Strategy Integration (Week 4) -**Priority**: Medium -**Duration**: 5-7 days -**Objectives**: -- Modify content strategy generation to use onboarding data -- Implement data freshness validation -- Create data migration utilities -- Test end-to-end integration - -#### 4.1 Content Strategy Service Modification -**File**: `backend/api/content_planning/services/content_strategy/onboarding/data_processor.py` -**Modifications**: -- Read from onboarding analysis data -- Skip API calls if data exists and is fresh -- Add data validation and refresh logic -- Implement fallback to API calls if needed - -#### 4.2 Data Migration -**Implementation**: -- Migrate existing user data -- Validate data integrity -- Handle missing data gracefully -- Provide data refresh options - -## Technical Implementation Details - -### API Efficiency Strategy - -#### 1. Data Caching -**Implementation**: -```python -# Check for existing data before API calls -if onboarding_data.sitemap_analysis_data and is_fresh(onboarding_data.analysis_completed_at): - return onboarding_data.sitemap_analysis_data -else: - # Run analysis and store results - result = await sitemap_service.analyze_sitemap(url) - await store_analysis_result(onboarding_data, 'sitemap', result) - return result -``` - -#### 2. Parallel Processing -**Strategy**: -- Run independent analyses in parallel -- Sequential processing for dependent analyses -- Optimize API call order for efficiency - -#### 3. Error Recovery -**Approach**: -- Retry failed API calls with exponential backoff -- Continue with partial results if some analyses fail -- Provide clear error messages and recovery options - -### Logging and Monitoring - -#### 1. Comprehensive Logging -**Implementation**: -```python -# Structured logging for analysis steps -logger.info("Starting competitive analysis", extra={ - "user_id": user_id, - "step": "sitemap_analysis", - "website_url": website_url, - "timestamp": datetime.utcnow().isoformat() -}) -``` - -#### 2. Performance Monitoring -**Metrics**: -- Analysis completion time -- API response times -- Error rates by analysis type -- User completion rates - -#### 3. Data Quality Validation -**Checks**: -- Analysis data completeness -- Data freshness validation -- Result format verification -- Cross-analysis consistency - -### Exception Handling Strategy - -#### 1. Graceful Degradation -**Approach**: -- Continue onboarding with partial analysis results -- Provide clear feedback on missing data -- Offer manual data entry alternatives -- Suggest retry mechanisms - -#### 2. User Communication -**Implementation**: -- Clear error messages for users -- Progress indicators during analysis -- Success/failure notifications -- Recovery action suggestions - -#### 3. System Resilience -**Features**: -- Circuit breaker patterns for external APIs -- Retry mechanisms with backoff -- Fallback analysis options -- Data validation and sanitization - -## Quality Assurance - -### Testing Strategy - -#### 1. Unit Testing -**Coverage**: -- Individual analysis services -- Data processing functions -- Error handling scenarios -- Data validation logic - -#### 2. Integration Testing -**Scenarios**: -- End-to-end analysis workflow -- API integration points -- Database operations -- Frontend-backend communication - -#### 3. Performance Testing -**Metrics**: -- Analysis completion times -- Memory usage optimization -- API call efficiency -- Database query performance - -### Best Practices - -#### 1. Code Organization -**Structure**: -- Separate concerns (analysis, storage, presentation) -- Reusable service components -- Clear interface definitions -- Comprehensive documentation - -#### 2. Data Management -**Approaches**: -- Efficient data serialization -- Minimal storage requirements -- Data versioning support -- Cleanup and archival strategies - -#### 3. User Experience -**Principles**: -- Clear progress indication -- Intuitive error handling -- Responsive design -- Accessibility compliance - -## Success Metrics - -### Technical Metrics -- **Analysis Completion Rate**: >95% -- **Average Analysis Time**: <2 minutes -- **API Call Efficiency**: 50% reduction in duplicate calls -- **Error Recovery Rate**: >90% - -### Business Metrics -- **User Onboarding Completion**: >85% -- **Content Strategy Generation Speed**: 60% faster -- **User Satisfaction**: >4.5/5 rating -- **Feature Adoption**: >70% of users - -## Risk Mitigation - -### Technical Risks -- **API Rate Limiting**: Implement proper rate limiting and queuing -- **Data Loss**: Comprehensive backup and recovery mechanisms -- **Performance Issues**: Load testing and optimization -- **Integration Failures**: Robust error handling and fallbacks - -### Business Risks -- **User Abandonment**: Clear progress indication and value communication -- **Data Quality Issues**: Validation and verification processes -- **Feature Complexity**: Intuitive UI and guided workflows -- **Competitive Changes**: Flexible analysis framework - -## Future Enhancements - -### Phase 5: Advanced Analytics (Future) -- **Predictive Analytics**: Content performance forecasting -- **Market Trend Analysis**: Industry trend identification -- **Competitive Intelligence**: Automated competitor monitoring -- **Personalization**: AI-driven analysis customization - -### Phase 6: Integration Expansion (Future) -- **Third-party Tools**: Google Analytics, SEMrush integration -- **Social Media APIs**: Direct platform data access -- **CRM Integration**: Customer data correlation -- **Marketing Automation**: Workflow automation capabilities - -## Conclusion - -This implementation plan provides a comprehensive approach to building Step 4 of the onboarding process. By leveraging existing APIs and implementing efficient data management, we can create a powerful competitive analysis tool that enhances user onboarding and accelerates content strategy generation. - -The phased approach ensures manageable implementation while maintaining high quality and user experience standards. The focus on API efficiency, error handling, and data reuse creates a sustainable and scalable solution. diff --git a/docs/ONBOARDING_SYSTEM_COMPLETE.md b/docs/ONBOARDING_SYSTEM_COMPLETE.md deleted file mode 100644 index 694714aa..00000000 --- a/docs/ONBOARDING_SYSTEM_COMPLETE.md +++ /dev/null @@ -1,136 +0,0 @@ -# Onboarding System - Complete Implementation - -## ✅ **Successfully Completed** - -### **Problem Solved** -Step 6 (FinalStep) was not retrieving data from Steps 1-5, even though data was being saved to both cache/localStorage and database. - -### **Root Cause Identified** -1. **Database Schema Mismatch**: `OnboardingSession.user_id` was `Integer` but Clerk user IDs are strings -2. **Data Structure Mismatch**: Frontend sent nested structure, backend expected flat structure -3. **SQLAlchemy Cache Issue**: ORM cached old schema after adding new columns - -### **Complete Solution Implemented** - -#### ✅ **1. Database Schema Fix** -- **Updated**: `OnboardingSession.user_id` from `Integer` to `String(255)` -- **Migration**: `migrate_user_id_to_string.py` successfully executed -- **Result**: Database supports Clerk user IDs (strings) - -#### ✅ **2. Step 6 Data Retrieval Fix** -- **Updated**: `OnboardingSummaryService` to read from database instead of file-based storage -- **Added**: `get_persona_data()` method to `OnboardingDatabaseService` -- **Result**: Step 6 retrieves API keys, research preferences, and persona data - -#### ✅ **3. Complete Step 2 Data Storage** -- **Added**: `brand_analysis` and `content_strategy_insights` columns to `WebsiteAnalysis` model -- **Updated**: `OnboardingDatabaseService` to save all fields -- **Migration**: `add_brand_analysis_columns.py` successfully executed -- **Result**: All 10 data categories from website analysis are saved - -#### ✅ **4. Step 2 Existing Analysis Cache Fix** -- **Fixed**: SQLAlchemy cache issue by temporarily removing/re-adding columns -- **Result**: "Use existing analysis?" feature works correctly - -#### ✅ **5. Frontend Step 6 UI Improvements** -- **Refactored**: `FinalStep.tsx` into modular components -- **Fixed**: Readability issues (white text on white background) -- **Improved**: Layout and chip styling -- **Result**: Clean, readable, and modular Step 6 UI - -## **Complete Data Flow** - -``` -User Input (Steps 1-5) - ↓ -Save to BOTH: - ├─→ JSON File (.onboarding_progress_{user_id}.json) [Backward Compatibility] - └─→ Database (PostgreSQL/SQLite) [Production Ready] - -Step 6 Reads: - └─→ Database Only (via OnboardingDatabaseService) [Future Ready] -``` - -## **Complete Step 2 Data Now Saved** - -| Data Category | Fields | Status | -|--------------|---------|--------| -| Writing Style | tone, voice, complexity, engagement_level | ✅ Saved | -| Content Characteristics | sentence_structure, vocabulary_level | ✅ Saved | -| Target Audience | demographics, expertise_level, pain_points | ✅ Saved | -| Content Type | primary_type, secondary_types, purpose | ✅ Saved | -| Recommended Settings | writing_tone, target_audience, creativity_level | ✅ Saved | -| **Brand Analysis** | brand_voice, brand_values, positioning, trust_signals | ✅ **SAVED** | -| **Content Strategy Insights** | SWOT analysis, recommendations, content_gaps | ✅ **SAVED** | -| Crawl Result | Full website content | ✅ Saved | -| Style Patterns | consistency, unique_elements | ✅ Saved | -| Style Guidelines | guidelines, best_practices, ai_generation_tips | ✅ Saved | - -## **Current Status** - -✅ **Database schema updated** (user_id supports Clerk strings) -✅ **Step 6 reads from database** (production-ready) -✅ **User isolation implemented** (no cross-user data leakage) -✅ **Complete Step 2 data saved** (all 10 categories including brand analysis) -✅ **Existing analysis cache works** (backward compatible) -✅ **No breaking changes** (Steps 1-5 continue working as before) -✅ **Ready for production deployment** (Vercel + Render compatible) - -## **Files Modified** - -### **Backend** -- `backend/models/onboarding.py` - Database model updates -- `backend/services/onboarding_database_service.py` - Complete data saving -- `backend/services/api_key_manager.py` - Data transformation fix -- `backend/api/onboarding_utils/onboarding_summary_service.py` - Database retrieval -- `backend/api/component_logic.py` - Backward compatible existing analysis - -### **Frontend** -- `frontend/src/components/OnboardingWizard/FinalStep/` - Modular refactor -- `frontend/src/components/OnboardingWizard/Wizard.tsx` - Import updates - -### **Scripts** -- `backend/scripts/migrate_user_id_to_string.py` - Database migration -- `backend/scripts/add_brand_analysis_columns.py` - Column migration - -### **Documentation** -- `docs/STEP_6_DATABASE_MIGRATION_COMPLETE.md` -- `docs/STEP_2_COMPLETE_DATA_FLOW_ANALYSIS.md` -- `docs/STEP_2_SQLALCHEMY_CACHE_FIX.md` - -## **Benefits of Complete Implementation** - -1. **Richer Content Generation**: AI can align with brand values and voice -2. **Strategic Insights**: SWOT analysis informs content strategy -3. **Competitive Intelligence**: Differentiation factors for positioning -4. **Content Planning**: Actionable recommendations and gap analysis -5. **Quality Assurance**: Brand consistency checking -6. **Production Ready**: Vercel + Render deployment compatible -7. **User Isolation**: Secure multi-tenant architecture -8. **Backward Compatible**: No breaking changes to existing functionality - -## **Testing Results** - -✅ **Step 1**: API Keys configuration works -✅ **Step 2**: Website analysis works, existing analysis cache works -✅ **Step 3**: Research preferences work -✅ **Step 4**: Persona generation works -✅ **Step 5**: Final validation works -✅ **Step 6**: Complete data retrieval works - -## **Next Steps** - -1. **Final Testing**: Verify all steps work end-to-end -2. **Production Deployment**: Deploy to Vercel + Render -3. **Monitor**: Watch for any issues in production - -## **System Architecture** - -The onboarding system now implements a **dual persistence architecture** during migration: - -- **File-based storage**: Maintains backward compatibility -- **Database storage**: Provides production-ready scalability -- **User isolation**: Each user's data is properly segregated -- **Complete data capture**: All analysis insights are preserved - -**The onboarding system is now production-ready with complete database persistence, user isolation, and all data properly saved and retrieved!** 🚀 diff --git a/docs/PERSONA_DATA_MIGRATION_GUIDE.md b/docs/PERSONA_DATA_MIGRATION_GUIDE.md deleted file mode 100644 index 178da796..00000000 --- a/docs/PERSONA_DATA_MIGRATION_GUIDE.md +++ /dev/null @@ -1,215 +0,0 @@ -# Persona Data Table Migration Guide - -## Overview -This guide explains how to create the `persona_data` table for storing Step 4 (Persona Generation) data from the onboarding flow. - -## Background -The `persona_data` table was missing from the database schema, causing Step 4 onboarding data to only be saved to JSON files instead of the database. This migration adds the required table with proper user isolation. - -## Migration Methods - -### Method 1: Automatic Migration (Recommended) -The easiest way is to restart your backend server. The table will be created automatically when the application starts. - -```bash -# Stop the backend if running (Ctrl+C) -# Then restart it: -python backend/start_alwrity_backend.py --dev -``` - -**How it works:** -- The `init_database()` function in `backend/services/database.py` (line 69) calls `OnboardingBase.metadata.create_all(bind=engine)` -- This automatically creates all missing tables defined in the `OnboardingBase` models -- Since we added the `PersonaData` model, it will be created on startup - -### Method 2: Manual Migration Script -If you prefer to run the migration manually without restarting the backend: - -```bash -# From the project root directory: -python backend/scripts/create_persona_data_table.py -``` - -**What this script does:** -1. Checks if the `persona_data` table already exists -2. Creates the table if it doesn't exist -3. Verifies the table was created successfully -4. Shows the table structure (columns and types) -5. Lists all onboarding-related tables and their status - -### Method 3: SQL Migration (Production/Manual) -For production environments or manual database management: - -```bash -# Connect to your PostgreSQL database and run: -psql -U your_username -d your_database -f backend/database/migrations/add_persona_data_table.sql -``` - -**Or using psql command:** -```sql --- Connect to your database -\c your_database - --- Run the migration -\i backend/database/migrations/add_persona_data_table.sql - --- Verify the table was created -\dt persona_data -\d persona_data -``` - -## Table Structure - -The `persona_data` table includes: - -| Column | Type | Description | -|--------|------|-------------| -| `id` | SERIAL | Primary key | -| `session_id` | INTEGER | Foreign key to `onboarding_sessions.id` | -| `core_persona` | JSONB | Core persona data (demographics, psychographics, etc.) | -| `platform_personas` | JSONB | Platform-specific personas (LinkedIn, Twitter, etc.) | -| `quality_metrics` | JSONB | Quality assessment metrics | -| `selected_platforms` | JSONB | Array of selected platforms | -| `created_at` | TIMESTAMP | When the record was created | -| `updated_at` | TIMESTAMP | When the record was last updated | - -**Indexes:** -- `idx_persona_data_session_id` - For efficient session lookups -- `idx_persona_data_created_at` - For time-based queries - -**Constraints:** -- Foreign key to `onboarding_sessions.id` with `ON DELETE CASCADE` - -## Verification - -After running the migration, verify it was successful: - -### Using Python: -```python -from services.database import engine -from sqlalchemy import inspect - -inspector = inspect(engine) -tables = inspector.get_table_names() - -if 'persona_data' in tables: - print("✅ persona_data table exists") - columns = inspector.get_columns('persona_data') - for col in columns: - print(f" - {col['name']}: {col['type']}") -else: - print("❌ persona_data table not found") -``` - -### Using SQL: -```sql --- Check if table exists -SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_name = 'persona_data' -); - --- Show table structure -\d persona_data -``` - -### Using the Backend Logs: -After restarting the backend, look for this log message: -``` -Database initialized successfully with all models including subscription system and business info -``` - -Then, when a user completes Step 4, you should see: -``` -✅ DATABASE: Persona data saved to database for user user_xxxxx -``` - -## Expected Behavior After Migration - -Once the table is created and the backend is running with the updated code: - -1. **Step 4 Completion:** - - Persona data (corePersona, platformPersonas, qualityMetrics, selectedPlatforms) is saved to the database - - Database logs confirm: `✅ DATABASE: Persona data saved to database for user {user_id}` - -2. **User Isolation:** - - Each user's persona data is stored separately using their `user_id` - - Data is linked to the user's onboarding session - -3. **Data Persistence:** - - Persona data is no longer lost when JSON files are deleted - - Data survives backend restarts - - Data is accessible across different sessions - -## Troubleshooting - -### Table Already Exists Error -If you see "table already exists" errors: -- This is normal! It means the table was already created -- The migration scripts use `CREATE TABLE IF NOT EXISTS` to handle this -- No action needed - -### Permission Denied -If you get permission errors: -``` -ERROR: permission denied for schema public -``` -**Solution:** Ensure your database user has CREATE TABLE permissions: -```sql -GRANT CREATE ON SCHEMA public TO your_database_user; -``` - -### Foreign Key Constraint Fails -If the `onboarding_sessions` table doesn't exist: -1. Run the full database initialization first: - ```python - from services.database import init_database - init_database() - ``` -2. Then create the `persona_data` table - -### Missing Database Connection -If you see "database connection" errors: -1. Check your `DATABASE_URL` environment variable -2. Ensure PostgreSQL/SQLite is running -3. Verify database credentials - -## Rollback (If Needed) - -To remove the `persona_data` table: - -```sql -DROP TABLE IF EXISTS persona_data CASCADE; -``` - -**Warning:** This will delete all persona data. Use with caution! - -## Related Files - -- **Model:** `backend/models/onboarding.py` - `PersonaData` class (lines 149-183) -- **Service:** `backend/services/onboarding_database_service.py` - `save_persona_data()` method (lines 298-338) -- **Migration:** `backend/database/migrations/add_persona_data_table.sql` -- **Script:** `backend/scripts/create_persona_data_table.py` -- **Database Init:** `backend/services/database.py` - `init_database()` function (line 63-80) - -## Summary - -**Recommended approach for local development:** -```bash -# Just restart the backend - the table will be created automatically! -python backend/start_alwrity_backend.py --dev -``` - -**For production deployment:** -- The table will be created automatically on first deployment -- Or run the SQL migration manually before deployment -- No downtime required - the migration is additive only - -## Questions? - -If you encounter issues: -1. Check the backend logs for detailed error messages -2. Verify all onboarding tables exist using the verification script -3. Ensure your database user has proper permissions -4. Check that the `PersonaData` model is imported correctly in `backend/services/onboarding_database_service.py` - diff --git a/docs/PHASE2_QUICK_WINS_IMPLEMENTED.md b/docs/PHASE2_QUICK_WINS_IMPLEMENTED.md deleted file mode 100644 index aec0cfba..00000000 --- a/docs/PHASE2_QUICK_WINS_IMPLEMENTED.md +++ /dev/null @@ -1,280 +0,0 @@ -# Phase 2 Quick Wins - Implementation Summary - -## ✅ All 4 Quick Wins Completed (2 hours total) - -### 1. Industry-Specific Placeholder Rotation ✅ (30min) -**Status**: Completed - -**What Changed**: -- Created `getIndustryPlaceholders()` function with 8 industry-specific placeholder sets -- Each industry has 3 tailored research examples (Healthcare, Technology, Finance, Marketing, Business, Education, Real Estate, Travel) -- Placeholders automatically update when industry dropdown changes -- Fallback to generic placeholders for unlisted industries - -**Example**: -```typescript -// Healthcare industry shows: -"Research: AI-powered diagnostic tools in clinical practice -💡 What you'll get: -• FDA-approved AI medical devices -• Clinical accuracy and patient outcomes -• Implementation costs and ROI" - -// Technology industry shows: -"Investigate: Latest developments in edge computing and IoT -💡 What you'll get: -• Edge AI deployment strategies -• 5G integration and performance -• Industry use cases and benchmarks" -``` - -**User Experience**: -- Users see relevant examples for their industry immediately -- Reduces cognitive load (no generic "research this topic" suggestions) -- Showcases research capabilities for specific domains - ---- - -### 2. Persona-Specific Preset Generation ✅ (30min) -**Status**: Completed - -**What Changed**: -- Created `generatePersonaPresets()` function in `ResearchTest.tsx` -- Dynamically generates 3 persona-aware presets on page load: - 1. `{Industry} Trends` - Comprehensive research on latest innovations - 2. `{Audience} Insights` - Targeted research on audience pain points - 3. `{Industry} Best Practices` - Success stories and implementations -- Pulls industry, audience, Exa category, and domains from persona API -- Fallback to default presets if no persona data - -**Example**: -```typescript -// For a Healthcare professional targeting medical professionals: -Presets generated: -1. "Healthcare Trends" (Comprehensive, Exa, research papers, pubmed.gov) -2. "Medical professionals Insights" (Targeted, Exa, research papers) -3. "Healthcare Best Practices" (Comprehensive, Exa, research papers) -``` - -**User Experience**: -- First-time users see presets tailored to their onboarding data -- One-click research with optimized configurations -- No manual setup required for common research tasks - ---- - -### 3. Dynamic Domain Updates on Industry Change ✅ (15min) -**Status**: Completed - -**What Changed**: -- Added `useEffect` hook that watches `state.industry` -- Automatically updates Exa `include_domains` when industry changes -- Automatically updates Exa `category` based on industry -- Uses same domain/category maps as backend API (13 industries covered) - -**Example**: -```typescript -// User changes industry from "General" to "Healthcare" -Auto-updates: -- exa_include_domains: ['pubmed.gov', 'nejm.org', 'thelancet.com', 'nih.gov'] -- exa_category: 'research paper' - -// User changes to "Finance" -Auto-updates: -- exa_include_domains: ['wsj.com', 'bloomberg.com', 'ft.com', 'reuters.com'] -- exa_category: 'financial report' -``` - -**User Experience**: -- No manual domain input required -- Industry experts get authoritative sources automatically -- Seamless experience when switching industries - ---- - -### 4. Auto-Suggest Research Mode Badge ✅ (45min) -**Status**: Completed - -**What Changed**: -- Created `suggestResearchMode()` function analyzing query complexity -- Logic: - - URL detected → `comprehensive` - - >20 words → `comprehensive` - - >10 words or >3 keywords → `targeted` - - Simple query → `basic` -- Added green "💡 Try {mode}" button when suggestion differs from selected mode -- Button appears only when keywords are entered -- One-click to apply suggested mode - -**Example**: -```typescript -// User types: "AI tools" -Suggests: basic ✅ (matches current selection) - -// User types: "Research AI-powered marketing automation tools with ROI analysis" -Suggests: comprehensive 💡 Try comprehensive (button appears) - -// User types: "https://techcrunch.com/ai-trends" -Suggests: comprehensive 💡 Try comprehensive (URL detected) -``` - -**User Experience**: -- Smart guidance without being intrusive -- Users can ignore suggestion or apply with one click -- Reduces decision paralysis for new users - ---- - -## Files Modified - -### Frontend -1. **`frontend/src/components/Research/steps/ResearchInput.tsx`** (major changes) - - Added `getIndustryPlaceholders()` function - - Added `suggestResearchMode()` function - - Added dynamic placeholder rotation based on industry - - Added dynamic domain/category updates - - Added suggestion badge UI - - Added 3 new `useEffect` hooks - -2. **`frontend/src/pages/ResearchTest.tsx`** (moderate changes) - - Added `generatePersonaPresets()` function - - Added `personaData` and `displayPresets` state - - Added `useEffect` to load persona and generate presets - - Changed preset rendering from `samplePresets` to `displayPresets` - -3. **`frontend/src/api/researchConfig.ts`** (already exists) - - No changes needed (API already created in previous phase) - -### Backend -- No backend changes required! All features use existing APIs. - ---- - -## Code Statistics - -- **Total Lines Added**: ~350 lines -- **New Functions**: 3 (getIndustryPlaceholders, suggestResearchMode, generatePersonaPresets) -- **New useEffects**: 4 -- **New State Variables**: 2 (suggestedMode, displayPresets, personaData) -- **Industries Supported**: 13 (Healthcare, Technology, Finance, Marketing, Business, Education, Real Estate, Travel, Fashion, Sports, Science, Law, Entertainment) - ---- - -## Testing Checklist - -### Feature 1: Industry Placeholders -- [ ] Open research wizard -- [ ] Select "Healthcare" → See medical-related placeholders -- [ ] Select "Technology" → See tech-related placeholders -- [ ] Select "General" → See generic placeholders -- [ ] Wait 4 seconds → Placeholder rotates - -### Feature 2: Persona Presets -- [ ] Complete onboarding with "Technology" industry -- [ ] Open `/research-test` page -- [ ] See "Technology Trends" preset generated -- [ ] Click preset → All fields auto-filled with tech domains - -### Feature 3: Dynamic Domains -- [ ] Enter keywords in textarea -- [ ] Change industry to "Healthcare" -- [ ] Select "Comprehensive" mode -- [ ] Check Exa domains → Should show pubmed.gov, nejm.org -- [ ] Change to "Finance" → Domains update to wsj.com, bloomberg.com - -### Feature 4: Mode Suggestion -- [ ] Type short query (e.g., "AI tools") → No suggestion (basic is correct) -- [ ] Type long query (e.g., "Research comprehensive AI marketing automation...") → See "💡 Try comprehensive" button -- [ ] Paste URL → See "💡 Try comprehensive" button -- [ ] Click suggestion button → Mode changes automatically - ---- - -## Performance Impact - -- **Initial Load**: +0.2s (one-time API call for persona data) -- **Industry Change**: <10ms (local computation only) -- **Placeholder Rotation**: Negligible (interval-based, no re-renders) -- **Mode Suggestion**: <5ms (simple word counting logic) -- **Memory**: +2KB (placeholder and preset data in memory) - ---- - -## User Impact (Expected) - -### Quantitative -- **Time to Start Research**: -40% (reduced from ~60s to ~36s) -- **Configuration Accuracy**: +65% (auto-filled domains/categories) -- **Preset Usage**: +80% (persona-specific presets more relevant) -- **Mode Selection Errors**: -50% (smart suggestions guide users) - -### Qualitative -- **Beginner Experience**: "It feels like the system knows what I'm trying to do" -- **Expert Experience**: "I can still customize, but defaults are spot-on" -- **Personalization**: "The examples shown are actually relevant to my work" -- **Confidence**: "The suggestions help me feel like I'm making the right choices" - ---- - -## Next Steps (Phase 2 - Medium Priority) - -### 5. Smart Keyword Expansion (1 hour) -- Expand user keywords with industry-specific terms -- Example: "AI tools" + Healthcare → ["AI tools", "medical AI", "healthcare automation"] - -### 6. Research History Hints (1 hour) -- Track last 5 research queries in localStorage -- Show "Recently researched" quick-select buttons - ---- - -## Backward Compatibility - -- ✅ All existing functionality preserved -- ✅ No breaking changes to APIs -- ✅ Works with or without persona data (graceful fallback) -- ✅ No database migrations required -- ✅ Works with existing presets (persona presets are additive) - ---- - -## Success Metrics (30 days post-deployment) - -### Track -1. **Preset Click Rate**: % of users who click persona-generated presets -2. **Suggestion Acceptance Rate**: % of users who accept mode suggestions -3. **Industry-Specific Placeholder Views**: Unique users who see personalized placeholders -4. **Configuration Changes**: Average number of manual config changes (should decrease) - -### Goal -- 70% of users use persona-generated presets at least once -- 60% of mode suggestions are accepted -- 50% reduction in manual domain/category configuration -- 4.5+ star rating for research UX (up from baseline) - ---- - -## Lessons Learned - -### What Worked Well -1. **No Backend Changes**: All features client-side = faster implementation -2. **Graceful Fallbacks**: System works even without persona data -3. **Progressive Enhancement**: Each feature adds value independently -4. **Code Reuse**: Domain/category maps used in multiple places - -### Challenges -1. **State Management**: Multiple `useEffect` hooks required careful dependency arrays -2. **Placeholder Rotation**: Needed to reset index on industry change -3. **Suggestion Timing**: Decided to show suggestions only after keywords entered (not on every keystroke) - ---- - -## Conclusion - -All 4 quick wins delivered on time (2 hours total). The research experience is now significantly more intelligent and personalized without requiring AI APIs. Foundation ready for advanced AI enhancements (smart query expansion, learning from history). - -**Status**: ✅ Production Ready -**Deployment**: Can be deployed immediately -**Risk**: Low (client-side only, graceful fallbacks) -**User Impact**: High (immediate personalization) - diff --git a/docs/PLATFORM_SPECIFIC_EDITOR_ARCHITECTURE.md b/docs/PLATFORM_SPECIFIC_EDITOR_ARCHITECTURE.md deleted file mode 100644 index 2caee19c..00000000 --- a/docs/PLATFORM_SPECIFIC_EDITOR_ARCHITECTURE.md +++ /dev/null @@ -1,429 +0,0 @@ -# 🏗️ Platform-Specific Editor Architecture & Smart Sharing Strategy - -## 📋 Overview - -This document outlines ALwrity's approach to building platform-specific editors that maintain excellence while sharing common utilities. The strategy prioritizes platform-specific user experience over generic reusability, ensuring each writing tool feels native to its platform while avoiding code duplication where it makes sense. - -## 🎯 Core Philosophy - -### **Platform-First Design** -- **User Experience Priority**: Each platform editor should feel native and familiar to its users -- **Platform-Specific Requirements**: Different social platforms have fundamentally different content needs -- **Brand Consistency**: Maintain platform personality and visual language -- **Feature Relevance**: Not all platforms need the same capabilities - -### **Smart Sharing Strategy** -- **Share Algorithms, Not UI**: Common utilities and logic, not presentation components -- **Share Utilities, Not Experiences**: Reusable functions, not user interface elements -- **Share Logic, Not Presentation**: Business logic and processing, not visual components -- **Quality Over Reusability**: Better to have excellent platform-specific editors than mediocre shared ones - -## 🏗️ Architecture Overview - -### **Directory Structure** -``` -frontend/src/components/ -├── shared/ # Truly platform-agnostic utilities -│ ├── core/ # Core shared components -│ │ ├── DiffPreview.tsx # Advanced diff system (algorithm only) -│ │ ├── ContentValidator.tsx # Basic validation logic -│ │ ├── ExportManager.tsx # Export utilities -│ │ └── Accessibility.tsx # Accessibility helpers -│ ├── hooks/ # Shared business logic hooks -│ │ ├── useEditorState.ts # Basic editor state management -│ │ ├── useContentHistory.ts # Undo/redo functionality -│ │ └── useAutoSave.ts # Auto-save logic -│ └── utils/ # Pure utility functions -│ ├── diffAlgorithms.ts # Diff computation algorithms -│ ├── textProcessing.ts # Text manipulation utilities -│ └── fileHandling.ts # File operations -├── LinkedInWriter/ # Platform-specific LinkedIn editor -│ ├── LinkedInEditor.tsx # LinkedIn-specific editor UI -│ ├── LinkedInPreview.tsx # LinkedIn preview rendering -│ ├── LinkedInMetrics.tsx # LinkedIn quality metrics -│ └── LinkedInActions.tsx # LinkedIn CopilotKit actions -├── FacebookWriter/ # Platform-specific Facebook editor -│ ├── FacebookEditor.tsx # Facebook-specific editor UI -│ ├── FacebookPreview.tsx # Facebook preview rendering -│ ├── FacebookMetrics.tsx # Facebook engagement metrics -│ └── FacebookActions.tsx # Facebook CopilotKit actions -└── TwitterWriter/ # Platform-specific Twitter editor - ├── TwitterEditor.tsx # Twitter-specific editor UI - ├── TwitterPreview.tsx # Twitter preview rendering - ├── TwitterMetrics.tsx # Twitter reach metrics - └── TwitterActions.tsx # Twitter CopilotKit actions -``` - -## 🔍 Platform-Specific Requirements Analysis - -### **LinkedIn (Professional Focus)** -- **Content Type**: Professional insights, industry analysis, B2B content -- **Tone**: Professional, authoritative, industry-focused -- **Features**: Citations, research sources, quality metrics, industry targeting -- **Limitations**: 3000 character limit, professional audience -- **UI/UX**: Clean, professional, business-oriented interface - -### **Facebook (Engagement Focus)** -- **Content Type**: Community engagement, personal stories, visual content -- **Tone**: Casual, friendly, community-oriented -- **Features**: Emotion selection, hashtag management, ad variations, story creation -- **Limitations**: 63,206 character limit, diverse audience -- **UI/UX**: Warm, engaging, community-focused interface - -### **Twitter (Viral Focus)** -- **Content Type**: Concise insights, trending topics, thread management -- **Tone**: Conversational, trending, viral potential -- **Features**: Character count, trending hashtags, thread builder, viral metrics -- **Limitations**: 280 character limit, fast-paced content -- **UI/UX**: Compact, fast, trending-focused interface - -### **Instagram (Visual Focus)** -- **Content Type**: Visual storytelling, aesthetic content, hashtag strategy -- **Tone**: Creative, aesthetic, lifestyle-oriented -- **Features**: Visual preview, hashtag optimization, story sequences -- **Limitations**: Image-first content, hashtag limits -- **UI/UX**: Visual, creative, aesthetic-focused interface - -### **YouTube (SEO Focus)** -- **Content Type**: Video descriptions, SEO optimization, playlist management -- **Tone**: Informative, SEO-focused, audience retention -- **Features**: SEO analysis, thumbnail optimization, description formatting -- **Limitations**: Description length, SEO requirements -- **UI/UX**: SEO-focused, analytical, retention-oriented interface - -## 🎨 What to Share vs. What to Keep Platform-Specific - -### **✅ DO Share (Common Utilities)** - -#### **1. Diff Preview System (High Value, Low Customization)** -```typescript -// frontend/src/components/shared/core/DiffPreview.tsx -export const DiffPreview: React.FC = ({ - originalText, - newText, - customStyles, // Platform can override styling - showLineNumbers = false, - showWordLevel = true -}) => { - // Core diff algorithm (platform-agnostic) - const diffResult = computeDiff(originalText, newText); - - return ( -
- {/* Platform can customize styling but logic is shared */} - {diffResult.changes.map(change => ( - - ))} -
- ); -}; -``` - -#### **2. Content Validation (Basic Rules)** -```typescript -// frontend/src/components/shared/core/ContentValidator.tsx -export class ContentValidator { - // Platform-agnostic validations - static hasContent(text: string): boolean; - static hasMinLength(text: string, min: number): boolean; - static hasMaxLength(text: string, max: number): boolean; - static hasProfanity(text: string): boolean; - - // Platform-specific validations (override in platform) - static validateForPlatform(text: string, platform: string): ValidationResult; -} -``` - -#### **3. Export Utilities (Pure Functions)** -```typescript -// frontend/src/components/shared/utils/exportUtils.ts -export const exportAsText = (content: string): string; -export const exportAsMarkdown = (content: string): string; -export const exportAsHTML = (content: string): string; -export const exportAsJSON = (content: string, metadata: any): string; -``` - -#### **4. Text Processing (Algorithms)** -```typescript -// frontend/src/components/shared/utils/textProcessing.ts -export const wordCount = (text: string): number; -export const readingTime = (text: string): number; -export const extractHashtags = (text: string): string[]; -export const cleanText = (text: string): string; -``` - -### **❌ DON'T Share (Keep Platform-Specific)** - -#### **1. Editor UI Components** -- Text area components -- Toolbar layouts -- Button styles -- Color schemes -- Typography choices - -#### **2. Preview Rendering** -- Content display logic -- Platform-specific formatting -- Visual styling -- Layout arrangements - -#### **3. Quality Metrics Display** -- Metric visualization -- Score presentation -- Platform-specific KPIs -- Visual indicators - -#### **4. CopilotKit Actions** -- Platform-specific suggestions -- Workflow automation -- AI interaction patterns -- Context awareness - -#### **5. Platform Validation Rules** -- Character limits -- Content restrictions -- Platform policies -- Feature availability - -## 🚀 Implementation Examples - -### **LinkedIn Editor (Professional Focus)** -```typescript -// frontend/src/components/LinkedInWriter/LinkedInEditor.tsx -const LinkedInEditor: React.FC = () => { - return ( -
- {/* LinkedIn-specific UI */} - - - - - - - {/* LinkedIn-specific editor */} - - - {/* LinkedIn-specific preview */} - - - {/* Shared diff preview with LinkedIn styling */} - -
- ); -}; -``` - -### **Facebook Editor (Engagement Focus)** -```typescript -// frontend/src/components/FacebookWriter/FacebookEditor.tsx -const FacebookEditor: React.FC = () => { - return ( -
- {/* Facebook-specific UI */} - - - - - - - {/* Facebook-specific editor */} - - - {/* Facebook-specific preview */} - - - {/* Shared diff preview with Facebook styling */} - -
- ); -}; -``` - -### **Twitter Editor (Viral Focus)** -```typescript -// frontend/src/components/TwitterWriter/TwitterEditor.tsx -const TwitterEditor: React.FC = () => { - return ( -
- {/* Twitter-specific UI */} - - - - - - - {/* Twitter-specific editor */} - - - {/* Twitter-specific preview */} - - - {/* Shared diff preview with Twitter styling */} - -
- ); -}; -``` - -## 📅 Implementation Roadmap - -### **Phase 1: Platform-Specific Editors (Weeks 1-2)** -1. **Keep existing LinkedIn editor** as-is (it's already excellent) -2. **Enhance Facebook editor** with platform-specific features -3. **Create Twitter editor** with Twitter-specific UI/UX -4. **No shared components yet** - focus on platform excellence - -### **Phase 2: Smart Sharing (Weeks 3-4)** -1. **Extract only truly common utilities**: - - Diff algorithms - - Text processing functions - - File handling - - Basic validation -2. **Keep platform-specific**: - - Editor UI - - Preview rendering - - Quality metrics - - CopilotKit actions - -### **Phase 3: Platform Enhancement (Weeks 5-6)** -1. **Enhance each platform editor** with unique features -2. **Add platform-specific CopilotKit actions** -3. **Implement platform-specific quality metrics** -4. **Create platform-specific export formats** - -### **Phase 4: Advanced Features (Weeks 7-8)** -1. **Platform-specific analytics** -2. **Advanced CopilotKit integrations** -3. **Performance optimization** -4. **Accessibility improvements** - -## 🎯 Key Principles - -### **1. Platform-First Design** -- Start with platform-specific requirements -- Don't force commonality where it doesn't exist -- Each platform should feel native to its users - -### **2. Smart Sharing** -- Share algorithms, not UI components -- Share utilities, not experiences -- Share logic, not presentation - -### **3. CopilotKit Integration** -- Each platform gets its own CopilotKit actions -- Platform-specific suggestions and workflows -- Maintain platform personality in AI interactions - -### **4. Quality Over Reusability** -- Better to have 3 excellent platform-specific editors -- Than 1 mediocre shared editor -- Focus on user experience, not code reuse - -### **5. Incremental Improvement** -- Start with platform-specific excellence -- Add shared utilities gradually -- Measure impact before expanding sharing - -## 🔧 Technical Considerations - -### **1. State Management** -- Each platform maintains its own state -- Shared utilities are stateless -- Platform-specific hooks for complex logic - -### **2. Styling Strategy** -- Platform-specific CSS modules -- Shared utility classes for common patterns -- CSS custom properties for theming - -### **3. Performance** -- Lazy load platform-specific components -- Shared utilities are tree-shakeable -- Platform-specific code splitting - -### **4. Testing Strategy** -- Platform-specific test suites -- Shared utility unit tests -- Integration tests for shared components - -## 📊 Success Metrics - -### **1. User Experience** -- Platform-specific satisfaction scores -- Feature adoption rates -- User engagement metrics - -### **2. Development Efficiency** -- Time to implement new platforms -- Bug fix resolution time -- Feature development velocity - -### **3. Code Quality** -- Platform-specific component quality -- Shared utility reliability -- Overall maintainability - -### **4. Business Impact** -- Platform-specific user retention -- Feature usage across platforms -- Overall platform adoption - -## 🎉 Conclusion - -This architecture strikes the right balance between platform excellence and smart code sharing. By keeping editors platform-specific while sharing only truly common utilities, we maintain the quality user experience that makes each platform feel native while avoiding unnecessary code duplication. - -The key is to start with platform-specific excellence and add shared utilities incrementally, always measuring the impact on both user experience and development efficiency. This approach ensures that ALwrity's writing tools remain best-in-class for each platform while maintaining a sustainable and maintainable codebase. - ---- - -**Document Version**: 1.0 -**Last Updated**: January 2025 -**Next Review**: February 2025 -**Contributors**: AI Assistant, Development Team diff --git a/docs/REMAINING_SESSION_ID_ISSUES.md b/docs/REMAINING_SESSION_ID_ISSUES.md deleted file mode 100644 index b1d34cd4..00000000 --- a/docs/REMAINING_SESSION_ID_ISSUES.md +++ /dev/null @@ -1,105 +0,0 @@ -# Remaining Hardcoded Session ID Issues -**Date:** October 1, 2025 -**Status:** ✅ COMPLETED -**Priority:** ✅ All Critical Issues Fixed - ---- - -## Overview - -While fixing the critical user isolation issue in `component_logic.py`, I discovered additional files with hardcoded session IDs. - -**All Critical Files Fixed:** -- ✅ `backend/api/component_logic.py` - All instances fixed -- ✅ `backend/api/onboarding_utils/onboarding_summary_service.py` - All instances fixed -- ✅ `backend/api/content_planning/services/calendar_generation_service.py` - All instances fixed -- ✅ `backend/api/content_planning/api/routes/calendar_generation.py` - All instances fixed - ---- - -## Why These Are Less Critical - -### **component_logic.py (FIXED TODAY):** -- 🔴 **Critical:** Used in onboarding (Step 2, Step 3) -- 🔴 **High Traffic:** Every user goes through onboarding -- 🔴 **Sensitive Data:** Website analyses, preferences -- 🔴 **Direct Impact:** Users see each other's data - -### **Remaining Files:** -- 🟡 **Medium:** Used in specific features (calendar, summaries) -- 🟡 **Lower Traffic:** Not all users use these features -- 🟡 **Less Sensitive:** Summary data, calendar preferences -- 🟡 **Indirect Impact:** Mostly read operations - -**Priority:** Fix in next iteration, not blocking production - ---- - -## Recommended Fix Strategy - -### **Same Pattern as Today:** - -```python -# 1. Add import -from middleware.auth_middleware import get_current_user - -# 2. Update function signature -async def endpoint_name( - request, - current_user: Dict[str, Any] = Depends(get_current_user) -): - # 3. Get user ID - user_id = str(current_user.get('id')) - user_id_int = hash(user_id) % 2147483647 - - # 4. Use user_id_int instead of session_id = 1 -``` - ---- - -## Files to Fix - -### **1. onboarding_summary_service.py** -**Estimated Effort:** 15 minutes -**Impact:** Summary feature user isolation - -### **2. calendar_generation_service.py** -**Estimated Effort:** 20 minutes -**Impact:** Calendar feature user isolation - -### **3. calendar_generation.py** -**Estimated Effort:** 15 minutes -**Impact:** Calendar routes user isolation - -**Total Estimated:** 50 minutes - ---- - -## Testing Plan (When Fixed) - -```python -# Test 1: User A generates calendar -calendar_a = generate_calendar(user_a_id) - -# Test 2: User B generates calendar -calendar_b = generate_calendar(user_b_id) - -# Test 3: Verify isolation -assert calendar_a != calendar_b -assert user_a_id in calendar_a_data -assert user_b_id not in calendar_a_data -``` - ---- - -## Conclusion - -✅ **Critical onboarding endpoints:** FIXED COMPLETELY -✅ **Calendar generation endpoints:** FIXED COMPLETELY -✅ **Summary service endpoints:** FIXED COMPLETELY -✅ **No linting errors:** All changes compile perfectly -✅ **Security:** 100% of critical vulnerabilities eliminated - -**All critical user isolation issues have been resolved!** -See `docs/USER_ISOLATION_COMPLETE_FIX.md` for full details. - diff --git a/docs/STEP3_USER_ISOLATION_FIX.md b/docs/STEP3_USER_ISOLATION_FIX.md deleted file mode 100644 index 6367aee2..00000000 --- a/docs/STEP3_USER_ISOLATION_FIX.md +++ /dev/null @@ -1,255 +0,0 @@ -# Step 3 Competitor Discovery - User Isolation & Logging Fix -**Date:** October 1, 2025 -**Status:** ✅ COMPLETE -**Priority:** 🔴 Critical (User-Blocking Issue) - ---- - -## 🐛 Issue Summary - -### User-Reported Problem: -When navigating from Step 2 to Step 3 in the onboarding flow, users encountered a **500 Internal Server Error**. - -### Root Causes: -1. **Missing Clerk Authentication**: Step 3 `/discover-competitors` endpoint was not using Clerk auth, resulting in `session_id=None` -2. **Pydantic Validation Error**: `CompetitorDiscoveryResponse` model requires `session_id` to be a string, but received `None` -3. **Verbose Logging**: Exa API responses with markdown content were being logged in full, cluttering console output - ---- - -## ✅ Fixes Applied - -### 1. Added Clerk Authentication to Step 3 - -**File:** `backend/api/onboarding_utils/step3_routes.py` - -**Changes:** -```python -# Before: No authentication -async def discover_competitors( - request: CompetitorDiscoveryRequest, - background_tasks: BackgroundTasks -) - -# After: Clerk authentication added -async def discover_competitors( - request: CompetitorDiscoveryRequest, - background_tasks: BackgroundTasks, - current_user: dict = Depends(get_current_user) # ✅ NEW -) -``` - -**Impact:** -- Now uses Clerk user ID instead of deprecated `session_id` -- Ensures user isolation - each user's competitor data is separate -- Fixes the `session_id=None` error - ---- - -### 2. Updated Session ID Handling - -**Before:** -```python -# ❌ Could be None -session_id = request.session_id if request.session_id else "user_authenticated" -result = await step3_research_service.discover_competitors_for_onboarding( - session_id=request.session_id # Could be None -) -``` - -**After:** -```python -# ✅ Always has value from Clerk -clerk_user_id = str(current_user.get('id')) -result = await step3_research_service.discover_competitors_for_onboarding( - session_id=clerk_user_id # Always valid Clerk user ID -) -``` - ---- - -### 3. Reduced Verbose Exa API Logging - -**File:** `backend/services/research/exa_service.py` - -**Before (Lines 137-144):** -```python -# ❌ Logs ENTIRE response including markdown content -logger.info(f"Raw Exa API response for {user_url}:") -logger.info(f" - Request ID: {getattr(search_result, 'request_id', 'N/A')}") -logger.info(f" - Results count: {len(getattr(search_result, 'results', []))}") -logger.info(f" - Cost: ${getattr(getattr(search_result, 'cost_dollars', None), 'total', 0)}") -logger.info(f" - Full raw response: {search_result}") # 🔴 VERBOSE! -``` - -**After:** -```python -# ✅ Logs only summary, avoids markdown content -logger.info(f"📊 Exa API response for {user_url}:") -logger.info(f" ├─ Request ID: {getattr(search_result, 'request_id', 'N/A')}") -logger.info(f" ├─ Results count: {len(getattr(search_result, 'results', []))}") -logger.info(f" └─ Cost: ${getattr(getattr(search_result, 'cost_dollars', None), 'total', 0)}") -# Note: Full raw response contains verbose markdown content - logging only summary -# To see full response, set EXA_DEBUG=true in environment -``` - -**Similar fix applied to line 420-421 (social media discovery)** - ---- - -## 📊 Before vs After - -### Error Flow (Before): - -``` -User clicks "Continue" in Step 2 - ↓ -Frontend calls POST /api/onboarding/step3/discover-competitors - ↓ -Backend: session_id = request.session_id # None - ↓ -Service returns result with session_id=None - ↓ -Pydantic validation: CompetitorDiscoveryResponse - ↓ -❌ ERROR: session_id must be string, got None - ↓ -500 Internal Server Error shown to user -``` - -### Success Flow (After): - -``` -User clicks "Continue" in Step 2 - ↓ -Frontend calls POST /api/onboarding/step3/discover-competitors (with JWT) - ↓ -Backend: Clerk middleware validates JWT → current_user - ↓ -clerk_user_id = current_user.get('id') # ✅ Valid Clerk ID - ↓ -Service performs discovery with clerk_user_id - ↓ -Returns CompetitorDiscoveryResponse with valid session_id - ↓ -✅ SUCCESS: User sees competitor results -``` - ---- - -## 🔍 Console Output Comparison - -### Before (Verbose): -``` -INFO|exa_service.py:138| Raw Exa API response for https://alwrity.com: -INFO|exa_service.py:144| - Full raw response: SearchResponse( - results=[ - Result( - url='https://competitor1.com', - title='Competitor 1', - text='# Long markdown content here...\n\n## Section 1\n\nLorem ipsum dolor sit amet...\n\n## Section 2\n\nConsectetur adipiscing elit...\n\n[Full page content - 5000+ characters]', - ... - ), - Result( - url='https://competitor2.com', - title='Competitor 2', - text='# Another long markdown...\n\n[Another 5000+ characters]', - ... - ), - ... [10 more results with full markdown content] - ] -) -``` - -### After (Clean): -``` -INFO|exa_service.py:138| 📊 Exa API response for https://alwrity.com: -INFO|exa_service.py:139| ├─ Request ID: req_abc123xyz -INFO|exa_service.py:140| ├─ Results count: 10 -INFO|exa_service.py:141| └─ Cost: $0.05 -``` - -**Reduction:** ~95% less console output! 🎉 - ---- - -## 🧪 Testing Performed - -### Manual Testing: -1. ✅ Step 2 → Step 3 navigation works -2. ✅ No 500 errors -3. ✅ Competitor discovery completes successfully -4. ✅ Console logs are clean and readable -5. ✅ User data is isolated per Clerk user ID - -### Linting: -```bash -✅ No Python linting errors -✅ No TypeScript errors -✅ All imports resolved -``` - ---- - -## 📝 Additional Notes - -### Environment Variable (Optional): -For advanced debugging, you can enable full Exa API response logging: - -```bash -# In .env file -EXA_DEBUG=true -``` - -This will restore the full response logging for troubleshooting purposes. - -### User Testing Recommendation: -The user mentioned testing with `num_results=1` to optimize. The current default is: - -**File:** `backend/api/onboarding_utils/step3_routes.py:29` -```python -num_results: int = Field(25, ge=1, le=100, description="Number of competitors to discover") -``` - -**Suggestion:** User can adjust this in the frontend request or we can reduce the default to 10 for faster responses: - -```python -num_results: int = Field(10, ge=1, le=100, description="Number of competitors to discover") -``` - ---- - -## 🎯 Impact - -| Metric | Before | After | Change | -|--------|--------|-------|--------| -| **Step 3 Success Rate** | ❌ 0% (500 errors) | ✅ 100% | +100% | -| **User Isolation** | ⚠️ Partial | ✅ Complete | 100% | -| **Console Log Lines** | 🔴 5000+ per request | ✅ 4 per request | -99% | -| **User Experience** | ❌ Broken | ✅ Working | Fixed | - ---- - -## 🚀 Deployment Status - -✅ **Ready for Production** -- No breaking changes -- Backward compatible -- Immediate fix for user-blocking issue -- Clean console output for better debugging - ---- - -## 📚 Related Documentation - -- `docs/USER_ISOLATION_COMPLETE_FIX.md` - Overall user isolation strategy -- `docs/SESSION_SUMMARY_USER_ISOLATION_FIX.md` - Previous session fixes -- `backend/api/onboarding_utils/step3_routes.py` - Step 3 routes implementation -- `backend/services/research/exa_service.py` - Exa API service - ---- - -**Fixed by:** AI Assistant (Claude Sonnet 4.5) -**Tested:** Manual testing completed -**Status:** ✅ Production Ready - diff --git a/docs/STEP_2_BACKWARD_COMPATIBLE_FIX.md b/docs/STEP_2_BACKWARD_COMPATIBLE_FIX.md deleted file mode 100644 index cb746dc7..00000000 --- a/docs/STEP_2_BACKWARD_COMPATIBLE_FIX.md +++ /dev/null @@ -1,67 +0,0 @@ -# Step 2 Backward Compatible Fix - -## Problem - -After updating Step 2 and Step 6 for database migration, the "existing analysis cache" feature in Step 2 stopped working because we have two different `session_id` strategies: - -1. **Legacy**: SHA256 hash of Clerk user_id → `session_id = 724716666` -2. **New**: `OnboardingSession.id` (auto-increment) → `session_id = 1, 2, 3...` - -## Non-Breaking Solution - -Made the `check-existing` endpoint **support BOTH approaches** for backward compatibility. - -### Change Made - -**File**: `backend/api/component_logic.py` (Line 660-696) - -```python -@router.get("/style-detection/check-existing/{website_url:path}") -async def check_existing_analysis(website_url, current_user): - """Check if analysis exists (supports both session_id types).""" - - # Try Approach 1: SHA256 hash (legacy) - user_id_int = clerk_user_id_to_int(user_id) - existing_analysis = analysis_service.check_existing_analysis(user_id_int, website_url) - - # Try Approach 2: OnboardingSession.id (new) if not found - if not existing_analysis or not existing_analysis.get('exists'): - onboarding_service = OnboardingDatabaseService() - session = onboarding_service.get_session_by_user(user_id, db_session) - if session: - existing_analysis = analysis_service.check_existing_analysis(session.id, website_url) - - return existing_analysis -``` - -## Benefits - -✅ **No breaking changes** - Steps 1-5 continue working as before -✅ **Backward compatible** - Finds analysis saved with either session_id type -✅ **Cache works** - Existing analysis feature now works correctly -✅ **Step 6 works** - Can retrieve data saved via OnboardingSession approach - -## Testing - -1. **Restart backend** to load the updated endpoint -2. **Go to Step 2** and enter a website URL you've analyzed before -3. **Verify** you see the "Use existing analysis?" dialog -4. **Click "Use Existing"** to load previous analysis -5. **Navigate to Step 6** to verify all data displays correctly - -## What This Fixes - -- ✅ Existing analysis cache now works -- ✅ Step 6 can retrieve website analysis -- ✅ No impact on Steps 1, 3, 4, 5 -- ✅ Backward compatible with old data - -## Status - -✅ **Fixed**: Backward-compatible endpoint update applied -⏳ **Pending**: Restart backend and test - ---- - -**Next Action**: Restart backend server and test the existing analysis feature in Step 2. - diff --git a/docs/STEP_2_COLUMN_ERROR_FIX.md b/docs/STEP_2_COLUMN_ERROR_FIX.md deleted file mode 100644 index 81598c1e..00000000 --- a/docs/STEP_2_COLUMN_ERROR_FIX.md +++ /dev/null @@ -1,63 +0,0 @@ -# Step 2 Column Error Fix - -## Problem - -After adding `brand_analysis` and `content_strategy_insights` columns to the `WebsiteAnalysis` model, the `/api/onboarding/style-detection/session-analyses` endpoint is failing with: - -``` -ERROR|website_analysis_service.py:164:get_session_analyses| Error retrieving analyses for session 360913797: (sqlite3.OperationalError) no such column: website_analyses.brand_analysis -``` - -## Root Cause - -The `WebsiteAnalysisService` is trying to query the `website_analyses` table, but there's a mismatch between: - -1. **Model Definition**: Includes `brand_analysis` and `content_strategy_insights` columns -2. **Database Schema**: The columns exist (verified by migration script) -3. **Runtime**: SQLAlchemy is failing to find the columns - -## Possible Causes - -1. **Multiple Database Files**: The service might be connecting to a different database file than the one we migrated -2. **Connection Caching**: SQLAlchemy might be using cached schema information -3. **Backend Restart Needed**: The model changes require a backend restart - -## Solution - -**Restart the backend server** to reload the updated model definitions and database connections. - -### Steps - -1. **Stop the current backend server** (Ctrl+C) -2. **Start the backend server**: - ```bash - python backend/start_alwrity_backend.py - ``` - -## Verification - -After restart, the `/api/onboarding/style-detection/session-analyses` endpoint should work without errors. - -## What We Kept - -- ✅ **New database columns**: `brand_analysis` and `content_strategy_insights` -- ✅ **Migration completed**: Columns exist in database -- ✅ **Model updated**: `WebsiteAnalysis` includes new fields -- ✅ **Service updated**: `OnboardingDatabaseService` saves new fields - -## What We Reverted - -- 🔄 **Data transformation**: Back to simple `step.data` passing -- 🔄 **Check-existing endpoint**: Back to original SHA256 approach - -## Expected Result - -After restart: -- ✅ **Existing analysis cache works** (Step 2) -- ✅ **Step 6 data retrieval works** (FinalStep) -- ✅ **Complete data saved** (including brand analysis) -- ✅ **No breaking changes** (Steps 1-5) - ---- - -**Next Action**: Restart backend server and test both Step 2 and Step 6. diff --git a/docs/STEP_2_COMPLETE_DATA_FLOW_ANALYSIS.md b/docs/STEP_2_COMPLETE_DATA_FLOW_ANALYSIS.md deleted file mode 100644 index faa2606d..00000000 --- a/docs/STEP_2_COMPLETE_DATA_FLOW_ANALYSIS.md +++ /dev/null @@ -1,435 +0,0 @@ -# Step 2 (Website Analysis) - Complete Data Flow Analysis - -## Overview - -Step 2 performs comprehensive website analysis including crawling, style detection, pattern analysis, and guideline generation. This document maps the complete data flow from frontend to database. - -## API Endpoints Called - -### 1. `/api/onboarding/style-detection/complete` (PRIMARY) - -**Purpose**: Main analysis endpoint that performs the complete workflow - -**Request** (`POST`): -```typescript -{ - url: string, - include_patterns: true, - include_guidelines: true -} -``` - -**Response**: -```typescript -{ - success: boolean, - crawl_result: { - content: string, - success: boolean, - timestamp: string - }, - style_analysis: { - writing_style: {...}, - content_characteristics: {...}, - target_audience: {...}, - content_type: {...}, - recommended_settings: {...}, - brand_analysis: {...}, // ← Rich brand insights - content_strategy_insights: {...} // ← SWOT analysis - }, - style_patterns: { - style_consistency: {...}, - unique_elements: {...} - }, - style_guidelines: { - guidelines: [...], - best_practices: [...], - avoid_elements: [...], - content_strategy: [...], - ai_generation_tips: [...], - competitive_advantages: [...], - content_calendar_suggestions: [...] - }, - analysis_id: number, - warning?: string -} -``` - -### 2. `/api/onboarding/style-detection/check-existing/{url}` (OPTIONAL) - -**Purpose**: Check if analysis already exists for this URL - -**Response**: -```typescript -{ - exists: boolean, - analysis_id?: number, - analysis?: {...} // Full analysis data if exists -} -``` - -### 3. `/api/onboarding/style-detection/analysis/{id}` (OPTIONAL) - -**Purpose**: Load existing analysis by ID - -### 4. `/api/onboarding/style-detection/session-analyses` (OPTIONAL) - -**Purpose**: Get last analysis from session for pre-filling - -## Complete Data Structure Collected - -### 1. **Writing Style** (`writing_style`) -```json -{ - "tone": "Professional, Informative", - "voice": "Active, Direct", - "complexity": "Moderate", - "engagement_level": "High", - "brand_personality": "Trustworthy, Expert", - "formality_level": "Semi-formal", - "emotional_appeal": "Rational with emotional hooks" -} -``` - -### 2. **Content Characteristics** (`content_characteristics`) -```json -{ - "sentence_structure": "Mix of short and medium sentences", - "vocabulary_level": "Professional/Business", - "paragraph_organization": "Clear topic sentences", - "content_flow": "Logical progression", - "readability_score": "8th-10th grade", - "content_density": "Information-rich", - "visual_elements_usage": "Moderate" -} -``` - -### 3. **Target Audience** (`target_audience`) -```json -{ - "demographics": ["B2B", "Enterprise clients", "IT professionals"], - "expertise_level": "Intermediate to Advanced", - "industry_focus": "Technology/SaaS", - "geographic_focus": "Global, US-focused", - "psychographic_profile": "Innovation-driven, ROI-focused", - "pain_points": ["Efficiency", "Scalability"], - "motivations": ["Business growth", "Competitive advantage"] -} -``` - -### 4. **Content Type** (`content_type`) -```json -{ - "primary_type": "Educational/Thought Leadership", - "secondary_types": ["Case Studies", "Product Descriptions"], - "purpose": "Inform and convert", - "call_to_action": "Demo request, Free trial", - "conversion_focus": "Lead generation", - "educational_value": "High" -} -``` - -### 5. **Brand Analysis** (`brand_analysis`) ⭐ **IMPORTANT** -```json -{ - "brand_voice": "Authoritative yet approachable", - "brand_values": ["Innovation", "Reliability", "Customer success"], - "brand_positioning": "Premium solution provider", - "competitive_differentiation": "AI-powered automation", - "trust_signals": ["Case studies", "Testimonials", "Security badges"], - "authority_indicators": ["Industry certifications", "Expert team"] -} -``` - -### 6. **Content Strategy Insights** (`content_strategy_insights`) ⭐ **IMPORTANT** -```json -{ - "strengths": [ - "Clear value proposition", - "Strong technical authority", - "Engaging storytelling" - ], - "weaknesses": [ - "Limited social proof", - "Technical jargon overuse" - ], - "opportunities": [ - "Video content", - "Interactive demos", - "Industry thought leadership" - ], - "threats": [ - "Competitor content marketing", - "Market saturation" - ], - "recommended_improvements": [ - "Add more case studies", - "Simplify technical explanations", - "Increase content frequency" - ], - "content_gaps": [ - "Beginner-level tutorials", - "Comparison guides", - "Industry trend analysis" - ] -} -``` - -### 7. **Recommended Settings** (`recommended_settings`) -```json -{ - "writing_tone": "Professional yet conversational", - "target_audience": "B2B decision makers", - "content_type": "Educational with conversion focus", - "creativity_level": "Balanced", - "geographic_location": "US/Global", - "industry_context": "B2B SaaS" -} -``` - -### 8. **Crawl Result** (`crawl_result`) -```json -{ - "content": "Full crawled text content...", - "success": true, - "timestamp": "2025-10-11T12:00:00Z" -} -``` - -### 9. **Style Patterns** (`style_patterns`) -```json -{ - "style_consistency": { - "consistency_score": 0.85, - "common_patterns": ["Data-driven claims", "Action-oriented CTAs"], - "variations": ["Blog vs landing page tone"] - }, - "unique_elements": [ - "Custom terminology", - "Brand-specific phrases", - "Signature formatting" - ] -} -``` - -### 10. **Style Guidelines** (`style_guidelines`) -```json -{ - "guidelines": [ - "Use active voice", - "Start with benefit statements", - "Support claims with data" - ], - "best_practices": [ - "Lead with customer pain points", - "Include social proof", - "Clear CTAs" - ], - "avoid_elements": [ - "Passive voice", - "Overly technical jargon", - "Generic claims" - ], - "content_strategy": [ - "Focus on thought leadership", - "Build trust through expertise", - "Address buyer journey stages" - ], - "ai_generation_tips": [ - "Emphasize ROI and metrics", - "Use industry-specific examples", - "Balance technical depth with clarity" - ], - "competitive_advantages": [ - "Unique positioning statement", - "Differentiating features", - "Customer success stories" - ], - "content_calendar_suggestions": [ - "Weekly blog posts", - "Monthly case studies", - "Quarterly industry reports" - ] -} -``` - -## Current Database Storage (OnboardingDatabaseService) - -### What's Saved to `onboarding_sessions.website_analyses` Table: - -**File**: `backend/services/onboarding_database_service.py` (Line 173) - -```python -WebsiteAnalysis( - session_id=session.id, - website_url=analysis_data.get('website_url'), - writing_style=analysis_data.get('writing_style'), # ✅ - content_characteristics=analysis_data.get('content_characteristics'), # ✅ - target_audience=analysis_data.get('target_audience'), # ✅ - content_type=analysis_data.get('content_type'), # ✅ - recommended_settings=analysis_data.get('recommended_settings'),# ✅ - crawl_result=analysis_data.get('crawl_result'), # ✅ - style_patterns=analysis_data.get('style_patterns'), # ✅ - style_guidelines=analysis_data.get('style_guidelines'), # ✅ - status='completed' -) -``` - -### ❌ What's MISSING from Database Storage: - -1. **brand_analysis** - NOT saved to `onboarding_database_service` -2. **content_strategy_insights** - NOT saved to `onboarding_database_service` - -### ✅ What's Saved to `website_analyses` Table (via WebsiteAnalysisService): - -**File**: `backend/services/website_analysis_service.py` (Lines 44-87) - -This service saves to a DIFFERENT table (`website_analyses` not `onboarding_sessions.website_analyses`). - -```python -# Saves to: website_analyses table -WebsiteAnalysis( - session_id=session_id, # Integer session ID - website_url=website_url, - writing_style=style_analysis.get('writing_style'), - content_characteristics=style_analysis.get('content_characteristics'), - target_audience=style_analysis.get('target_audience'), - content_type=style_analysis.get('content_type'), - recommended_settings=style_analysis.get('recommended_settings'), - brand_analysis=style_analysis.get('brand_analysis'), # ✅ SAVED HERE! - content_strategy_insights=style_analysis.get('content_strategy_insights'), # ✅ SAVED HERE! - crawl_result=analysis_data.get('crawl_result'), - style_patterns=analysis_data.get('style_patterns'), - style_guidelines=analysis_data.get('style_guidelines'), - status='completed' -) -``` - -## The Problem: Dual Database Persistence - -We have **TWO separate database save operations** happening: - -### 1. `/style-detection/complete` endpoint (component_logic.py) -- Saves to `website_analyses` table via `WebsiteAnalysisService` -- Uses **Integer session_id** (converted from Clerk ID via SHA256) -- Saves **ALL fields** including `brand_analysis` and `content_strategy_insights` - -### 2. `OnboardingProgress.save_progress()` (api_key_manager.py) -- Saves to `onboarding_sessions.website_analyses` table via `OnboardingDatabaseService` -- Uses **String user_id** (Clerk ID) -- **MISSING** `brand_analysis` and `content_strategy_insights` - -## Current Frontend Data Structure - -**File**: `frontend/src/components/OnboardingWizard/WebsiteStep.tsx` (Line 386) - -```typescript -const stepData = { - website: fixedUrl, // ← Should be "website_url" - domainName: domainName, - analysis: { // ← Nested structure - writing_style: {...}, - content_characteristics: {...}, - target_audience: {...}, - content_type: {...}, - brand_analysis: {...}, // ✅ Present - content_strategy_insights: {...}, // ✅ Present - recommended_settings: {...}, - // ... ALL the fields from API response - guidelines: [...], - best_practices: [...], - avoid_elements: [...], - style_patterns: {...}, - // etc. - }, - useAnalysisForGenAI: true -}; -``` - -## Solution Required - -### 1. Fix Data Transformation (COMPLETED ✅) - -**File**: `backend/services/api_key_manager.py` (Line 278) - -Already fixed to flatten the structure: - -```python -elif step.step_number == 2: # Website Analysis - # Transform frontend data structure to match database schema - analysis_for_db = { - 'website_url': step.data.get('website', ''), - 'status': 'completed' - } - # Merge analysis fields if they exist - if 'analysis' in step.data and step.data['analysis']: - analysis_for_db.update(step.data['analysis']) - - self.db_service.save_website_analysis(self.user_id, analysis_for_db, db) -``` - -### 2. Update OnboardingDatabaseService to Save ALL Fields - -**File**: `backend/services/onboarding_database_service.py` - -**NEEDED**: Add `brand_analysis` and `content_strategy_insights` to the save operation. - -Check if `WebsiteAnalysis` model has these columns: - -```python -# Line 206-213 (existing code) -website_url=analysis_data.get('website_url', ''), -writing_style=analysis_data.get('writing_style'), -content_characteristics=analysis_data.get('content_characteristics'), -target_audience=analysis_data.get('target_audience'), -content_type=analysis_data.get('content_type'), -recommended_settings=analysis_data.get('recommended_settings'), -brand_analysis=analysis_data.get('brand_analysis'), # ← ADD THIS -content_strategy_insights=analysis_data.get('content_strategy_insights'), # ← ADD THIS -crawl_result=analysis_data.get('crawl_result'), -style_patterns=analysis_data.get('style_patterns'), -style_guidelines=analysis_data.get('style_guidelines'), -``` - -### 3. Verify Database Model Supports These Fields - -**File**: `backend/models/onboarding.py` - -Check `WebsiteAnalysis` model for: -- `brand_analysis` column (JSON) -- `content_strategy_insights` column (JSON) - -If missing, add migration. - -## Recommendation - -1. ✅ **Data transformation fix is complete** (api_key_manager.py updated) -2. ⏳ **Check WebsiteAnalysis model** for brand_analysis and content_strategy_insights columns -3. ⏳ **Update OnboardingDatabaseService.save_website_analysis()** to include these fields -4. ⏳ **Restart backend** to apply changes -5. ⏳ **Re-run Step 2** to save complete data -6. ⏳ **Verify Step 6** displays all fields - -## Benefits of Complete Data Storage - -With `brand_analysis` and `content_strategy_insights` saved: - -1. **Better Content Generation**: AI can align with brand values -2. **Strategic Insights**: SWOT analysis informs content strategy -3. **Competitive Intelligence**: Differentiation factors for positioning -4. **Content Planning**: Recommendations and calendar suggestions -5. **Quality Assurance**: Consistency checking against brand guidelines - -## Status - -- ✅ API endpoint returns complete data -- ✅ Frontend receives and displays complete data -- ✅ Data transformation fix applied (flattening structure) -- ⏳ Database model verification needed -- ⏳ OnboardingDatabaseService update needed -- ⏳ Testing required - ---- - -**Next Action**: Check `WebsiteAnalysis` model and update `OnboardingDatabaseService` to save ALL fields. - diff --git a/docs/STEP_2_DUAL_PERSISTENCE_ISSUE_AND_FIX.md b/docs/STEP_2_DUAL_PERSISTENCE_ISSUE_AND_FIX.md deleted file mode 100644 index 211be449..00000000 --- a/docs/STEP_2_DUAL_PERSISTENCE_ISSUE_AND_FIX.md +++ /dev/null @@ -1,170 +0,0 @@ -# Step 2 Dual Persistence Issue and Fix - -## Problem Discovery - -User reported that after our database migration changes, they cannot see previous analysis in Step 2's cache/existing analysis feature. - -## Root Cause Analysis - -### Two Competing Systems Writing to Same Table - -Both systems write to `website_analyses` table but with **different `session_id` strategies**: - -#### 1. Style Detection System (Original) -**Endpoints**: `/api/onboarding/style-detection/*` -**Service**: `WebsiteAnalysisService` -**Session ID Type**: `INTEGER` (SHA256 hash of Clerk user_id) - -```python -# component_logic.py line 523 -user_id_int = clerk_user_id_to_int(user_id) # SHA256 hash → 724716666 - -# Saves to website_analyses table -analysis_service.save_analysis(user_id_int, request.url, response_data) -# Result: session_id = 724716666 -``` - -#### 2. Onboarding System (New) -**Service**: `OnboardingDatabaseService` -**Session ID Type**: Auto-increment integer from `OnboardingSession.id` - -```python -# OnboardingDatabaseService -session = self.get_or_create_session(user_id, session_db) # user_id is Clerk string -# session.id = 1, 2, 3, etc. (auto-increment) - -# Saves to website_analyses table -analysis = WebsiteAnalysis(session_id=session.id, ...) # session_id = 1, 2, 3... -``` - -### The Conflict - -When a user analyzes their website: - -1. **Analysis happens** → `/style-detection/complete` saves with `session_id = 724716666` -2. **Check existing** → Queries for `session_id = 724716666` ✅ **FINDS IT** -3. **User clicks Continue** → `OnboardingProgress.save_progress()` saves with `session_id = 3` (from `OnboardingSession.id`) -4. **Result**: **TWO records** in `website_analyses` for same URL but different `session_id` values! - -```sql --- Table: website_analyses -id | session_id | website_url | writing_style | ... -----|-------------|-----------------------|---------------|---- -42 | 724716666 | https://example.com | {...} | ... (from /style-detection/complete) -43 | 3 | https://example.com | {...} | ... (from OnboardingProgress.save_progress) -``` - -### Why User Can't See Previous Analysis - -After our migration: -- `OnboardingSession.user_id` changed to **STRING** (Clerk ID) -- `OnboardingSession.id` is auto-increment (1, 2, 3...) -- Step 2 queries using SHA256 hash approach (724716666) -- Onboarding system saves using auto-increment ID (3) -- They never match! - -## Solutions - -### Option 1: Unified Session ID Strategy (RECOMMENDED) - -Make **both systems** use the same `session_id` approach: the `OnboardingSession.id`. - -**Changes Required**: - -1. Update `/style-detection/complete` endpoint to use `OnboardingSession`: - -```python -# backend/api/component_logic.py -@router.post("/style-detection/complete") -async def complete_style_detection(request, current_user): - user_id = str(current_user.get('id')) - - # Get or create OnboardingSession (not SHA256 hash) - from services.onboarding_database_service import OnboardingDatabaseService - onboarding_service = OnboardingDatabaseService() - db = next(get_db()) - session = onboarding_service.get_or_create_session(user_id, db) - session_id = session.id # Use OnboardingSession.id instead of hash - - # Save using this session_id - analysis_service.save_analysis(session_id, request.url, response_data) -``` - -2. Update `check-existing` endpoint similarly: - -```python -@router.get("/style-detection/check-existing/{website_url:path}") -async def check_existing_analysis(website_url, current_user): - user_id = str(current_user.get('id')) - - # Get OnboardingSession (not SHA256 hash) - onboarding_service = OnboardingDatabaseService() - db = next(get_db()) - session = onboarding_service.get_session_by_user(user_id, db) - - if not session: - return {"exists": False} - - # Query using OnboardingSession.id - existing = analysis_service.check_existing_analysis(session.id, website_url) - return existing -``` - -3. Update `get-analysis/:id` endpoint similarly. - -### Option 2: Keep Dual System, Sync Both Records - -Keep both approaches but ensure both records are created/updated together. - -❌ **Not recommended** - More complexity, potential for sync issues. - -### Option 3: Query Both Ways - -Query by both session_id types and merge results. - -❌ **Not recommended** - Hacky, doesn't solve root cause. - -## Implementation Plan - -### Phase 1: Update Style Detection Endpoints ✅ - -1. Update `/style-detection/complete` to use `OnboardingSession.id` -2. Update `/style-detection/check-existing/{url}` to use `OnboardingSession.id` -3. Update `/style-detection/analysis/{id}` to use `OnboardingSession.id` -4. Update `/style-detection/session-analyses` to use `OnboardingSession.id` - -### Phase 2: Data Migration - -Clean up duplicate records: - -```sql --- Keep only OnboardingSession-based records -DELETE FROM website_analyses -WHERE session_id NOT IN ( - SELECT id FROM onboarding_sessions -); -``` - -### Phase 3: Remove SHA256 Hash Approach - -Remove `clerk_user_id_to_int()` function as it's no longer needed. - -## Benefits of Unified Approach - -1. ✅ **Single source of truth** for session_id -2. ✅ **No duplicate records** -3. ✅ **Consistent user isolation** -4. ✅ **Simpler codebase** -5. ✅ **Cache/existing analysis works correctly** -6. ✅ **Step 6 can retrieve data** - -## Status - -- ⏳ **Pending**: Update style detection endpoints -- ⏳ **Pending**: Test existing analysis feature -- ⏳ **Pending**: Data migration script - ---- - -**Next Action**: Update `/style-detection/*` endpoints to use `OnboardingSession.id` instead of SHA256 hash. - diff --git a/docs/STEP_2_REVERT_SUMMARY.md b/docs/STEP_2_REVERT_SUMMARY.md deleted file mode 100644 index e51365d5..00000000 --- a/docs/STEP_2_REVERT_SUMMARY.md +++ /dev/null @@ -1,99 +0,0 @@ -# Step 2 Changes - Revert Summary - -## What We Kept (✅) - -### 1. **New Database Fields Added** -- **Model**: `backend/models/onboarding.py` - Added `brand_analysis` and `content_strategy_insights` columns -- **Service**: `backend/services/onboarding_database_service.py` - Updated to save these new fields -- **Migration**: `backend/scripts/add_brand_analysis_columns.py` - Successfully ran - -**Result**: Step 2 now saves complete data including brand analysis and content strategy insights. - -### 2. **Database Model Updates** -- **OnboardingSession**: `user_id` changed from `Integer` to `String(255)` for Clerk compatibility -- **Migration**: `backend/scripts/migrate_user_id_to_string.py` - Successfully ran - -**Result**: Database supports Clerk user IDs (strings). - -### 3. **Step 6 Data Retrieval** -- **OnboardingSummaryService**: Updated to read from database instead of file-based storage -- **OnboardingDatabaseService**: Added `get_persona_data()` method - -**Result**: Step 6 can retrieve data from previous steps. - -## What We Reverted (🔄) - -### 1. **Data Transformation Logic** -**Reverted**: `backend/services/api_key_manager.py` (Lines 278-289) - -**Before** (complex transformation): -```python -# Transform frontend data structure to match database schema -analysis_for_db = { - 'website_url': step.data.get('website', ''), - 'status': 'completed' -} -# Merge analysis fields if they exist -if 'analysis' in step.data and step.data['analysis']: - analysis_for_db.update(step.data['analysis']) - -self.db_service.save_website_analysis(self.user_id, analysis_for_db, db) -``` - -**After** (simple, original): -```python -self.db_service.save_website_analysis(self.user_id, step.data, db) -``` - -### 2. **Check-Existing Endpoint** -**Reverted**: `backend/api/component_logic.py` (Lines 660-689) - -**Before** (dual session_id support): -```python -# Try BOTH session_id approaches for backward compatibility -# Approach 1: SHA256 hash (legacy) -user_id_int = clerk_user_id_to_int(user_id) -existing_analysis = analysis_service.check_existing_analysis(user_id_int, website_url) - -# Approach 2: OnboardingSession.id (new) -if not existing_analysis or not existing_analysis.get('exists'): - # ... complex dual lookup -``` - -**After** (original simple approach): -```python -# Use authenticated Clerk user ID for proper user isolation -user_id_int = clerk_user_id_to_int(user_id) -existing_analysis = analysis_service.check_existing_analysis(user_id_int, website_url) -``` - -## Current State - -### ✅ **What Works** -- **Step 2**: Analyzes websites and saves complete data (including new fields) -- **Existing Analysis Cache**: Should work with original logic -- **Step 6**: Can retrieve data from database -- **Database**: Supports Clerk user IDs and new fields - -### ⏳ **What to Test** -1. **Restart backend server** to load reverted changes -2. **Test Step 2 existing analysis cache** - should work now -3. **Test Step 6 data retrieval** - should still work - -## Why We Reverted - -The complex changes were causing issues with the existing analysis cache. By reverting to the original simple logic while keeping the new database fields, we get: - -- ✅ **Complete data saved** (including brand_analysis and content_strategy_insights) -- ✅ **Existing analysis cache works** (original logic restored) -- ✅ **Step 6 works** (database retrieval still functional) -- ✅ **No breaking changes** (Steps 1-5 continue working) - -## Next Steps - -1. **Restart backend server** -2. **Test existing analysis feature** in Step 2 -3. **Verify Step 6** still shows data correctly - -The system should now work as expected with complete data storage but without the complex transformation logic that was breaking the cache feature. - diff --git a/docs/STEP_2_SQLALCHEMY_CACHE_FIX.md b/docs/STEP_2_SQLALCHEMY_CACHE_FIX.md deleted file mode 100644 index a89d3139..00000000 --- a/docs/STEP_2_SQLALCHEMY_CACHE_FIX.md +++ /dev/null @@ -1,84 +0,0 @@ -# Step 2 SQLAlchemy Cache Fix - -## Problem - -After adding `brand_analysis` and `content_strategy_insights` columns to the database and model, the `/api/onboarding/style-detection/session-analyses` endpoint was failing with: - -``` -ERROR|website_analysis_service.py:164:get_session_analyses| Error retrieving analyses for session 360913797: (sqlite3.OperationalError) no such column: website_analyses.brand_analysis -``` - -## Root Cause - -**SQLAlchemy ORM Schema Caching**: The SQLAlchemy ORM had cached the old table schema and was not picking up the new columns, even though: - -- ✅ The database migration was successful -- ✅ The columns exist in the database (verified by direct SQL queries) -- ✅ The backend server was restarted - -This is a known issue with SQLAlchemy when adding new columns to existing models. - -## Solution - -**Temporarily remove the new columns from the model** to clear the SQLAlchemy cache, then restart the backend. - -### Changes Made - -#### 1. **Model Changes** (`backend/models/onboarding.py`) -```python -# Commented out the new columns temporarily -# brand_analysis = Column(JSON) # Brand voice, values, positioning, competitive differentiation -# content_strategy_insights = Column(JSON) # SWOT analysis, strengths, weaknesses, opportunities, threats - -def to_dict(self): - return { - # ... other fields ... - # 'brand_analysis': self.brand_analysis, - # 'content_strategy_insights': self.content_strategy_insights, - # ... rest of fields ... - } -``` - -#### 2. **Service Changes** (`backend/services/onboarding_database_service.py`) -```python -# Commented out the new field assignments -# existing.brand_analysis = analysis_data.get('brand_analysis') -# existing.content_strategy_insights = analysis_data.get('content_strategy_insights') - -# brand_analysis=analysis_data.get('brand_analysis'), -# content_strategy_insights=analysis_data.get('content_strategy_insights'), -``` - -## Expected Result - -After restarting the backend: - -- ✅ **Step 2 existing analysis cache works** (no more SQL errors) -- ✅ **Step 6 data retrieval works** (core functionality preserved) -- ✅ **All existing functionality preserved** (Steps 1-5 continue working) - -## Next Steps - -1. **Restart the backend server** to load the updated model -2. **Test Step 2** - existing analysis cache should work without errors -3. **Test Step 6** - data retrieval should work -4. **Later**: Re-add the new columns once the cache issue is resolved - -## Alternative Solutions (Future) - -Once the cache issue is resolved, we can: - -1. **Re-add the new columns** to the model -2. **Use `MetaData.reflect()`** to force schema refresh -3. **Restart the backend** to pick up the new columns -4. **Test complete data storage** including brand analysis - -## Status - -✅ **Temporary fix applied** - commented out problematic columns -⏳ **Pending**: Backend restart and testing -⏳ **Future**: Re-add new columns once cache is cleared - ---- - -**Next Action**: Restart backend server and test Step 2 and Step 6 functionality. diff --git a/docs/STEP_2_WEBSITE_ANALYSIS_DATA_TRANSFORMATION_FIX.md b/docs/STEP_2_WEBSITE_ANALYSIS_DATA_TRANSFORMATION_FIX.md deleted file mode 100644 index 27643187..00000000 --- a/docs/STEP_2_WEBSITE_ANALYSIS_DATA_TRANSFORMATION_FIX.md +++ /dev/null @@ -1,188 +0,0 @@ -# Step 2 Website Analysis Data Transformation Fix - -## Problem - -Step 6 (FinalStep) was not displaying website analysis data, even though: -- API Keys were successfully saved and retrieved ✅ -- Research Preferences were successfully saved and retrieved ✅ -- Persona Data was successfully saved and retrieved ✅ -- Website Analysis was **NOT being saved** to the database ❌ - -## Root Cause - -**Data Structure Mismatch** between frontend and backend: - -### Frontend Data Structure (WebsiteStep.tsx) - -```typescript -const stepData = { - website: "https://example.com", // ← Note: "website", not "website_url" - domainName: "example.com", - analysis: { // ← Nested object - writing_style: { ... }, - content_characteristics: { ... }, - target_audience: { ... }, - content_type: { ... }, - // etc. - }, - useAnalysisForGenAI: true -}; -``` - -### Database Schema Expects (Flat Structure) - -```python -{ - 'website_url': 'https://example.com', # ← "website_url" at root level - 'writing_style': { ... }, # ← All fields at root level - 'content_characteristics': { ... }, - 'target_audience': { ... }, - 'content_type': { ... }, - 'recommended_settings': { ... }, - 'crawl_result': { ... }, - 'style_patterns': { ... }, - 'style_guidelines': { ... }, - 'status': 'completed' -} -``` - -## The Issue - -In `backend/services/api_key_manager.py` (line 278-280), the code was passing `step.data` directly to `save_website_analysis()`: - -```python -elif step.step_number == 2: # Website Analysis - self.db_service.save_website_analysis(self.user_id, step.data, db) -``` - -But `step.data` had this structure: -```python -{ - 'website': 'https://example.com', - 'analysis': { - 'writing_style': { ... }, - # ... - } -} -``` - -The database service expected `website_url` at the root level and all analysis fields flattened, so it couldn't find any of the data and saved an empty record (or didn't save at all). - -## Solution - -Transform the frontend data structure to match the database schema before saving: - -**File**: `backend/services/api_key_manager.py` (lines 278-289) - -```python -elif step.step_number == 2: # Website Analysis - # Transform frontend data structure to match database schema - analysis_for_db = { - 'website_url': step.data.get('website', ''), - 'status': 'completed' - } - # Merge analysis fields if they exist - if 'analysis' in step.data and step.data['analysis']: - analysis_for_db.update(step.data['analysis']) - - self.db_service.save_website_analysis(self.user_id, analysis_for_db, db) - logger.info(f"✅ DATABASE: Website analysis saved to database for user {self.user_id}") -``` - -### What This Does: - -1. **Creates base structure**: `{'website_url': '...', 'status': 'completed'}` -2. **Flattens nested `analysis` object**: Uses `.update()` to merge all analysis fields to root level -3. **Result**: Data matches database schema exactly - -### Example Transformation: - -**Before** (frontend format): -```python -{ - 'website': 'https://example.com', - 'analysis': { - 'writing_style': {'tone': 'Professional'}, - 'target_audience': {'demographics': ['B2B']} - } -} -``` - -**After** (database format): -```python -{ - 'website_url': 'https://example.com', - 'status': 'completed', - 'writing_style': {'tone': 'Professional'}, - 'target_audience': {'demographics': ['B2B']} -} -``` - -## Testing - -To verify the fix: - -1. **Restart the backend server** to load the updated code -2. **Complete Step 2** (Website Analysis) in the onboarding flow -3. **Check backend logs** for: - ``` - ✅ DATABASE: Website analysis saved to database for user {user_id} - ``` -4. **Navigate to Step 6** (FinalStep) -5. **Verify** website URL and style analysis are displayed - -### Expected Backend Logs After Fix: - -``` -INFO|api_key_manager.py:289|✅ DATABASE: Website analysis saved to database for user {user_id} -INFO|onboarding_summary_service.py:85|Retrieved website analysis from database for user {user_id} -``` - -## Related Files - -- `frontend/src/components/OnboardingWizard/WebsiteStep.tsx` - Frontend data structure -- `backend/services/api_key_manager.py` - Data transformation logic -- `backend/services/onboarding_database_service.py` - Database save/retrieve methods -- `backend/models/onboarding.py` - WebsiteAnalysis model schema - -## Why This Pattern? - -This is a common issue in full-stack applications where: -1. **Frontend** optimizes for UI structure (nested for component organization) -2. **Database** optimizes for query performance (flat for indexing) -3. **Backend middleware** transforms between the two - -## Alternative Solutions Considered - -### Option 1: Change Frontend Structure -❌ **Rejected**: Would break all existing Step 2 components and localStorage caching - -### Option 2: Change Database Schema -❌ **Rejected**: Would require complex JSON queries and lose type safety - -### Option 3: Transform in Middleware (Selected) ✅ -✅ **Best**: Minimal code change, maintains backward compatibility, clear separation of concerns - -## Future Improvements - -Consider adding a **data transformation layer** for all onboarding steps to handle similar mismatches proactively: - -```python -class OnboardingDataTransformer: - @staticmethod - def transform_step_2(frontend_data: Dict) -> Dict: - """Transform Step 2 data from frontend to database format.""" - return { - 'website_url': frontend_data.get('website', ''), - 'status': 'completed', - **frontend_data.get('analysis', {}) - } -``` - -This would centralize all data transformations and make the codebase more maintainable. - -## Status - -✅ **Fixed**: Website analysis data now saves correctly to database -⏳ **Pending**: Restart backend and test with actual user flow - diff --git a/docs/STEP_6_DATABASE_MIGRATION_COMPLETE.md b/docs/STEP_6_DATABASE_MIGRATION_COMPLETE.md deleted file mode 100644 index e6f36efa..00000000 --- a/docs/STEP_6_DATABASE_MIGRATION_COMPLETE.md +++ /dev/null @@ -1,273 +0,0 @@ -# Step 6 Data Retrieval Fix - Complete Documentation - -## Problem Summary - -Step 6 (FinalStep) of the onboarding wizard was not retrieving data from Steps 1-5, even though the data was being saved to both cache/localStorage and the database. - -## Root Cause - -The system is in **migration mode**: transitioning from **file-based storage** to **database storage**. - -### What Was Happening: - -1. **Steps 1-5**: Saving data to BOTH: - - JSON files (`.onboarding_progress_{user_id}.json`) for backward compatibility - - Database tables (`api_keys`, `website_analyses`, `research_preferences`, `persona_data`) - -2. **Step 6**: Was trying to read from file-based storage using `OnboardingProgress.get_step()`, which was inconsistent with the database-first approach needed for production deployment. - -3. **Database Schema Mismatch**: - - The `OnboardingSession.user_id` column was defined as `Integer` in `backend/models/onboarding.py` - - The entire system uses **Clerk user IDs** which are **strings** (e.g., `"user_2abc123xyz"`) - - When querying the database with `OnboardingSession.user_id == user_id` (string), no results were returned - -## Solution Implemented - -### 1. Updated Database Model ✅ - -**File**: `backend/models/onboarding.py` - -```python -class OnboardingSession(Base): - __tablename__ = 'onboarding_sessions' - id = Column(Integer, primary_key=True, autoincrement=True) - user_id = Column(String(255), nullable=False) # Changed from Integer to String(255) - current_step = Column(Integer, default=1) - progress = Column(Float, default=0.0) - # ... rest of the model -``` - -**Why**: To accommodate Clerk user IDs which are strings, not integers. - -### 2. Ran Database Migration ✅ - -**Script**: `backend/scripts/migrate_user_id_to_string.py` - -The migration script: -- Backs up the existing database -- Creates a new table with `user_id` as `VARCHAR(255)` -- Copies all existing data -- Drops the old table -- Renames the new table -- **SQLite compatible** (handles SQLite's limitations with ALTER COLUMN) - -**Execution Result**: Successfully migrated the database schema. - -### 3. Updated OnboardingSummaryService ✅ - -**File**: `backend/api/onboarding_utils/onboarding_summary_service.py` - -**Changed FROM**: Reading from file-based `OnboardingProgress` - -```python -# OLD APPROACH (file-based) -self.onboarding_progress = get_onboarding_progress_for_user(user_id) -step_2 = self.onboarding_progress.get_step(2) -``` - -**Changed TO**: Reading from database using `OnboardingDatabaseService` - -```python -# NEW APPROACH (database) -self.db_service = OnboardingDatabaseService() - -# Get API keys from database -api_keys = self.db_service.get_api_keys(self.user_id, db) - -# Get website analysis from database -website_data = self.db_service.get_website_analysis(self.user_id, db) - -# Get research preferences from database -research_data = self.db_service.get_research_preferences(self.user_id, db) - -# Get persona data from database -persona_data = self.db_service.get_persona_data(self.user_id, db) -``` - -**Why**: To align with the database-first architecture needed for production deployment on Vercel + Render. - -### 4. Added Missing Database Method ✅ - -**File**: `backend/services/onboarding_database_service.py` - -Added new method: - -```python -def get_persona_data(self, user_id: str, db: Session = None) -> Optional[Dict[str, Any]]: - """Get persona data for user from database.""" - session = self.get_session_by_user(user_id, session_db) - if not session: - return None - - persona = session_db.query(PersonaData).filter( - PersonaData.session_id == session.id - ).first() - - return { - 'corePersona': persona.core_persona, - 'platformPersonas': persona.platform_personas, - 'qualityMetrics': persona.quality_metrics, - 'selectedPlatforms': persona.selected_platforms - } if persona else None -``` - -**Why**: This method was missing but needed by `OnboardingSummaryService` to retrieve persona data from the database. - -## Migration Architecture - -### Current State: Dual Persistence - -The system currently implements **dual persistence** during migration: - -``` -User Input (Steps 1-5) - ↓ -Save to BOTH: - ├─→ JSON File (.onboarding_progress_{user_id}.json) [Backward Compatibility] - └─→ Database (PostgreSQL/SQLite) [Production Ready] - -Step 6 Reads: - └─→ Database Only (via OnboardingDatabaseService) [Future Ready] -``` - -### Why Dual Persistence? - -1. **Backward Compatibility**: Existing development workflows continue to work -2. **Incremental Migration**: Can test database persistence without breaking anything -3. **Rollback Safety**: Can revert to file-based if issues arise -4. **Local Development**: `.env` files still work for local API keys - -### Production Deployment (Vercel + Render) - -**Vercel (Frontend)**: -- Ephemeral filesystem -- No persistent file storage -- **Must** use database for all data - -**Render (Backend)**: -- Ephemeral filesystem -- File-based storage lost on restart -- **Must** use database for persistence - -## Database Schema - -### OnboardingSession Table - -```sql -CREATE TABLE onboarding_sessions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id VARCHAR(255) NOT NULL, -- Clerk user ID (string) - current_step INTEGER DEFAULT 1, - progress FLOAT DEFAULT 0.0, - started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -``` - -### Related Tables - -- **api_keys**: Stores user-specific API keys -- **website_analyses**: Stores website analysis results -- **research_preferences**: Stores research and writing preferences -- **persona_data**: Stores generated persona data - -All tables use `session_id` (foreign key) to link to `onboarding_sessions.id`. - -## User Isolation - -The system now properly isolates user data: - -1. Each user gets their own `onboarding_session` record (by Clerk `user_id`) -2. All related data is scoped to that user's session -3. Queries always filter by `user_id` first -4. No cross-user data leakage possible - -## Testing Verification - -To verify the fix works: - -1. **Check Database Tables**: - ```bash - python backend/scripts/verify_onboarding_data.py - ``` - -2. **Test Step 6**: - - Complete Steps 1-5 in the frontend - - Navigate to Step 6 (FinalStep) - - Verify that all data from previous steps is displayed: - - API Keys count - - Website URL - - Research preferences - - Persona data - - Capabilities overview - -3. **Check Backend Logs**: - Look for these success messages: - ``` - ✅ DATABASE: API key for {provider} saved to database for user {user_id} - ✅ DATABASE: Website analysis saved to database for user {user_id} - ✅ DATABASE: Research preferences saved to database for user {user_id} - ✅ DATABASE: Persona data saved to database for user {user_id} - ``` - -## Files Changed - -### Backend - -1. `backend/models/onboarding.py` - - Changed `user_id` from `Integer` to `String(255)` - -2. `backend/services/onboarding_database_service.py` - - Added `get_persona_data()` method - -3. `backend/api/onboarding_utils/onboarding_summary_service.py` - - Refactored to use database instead of file-based storage - - Updated `_get_api_keys()` to read from database - - Updated `_get_website_analysis()` to read from database - - Updated `_get_research_preferences()` to read from database - - Updated `_get_personalization_settings()` to read from database - -4. `backend/scripts/migrate_user_id_to_string.py` - - Created SQLite-compatible migration script - - Successfully migrated database schema - -### Frontend - -No frontend changes required. The frontend already sends Clerk user IDs correctly. - -## Next Steps - -1. ✅ **Completed**: Database schema updated -2. ✅ **Completed**: Step 6 reads from database -3. ⏳ **Pending**: Test Step 6 with actual user data -4. ⏳ **Future**: Remove file-based persistence entirely (after full migration) - -## Deployment Readiness - -### Local Development -- ✅ Database persistence working -- ✅ File-based persistence still working (backward compatible) -- ✅ `.env` files still supported - -### Production (Vercel + Render) -- ✅ Database persistence working -- ✅ User isolation implemented -- ✅ No file-based dependencies -- ✅ Clerk user IDs fully supported - -**Status**: Ready for production deployment to Vercel + Render. - -## Key Takeaways - -1. **Clerk User IDs are Strings**: Always use `String(255)` for `user_id` columns -2. **Database-First for Production**: File-based storage won't work on Vercel/Render -3. **Dual Persistence is Temporary**: Eventually, remove file-based storage -4. **User Isolation is Critical**: All queries must filter by `user_id` -5. **Migration is Incremental**: Steps 1-5 save to both, Step 6 reads from database - -## Related Documentation - -- `docs/CRITICAL_ONBOARDING_DATABASE_MIGRATION.md` - Initial migration plan -- `docs/PERSONA_DATA_MIGRATION_GUIDE.md` - Persona data migration details -- `backend/database/migrations/` - SQL migration scripts - diff --git a/docs/STORY_GENERATION_READINESS_ASSESSMENT.md b/docs/STORY_GENERATION_READINESS_ASSESSMENT.md deleted file mode 100644 index d92ba9ac..00000000 --- a/docs/STORY_GENERATION_READINESS_ASSESSMENT.md +++ /dev/null @@ -1,157 +0,0 @@ -# Story Generation Feature - Readiness Assessment - -## Summary - -This document provides a quick assessment of existing story generation modules and their readiness for integration into the main application. - -## Existing Modules Status - -### ✅ Ready for Migration (High Priority) - -#### 1. Story Writer Core (`ai_story_generator.py`) -**Readiness**: 85% -- ✅ Core logic is sound and follows prompt chaining pattern -- ✅ Well-structured with clear separation of concerns -- ✅ Supports comprehensive story parameters -- ❌ Needs import path updates -- ❌ Needs subscription integration -- ❌ Needs user_id parameter addition - -**Migration Effort**: Low-Medium (2-3 days) - -#### 2. Story Illustrator (`story_illustrator.py`) -**Readiness**: 80% -- ✅ Complete illustration workflow -- ✅ Multiple style support -- ✅ PDF and ZIP export functionality -- ❌ Needs import path updates -- ❌ Needs subscription integration -- ❌ Image generation API needs verification - -**Migration Effort**: Medium (3-4 days) - -### ⚠️ Functional but Complex (Medium Priority) - -#### 3. Story Video Generator (`story_video_generator.py`) -**Readiness**: 70% -- ✅ Complete video generation workflow -- ✅ Image generation and text overlay -- ✅ Video compilation with audio -- ❌ Heavy dependencies (MoviePy, imageio, ffmpeg) -- ❌ Complex error handling needed -- ❌ Resource-intensive operations - -**Migration Effort**: High (5-7 days) -**Recommendation**: Defer to Phase 2, focus on core story generation first - -## Infrastructure Readiness - -### ✅ Production-Ready Infrastructure - -#### 1. Main Text Generation (`main_text_generation.py`) -**Status**: ✅ Ready -- ✅ Supports Gemini and HuggingFace -- ✅ Subscription integration built-in -- ✅ Usage tracking -- ✅ Error handling and fallback -- ✅ Structured JSON response support - -**Integration**: Direct - just import and use - -#### 2. Subscription System (`subscription_models.py`) -**Status**: ✅ Ready -- ✅ Complete usage tracking -- ✅ Token and call limits -- ✅ Billing period management -- ✅ Already integrated with main_text_generation - -**Integration**: Automatic - already working - -#### 3. Blog Writer Reference Implementation -**Status**: ✅ Excellent Reference -- ✅ Phase navigation pattern -- ✅ CopilotKit integration -- ✅ Task management with polling -- ✅ State management hooks -- ✅ Error handling patterns - -**Integration**: Follow same patterns - -## Key Findings - -### Strengths -1. **Core Logic is Sound**: The prompt chaining approach in `ai_story_generator.py` is well-designed and follows the Gemini cookbook examples -2. **Comprehensive Parameters**: Story writer supports extensive customization (11 personas, multiple styles, tones, POVs, etc.) -3. **Infrastructure Ready**: All required backend infrastructure (LLM providers, subscription, task management) is already in place -4. **Reference Implementation**: Blog Writer provides excellent patterns to follow - -### Gaps -1. **Import Paths**: All story modules use legacy import paths that need updating -2. **Subscription Integration**: No user_id or subscription checks in story modules -3. **UI Framework**: All modules use Streamlit - need React/CopilotKit migration -4. **Task Management**: No async task management - need polling support -5. **Error Handling**: Basic error handling - needs enhancement for production - -### Opportunities -1. **Structured Responses**: Can enhance outline generation with structured JSON (already supported by main_text_generation) -2. **Streaming Support**: Future enhancement for real-time story generation -3. **Illustration Integration**: Can be optional phase - doesn't block core story generation -4. **Template System**: Can add pre-defined story templates based on personas - -## Recommended Approach - -### Phase 1: Core Story Generation (Priority 1) -**Focus**: Get basic story generation working end-to-end -- Migrate `ai_story_generator.py` to backend service -- Create API endpoints with task management -- Build React UI with phase navigation -- Integrate CopilotKit actions -- **Timeline**: 1-2 weeks - -### Phase 2: Illustration Support (Priority 2) -**Focus**: Add optional illustration phase -- Migrate `story_illustrator.py` to backend service -- Add illustration phase to frontend -- Integrate with image generation API -- **Timeline**: 1 week - -### Phase 3: Video Generation (Priority 3) -**Focus**: Advanced feature for future -- Migrate `story_video_generator.py` -- Handle heavy dependencies -- Add video generation phase -- **Timeline**: 2 weeks (defer to later) - -## Migration Complexity Matrix - -| Module | Complexity | Dependencies | Effort | Priority | -|--------|-----------|--------------|--------|----------| -| Story Writer Core | Low-Medium | Low | 2-3 days | P0 | -| Story Illustrator | Medium | Medium | 3-4 days | P1 | -| Story Video Generator | High | High | 5-7 days | P2 | - -## Risk Assessment - -### Low Risk ✅ -- Story writer core migration (well-understood patterns) -- Integration with main_text_generation (already tested) -- Phase navigation UI (proven pattern from Blog Writer) - -### Medium Risk ⚠️ -- Illustration integration (depends on image generation API availability) -- Long-running story generation tasks (need proper timeout handling) -- Subscription limit handling during long generations - -### High Risk ❌ -- Video generation (heavy dependencies, resource-intensive) -- Real-time streaming (not currently supported by main_text_generation) - -## Conclusion - -The story generation feature is **highly feasible** with existing infrastructure. The core story writer module is well-designed and can be migrated relatively quickly. The main work is: - -1. **Backend Migration** (Low-Medium effort): Update imports, add subscription integration -2. **Frontend Development** (Medium effort): Build React UI following Blog Writer patterns -3. **CopilotKit Integration** (Low effort): Follow existing patterns - -**Recommended Start**: Begin with core story generation (Phase 1), then add illustrations (Phase 2), and defer video generation (Phase 3) to a later release. diff --git a/docs/STORY_WRITER_BACKEND_MIGRATION_COMPLETE.md b/docs/STORY_WRITER_BACKEND_MIGRATION_COMPLETE.md deleted file mode 100644 index 206e7349..00000000 --- a/docs/STORY_WRITER_BACKEND_MIGRATION_COMPLETE.md +++ /dev/null @@ -1,137 +0,0 @@ -# Story Writer Backend Migration - Complete ✅ - -## Summary - -Successfully migrated story generation code from `ToBeMigrated/ai_writers/ai_story_writer/` to production backend structure with minimal rewriting. All code has been adapted to use `main_text_generation` and subscription system. - -## What Was Created - -### 1. Service Layer (`backend/services/story_writer/`) -- ✅ `story_service.py` - Core story generation logic - - Migrated from `ai_story_generator.py` - - Updated imports to use `main_text_generation` - - Added `user_id` parameter for subscription support - - Removed Streamlit dependencies - - Modular methods: `generate_premise`, `generate_outline`, `generate_story_start`, `continue_story`, `generate_full_story` - -### 2. API Layer (`backend/api/story_writer/`) -- ✅ `router.py` - RESTful API endpoints - - Synchronous endpoints for premise, outline, start, continue - - Asynchronous endpoint for full story generation with task management - - Task status and result endpoints - - Cache management endpoints -- ✅ `task_manager.py` - Async task execution and tracking - - Background task execution - - Progress tracking - - Status management -- ✅ `cache_manager.py` - Result caching - - Cache key generation - - Cache statistics - - Cache clearing - -### 3. Models (`backend/models/story_models.py`) -- ✅ Pydantic models for all requests and responses -- ✅ Type-safe API contracts - -### 4. Router Registration -- ✅ Added to `alwrity_utils/router_manager.py` in optional routers section -- ✅ Automatic registration on app startup - -## Key Changes Made - -### Import Updates -```python -# Before (Legacy) -from ...gpt_providers.text_generation.main_text_generation import llm_text_gen - -# After (Production) -from services.llm_providers.main_text_generation import llm_text_gen -``` - -### Subscription Integration -```python -# Before -def generate_with_retry(prompt, system_prompt=None): - return llm_text_gen(prompt, system_prompt) - -# After -def generate_with_retry(prompt, system_prompt=None, user_id: str = None): - if not user_id: - raise RuntimeError("user_id is required") - return llm_text_gen(prompt=prompt, system_prompt=system_prompt, user_id=user_id) -``` - -### Error Handling -- Added HTTPException handling for subscription limits (429) -- Proper error propagation -- Comprehensive logging - -### Removed Dependencies -- Removed Streamlit (`st.info`, `st.error`, etc.) -- Removed UI-specific code -- Kept core business logic intact - -## API Endpoints Available - -### Story Generation -- `POST /api/story/generate-premise` - Generate premise -- `POST /api/story/generate-outline` - Generate outline -- `POST /api/story/generate-start` - Generate story start -- `POST /api/story/continue` - Continue story -- `POST /api/story/generate-full` - Full story (async) - -### Task Management -- `GET /api/story/task/{task_id}/status` - Task status -- `GET /api/story/task/{task_id}/result` - Task result - -### Cache -- `GET /api/story/cache/stats` - Cache statistics -- `POST /api/story/cache/clear` - Clear cache - -## Project Structure - -``` -backend/ -├── services/ -│ └── story_writer/ -│ ├── __init__.py -│ ├── story_service.py ✅ Core logic (migrated) -│ └── README.md -├── api/ -│ └── story_writer/ -│ ├── __init__.py -│ ├── router.py ✅ API endpoints -│ ├── task_manager.py ✅ Async tasks -│ └── cache_manager.py ✅ Caching -├── models/ -│ └── story_models.py ✅ Pydantic models -└── alwrity_utils/ - └── router_manager.py ✅ Router registration -``` - -## Testing Checklist - -- [ ] Test premise generation endpoint -- [ ] Test outline generation endpoint -- [ ] Test story start generation endpoint -- [ ] Test story continuation endpoint -- [ ] Test full story generation (async) -- [ ] Test task status polling -- [ ] Test subscription limits (429 errors) -- [ ] Test with both Gemini and HuggingFace providers -- [ ] Test cache functionality -- [ ] Verify error handling - -## Next Steps - -1. **Frontend Implementation** - Build React UI with CopilotKit integration -2. **Testing** - Add unit and integration tests -3. **Documentation** - API documentation and usage examples -4. **Illustration Support** - Migrate story illustrator (Phase 2) - -## Notes - -- All existing logic preserved - only imports and subscription integration changed -- No breaking changes to story generation algorithm -- Follows same patterns as Blog Writer for consistency -- Ready for frontend integration diff --git a/docs/STORY_WRITER_NEXT_STEPS.md b/docs/STORY_WRITER_NEXT_STEPS.md deleted file mode 100644 index 241b2970..00000000 --- a/docs/STORY_WRITER_NEXT_STEPS.md +++ /dev/null @@ -1,312 +0,0 @@ -# Story Writer - Next Steps & Recommendations - -## Current Status: ✅ Foundation Complete - -The Story Writer feature has a solid foundation with: -- ✅ Complete backend API (10 endpoints) -- ✅ Complete frontend components (5 phases) -- ✅ State management and phase navigation -- ✅ Route integration -- ✅ API integration verified - -## 🎯 Recommended Next Steps (Prioritized) - -### Phase 1: End-to-End Testing & Validation (IMMEDIATE) - -**Priority**: 🔴 High -**Estimated Time**: 2-4 hours -**Goal**: Verify the complete flow works with real backend - -#### Tasks: -1. **Manual Testing** - - [ ] Test Setup → Premise → Outline → Writing → Export flow - - [ ] Test error scenarios (network errors, API errors, validation) - - [ ] Test state persistence (refresh page) - - [ ] Test phase navigation (forward/backward) - - [ ] Test with different story parameters - -2. **API Testing** - - [ ] Verify all endpoints respond correctly - - [ ] Test authentication flow - - [ ] Test subscription limit handling - - [ ] Test error responses - -3. **Bug Fixes** - - [ ] Fix any issues discovered during testing - - [ ] Improve error messages if needed - - [ ] Add missing validation - -**Deliverable**: Working end-to-end flow with documented issues/fixes - ---- - -### Phase 2: CopilotKit Integration (HIGH PRIORITY) - -**Priority**: 🟡 High -**Estimated Time**: 4-6 hours -**Goal**: Add AI assistance via CopilotKit (similar to BlogWriter) - -#### Tasks: - -1. **Create CopilotKit Actions Hook** - - [ ] Create `useStoryWriterCopilotActions.ts` - - [ ] Add actions for: - - `generatePremise` - Generate story premise - - `generateOutline` - Generate story outline - - `generateStoryStart` - Start writing story - - `continueStory` - Continue writing story - - `regeneratePremise` - Regenerate premise - - `regenerateOutline` - Regenerate outline - - `exportStory` - Export completed story - -2. **Create CopilotKit Sidebar Component** - - [ ] Create `StoryWriterCopilotSidebar.tsx` - - [ ] Follow BlogWriter pattern (`WriterCopilotSidebar.tsx`) - - [ ] Add context about current phase and story state - - [ ] Provide helpful suggestions based on phase - -3. **Integrate CopilotKit Components** - - [ ] Add CopilotKit wrapper to `StoryWriter.tsx` - - [ ] Register actions in main component - - [ ] Add sidebar to UI - - [ ] Test all CopilotKit actions - -4. **Add Context to CopilotKit** - - [ ] Provide story parameters as context - - [ ] Provide current phase information - - [ ] Provide generated content (premise, outline, story) - -**Reference**: -- `frontend/src/components/BlogWriter/BlogWriterUtils/useBlogWriterCopilotActions.ts` -- `frontend/src/components/BlogWriter/BlogWriterUtils/WriterCopilotSidebar.tsx` -- `frontend/src/components/BlogWriter/BlogWriterUtils/CopilotKitComponents.tsx` - -**Deliverable**: Fully functional CopilotKit integration with AI assistance - ---- - -### Phase 3: UX Enhancements & Polish (MEDIUM PRIORITY) - -**Priority**: 🟢 Medium -**Estimated Time**: 3-5 hours -**Goal**: Improve user experience and visual polish - -#### Tasks: - -1. **Loading States** - - [ ] Add skeleton loaders for content generation - - [ ] Add progress indicators for long operations - - [ ] Show estimated time remaining - - [ ] Add token count display (if available) - -2. **Error Handling** - - [ ] More specific error messages - - [ ] Retry buttons for failed operations - - [ ] Better error recovery - - [ ] Network error detection and handling - -3. **Visual Improvements** - - [ ] Add animations/transitions between phases - - [ ] Improve spacing and layout - - [ ] Add icons to phase navigation - - [ ] Enhance color scheme and typography - - [ ] Add loading spinners and progress bars - -4. **User Feedback** - - [ ] Add success notifications - - [ ] Add toast messages for actions - - [ ] Add confirmation dialogs for destructive actions - - [ ] Add tooltips for help text - -5. **Responsive Design** - - [ ] Test and fix mobile responsiveness - - [ ] Optimize for tablet views - - [ ] Ensure touch-friendly interactions - -**Deliverable**: Polished, production-ready UI - ---- - -### Phase 4: Advanced Features (LOW PRIORITY) - -**Priority**: 🔵 Low -**Estimated Time**: 8-12 hours -**Goal**: Add advanced functionality for power users - -#### Tasks: - -1. **Draft Management** - - [ ] Backend: Add draft saving endpoint - - [ ] Backend: Add draft loading endpoint - - [ ] Frontend: Add "Save Draft" button - - [ ] Frontend: Add "Load Draft" functionality - - [ ] Frontend: Add draft list/management UI - -2. **Rich Text Editing** - - [ ] Integrate rich text editor (e.g., TipTap, Quill) - - [ ] Add formatting options (bold, italic, headings) - - [ ] Add markdown support - - [ ] Add word count display - -3. **Story Analytics** - - [ ] Track generation time - - [ ] Track word count per phase - - [ ] Track iterations for completion - - [ ] Display statistics dashboard - -4. **Export Enhancements** - - [ ] Add PDF export - - [ ] Add DOCX export - - [ ] Add EPUB export - - [ ] Add formatting options for export - - [ ] Add share functionality - -5. **Story Templates** - - [ ] Pre-defined story templates - - [ ] Save custom templates - - [ ] Template library UI - -6. **Collaboration Features** - - [ ] Share story with others - - [ ] Comment/feedback system - - [ ] Version history - -**Deliverable**: Advanced feature set for power users - ---- - -### Phase 5: Performance & Optimization (ONGOING) - -**Priority**: 🟢 Medium -**Estimated Time**: Ongoing -**Goal**: Optimize performance and reduce costs - -#### Tasks: - -1. **Caching** - - [ ] Verify cache is working correctly - - [ ] Add cache invalidation strategies - - [ ] Add cache statistics display - -2. **API Optimization** - - [ ] Add request debouncing - - [ ] Optimize payload sizes - - [ ] Add request cancellation - - [ ] Implement retry logic with exponential backoff - -3. **Frontend Optimization** - - [ ] Code splitting for phase components - - [ ] Lazy loading for heavy components - - [ ] Optimize re-renders - - [ ] Add memoization where needed - -4. **Monitoring** - - [ ] Add error tracking (Sentry, etc.) - - [ ] Add performance monitoring - - [ ] Add usage analytics - - [ ] Track API call success rates - -**Deliverable**: Optimized, performant application - ---- - -## 📋 Quick Start Guide - -### For Immediate Testing: - -1. **Start Backend**: - ```bash - cd backend - python -m uvicorn app:app --reload - ``` - -2. **Start Frontend**: - ```bash - cd frontend - npm start - ``` - -3. **Test Flow**: - - Navigate to `/story-writer` - - Fill in Setup form - - Generate Premise - - Generate Outline - - Generate Story Start - - Continue Writing - - Export Story - -### For CopilotKit Integration: - -1. **Study BlogWriter Implementation**: - - Review `useBlogWriterCopilotActions.ts` - - Review `WriterCopilotSidebar.tsx` - - Review `CopilotKitComponents.tsx` - -2. **Create StoryWriter Equivalents**: - - Create `useStoryWriterCopilotActions.ts` - - Create `StoryWriterCopilotSidebar.tsx` - - Integrate into `StoryWriter.tsx` - -3. **Test Actions**: - - Test each CopilotKit action - - Verify context is provided correctly - - Test sidebar suggestions - ---- - -## 🎯 Recommended Order of Execution - -1. **Week 1**: Phase 1 (Testing) + Phase 2 (CopilotKit) -2. **Week 2**: Phase 3 (UX Polish) -3. **Week 3+**: Phase 4 (Advanced Features) + Phase 5 (Optimization) - ---- - -## 📝 Notes - -- **CopilotKit Integration** is the highest priority feature addition as it significantly enhances user experience -- **Testing** should be done before adding new features to ensure stability -- **UX Polish** can be done incrementally alongside other work -- **Advanced Features** can be prioritized based on user feedback - ---- - -## 🔗 Related Documentation - -- `docs/STORY_WRITER_IMPLEMENTATION_REVIEW.md` - Detailed implementation review -- `docs/STORY_WRITER_FRONTEND_FOUNDATION_COMPLETE.md` - Frontend foundation details -- `backend/services/story_writer/README.md` - Backend service documentation - ---- - -## ✅ Success Criteria - -### Phase 1 (Testing): -- All endpoints work correctly -- Complete flow works end-to-end -- No critical bugs - -### Phase 2 (CopilotKit): -- All CopilotKit actions work -- Sidebar provides helpful suggestions -- Context is properly provided - -### Phase 3 (UX): -- UI is polished and professional -- Loading states are clear -- Errors are handled gracefully - -### Phase 4 (Advanced): -- Draft saving/loading works -- Rich text editing available -- Export options functional - -### Phase 5 (Performance): -- Fast response times -- Efficient API usage -- Good user experience - ---- - -**Last Updated**: Current Date -**Status**: Ready for Phase 1 (Testing) diff --git a/docs/STORY_WRITER_REVIEW_AND_NEXT_STEPS.md b/docs/STORY_WRITER_REVIEW_AND_NEXT_STEPS.md deleted file mode 100644 index 6c617f45..00000000 --- a/docs/STORY_WRITER_REVIEW_AND_NEXT_STEPS.md +++ /dev/null @@ -1,436 +0,0 @@ -# Story Writer Backend Migration - Review & Next Steps - -## ✅ What Was Accomplished - -### 1. Backend Service Layer (`backend/services/story_writer/`) -**Status**: ✅ Complete - -- **`story_service.py`** - Core story generation service - - Migrated from `ToBeMigrated/ai_writers/ai_story_writer/ai_story_generator.py` - - Updated imports to use `services.llm_providers.main_text_generation` - - Added `user_id` parameter for subscription integration - - Removed Streamlit dependencies - - Modular methods: - - `generate_premise()` - Generate story premise - - `generate_outline()` - Generate story outline - - `generate_story_start()` - Generate story beginning - - `continue_story()` - Continue story generation - - `generate_full_story()` - Complete story generation with iterations - -**Key Features**: -- ✅ Subscription support via `main_text_generation` -- ✅ Supports both Gemini and HuggingFace providers -- ✅ Proper error handling with HTTPException support -- ✅ Comprehensive logging - -### 2. API Layer (`backend/api/story_writer/`) -**Status**: ✅ Complete - -- **`router.py`** - RESTful API endpoints - - Synchronous endpoints: premise, outline, start, continue - - Asynchronous endpoint: full story generation with task management - - Task status and result endpoints - - Cache management endpoints - - Health check endpoint - -- **`task_manager.py`** - Async task execution - - Background task execution - - Progress tracking (0-100%) - - Status management (pending, processing, completed, failed) - - Automatic cleanup of old tasks - -- **`cache_manager.py`** - Result caching - - MD5-based cache key generation - - Cache statistics - - Cache clearing - -### 3. Models (`backend/models/story_models.py`) -**Status**: ✅ Complete - -- Pydantic models for type-safe API: - - `StoryGenerationRequest` - Input parameters - - `StoryPremiseResponse` - Premise generation response - - `StoryOutlineResponse` - Outline generation response - - `StoryContentResponse` - Story content response - - `StoryFullGenerationResponse` - Complete story response - - `StoryContinueRequest/Response` - Continuation models - - `TaskStatus` - Task tracking model - -### 4. Router Registration -**Status**: ✅ Complete - -- Added to `alwrity_utils/router_manager.py` in optional routers section -- Automatic registration on app startup -- Error handling for graceful failures - -## 📊 API Endpoints Summary - -### Synchronous Endpoints -``` -POST /api/story/generate-premise -POST /api/story/generate-outline -POST /api/story/generate-start -POST /api/story/continue -``` - -### Asynchronous Endpoints -``` -POST /api/story/generate-full → Returns task_id -GET /api/story/task/{task_id}/status -GET /api/story/task/{task_id}/result -``` - -### Utility Endpoints -``` -GET /api/story/health -GET /api/story/cache/stats -POST /api/story/cache/clear -``` - -## 🎯 Next Steps - Implementation Roadmap - -### Phase 1: Backend Testing & Validation (Priority: High) -**Estimated Time**: 1-2 days - -**Tasks**: -1. **API Testing** - - [ ] Test all synchronous endpoints with Postman/curl - - [ ] Test async task flow (generate-full → status → result) - - [ ] Verify subscription limits work (429 errors) - - [ ] Test with both Gemini and HuggingFace providers - - [ ] Test error handling (invalid inputs, API failures) - -2. **Integration Testing** - - [ ] Test with real user authentication - - [ ] Verify usage tracking in database - - [ ] Test cache functionality - - [ ] Test task cleanup (old tasks removal) - -3. **Performance Testing** - - [ ] Measure response times for each endpoint - - [ ] Test concurrent requests - - [ ] Monitor memory usage during long story generation - -**Deliverables**: -- API test suite (Postman collection or pytest) -- Test results document -- Performance benchmarks - ---- - -### Phase 2: Frontend Foundation (Priority: High) -**Estimated Time**: 2-3 days - -**Tasks**: -1. **Create Frontend Structure** - - [ ] Create `frontend/src/components/StoryWriter/` directory - - [ ] Create `frontend/src/services/storyWriterApi.ts` (API client) - - [ ] Create `frontend/src/hooks/useStoryWriterState.ts` (state management) - - [ ] Create `frontend/src/hooks/useStoryWriterPhaseNavigation.ts` (phase navigation) - -2. **API Service Layer** - ```typescript - // frontend/src/services/storyWriterApi.ts - - generatePremise() - - generateOutline() - - generateStoryStart() - - continueStory() - - generateFullStory() // async with polling - - getTaskStatus() - - getTaskResult() - ``` - -3. **State Management Hook** - ```typescript - // frontend/src/hooks/useStoryWriterState.ts - - Story parameters (persona, setting, characters, etc.) - - Premise, outline, story content - - Generation progress - - Task management - ``` - -4. **Phase Navigation Hook** - ```typescript - // Similar to usePhaseNavigation.ts from Blog Writer - Phases: Setup → Premise → Outline → Writing → Export - ``` - -**Deliverables**: -- Frontend directory structure -- API service with TypeScript types -- State management hooks -- Phase navigation hook - ---- - -### Phase 3: UI Components - Core (Priority: High) -**Estimated Time**: 3-4 days - -**Tasks**: -1. **Main Component** - - [ ] `StoryWriter.tsx` - Main container component - - [ ] Similar structure to `BlogWriter.tsx` - -2. **Phase Components** - - [ ] `StorySetup.tsx` - Phase 1: Input story parameters - - Persona selector (11 options) - - Story setting input - - Characters input - - Plot elements input - - Writing style, tone, POV selectors - - Audience age group, content rating, ending preference - - - [ ] `StoryPremise.tsx` - Phase 2: Review premise - - Display generated premise - - Regenerate option - - Continue to outline button - - - [ ] `StoryOutline.tsx` - Phase 3: Review outline - - Display generated outline - - Edit/refine option - - Continue to writing button - - - [ ] `StoryContent.tsx` - Phase 4: Generated story - - Display story content - - Markdown editor for editing - - Continue generation button - - Progress indicator for async generation - - - [ ] `StoryExport.tsx` - Phase 5: Export options - - Download as text/markdown - - Copy to clipboard - - Share options - -3. **Utility Components** - - [ ] `HeaderBar.tsx` - Phase navigation header (like Blog Writer) - - [ ] `PhaseContent.tsx` - Phase content wrapper - - [ ] `TaskProgressModal.tsx` - Progress modal for async operations - -**Deliverables**: -- All phase components -- Main StoryWriter component -- Utility components - ---- - -### Phase 4: CopilotKit Integration (Priority: Medium) -**Estimated Time**: 2-3 days - -**Tasks**: -1. **CopilotKit Actions** - - [ ] `useStoryWriterCopilotActions.ts` hook - - [ ] Actions: - - `generateStoryPremise` - Generate premise - - `generateStoryOutline` - Generate outline - - `startStoryWriting` - Begin story generation - - `continueStoryWriting` - Continue story - - `refineStoryOutline` - Refine outline - - `exportStory` - Export story - -2. **CopilotKit Sidebar** - - [ ] `WriterCopilotSidebar.tsx` - Suggestions sidebar - - [ ] Context-aware suggestions based on current phase - - [ ] Action buttons for common tasks - -3. **Integration** - - [ ] Register actions in StoryWriter component - - [ ] Connect sidebar to component state - - [ ] Test CopilotKit interactions - -**Reference**: `frontend/src/components/BlogWriter/BlogWriterUtils/useBlogWriterCopilotActions.ts` - -**Deliverables**: -- CopilotKit actions hook -- CopilotKit sidebar component -- Integrated with main component - ---- - -### Phase 5: Polish & Enhancement (Priority: Low) -**Estimated Time**: 2-3 days - -**Tasks**: -1. **Error Handling** - - [ ] User-friendly error messages - - [ ] Retry mechanisms - - [ ] Error boundaries - -2. **Loading States** - - [ ] Skeleton loaders - - [ ] Progress indicators - - [ ] Optimistic UI updates - -3. **UX Improvements** - - [ ] Keyboard shortcuts - - [ ] Auto-save draft - - [ ] Undo/redo functionality - - [ ] Story preview - -4. **Styling** - - [ ] Match Blog Writer design system - - [ ] Responsive design - - [ ] Dark mode support (if applicable) - -**Deliverables**: -- Polished UI/UX -- Error handling improvements -- Loading states - ---- - -### Phase 6: Illustration Support (Optional - Future) -**Estimated Time**: 3-4 days - -**Tasks**: -1. **Backend Migration** - - [ ] Migrate `story_illustrator.py` to backend service - - [ ] Create illustration API endpoints - - [ ] Integrate with image generation API - -2. **Frontend Integration** - - [ ] Add illustration phase - - [ ] Illustration generation UI - - [ ] Preview and download illustrations - -**Note**: Defer to Phase 2 if core story generation is priority - ---- - -## 🚀 Quick Start Guide - -### Testing Backend API - -```bash -# Health check -curl http://localhost:8000/api/story/health - -# Generate premise (requires auth token) -curl -X POST http://localhost:8000/api/story/generate-premise \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "persona": "Award-Winning Science Fiction Author", - "story_setting": "A futuristic city in 2150", - "character_input": "John, a brave explorer", - "plot_elements": "The hero's journey", - "writing_style": "Formal", - "story_tone": "Suspenseful", - "narrative_pov": "Third Person Limited", - "audience_age_group": "Adults", - "content_rating": "PG-13", - "ending_preference": "Happy" - }' -``` - -### Frontend Development Order - -1. **Start with API Service** (`storyWriterApi.ts`) - - Define all API calls - - Add TypeScript types - - Test with mock data - -2. **Build State Management** (`useStoryWriterState.ts`) - - Define state structure - - Add state setters/getters - - Test state updates - -3. **Create Phase Navigation** (`useStoryWriterPhaseNavigation.ts`) - - Define phases - - Add navigation logic - - Test phase transitions - -4. **Build Components** (Start with Setup phase) - - StorySetup component - - Test form submission - - Connect to API - -5. **Add Remaining Phases** - - Premise → Outline → Writing → Export - - Test each phase independently - -6. **Integrate CopilotKit** - - Add actions - - Connect sidebar - - Test interactions - ---- - -## 📝 Key Decisions Made - -1. **Modular Structure**: Follows Blog Writer patterns for consistency -2. **Async Task Pattern**: Long-running operations use task management with polling -3. **Subscription Integration**: Automatic via `main_text_generation` -4. **Provider Support**: Works with both Gemini and HuggingFace automatically -5. **Caching**: Results cached to avoid duplicate generations -6. **Error Handling**: Comprehensive with HTTPException support - ---- - -## ⚠️ Important Notes - -1. **Authentication Required**: All endpoints require valid Clerk authentication token -2. **Subscription Limits**: Will return 429 if limits exceeded -3. **Long Operations**: Full story generation can take several minutes - use async pattern -4. **Task Cleanup**: Tasks older than 1 hour are automatically cleaned up -5. **Cache Keys**: Based on request parameters - identical requests return cached results - ---- - -## 🎯 Recommended Immediate Next Steps - -1. **Test Backend API** (Today) - - Verify all endpoints work - - Test subscription integration - - Document any issues - -2. **Create Frontend API Service** (Day 1-2) - - Set up TypeScript types - - Create API client functions - - Test with Postman/curl responses - -3. **Build StorySetup Component** (Day 2-3) - - Create form with all parameters - - Connect to API - - Test premise generation - -4. **Add Phase Navigation** (Day 3-4) - - Implement phase hook - - Add HeaderBar component - - Test phase transitions - -5. **Complete Remaining Phases** (Day 4-7) - - Build each phase component - - Connect to API - - Test full flow - ---- - -## 📚 Reference Files - -- **Blog Writer** (Reference implementation): - - `frontend/src/components/BlogWriter/BlogWriter.tsx` - - `frontend/src/hooks/usePhaseNavigation.ts` - - `frontend/src/components/BlogWriter/BlogWriterUtils/useBlogWriterCopilotActions.ts` - -- **Backend Patterns**: - - `backend/api/blog_writer/router.py` - - `backend/api/blog_writer/task_manager.py` - - `backend/services/blog_writer/blog_service.py` - ---- - -## ✅ Success Criteria - -- [ ] All backend endpoints tested and working -- [ ] Frontend API service complete -- [ ] All phase components built -- [ ] Phase navigation working -- [ ] CopilotKit integrated -- [ ] Full story generation flow works end-to-end -- [ ] Error handling comprehensive -- [ ] Loading states implemented -- [ ] UI matches Blog Writer design - ---- - -**Ready to proceed with Phase 1 (Backend Testing) or Phase 2 (Frontend Foundation)?** diff --git a/docs/STORY_WRITER_TESTING_GUIDE.md b/docs/STORY_WRITER_TESTING_GUIDE.md deleted file mode 100644 index 36b7acbf..00000000 --- a/docs/STORY_WRITER_TESTING_GUIDE.md +++ /dev/null @@ -1,424 +0,0 @@ -# Story Writer - Testing Guide & Current Status - -## Overview - -The Story Writer feature is a comprehensive AI-powered story generation system that allows users to create complete stories with multimedia capabilities including images, audio narration, and video composition. - -## Current Status: ✅ Ready for Testing - -### ✅ Completed Features - -1. **Core Story Generation** - - Premise generation - - Structured outline generation (JSON schema with scenes) - - Story start generation (min 4000 words) - - Story continuation (iterative until completion) - - Full story generation (async with task management) - -2. **Multimedia Generation** - - Image generation for story scenes - - Audio narration generation (TTS) for scenes - - Video composition from images and audio - -3. **Backend API** - - 15+ endpoints for all operations - - Task management with progress tracking - - Authentication and subscription integration - - Error handling and logging - -4. **Frontend Components** - - 5-phase workflow (Setup → Premise → Outline → Writing → Export) - - State management with localStorage persistence - - Phase navigation with prerequisite checking - - Multimedia display (images, audio, video) - -5. **End-to-End Video Generation** - - Complete workflow: Outline → Images → Audio → Video - - Progress tracking with granular updates - - Async task execution with polling support - -### 🔧 Recent Fixes - -1. **Async Function Fix**: Fixed `execute_complete_video_generation` to be a synchronous function (not async) since it performs blocking operations -2. **Progress Callback**: Improved progress tracking with proper mapping of sub-progress to overall progress -3. **Error Handling**: Enhanced error messages and exception logging -4. **Path Validation**: Added validation for image and audio file paths before video generation - -## Testing Guide - -### Prerequisites - -1. **Backend Setup** - ```bash - cd backend - pip install -r requirements.txt - ``` - -2. **Frontend Setup** - ```bash - cd frontend - npm install - ``` - -3. **Environment Variables** - - Ensure `.env` file is configured with: - - `CLERK_SECRET_KEY` for authentication - - `GEMINI_API_KEY` or `HUGGINGFACE_API_KEY` for LLM - - Image generation API keys (if using image generation) - -4. **Dependencies** - - MoviePy (for video generation): `pip install moviepy imageio imageio-ffmpeg` - - gTTS (for audio generation): `pip install gtts` - - FFmpeg (system dependency for video processing) - -### Test Scenarios - -#### 1. Basic Story Generation Flow - -**Steps:** -1. Navigate to `/story-writer` -2. Fill in the Setup form: - - Select a persona (e.g., "Fantasy Writer") - - Enter story setting (e.g., "A magical kingdom") - - Enter characters (e.g., "A young wizard and a dragon") - - Enter plot elements (e.g., "A quest to find a lost artifact") - - Select writing style, tone, POV, audience, content rating, ending preference -3. Click "Generate Premise" -4. Review the generated premise -5. Click "Generate Outline" -6. Review the structured outline with scenes -7. Click "Generate Story Start" -8. Review the story beginning -9. Click "Continue Writing" multiple times until story is complete -10. Click "Export Story" to view the complete story - -**Expected Results:** -- Premise is generated successfully -- Structured outline is generated with scene-by-scene details -- Story start is generated (min 4000 words) -- Story continuation works iteratively -- Story completion is detected when "IAMDONE" marker is found -- Complete story is displayed in the Export phase - -#### 2. Structured Outline with Images and Audio - -**Steps:** -1. Complete steps 1-6 from the basic flow -2. In the Outline phase, verify that structured scenes are displayed -3. Click "Generate Images" button -4. Wait for images to be generated for all scenes -5. Click "Generate Audio" button -6. Wait for audio narration to be generated for all scenes -7. Review the generated images and audio players - -**Expected Results:** -- Images are generated for each scene -- Images are displayed in the Outline phase -- Audio files are generated for each scene -- Audio players are displayed for each scene -- Images and audio are persisted in state - -#### 3. Video Generation - -**Steps:** -1. Complete steps 1-6 from the basic flow (with images and audio generated) -2. Navigate to the Export phase -3. Click "Generate Video" button -4. Wait for video generation to complete -5. Review the generated video - -**Expected Results:** -- Video is generated from images and audio -- Video is displayed in the Export phase -- Video can be downloaded -- Video composition combines all scenes into a single video - -#### 4. End-to-End Video Generation (Async) - -**Steps:** -1. Navigate to `/story-writer` -2. Fill in the Setup form -3. Use the API endpoint `/api/story/generate-complete-video` (via Postman or frontend) -4. Poll the task status using `/api/story/task/{task_id}/status` -5. Retrieve the result using `/api/story/task/{task_id}/result` - -**Expected Results:** -- Task is created successfully -- Progress updates are provided at each step: - - 10%: Premise generation - - 20%: Outline generation - - 30-50%: Image generation - - 50-70%: Audio generation - - 70%: Preparing video assets - - 75-95%: Video composition - - 100%: Complete -- Result contains premise, outline, images, audio, and video -- Video URL is provided for serving the video - -#### 5. Error Handling - -**Test Cases:** -1. **Invalid Story Parameters** - - Submit form with missing required fields - - Expected: Validation error message - -2. **Network Errors** - - Disconnect network during generation - - Expected: Error message displayed, state preserved - -3. **Subscription Limits** - - Exceed subscription limits - - Expected: 429 error with appropriate message - -4. **Missing Dependencies** - - Remove MoviePy or gTTS - - Expected: Error message indicating missing dependency - -5. **File Not Found** - - Delete generated images or audio before video generation - - Expected: Error message with details about missing files - -#### 6. State Persistence - -**Steps:** -1. Complete steps 1-3 from the basic flow -2. Refresh the page -3. Verify that state is preserved - -**Expected Results:** -- Premise is preserved -- Outline is preserved -- Story content is preserved -- Generated images and audio are preserved -- Phase navigation state is preserved - -#### 7. Phase Navigation - -**Steps:** -1. Complete the basic flow up to the Writing phase -2. Navigate back to the Outline phase -3. Modify the outline -4. Navigate forward to the Writing phase -5. Verify that changes are reflected - -**Expected Results:** -- Backward navigation works correctly -- Forward navigation respects prerequisites -- State is preserved during navigation -- Changes are reflected in subsequent phases - -### API Endpoint Testing - -#### 1. Premise Generation -```bash -POST /api/story/generate-premise -Content-Type: application/json -Authorization: Bearer - -{ - "persona": "Fantasy Writer", - "story_setting": "A magical kingdom", - "character_input": "A young wizard", - "plot_elements": "A quest", - ... -} -``` - -#### 2. Outline Generation -```bash -POST /api/story/generate-outline?premise=&use_structured=true -Content-Type: application/json -Authorization: Bearer - -{ - "persona": "Fantasy Writer", - ... -} -``` - -#### 3. Image Generation -```bash -POST /api/story/generate-images -Content-Type: application/json -Authorization: Bearer - -{ - "scenes": [ - { - "scene_number": 1, - "title": "Scene 1", - "image_prompt": "A magical kingdom with a young wizard", - ... - } - ], - "provider": "gemini", - "width": 1024, - "height": 1024 -} -``` - -#### 4. Audio Generation -```bash -POST /api/story/generate-audio -Content-Type: application/json -Authorization: Bearer - -{ - "scenes": [ - { - "scene_number": 1, - "title": "Scene 1", - "audio_narration": "Once upon a time...", - ... - } - ], - "provider": "gtts", - "lang": "en", - "slow": false -} -``` - -#### 5. Video Generation -```bash -POST /api/story/generate-video -Content-Type: application/json -Authorization: Bearer - -{ - "scenes": [...], - "image_urls": ["/api/story/images/scene_1_image.png", ...], - "audio_urls": ["/api/story/audio/scene_1_audio.mp3", ...], - "story_title": "My Story", - "fps": 24, - "transition_duration": 0.5 -} -``` - -#### 6. Complete Video Generation (Async) -```bash -POST /api/story/generate-complete-video -Content-Type: application/json -Authorization: Bearer - -{ - "persona": "Fantasy Writer", - ... -} - -# Response: -{ - "task_id": "uuid", - "status": "pending", - "message": "Complete video generation started" -} - -# Poll status: -GET /api/story/task/{task_id}/status - -# Get result: -GET /api/story/task/{task_id}/result -``` - -## Known Issues & Limitations - -1. **Video Generation Dependencies** - - Requires FFmpeg to be installed on the system - - MoviePy can be resource-intensive for long videos - - Video generation may take several minutes for multiple scenes - -2. **Audio Generation** - - gTTS requires internet connection - - pyttsx3 is offline but may have lower quality - - Audio generation may take time for long narration texts - -3. **Image Generation** - - Image generation may take time for multiple scenes - - Rate limits may apply based on provider - - Image quality depends on the provider used - -4. **State Persistence** - - Large state objects may cause localStorage issues - - Map serialization is handled but may have edge cases - -5. **Progress Tracking** - - Progress callbacks may not be perfectly granular - - Some operations may not provide detailed progress - -## Next Steps - -### Phase 1: End-to-End Testing (Current) -- [x] Fix async function issues -- [x] Improve progress tracking -- [x] Enhance error handling -- [ ] Complete manual testing of all flows -- [ ] Test with different story parameters -- [ ] Test error scenarios -- [ ] Test state persistence - -### Phase 2: CopilotKit Integration (Next) -- [ ] Create CopilotKit actions hook -- [ ] Create CopilotKit sidebar component -- [ ] Integrate CopilotKit into Story Writer -- [ ] Test CopilotKit actions - -### Phase 3: UX Enhancements -- [ ] Add loading states and progress indicators -- [ ] Improve error messages -- [ ] Add animations and transitions -- [ ] Enhance responsive design - -### Phase 4: Advanced Features -- [ ] Draft management -- [ ] Rich text editing -- [ ] Export enhancements (PDF, DOCX, EPUB) -- [ ] Story templates - -## Troubleshooting - -### Issue: Video generation fails -**Solution**: -- Verify FFmpeg is installed: `ffmpeg -version` -- Check that image and audio files exist -- Verify file paths are correct -- Check system resources (memory, disk space) - -### Issue: Audio generation fails -**Solution**: -- Verify internet connection (for gTTS) -- Check that gTTS is installed: `pip install gtts` -- Verify audio narration text is not empty -- Check system audio dependencies - -### Issue: Image generation fails -**Solution**: -- Verify image generation API keys are configured -- Check that image prompts are not empty -- Verify provider is available -- Check subscription limits - -### Issue: State not persisting -**Solution**: -- Check browser localStorage limits -- Verify state serialization is working -- Check for JavaScript errors in console -- Clear localStorage and try again - -## Support - -For issues or questions: -1. Check the logs in `backend/logs/` -2. Review error messages in the UI -3. Check browser console for frontend errors -4. Review API responses for backend errors - -## Conclusion - -The Story Writer feature is ready for comprehensive testing. All core functionality is implemented and working. The system supports: -- Complete story generation workflow -- Multimedia generation (images, audio, video) -- Async task management with progress tracking -- State persistence and phase navigation -- Error handling and logging - -End users can now test the complete flow and provide feedback for improvements. - diff --git a/docs/SUBSCRIPTION_DOCS_UPDATE_PLAN.md b/docs/SUBSCRIPTION_DOCS_UPDATE_PLAN.md new file mode 100644 index 00000000..173e2298 --- /dev/null +++ b/docs/SUBSCRIPTION_DOCS_UPDATE_PLAN.md @@ -0,0 +1,222 @@ +# Subscription Documentation Update Plan + +## Current State Analysis + +### Issues Found + +1. **Pricing Page Discrepancies**: + - Documentation shows outdated plan limits + - Missing unified `ai_text_generation_calls_limit` for Basic plan (10 calls) + - Missing video generation limits and pricing + - Missing Exa search pricing details + - Gemini pricing is outdated (docs show old models, code has 2.5 Pro, 2.5 Flash, etc.) + - Missing detailed Gemini model breakdowns + +2. **Missing Billing Dashboard Documentation**: + - No documentation for dedicated billing dashboard page (`/billing`) + - Multiple dashboard components exist (BillingDashboard, EnhancedBillingDashboard, CompactBillingDashboard) + - No documentation for billing page features and usage + +3. **Outdated Implementation Status**: + - Documentation doesn't reflect current billing dashboard implementation + - Missing information about subscription renewal history + - Missing usage logs table documentation + - Missing comprehensive API breakdown component + +## Actual Values from Code + +### Subscription Plans (from `pricing_service.py`) + +#### Free Tier +- Price: $0/month, $0/year +- Gemini calls: 100/month +- OpenAI calls: 0 +- Anthropic calls: 0 +- Mistral calls: 50/month +- Tavily calls: 20/month +- Serper calls: 20/month +- Metaphor calls: 10/month +- Firecrawl calls: 10/month +- Stability calls: 5/month +- Exa calls: 100/month +- Video calls: 0 +- Gemini tokens: 100,000/month +- Monthly cost limit: $0.0 +- Features: ["basic_content_generation", "limited_research"] + +#### Basic Tier +- Price: $29/month, $290/year +- **ai_text_generation_calls_limit: 10** (unified limit for all LLM providers) +- Gemini calls: 1000/month (legacy, not used for enforcement) +- OpenAI calls: 500/month (legacy) +- Anthropic calls: 200/month (legacy) +- Mistral calls: 500/month (legacy) +- Tavily calls: 200/month +- Serper calls: 200/month +- Metaphor calls: 100/month +- Firecrawl calls: 100/month +- Stability calls: 5/month +- Exa calls: 500/month +- Video calls: 20/month +- Gemini tokens: 20,000/month (increased from 5,000) +- OpenAI tokens: 20,000/month +- Anthropic tokens: 20,000/month +- Mistral tokens: 20,000/month +- Monthly cost limit: $50.0 +- Features: ["full_content_generation", "advanced_research", "basic_analytics"] + +#### Pro Tier +- Price: $79/month, $790/year +- Gemini calls: 5000/month +- OpenAI calls: 2500/month +- Anthropic calls: 1000/month +- Mistral calls: 2500/month +- Tavily calls: 1000/month +- Serper calls: 1000/month +- Metaphor calls: 500/month +- Firecrawl calls: 500/month +- Stability calls: 200/month +- Exa calls: 2000/month +- Video calls: 50/month +- Gemini tokens: 5,000,000/month +- OpenAI tokens: 2,500,000/month +- Anthropic tokens: 1,000,000/month +- Mistral tokens: 2,500,000/month +- Monthly cost limit: $150.0 +- Features: ["unlimited_content_generation", "premium_research", "advanced_analytics", "priority_support"] + +#### Enterprise Tier +- Price: $199/month, $1990/year +- All calls: Unlimited (0 = unlimited) +- All tokens: Unlimited (0 = unlimited) +- Video calls: Unlimited +- Monthly cost limit: $500.0 +- Features: ["unlimited_everything", "white_label", "dedicated_support", "custom_integrations"] + +### API Pricing (from `pricing_service.py`) + +#### Gemini API Models +- **gemini-2.5-pro**: $1.25/$10.00 per 1M input/output tokens +- **gemini-2.5-pro-large**: $2.50/$15.00 per 1M input/output tokens +- **gemini-2.5-flash**: $0.30/$2.50 per 1M input/output tokens +- **gemini-2.5-flash-audio**: $1.00/$2.50 per 1M input/output tokens +- **gemini-2.5-flash-lite**: $0.10/$0.40 per 1M input/output tokens +- **gemini-2.5-flash-lite-audio**: $0.30/$0.40 per 1M input/output tokens +- **gemini-1.5-flash**: $0.075/$0.30 per 1M input/output tokens +- **gemini-1.5-flash-large**: $0.15/$0.60 per 1M input/output tokens +- **gemini-1.5-flash-8b**: $0.0375/$0.15 per 1M input/output tokens +- **gemini-1.5-flash-8b-large**: $0.075/$0.30 per 1M input/output tokens +- **gemini-1.5-pro**: $1.25/$5.00 per 1M input/output tokens +- **gemini-1.5-pro-large**: $2.50/$10.00 per 1M input/output tokens +- **gemini-embedding**: $0.15 per 1M input tokens +- **gemini-grounding-search**: $35 per 1,000 requests (after free tier) + +#### OpenAI Models +- **gpt-4o**: $2.50/$10.00 per 1M input/output tokens +- **gpt-4o-mini**: $0.15/$0.60 per 1M input/output tokens + +#### Anthropic Models +- **claude-3.5-sonnet**: $3.00/$15.00 per 1M input/output tokens + +#### HuggingFace/Mistral (GPT-OSS-120B via Groq) +- Configurable via env vars: `HUGGINGFACE_INPUT_TOKEN_COST` and `HUGGINGFACE_OUTPUT_TOKEN_COST` +- Default: $1/$3 per 1M input/output tokens + +#### Search APIs +- **Tavily**: $0.001 per search +- **Serper**: $0.001 per search +- **Metaphor**: $0.003 per search +- **Exa**: $0.005 per search (1-25 results) +- **Firecrawl**: $0.002 per page + +#### Other APIs +- **Stability AI**: $0.04 per image +- **Video Generation (HunyuanVideo)**: $0.10 per video generation + +## Billing Dashboard Components + +### Available Components +1. **BillingDashboard** (`components/billing/BillingDashboard.tsx`) - Main dashboard +2. **EnhancedBillingDashboard** (`components/billing/EnhancedBillingDashboard.tsx`) - Enhanced version +3. **CompactBillingDashboard** (`components/billing/CompactBillingDashboard.tsx`) - Compact version +4. **BillingPage** (`pages/BillingPage.tsx`) - Dedicated billing page route + +### Features to Document +- Real-time usage monitoring +- Cost breakdown by provider +- Usage trends and projections +- System health indicators +- Usage alerts +- Subscription renewal history +- Usage logs table +- Comprehensive API breakdown + +## Update Plan + +### 1. Update Pricing Page (`docs-site/docs/features/subscription/pricing.md`) +- [ ] Update all subscription plan limits to match actual database values +- [ ] Add unified `ai_text_generation_calls_limit` explanation for Basic plan +- [ ] Update Gemini API pricing with all current models +- [ ] Update OpenAI pricing with actual values (gpt-4o, gpt-4o-mini) +- [ ] Update Anthropic pricing with actual values (claude-3.5-sonnet) +- [ ] Add Exa search pricing ($0.005 per search) +- [ ] Add video generation pricing and limits +- [ ] Add yearly pricing for all plans +- [ ] Update token limits to reflect actual values (20K for Basic, not 1M/500K) +- [ ] Add all search API limits per plan +- [ ] Add image generation limits per plan +- [ ] Add video generation limits per plan + +### 2. Create/Update Billing Dashboard Documentation +- [ ] Create new page: `docs-site/docs/features/subscription/billing-dashboard.md` +- [ ] Document billing page route (`/billing`) +- [ ] Document all dashboard components (BillingDashboard, Enhanced, Compact) +- [ ] Document features: usage monitoring, cost breakdown, trends, alerts +- [ ] Document subscription renewal history component +- [ ] Document usage logs table +- [ ] Document comprehensive API breakdown component +- [ ] Add screenshots or descriptions of dashboard views +- [ ] Document how to access billing dashboard + +### 3. Update Overview Page +- [ ] Add billing dashboard to features list +- [ ] Update supported API providers list (add Exa, Video generation) +- [ ] Update architecture to mention billing dashboard + +### 4. Update Implementation Status +- [ ] Update to reflect billing dashboard implementation +- [ ] Add subscription renewal history feature +- [ ] Add usage logs table feature +- [ ] Update component count and features + +### 5. Update API Reference +- [ ] Verify all endpoints are documented +- [ ] Add any missing endpoints for renewal history or usage logs + +### 6. Update Navigation +- [ ] Add billing dashboard page to mkdocs.yml navigation + +## Priority Order + +1. **High Priority**: Update pricing page with correct values (users need accurate info) +2. **High Priority**: Create billing dashboard documentation (major feature missing) +3. **Medium Priority**: Update overview and implementation status +4. **Low Priority**: Update API reference and navigation + +## Files to Update + +1. `docs-site/docs/features/subscription/pricing.md` - Major update needed +2. `docs-site/docs/features/subscription/overview.md` - Minor updates +3. `docs-site/docs/features/subscription/implementation-status.md` - Updates needed +4. `docs-site/docs/features/subscription/billing-dashboard.md` - **NEW FILE** +5. `docs-site/mkdocs.yml` - Add billing dashboard to nav + +## Notes + +- The Basic plan has a critical unified limit: `ai_text_generation_calls_limit: 10` - this applies to ALL LLM providers combined (Gemini, OpenAI, Anthropic, Mistral) +- Token limits for Basic plan are much lower than documented: 20K per provider, not 1M/500K +- Video generation is a new feature with pricing and limits per plan +- Exa search is a separate provider from Metaphor with different pricing +- Multiple Gemini models exist with different pricing tiers +- Billing dashboard is a dedicated page, not just a component in main dashboard + diff --git a/docs/SUBSCRIPTION_SYSTEM_README.md b/docs/SUBSCRIPTION_SYSTEM_README.md deleted file mode 100644 index b80b98be..00000000 --- a/docs/SUBSCRIPTION_SYSTEM_README.md +++ /dev/null @@ -1,372 +0,0 @@ -# ALwrity Usage-Based Subscription System - -A comprehensive usage-based subscription system with API cost tracking, usage limits, and real-time monitoring for the ALwrity platform. - -## 🚀 Features - -### Core Functionality -- **Usage-Based Billing**: Track API calls, tokens, and costs across all providers -- **Subscription Tiers**: Free, Basic, Pro, and Enterprise plans with different limits -- **Real-Time Monitoring**: Live usage tracking and limit enforcement -- **Cost Calculation**: Accurate pricing for Gemini, OpenAI, Anthropic, and other APIs -- **Usage Alerts**: Automatic notifications at 80%, 90%, and 100% usage thresholds -- **Robust Error Handling**: Comprehensive logging and exception management - -### Supported API Providers -- **Gemini API**: Google's AI models with latest pricing -- **OpenAI**: GPT models and embeddings -- **Anthropic**: Claude models -- **Mistral AI**: Mistral models -- **Tavily**: AI-powered search -- **Serper**: Google search API -- **Metaphor/Exa**: Advanced search -- **Firecrawl**: Web content extraction -- **Stability AI**: Image generation - -## 📊 Database Schema - -### Core Tables -- `subscription_plans`: Available subscription tiers and limits -- `user_subscriptions`: User subscription information -- `api_usage_logs`: Detailed log of every API call -- `usage_summaries`: Aggregated usage per user per billing period -- `api_provider_pricing`: Pricing configuration for all providers -- `usage_alerts`: Usage notifications and warnings -- `billing_history`: Historical billing records - -## 🛠️ Installation & Setup - -### 1. Database Migration -```bash -cd backend -python scripts/create_subscription_tables.py -``` - -### 2. Verify Installation -```bash -python test_subscription_system.py -``` - -### 3. Start the Server -```bash -python start_alwrity_backend.py -``` - -## 🔧 Configuration - -### Default Subscription Plans - -#### Free Tier -- **Price**: $0/month -- **Gemini Calls**: 100/month -- **Tokens**: 100,000/month -- **Features**: Basic content generation - -#### Basic Tier -- **Price**: $29/month -- **Gemini Calls**: 1,000/month -- **OpenAI Calls**: 500/month -- **Tokens**: 1M Gemini, 500K OpenAI -- **Cost Limit**: $50/month - -#### Pro Tier -- **Price**: $79/month -- **Gemini Calls**: 5,000/month -- **OpenAI Calls**: 2,500/month -- **Tokens**: 5M Gemini, 2.5M OpenAI -- **Cost Limit**: $150/month - -#### Enterprise Tier -- **Price**: $199/month -- **Unlimited API calls** (with cost limits) -- **Cost Limit**: $500/month -- **Premium features**: White-label, dedicated support - -### API Pricing (Current) - -#### Gemini API -- **Gemini 2.0 Flash Lite**: $0.075/$0.30 per 1M input/output tokens -- **Gemini 2.5 Flash**: $0.125/$0.375 per 1M input/output tokens -- **Gemini 2.5 Pro**: $1.25/$10.00 per 1M input/output tokens - -#### Search APIs -- **Tavily**: $0.001 per search -- **Serper**: $0.001 per search -- **Metaphor**: $0.003 per search - -## 📡 API Endpoints - -### Subscription Management -``` -GET /api/subscription/plans # Get all subscription plans -GET /api/subscription/user/{user_id}/subscription # Get user subscription -GET /api/subscription/pricing # Get API pricing info -``` - -### Usage Tracking -``` -GET /api/subscription/usage/{user_id} # Get current usage stats -GET /api/subscription/usage/{user_id}/trends # Get usage trends -GET /api/subscription/dashboard/{user_id} # Get dashboard data -``` - -### Alerts & Notifications -``` -GET /api/subscription/alerts/{user_id} # Get usage alerts -POST /api/subscription/alerts/{alert_id}/mark-read # Mark alert as read -``` - -## 🔍 Usage Monitoring - -### Middleware Integration -The system automatically tracks API usage through enhanced middleware: - -```python -# Automatic usage tracking for all API calls -await usage_service.track_api_usage( - user_id=user_id, - provider=APIProvider.GEMINI, - endpoint="/api/generate", - method="POST", - tokens_input=1000, - tokens_output=500, - cost=0.00125, - response_time=2.5 -) -``` - -### Usage Limit Enforcement -```python -# Check limits before processing requests -can_proceed, message, usage_info = await usage_service.enforce_usage_limits( - user_id=user_id, - provider=APIProvider.GEMINI, - tokens_requested=1000 -) - -if not can_proceed: - return JSONResponse( - status_code=429, - content={"error": "Usage limit exceeded", "message": message} - ) -``` - -## 📈 Dashboard Integration - -### Usage Statistics -```javascript -// Get comprehensive usage data -const response = await fetch(`/api/subscription/dashboard/${userId}`); -const data = await response.json(); - -console.log(data.data.summary); -// { -// total_api_calls_this_month: 1250, -// total_cost_this_month: 15.75, -// usage_status: "active", -// unread_alerts: 2 -// } -``` - -### Real-Time Monitoring -```javascript -// Get current usage percentages -const usage = data.data.current_usage; -console.log(usage.usage_percentages); -// { -// gemini_calls: 65.5, -// openai_calls: 23.8, -// cost: 31.5 -// } -``` - -## 🚨 Error Handling - -### Exception Types -- `UsageLimitExceededException`: When usage limits are reached -- `PricingException`: Pricing calculation errors -- `TrackingException`: Usage tracking failures -- `SubscriptionException`: General subscription errors - -### Usage -```python -from services.subscription_exception_handler import handle_usage_limit_error - -# Handle usage limit errors -error_response = handle_usage_limit_error( - user_id="user123", - provider=APIProvider.GEMINI, - limit_type="api_calls", - current_usage=1000, - limit_value=1000 -) -``` - -## 🔒 Security & Privacy - -### Data Protection -- User usage data is encrypted at rest -- API keys are never logged in usage tracking -- Sensitive information is excluded from error logs -- GDPR-compliant data handling - -### Rate Limiting -- Pre-request usage validation -- Automatic limit enforcement -- Graceful degradation when limits are reached -- User-friendly error messages - -## 📊 Monitoring & Analytics - -### Usage Trends -- Historical usage data over time -- Provider-specific breakdowns -- Cost projections and forecasting -- Performance metrics (response times, error rates) - -### Alerts & Notifications -- Automatic threshold alerts (80%, 90%, 100%) -- Email notifications (configurable) -- Dashboard notifications -- Usage recommendations - -## 🔧 Customization - -### Adding New API Providers -1. Add provider to `APIProvider` enum -2. Configure pricing in `api_provider_pricing` table -3. Update detection patterns in middleware -4. Add usage tracking logic - -### Modifying Subscription Plans -1. Update plans in database or via API -2. Modify limits and pricing -3. Add/remove features -4. Update billing integration - -## 🧪 Testing - -### Run Tests -```bash -python test_subscription_system.py -``` - -### Test Coverage -- Database table creation -- Pricing calculations -- Usage tracking -- Limit enforcement -- Error handling -- API endpoints - -## 🚀 Deployment - -### Environment Variables -```env -DATABASE_URL=sqlite:///./alwrity.db -GEMINI_API_KEY=your_gemini_key -OPENAI_API_KEY=your_openai_key -# ... other API keys -``` - -### Production Setup -1. Use PostgreSQL for production database -2. Set up Redis for caching -3. Configure email notifications -4. Set up monitoring and alerting -5. Implement payment processing - -## 📝 API Examples - -### Get User Usage -```bash -curl -X GET "http://localhost:8000/api/subscription/usage/user123" \ - -H "Content-Type: application/json" -``` - -### Get Dashboard Data -```bash -curl -X GET "http://localhost:8000/api/subscription/dashboard/user123" \ - -H "Content-Type: application/json" -``` - -### Response Example -```json -{ - "success": true, - "data": { - "current_usage": { - "billing_period": "2025-01", - "total_calls": 1250, - "total_cost": 15.75, - "usage_status": "active", - "provider_breakdown": { - "gemini": {"calls": 800, "cost": 10.50}, - "openai": {"calls": 450, "cost": 5.25} - } - }, - "limits": { - "plan_name": "Pro", - "limits": { - "gemini_calls": 5000, - "monthly_cost": 150.0 - } - }, - "projections": { - "projected_monthly_cost": 47.25, - "projected_usage_percentage": 31.5 - } - } -} -``` - -## 🤝 Contributing - -### Development Workflow -1. Create feature branch -2. Implement changes -3. Add tests -4. Update documentation -5. Submit pull request - -### Code Standards -- Follow PEP 8 for Python code -- Use type hints -- Add comprehensive logging -- Include error handling -- Write unit tests - -## 📚 Additional Resources - -- [Gemini API Pricing](https://ai.google.dev/gemini-api/docs/pricing) -- [OpenAI API Pricing](https://openai.com/pricing) -- [FastAPI Documentation](https://fastapi.tiangolo.com/) -- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/) - -## 🐛 Troubleshooting - -### Common Issues -1. **Database Connection Errors**: Check DATABASE_URL configuration -2. **Missing API Keys**: Verify all required keys are set -3. **Usage Not Tracking**: Check middleware integration -4. **Pricing Errors**: Verify provider pricing configuration - -### Debug Mode -```python -# Enable debug logging -import logging -logging.basicConfig(level=logging.DEBUG) -``` - -### Support -For issues and questions: -1. Check the logs in `logs/subscription_errors.log` -2. Run the test suite to identify problems -3. Review the error handling documentation -4. Contact the development team - ---- - -**Version**: 1.0.0 -**Last Updated**: January 2025 -**Maintainer**: ALwrity Development Team \ No newline at end of file diff --git a/docs/WIX_INTEGRATION_README.md b/docs/WIX_INTEGRATION_README.md deleted file mode 100644 index 16037d59..00000000 --- a/docs/WIX_INTEGRATION_README.md +++ /dev/null @@ -1,300 +0,0 @@ -# Wix Integration for ALwrity - -This document describes the Wix integration feature that allows ALwrity users to publish their generated blogs directly to their Wix websites. - -## Overview - -The Wix integration provides a seamless way for ALwrity users to: -- Connect their Wix account to ALwrity -- Publish blog posts directly from ALwrity to their Wix website -- Manage blog categories and tags -- Import images to Wix Media Manager - -## Architecture - -### Backend Components - -1. **WixService** (`services/wix_service.py`) - - Handles OAuth 2.0 authentication with Wix - - Manages token refresh and validation - - Converts content to Wix Ricos JSON format - - Imports images to Wix Media Manager - - Creates and publishes blog posts - -2. **Wix Routes** (`api/wix_routes.py`) - - `/api/wix/auth/url` - Get OAuth authorization URL - - `/api/wix/auth/callback` - Handle OAuth callback - - `/api/wix/connection/status` - Check connection status - - `/api/wix/publish` - Publish blog post to Wix - - `/api/wix/categories` - Get blog categories - - `/api/wix/tags` - Get blog tags - - `/api/wix/disconnect` - Disconnect Wix account - -### Frontend Components - -1. **WixTestPage** (`frontend/src/components/WixTestPage/WixTestPage.tsx`) - - Test page for Wix integration functionality - - Connection status display - - Blog post creation and publishing form - - Category and tag management - -2. **Enhanced Publisher** (`frontend/src/components/BlogWriter/Publisher.tsx`) - - Integrated Wix publishing into existing blog writer - - Connection status checking - - Enhanced error handling and user feedback - -## Setup Instructions - -### 1. Wix App Configuration - -1. Go to [Wix Developers](https://dev.wix.com/) -2. Create a new app or use an existing one -3. Configure OAuth settings: - - Redirect URI: `http://localhost:3000/wix/callback` (for development) - - Scopes: `BLOG.CREATE-DRAFT`, `BLOG.PUBLISH`, `MEDIA.MANAGE` -4. Note down your Client ID (no Client Secret required for Wix Headless OAuth) - -### 2. Environment Configuration - -Add the following environment variables to your `.env` file: - -```bash -# Wix Integration (Headless OAuth - Client ID only, no Client Secret required) -WIX_CLIENT_ID=your_wix_client_id_here -WIX_REDIRECT_URI=http://localhost:3000/wix/callback -``` - -**Important Note**: Wix Headless OAuth only requires a Client ID and does NOT use a Client Secret. This is different from traditional OAuth implementations and is designed for public clients like single-page applications. - -### 3. Database Setup - -The integration requires storing user tokens securely. You'll need to: - -1. Create a table to store Wix tokens: -```sql -CREATE TABLE wix_tokens ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id TEXT NOT NULL, - access_token TEXT NOT NULL, - refresh_token TEXT, - expires_at TIMESTAMP, - member_id TEXT, -- Store member ID for third-party app requirements - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -``` - -2. Implement token storage and retrieval functions in the WixService - -### 4. Important: Third-Party App Requirements - -**CRITICAL**: When creating blog posts as a third-party app, Wix requires a `memberId` field. This is mandatory and cannot be omitted. The integration will: - -1. Automatically retrieve the current member ID during the OAuth flow -2. Store the member ID with the user's tokens -3. Use the member ID when creating blog posts - -This requirement is enforced by Wix's API and cannot be bypassed. - -## Usage - -### 1. Testing the Integration - -1. Navigate to `/wix-test` in your ALwrity application -2. Click "Connect to Wix" to authorize the integration -3. Complete the OAuth flow in the popup window -4. Once connected, you can: - - Load categories and tags from your Wix blog - - Create and publish test blog posts - - Check connection status - -### 2. Publishing from Blog Writer - -1. Generate your blog content using ALwrity's AI tools -2. Use the CopilotKit action: "Publish to Wix" -3. The system will: - - Check your Wix connection status - - Convert your content to Wix format - - Import any images to Wix Media Manager - - Create and publish the blog post - - Return the published post URL - -## API Endpoints - -### Authentication - -#### Get Authorization URL -```http -GET /api/wix/auth/url?state=optional_state -``` - -#### Handle OAuth Callback -```http -POST /api/wix/auth/callback -Content-Type: application/json - -{ - "code": "authorization_code", - "state": "optional_state" -} -``` - -### Connection Management - -#### Check Connection Status -```http -GET /api/wix/connection/status -``` - -#### Disconnect Account -```http -POST /api/wix/disconnect -``` - -### Publishing - -#### Publish Blog Post -```http -POST /api/wix/publish -Content-Type: application/json - -{ - "title": "Blog Post Title", - "content": "Blog content in markdown", - "cover_image_url": "https://example.com/image.jpg", - "category_ids": ["category_id_1"], - "tag_ids": ["tag_id_1", "tag_id_2"], - "publish": true -} -``` - -### Content Management - -#### Get Blog Categories -```http -GET /api/wix/categories -``` - -#### Get Blog Tags -```http -GET /api/wix/tags -``` - -## Content Format Conversion - -The integration automatically converts ALwrity's markdown content to Wix's Ricos JSON format: - -### Supported Elements - -- **Headings**: `# Heading` → `HEADING` node -- **Paragraphs**: Regular text → `PARAGRAPH` node -- **Images**: External URLs → Imported to Wix Media Manager -- **Lists**: Markdown lists → `ORDERED_LIST`/`BULLETED_LIST` nodes - -### Example Conversion - -**Markdown Input:** -```markdown -# Welcome to My Blog - -This is a paragraph with some content. - -## Features - -- Feature 1 -- Feature 2 -``` - -**Ricos JSON Output:** -```json -{ - "nodes": [ - { - "type": "HEADING", - "nodes": [{ - "type": "TEXT", - "textData": { - "text": "Welcome to My Blog", - "decorations": [] - } - }], - "headingData": { "level": 1 } - }, - { - "type": "PARAGRAPH", - "nodes": [{ - "type": "TEXT", - "textData": { - "text": "This is a paragraph with some content.", - "decorations": [] - } - }], - "paragraphData": {} - } - ] -} -``` - -## Error Handling - -The integration includes comprehensive error handling for: - -- **Authentication Errors**: Invalid tokens, expired sessions -- **Permission Errors**: Insufficient Wix app permissions -- **Content Errors**: Invalid content format, missing required fields -- **Network Errors**: API timeouts, connection issues - -## Security Considerations - -1. **Token Storage**: Access and refresh tokens are stored securely -2. **HTTPS**: All API calls use HTTPS in production -3. **Scope Limitation**: Only requests necessary permissions -4. **Token Refresh**: Automatic token refresh when expired - -## Troubleshooting - -### Common Issues - -1. **"Wix account not connected"** - - Solution: Use the Wix Test Page to connect your account - -2. **"Insufficient permissions"** - - Solution: Reconnect your Wix account with proper permissions - -3. **"Failed to import image"** - - Solution: Check image URL accessibility and format - -4. **"Content format error"** - - Solution: Ensure content is valid markdown - -### Debug Mode - -Enable debug logging by setting the log level to DEBUG in your environment: - -```bash -LOG_LEVEL=DEBUG -``` - -## Future Enhancements - -1. **Scheduled Publishing**: Support for scheduled blog posts -2. **Bulk Publishing**: Publish multiple posts at once -3. **Content Templates**: Pre-defined content templates for Wix -4. **Analytics Integration**: Track published post performance -5. **Advanced Formatting**: Support for more Ricos node types - -## Support - -For issues or questions about the Wix integration: - -1. Check the troubleshooting section above -2. Review the Wix API documentation -3. Check the application logs for detailed error messages -4. Contact the development team - -## Related Documentation - -- [Wix REST API Documentation](https://dev.wix.com/docs/rest) -- [Wix Blog API](https://dev.wix.com/docs/rest/business-solutions/blog) -- [Wix OAuth 2.0](https://dev.wix.com/docs/rest/app-management/oauth-2) -- [Ricos JSON Format](https://dev.wix.com/docs/ricos/api-reference/ricos-document) diff --git a/docs/WIX_INTEGRATION_SUMMARY.md b/docs/WIX_INTEGRATION_SUMMARY.md deleted file mode 100644 index dd6c11d3..00000000 --- a/docs/WIX_INTEGRATION_SUMMARY.md +++ /dev/null @@ -1,188 +0,0 @@ -# Wix Integration Implementation Summary - -## 🎯 Project Overview - -Successfully implemented a comprehensive Wix integration feature for ALwrity that allows users to publish their AI-generated blogs directly to their Wix websites. - -## ✅ Completed Features - -### 1. **Backend Implementation** -- **WixService** (`backend/services/wix_service.py`) - - OAuth 2.0 authentication flow - - Token management and refresh - - Content conversion to Wix Ricos JSON format - - Image import to Wix Media Manager - - Blog post creation and publishing - -- **API Routes** (`backend/api/wix_routes.py`) - - `/api/wix/auth/url` - OAuth authorization URL - - `/api/wix/auth/callback` - OAuth callback handler - - `/api/wix/connection/status` - Connection status check - - `/api/wix/publish` - Blog publishing endpoint - - `/api/wix/categories` - Blog categories management - - `/api/wix/tags` - Blog tags management - - `/api/wix/disconnect` - Account disconnection - -### 2. **Frontend Implementation** -- **WixTestPage** (`frontend/src/components/WixTestPage/WixTestPage.tsx`) - - Complete test interface for Wix integration - - Connection status display - - Blog post creation form - - Category and tag selection - - Real-time publishing feedback - -- **Enhanced Publisher** (`frontend/src/components/BlogWriter/Publisher.tsx`) - - Integrated Wix publishing into existing blog writer - - Connection status checking - - Enhanced error handling - - User-friendly feedback messages - -### 3. **Integration Features** -- **Authentication Flow** - - Secure OAuth 2.0 implementation - - Permission scope management (`BLOG.CREATE-DRAFT`, `BLOG.PUBLISH`, `MEDIA.MANAGE`) - - Token storage and refresh handling - -- **Content Processing** - - Markdown to Ricos JSON conversion - - Image import to Wix Media Manager - - Support for headings, paragraphs, lists - - Cover image handling - -- **Error Handling** - - Comprehensive error messages - - Connection status validation - - Permission checking - - User guidance for common issues - -## 🚀 How It Works - -### **Publishing Flow** -1. **Check Connection**: Verify user has valid Wix tokens and permissions -2. **Content Conversion**: Convert ALwrity markdown to Wix Ricos format -3. **Image Processing**: Import external images to Wix Media Manager -4. **Blog Creation**: Create blog post using Wix Blog API -5. **Publishing**: Publish immediately or save as draft -6. **Feedback**: Return published post URL and status - -### **User Experience** -1. **Connect Account**: User clicks "Connect to Wix" → OAuth flow → Account connected -2. **Generate Content**: User creates blog content using ALwrity AI tools -3. **Publish**: User clicks "Publish to Wix" → Content published to Wix website -4. **View Result**: User gets published post URL and can view on their Wix site - -## 📁 File Structure - -``` -backend/ -├── services/ -│ └── wix_service.py # Core Wix integration service -├── api/ -│ └── wix_routes.py # Wix API endpoints -├── test_wix_integration.py # Test script -├── WIX_INTEGRATION_README.md # Detailed documentation -└── env_template.txt # Environment variables template - -frontend/src/components/ -├── WixTestPage/ -│ └── WixTestPage.tsx # Test page component -└── BlogWriter/ - └── Publisher.tsx # Enhanced publisher with Wix support -``` - -## 🔧 Setup Requirements - -### **Environment Variables** -```bash -# Wix Headless OAuth - Client ID only, no Client Secret required -WIX_CLIENT_ID=your_wix_client_id_here -WIX_REDIRECT_URI=http://localhost:3000/wix/callback -``` - -### **Wix App Configuration** -1. Create Wix app at [Wix Developers](https://dev.wix.com/) -2. Configure OAuth settings with required scopes -3. Set redirect URI for your environment -4. **Important**: Wix Headless OAuth only requires Client ID, no Client Secret needed - -### **Critical Third-Party App Requirements** -- **memberId is MANDATORY** for creating blog posts as a third-party app -- The integration automatically retrieves and stores member IDs during OAuth -- This requirement cannot be bypassed and is enforced by Wix's API - -### **Database Setup** -- Token storage table for user authentication -- Secure token encryption and management - -## 🧪 Testing - -### **Test Page** -- Navigate to `/wix-test` in ALwrity -- Complete OAuth flow -- Test blog publishing functionality -- Verify connection status - -### **Integration Testing** -- Run `python test_wix_integration.py` in backend directory -- Verify service initialization -- Test content conversion -- Check environment configuration - -## 📊 Test Results - -``` -🧪 Wix Integration Test Suite -================================================== -✅ Service Initialization: PASSED -✅ Content Conversion: PASSED (5 nodes generated) -⚠️ Authorization URL: Requires credentials -⚠️ Environment Variables: Requires setup -``` - -## 🎯 Key Benefits - -1. **Seamless Integration**: Direct publishing from ALwrity to Wix -2. **User-Friendly**: Simple OAuth flow and intuitive interface -3. **Robust Error Handling**: Clear feedback and guidance -4. **Content Preservation**: Maintains formatting and structure -5. **Image Support**: Automatic image import to Wix Media Manager -6. **Flexible Publishing**: Support for categories, tags, and scheduling - -## 🔮 Future Enhancements - -1. **Scheduled Publishing**: Support for future-dated posts -2. **Bulk Publishing**: Publish multiple posts at once -3. **Content Templates**: Pre-defined Wix-optimized templates -4. **Analytics Integration**: Track published post performance -5. **Advanced Formatting**: Support for more Ricos node types - -## 📚 Documentation - -- **Setup Guide**: `backend/WIX_INTEGRATION_README.md` -- **API Documentation**: Integrated into FastAPI docs -- **Test Instructions**: Included in test script -- **Environment Template**: `backend/env_template.txt` - -## 🎉 Success Metrics - -- ✅ **Complete OAuth 2.0 Flow**: Implemented and tested -- ✅ **Content Conversion**: Markdown to Ricos JSON working -- ✅ **API Integration**: All endpoints functional -- ✅ **Frontend Integration**: Test page and enhanced publisher ready -- ✅ **Error Handling**: Comprehensive error management -- ✅ **Documentation**: Complete setup and usage guides - -## 🚀 Ready for Production - -The Wix integration is **production-ready** with: -- Secure authentication flow -- Robust error handling -- Comprehensive testing -- Complete documentation -- User-friendly interface - -**Next Steps**: Configure Wix app credentials and deploy to production environment. - ---- - -*Implementation completed successfully! The Wix integration provides a seamless way for ALwrity users to publish their AI-generated content directly to their Wix websites.* diff --git a/docs/WIX_SEO_METADATA_COMPLETE.md b/docs/WIX_SEO_METADATA_COMPLETE.md deleted file mode 100644 index de6ee605..00000000 --- a/docs/WIX_SEO_METADATA_COMPLETE.md +++ /dev/null @@ -1,150 +0,0 @@ -# Complete Wix SEO Metadata Implementation - -## 📊 SEO Metadata Generated vs Posted - -### ✅ FULLY POSTED TO WIX - -#### 1. **SEO Keywords** (in `seoData.settings.keywords`) -- ✅ `focus_keyword` → Main keyword (`isMain: true`) -- ✅ `blog_tags` → Additional keywords (`isMain: false`) -- ✅ `social_hashtags` → Additional keywords (`isMain: false`) - -#### 2. **Meta Tags** (in `seoData.tags`) -- ✅ `meta_description` → `` -- ✅ `seo_title` → `` - -#### 3. **Open Graph Tags** (in `seoData.tags`) -- ✅ `open_graph.title` → `og:title` -- ✅ `open_graph.description` → `og:description` -- ✅ `open_graph.image` → `og:image` (HTTP/HTTPS URLs only) -- ✅ `og:type` → Always set to `article` -- ✅ `open_graph.url` or `canonical_url` → `og:url` - -#### 4. **Twitter Card Tags** (in `seoData.tags`) -- ✅ `twitter_card.title` → `twitter:title` -- ✅ `twitter_card.description` → `twitter:description` -- ✅ `twitter_card.image` → `twitter:image` (HTTP/HTTPS URLs only) -- ✅ `twitter_card.card` → `twitter:card` (default: `summary_large_image`) - -#### 5. **Canonical URL** (in `seoData.tags`) -- ✅ `canonical_url` → `` - -#### 6. **Blog Categories** (in `draftPost.categoryIds`) -- ✅ `blog_categories` → Lookup/create categories → `categoryIds` (UUIDs) -- **Implementation**: `lookup_or_create_categories()` method -- **Behavior**: Case-insensitive lookup, auto-create if missing - -#### 7. **Blog Tags** (in `draftPost.tagIds`) -- ✅ `blog_tags` → Lookup/create tags → `tagIds` (UUIDs) -- **Implementation**: `lookup_or_create_tags()` method -- **Behavior**: Case-insensitive lookup, auto-create if missing -- **Note**: `blog_tags` are also used in SEO keywords, but separately as post tags - -### ❌ NOT POSTED (Optional/Future) - -1. **JSON-LD Structured Data** (`json_ld_schema`) - - **Reason**: Wix doesn't support JSON-LD in backend API - - **Solution**: Would require frontend implementation using `@wix/site-seo` package - - **Status**: Not implemented (would need to be added to Wix site code) - -2. **URL Slug** (`url_slug`) - - **Reason**: Wix auto-generates URLs from title - - **Status**: Could be implemented if Wix API supports custom slugs - -3. **Reading Time** (`reading_time`) - - **Reason**: Metadata only, not part of Wix blog post structure - - **Status**: Not applicable - -4. **Optimization Score** (`optimization_score`) - - **Reason**: Internal metadata for ALwrity, not Wix field - - **Status**: Not applicable - -## 🔄 Conversion Methods - -### Markdown to Ricos Conversion - -**Primary Method**: Wix Official Ricos Documents API -- **Endpoint**: Tries multiple paths to find correct endpoint -- **Benefits**: Official conversion, handles all edge cases -- **Fallback**: Custom parser if API unavailable - -**Fallback Method**: Custom Markdown Parser -- **Location**: `backend/services/integrations/wix/content.py` -- **Supports**: Headings, paragraphs, lists, bold, italic, links, images, blockquotes - -## 📋 Complete Post Structure - -When publishing to Wix, the blog post includes: - -```json -{ - "draftPost": { - "title": "SEO optimized title", - "memberId": "author-member-id", - "richContent": { /* Ricos JSON document */ }, - "excerpt": "First 200 chars of content", - "categoryIds": ["uuid1", "uuid2"], // From blog_categories - "tagIds": ["uuid1", "uuid2"], // From blog_tags - "media": { /* Cover image if provided */ }, - "seoData": { - "settings": { - "keywords": [ - { "term": "main keyword", "isMain": true }, - { "term": "tag1", "isMain": false }, - { "term": "tag2", "isMain": false } - ] - }, - "tags": [ - { "type": "meta", "props": { "name": "description", "content": "..." } }, - { "type": "meta", "props": { "name": "title", "content": "..." } }, - { "type": "meta", "props": { "property": "og:title", "content": "..." } }, - { "type": "meta", "props": { "property": "og:description", "content": "..." } }, - { "type": "meta", "props": { "property": "og:image", "content": "..." } }, - { "type": "meta", "props": { "property": "og:type", "content": "article" } }, - { "type": "meta", "props": { "property": "og:url", "content": "..." } }, - { "type": "meta", "props": { "name": "twitter:title", "content": "..." } }, - { "type": "meta", "props": { "name": "twitter:description", "content": "..." } }, - { "type": "meta", "props": { "name": "twitter:image", "content": "..." } }, - { "type": "meta", "props": { "name": "twitter:card", "content": "summary_large_image" } }, - { "type": "link", "props": { "rel": "canonical", "href": "..." } } - ] - } - }, - "publish": true -} -``` - -## ✅ Implementation Status - -### Fully Implemented ✅ -- SEO keywords (main + additional) -- Meta description and title -- Open Graph tags (all standard fields) -- Twitter Card tags (all standard fields) -- Canonical URL -- **Blog categories** (lookup/create) -- **Blog tags** (lookup/create) -- Wix Ricos API integration (with fallback) - -### Partially Implemented ⚠️ -- Image handling (only HTTP/HTTPS URLs, base64 skipped) - -### Not Implemented ❌ -- JSON-LD structured data (requires frontend) -- URL slug customization -- Reading time (not applicable) -- Optimization score (not applicable) - -## 🎯 Summary - -**All major SEO metadata fields are now being posted to Wix:** -- ✅ Keywords -- ✅ Meta tags -- ✅ Open Graph -- ✅ Twitter Cards -- ✅ Canonical URL -- ✅ Categories (auto-lookup/create) -- ✅ Tags (auto-lookup/create) - -The only missing piece is JSON-LD structured data, which requires frontend implementation in the Wix site code using the `@wix/site-seo` package. - diff --git a/docs/WIX_SEO_METADATA_REVIEW.md b/docs/WIX_SEO_METADATA_REVIEW.md deleted file mode 100644 index 4d6f7a19..00000000 --- a/docs/WIX_SEO_METADATA_REVIEW.md +++ /dev/null @@ -1,102 +0,0 @@ -# Wix SEO Metadata Review - -## SEO Metadata We Generate (`BlogSEOMetadataResponse`) - -### Available Fields: -1. ✅ **seo_title** - SEO optimized title -2. ✅ **meta_description** - Meta description -3. ✅ **url_slug** - URL slug for the blog post -4. ✅ **blog_tags** - Array of tag strings (NOW being used for Wix post tags via lookup/create) -5. ✅ **blog_categories** - Array of category strings (NOW being used for Wix post categories via lookup/create) -6. ✅ **social_hashtags** - Hashtags for social media -7. ✅ **open_graph** - Open Graph metadata object: - - title - - description - - image - - url - - type -8. ✅ **twitter_card** - Twitter Card metadata object: - - title - - description - - image - - card (type) -9. ✅ **canonical_url** - Canonical URL -10. ✅ **focus_keyword** - Main SEO keyword -11. ❌ **json_ld_schema** - JSON-LD structured data (NOT being posted - would need frontend implementation) -12. ❌ **schema** - Legacy schema field (NOT being used) -13. ❌ **reading_time** - Estimated reading time (NOT being posted) -14. ❌ **optimization_score** - SEO optimization score (NOT being posted) -15. ❌ **generated_at** - Generation timestamp (NOT being posted) - -## What We're Currently Posting to Wix - -### ✅ Posted via `seoData`: -- **Keywords** (from `focus_keyword`, `blog_tags`, `social_hashtags`) - - Main keyword: `focus_keyword` → `isMain: true` - - Additional keywords: `blog_tags` and `social_hashtags` → `isMain: false` -- **Meta Tags**: - - `meta description` → `` - - `seo_title` → `` -- **Open Graph Tags**: - - `og:title`, `og:description`, `og:image`, `og:type`, `og:url` -- **Twitter Card Tags**: - - `twitter:title`, `twitter:description`, `twitter:image`, `twitter:card` -- **Canonical URL**: - - `` - -### ✅ NOW Being Posted (Recently Implemented): - -1. **Blog Categories** (`blog_categories`) - - ✅ **Implemented**: `lookup_or_create_categories()` method - - ✅ **Behavior**: Case-insensitive lookup, auto-create if missing - - ✅ **Result**: Categories from SEO metadata are posted as `categoryIds` (UUIDs) - -2. **Blog Tags** (`blog_tags` for post organization) - - ✅ **Implemented**: `lookup_or_create_tags()` method - - ✅ **Behavior**: Case-insensitive lookup, auto-create if missing - - ✅ **Result**: Tags from SEO metadata are posted as `tagIds` (UUIDs) - - **Note**: `blog_tags` are used BOTH for SEO keywords AND for Wix post tags - -3. **JSON-LD Structured Data** (`json_ld_schema`) - - **Issue**: Wix doesn't support JSON-LD in backend API - - **Solution**: Would need frontend implementation using `@wix/site-seo` package - - **Status**: Not implemented - -4. **URL Slug** (`url_slug`) - - **Issue**: Not being passed to Wix - - **Status**: Wix generates URL automatically, but we could potentially set it - -## Implementation Status - -### ✅ Fully Implemented: -- SEO keywords in `seoData.settings.keywords` -- Meta description tag -- SEO title tag -- Open Graph tags (title, description, image, type, url) -- Twitter Card tags (title, description, image, card type) -- Canonical URL link tag - -### ✅ Fully Implemented: -- **Blog Categories**: Auto-lookup/create from `blog_categories` -- **Blog Tags**: Auto-lookup/create from `blog_tags` -- **Wix Ricos API Integration**: Uses official Wix API with fallback to custom parser - -### ❌ Not Implemented (Optional): -- JSON-LD structured data (frontend only - requires `@wix/site-seo` package) -- URL slug setting (Wix auto-generates URLs) -- Reading time (metadata only, not applicable) -- Optimization score (metadata only, not applicable) - -## Summary - -✅ **All major SEO metadata is now being posted to Wix:** -- SEO keywords (main + additional) -- Meta tags (description, title) -- Open Graph tags (title, description, image, type, url) -- Twitter Card tags (title, description, image, card type) -- Canonical URL -- **Blog Categories** (auto-lookup/create) -- **Blog Tags** (auto-lookup/create) - -The only missing piece is JSON-LD structured data, which requires frontend implementation in the Wix site code using `@wix/site-seo` package (not a backend concern). - diff --git a/docs/WIX_TESTING_BYPASS_GUIDE.md b/docs/WIX_TESTING_BYPASS_GUIDE.md deleted file mode 100644 index 1c7b3356..00000000 --- a/docs/WIX_TESTING_BYPASS_GUIDE.md +++ /dev/null @@ -1,95 +0,0 @@ -# 🚀 Wix Integration Testing - Onboarding Bypass Guide - -## ✅ **Bypass Implemented Successfully** - -I've implemented multiple bypass options to allow you to test the Wix integration without completing onboarding: - -### 🔧 **Changes Made:** - -1. **✅ Removed ProtectedRoute from `/wix-test`** - Direct access to Wix test page -2. **✅ Disabled monitoring middleware** - Bypasses API rate limiting -3. **✅ Mocked onboarding status** - Returns `is_completed: true` -4. **✅ Added direct route** - `/wix-test-direct` as backup - -### 🎯 **Testing Options:** - -| Option | URL | Description | -|--------|-----|-------------| -| **Primary** | `http://localhost:3000/wix-test` | Main Wix test page (bypass enabled) | -| **Backup** | `http://localhost:3000/wix-test-direct` | Direct route (no protections) | -| **Backend** | `http://localhost:8000/api/wix/auth/url` | Direct API testing | - -### 🚀 **How to Test:** - -1. **Start Backend Server:** - ```bash - cd backend - python start_alwrity_backend.py - ``` - -2. **Start Frontend Server:** - ```bash - cd frontend - npm start - ``` - -3. **Navigate to Wix Test:** - - Go to: `http://localhost:3000/wix-test` - - You should now have direct access (no onboarding redirect) - -4. **Test Wix Integration:** - - Click "Connect Wix Account" - - Authorize with your Wix site - - Test blog publishing functionality - -### 📋 **Current Status:** - -- ✅ **Onboarding bypassed** - No redirect to onboarding page -- ✅ **Rate limiting disabled** - No API call limits -- ✅ **Wix service ready** - All components functional -- ✅ **Client ID configured** - Wix OAuth URLs are working -- ✅ **Test endpoints working** - No authentication required - -### 🔧 **Required Setup:** - -Add to your `backend/.env` file: -```bash -WIX_CLIENT_ID=your_wix_client_id_here -WIX_REDIRECT_URI=http://localhost:3000/wix/callback -``` - -### ⚠️ **Important: Restore After Testing** - -After testing, restore the protections by reverting these changes: - -1. **Re-enable monitoring middleware** in `backend/app.py`: - ```python - app.middleware("http")(monitoring_middleware) - ``` - -2. **Remove mock from** `backend/api/onboarding.py`: - - Uncomment the original code - - Remove the temporary mock - -3. **Restore ProtectedRoute** in `frontend/src/App.tsx`: - ```typescript - } /> - ``` - -### 🧪 **Test Script:** - -Run the test script to verify everything: -```bash -cd backend -python test_wix_bypass.py -``` - -### 🎉 **Expected Results:** - -- ✅ No onboarding redirect -- ✅ Direct access to Wix test page -- ✅ Wix OAuth flow works -- ✅ Blog posting functionality available -- ✅ No rate limiting errors - -The Wix integration is now ready for testing! 🚀 diff --git a/docs/alwrity_test_scripts/PHASE1_IMPLEMENTATION_SUMMARY.md b/docs/alwrity_test_scripts/PHASE1_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 6d97e0ad..00000000 --- a/docs/alwrity_test_scripts/PHASE1_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,280 +0,0 @@ -# Enhanced Strategy Service - Phase 1 Implementation Summary - -## 🎯 **Phase 1 Complete: Foundation & Infrastructure** - -**Implementation Period**: Weeks 1-2 -**Status**: ✅ **COMPLETED** -**Date**: December 2024 - ---- - -## 📊 **Phase 1 Deliverables Achieved** - -### ✅ **1.1 Database Schema Enhancement** - -**Enhanced Database Schema with 30+ Strategic Input Fields** - -- **EnhancedContentStrategy Model**: Complete with 30+ strategic input fields - - Business Context (8 inputs): business_objectives, target_metrics, content_budget, team_size, implementation_timeline, market_share, competitive_position, performance_metrics - - Audience Intelligence (6 inputs): content_preferences, consumption_patterns, audience_pain_points, buying_journey, seasonal_trends, engagement_metrics - - Competitive Intelligence (5 inputs): top_competitors, competitor_content_strategies, market_gaps, industry_trends, emerging_trends - - Content Strategy (7 inputs): preferred_formats, content_mix, content_frequency, optimal_timing, quality_metrics, editorial_guidelines, brand_voice - - Performance & Analytics (4 inputs): traffic_sources, conversion_rates, content_roi_targets, ab_testing_capabilities - -- **EnhancedAIAnalysisResult Model**: Stores comprehensive AI analysis results - - 5 specialized analysis types: comprehensive_strategy, audience_intelligence, competitive_intelligence, performance_optimization, content_calendar_optimization - - Enhanced data tracking with confidence scores and quality metrics - - Performance monitoring and processing time tracking - -- **OnboardingDataIntegration Model**: Tracks onboarding data integration - - Auto-population field mapping - - Data quality scoring - - Confidence level calculation - - Data freshness tracking - -### ✅ **1.2 Enhanced Strategy Service Core** - -**Complete EnhancedStrategyService Implementation** - -- **Core Methods**: - - `create_enhanced_strategy()`: Create strategies with 30+ inputs - - `get_enhanced_strategies()`: Retrieve strategies with comprehensive data - - `_enhance_strategy_with_onboarding_data()`: Auto-populate from onboarding - - `_generate_comprehensive_ai_recommendations()`: Generate 5 types of recommendations - -- **Data Integration Methods**: - - `_extract_content_preferences_from_style()`: Intelligent content preference extraction - - `_extract_brand_voice_from_guidelines()`: Brand voice analysis - - `_extract_editorial_guidelines_from_style()`: Editorial guidelines generation - - `_calculate_data_quality_scores()`: Data quality assessment - - `_calculate_confidence_levels()`: Confidence level calculation - -- **AI Analysis Methods**: - - `_calculate_strategic_scores()`: Strategic performance scoring - - `_extract_market_positioning()`: Market positioning analysis - - `_extract_competitive_advantages()`: Competitive advantage identification - - `_extract_strategic_risks()`: Risk assessment - - `_extract_opportunity_analysis()`: Opportunity identification - -### ✅ **1.3 AI Prompt Implementation** - -**5 Specialized AI Prompts Implemented** - -1. **Comprehensive Strategy Prompt** - - Strategic positioning and market analysis - - Content pillar recommendations - - Audience targeting strategies - - Competitive differentiation opportunities - - Implementation roadmap and timeline - - Success metrics and KPIs - - Risk assessment and mitigation strategies - -2. **Audience Intelligence Prompt** - - Audience persona development - - Content preference analysis - - Consumption pattern optimization - - Pain point addressing strategies - - Buying journey optimization - - Seasonal content opportunities - - Engagement improvement tactics - -3. **Competitive Intelligence Prompt** - - Competitor content strategy analysis - - Market gap identification - - Competitive advantage opportunities - - Industry trend analysis - - Emerging trend identification - - Differentiation strategies - - Partnership opportunities - -4. **Performance Optimization Prompt** - - Traffic source optimization - - Conversion rate improvement - - Content ROI enhancement - - A/B testing strategies - - Performance monitoring setup - - Analytics implementation - - Continuous improvement processes - -5. **Content Calendar Optimization Prompt** - - Publishing schedule optimization - - Content mix optimization - - Seasonal strategy development - - Engagement calendar creation - - Content type distribution - - Timing optimization - - Workflow efficiency - ---- - -## 🗄️ **Database Service Implementation** - -### ✅ **EnhancedStrategyDBService** - -**Complete Database Operations** - -- **CRUD Operations**: - - `create_enhanced_strategy()`: Create new enhanced strategies - - `get_enhanced_strategy()`: Retrieve individual strategies - - `get_enhanced_strategies_by_user()`: Get all strategies for a user - - `update_enhanced_strategy()`: Update strategy data - - `delete_enhanced_strategy()`: Delete strategies - -- **Analytics Operations**: - - `get_enhanced_strategies_with_analytics()`: Comprehensive analytics - - `get_latest_ai_analysis()`: Latest AI analysis results - - `get_onboarding_integration()`: Onboarding data integration - - `get_strategy_completion_stats()`: Completion statistics - - `get_ai_analysis_history()`: AI analysis history - -- **Advanced Operations**: - - `search_enhanced_strategies()`: Strategy search functionality - - `get_strategy_export_data()`: Comprehensive data export - - `update_strategy_ai_analysis()`: AI analysis updates - ---- - -## 🌐 **API Routes Implementation** - -### ✅ **Enhanced Strategy API Routes** - -**Complete REST API Endpoints** - -- **Core Strategy Operations**: - - `POST /enhanced-strategy/create`: Create enhanced strategy - - `GET /enhanced-strategy/strategies`: Get strategies with filters - - `GET /enhanced-strategy/strategies/{strategy_id}`: Get specific strategy - - `PUT /enhanced-strategy/strategies/{strategy_id}`: Update strategy - - `DELETE /enhanced-strategy/strategies/{strategy_id}`: Delete strategy - -- **Analytics & AI Operations**: - - `GET /enhanced-strategy/strategies/{strategy_id}/analytics`: Get comprehensive analytics - - `GET /enhanced-strategy/strategies/{strategy_id}/ai-analysis`: Get AI analysis history - - `POST /enhanced-strategy/strategies/{strategy_id}/regenerate-ai-analysis`: Regenerate AI analysis - -- **Completion & Integration**: - - `GET /enhanced-strategy/strategies/{strategy_id}/completion-stats`: Get completion statistics - - `GET /enhanced-strategy/users/{user_id}/completion-stats`: Get user completion stats - - `GET /enhanced-strategy/strategies/{strategy_id}/onboarding-integration`: Get onboarding integration - -- **Search & Export**: - - `GET /enhanced-strategy/strategies/search`: Search strategies - - `GET /enhanced-strategy/strategies/{strategy_id}/export`: Export strategy data - ---- - -## 🧪 **Testing & Validation** - -### ✅ **Comprehensive Test Suite** - -**All Phase 1 Tests Passing** - -- **Model Tests**: - - Enhanced strategy model creation with 30+ inputs - - Completion percentage calculation (100% accuracy) - - Enhanced strategy to_dict conversion - - AI analysis result model validation - - Onboarding integration model validation - -- **Service Tests**: - - Enhanced strategy service initialization (30 fields) - - Specialized prompt creation for all 5 analysis types - - Fallback recommendations for AI service failures - - Data quality calculation accuracy - - Confidence level calculation validation - -- **AI Analysis Tests**: - - Strategic scores calculation - - Market positioning extraction - - Competitive advantages extraction - - Strategic risks extraction - - Opportunity analysis extraction - ---- - -## 📈 **Key Features Implemented** - -### ✅ **Intelligent Auto-Population** - -- **Onboarding Data Integration**: Automatically populates strategy fields from existing onboarding data -- **Data Source Transparency**: Tracks which data sources were used for auto-population -- **Confidence Scoring**: Calculates confidence levels for auto-populated data -- **User Override Capability**: Allows users to modify auto-populated values - -### ✅ **Comprehensive AI Recommendations** - -- **5 Specialized Analysis Types**: Each with targeted prompts and recommendations -- **Fallback Mechanisms**: Robust error handling when AI services fail -- **Performance Monitoring**: Tracks processing time and service status -- **Quality Scoring**: Measures recommendation quality and confidence - -### ✅ **Strategic Input Management** - -- **30+ Strategic Inputs**: Comprehensive coverage of content strategy requirements -- **Progressive Disclosure**: Organized into logical categories for better UX -- **Completion Tracking**: Real-time completion percentage calculation -- **Data Validation**: Comprehensive validation for all input fields - ---- - -## 🚀 **Performance Metrics** - -### ✅ **Phase 1 Success Metrics** - -- **Input Completeness**: 100% completion rate achieved in testing -- **AI Accuracy**: Fallback mechanisms ensure 100% availability -- **Performance**: <2 second response time for all operations -- **User Experience**: Progressive disclosure reduces complexity - -### ✅ **Technical Achievements** - -- **Database Schema**: Enhanced with 30+ strategic input fields -- **Service Architecture**: Modular, scalable, and maintainable -- **API Design**: RESTful endpoints with comprehensive functionality -- **Error Handling**: Robust error handling and fallback mechanisms - ---- - -## 🎯 **Next Steps: Phase 2** - -**Phase 2 Focus: User Experience & Frontend Integration** - -1. **Enhanced Input System** - - Progressive input disclosure - - Comprehensive tooltip system - - Smart defaults and auto-population - - Input validation and guidance - -2. **Frontend Component Development** - - Strategy dashboard components - - Data visualization components - - Interactive components - - Progress tracking system - -3. **Data Mapping & Integration** - - API response structure optimization - - Frontend-backend data mapping - - State management implementation - - Real-time data synchronization - ---- - -## ✅ **Phase 1 Conclusion** - -**Phase 1 has been successfully completed with all deliverables achieved:** - -- ✅ Enhanced database schema with 30+ input fields -- ✅ Enhanced Strategy Service core implementation -- ✅ 5 specialized AI prompt implementations -- ✅ Onboarding data integration -- ✅ Comprehensive AI recommendations -- ✅ Complete API routes and database services -- ✅ Comprehensive test suite with 100% pass rate - -**The enhanced strategy service now provides a solid foundation for the subsequent content calendar phase and delivers significant value through improved personalization, comprehensiveness, and intelligent data integration.** - ---- - -**Implementation Team**: AI Assistant -**Review Date**: December 2024 -**Status**: ✅ **PHASE 1 COMPLETE** \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_ai_integration.py b/docs/alwrity_test_scripts/test_ai_integration.py deleted file mode 100644 index 3a243442..00000000 --- a/docs/alwrity_test_scripts/test_ai_integration.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for AI Integration -Verifies that the AI Engine Service is working with real AI calls. -""" - -import asyncio -import sys -import os -from pathlib import Path - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.content_gap_analyzer.ai_engine_service import AIEngineService -from loguru import logger - -async def test_ai_integration(): - """Test the AI integration functionality.""" - - print("🤖 Testing AI Integration...") - - # Initialize the AI Engine Service - ai_service = AIEngineService() - - # Test data - test_analysis_summary = { - 'target_url': 'https://example.com', - 'industry': 'Technology', - 'serp_opportunities': 15, - 'expanded_keywords_count': 50, - 'competitors_analyzed': 5, - 'dominant_themes': { - 'artificial_intelligence': 0.3, - 'machine_learning': 0.25, - 'data_science': 0.2, - 'automation': 0.15, - 'innovation': 0.1 - } - } - - test_market_data = { - 'industry': 'Technology', - 'competitors': [ - { - 'url': 'competitor1.com', - 'content_count': 150, - 'avg_quality_score': 8.5, - 'top_keywords': ['AI', 'ML', 'Data Science'] - }, - { - 'url': 'competitor2.com', - 'content_count': 200, - 'avg_quality_score': 7.8, - 'top_keywords': ['Automation', 'Innovation', 'Tech'] - } - ] - } - - try: - print("\n1. Testing Content Gap Analysis...") - content_gaps = await ai_service.analyze_content_gaps(test_analysis_summary) - print(f"✅ Content Gap Analysis completed: {len(content_gaps.get('strategic_insights', []))} insights generated") - - print("\n2. Testing Market Position Analysis...") - market_position = await ai_service.analyze_market_position(test_market_data) - print(f"✅ Market Position Analysis completed: {len(market_position.get('strategic_recommendations', []))} recommendations generated") - - print("\n3. Testing Content Recommendations...") - recommendations = await ai_service.generate_content_recommendations(test_analysis_summary) - print(f"✅ Content Recommendations completed: {len(recommendations)} recommendations generated") - - print("\n4. Testing Performance Predictions...") - predictions = await ai_service.predict_content_performance(test_analysis_summary) - print(f"✅ Performance Predictions completed: {predictions.get('traffic_predictions', {}).get('confidence_level', 'N/A')} confidence") - - print("\n5. Testing Strategic Insights...") - insights = await ai_service.generate_strategic_insights(test_analysis_summary) - print(f"✅ Strategic Insights completed: {len(insights)} insights generated") - - print("\n6. Testing Health Check...") - health = await ai_service.health_check() - print(f"✅ Health Check completed: {health.get('status', 'unknown')} status") - print(f" AI Integration Status: {health.get('capabilities', {}).get('ai_integration', 'unknown')}") - - print("\n🎉 All AI Integration Tests Passed!") - return True - - except Exception as e: - print(f"❌ AI Integration Test Failed: {str(e)}") - logger.error(f"AI Integration test failed: {str(e)}") - return False - -async def test_ai_fallback(): - """Test the fallback functionality when AI fails.""" - - print("\n🔄 Testing AI Fallback Functionality...") - - # Initialize the AI Engine Service - ai_service = AIEngineService() - - # Test with minimal data to trigger fallback - minimal_data = {'test': 'data'} - - try: - print("Testing fallback with minimal data...") - result = await ai_service.analyze_content_gaps(minimal_data) - - if result and 'strategic_insights' in result: - print("✅ Fallback functionality working correctly") - return True - else: - print("❌ Fallback functionality failed") - return False - - except Exception as e: - print(f"❌ Fallback test failed: {str(e)}") - return False - -async def main(): - """Main test function.""" - print("🚀 Starting AI Integration Tests...") - print("=" * 50) - - # Test 1: AI Integration - ai_success = await test_ai_integration() - - # Test 2: Fallback Functionality - fallback_success = await test_ai_fallback() - - print("\n" + "=" * 50) - print("📊 Test Results Summary:") - print(f"AI Integration: {'✅ PASSED' if ai_success else '❌ FAILED'}") - print(f"Fallback Functionality: {'✅ PASSED' if fallback_success else '❌ FAILED'}") - - if ai_success and fallback_success: - print("\n🎉 All tests passed! AI Integration is working correctly.") - return 0 - else: - print("\n⚠️ Some tests failed. Please check the AI configuration.") - return 1 - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_ai_service_debug.py b/docs/alwrity_test_scripts/test_ai_service_debug.py deleted file mode 100644 index ee886288..00000000 --- a/docs/alwrity_test_scripts/test_ai_service_debug.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to debug AI analytics service issues. -""" - -import asyncio -import sys -import traceback -from datetime import datetime - -# Add backend to path -sys.path.append('backend') - -async def test_ai_analytics_service(): - """Test the AI analytics service directly.""" - try: - print("🧪 Testing AI Analytics Service Directly") - print("=" * 50) - - # Import the service - from services.ai_analytics_service import AIAnalyticsService - - print("✅ AI Analytics Service imported successfully") - - # Create service instance - ai_service = AIAnalyticsService() - print("✅ AI Analytics Service instantiated") - - # Test performance trends analysis - print("\n🧪 Testing performance trends analysis...") - try: - performance_analysis = await ai_service.analyze_performance_trends( - strategy_id=1, - metrics=['engagement_rate', 'reach', 'conversion_rate'] - ) - print(f"✅ Performance analysis completed: {len(performance_analysis)} keys") - print(f" - Keys: {list(performance_analysis.keys())}") - - if 'trend_analysis' in performance_analysis: - print(f" - Trend analysis: {len(performance_analysis['trend_analysis'])} metrics") - else: - print(" - No trend_analysis found") - - except Exception as e: - print(f"❌ Performance analysis failed: {e}") - print(f" - Error type: {type(e).__name__}") - traceback.print_exc() - - # Test strategic intelligence - print("\n🧪 Testing strategic intelligence...") - try: - strategic_intelligence = await ai_service.generate_strategic_intelligence( - strategy_id=1 - ) - print(f"✅ Strategic intelligence completed: {len(strategic_intelligence)} keys") - print(f" - Keys: {list(strategic_intelligence.keys())}") - - except Exception as e: - print(f"❌ Strategic intelligence failed: {e}") - print(f" - Error type: {type(e).__name__}") - traceback.print_exc() - - # Test content evolution - print("\n🧪 Testing content evolution...") - try: - evolution_analysis = await ai_service.analyze_content_evolution( - strategy_id=1, - time_period="30d" - ) - print(f"✅ Content evolution completed: {len(evolution_analysis)} keys") - print(f" - Keys: {list(evolution_analysis.keys())}") - - except Exception as e: - print(f"❌ Content evolution failed: {e}") - print(f" - Error type: {type(e).__name__}") - traceback.print_exc() - - print("\n" + "=" * 50) - print("📊 AI Service Debug Complete") - - except Exception as e: - print(f"❌ AI service test failed: {e}") - traceback.print_exc() - -async def test_ai_engine_service(): - """Test the AI engine service that AI analytics depends on.""" - try: - print("\n🧪 Testing AI Engine Service") - print("=" * 30) - - from services.content_gap_analyzer.ai_engine_service import AIEngineService - - print("✅ AI Engine Service imported successfully") - - # Create service instance - ai_engine = AIEngineService() - print("✅ AI Engine Service instantiated") - - # Test a simple AI call - print("\n🧪 Testing simple AI call...") - try: - # Test with a simple prompt - result = await ai_engine.generate_recommendations( - website_analysis={"content_types": ["blog", "video"]}, - competitor_analysis={"top_performers": ["competitor1.com"]}, - gap_analysis={"content_gaps": ["AI content"]}, - keyword_analysis={"high_value_keywords": ["AI marketing"]} - ) - print(f"✅ AI engine call completed: {type(result)}") - print(f" - Result: {result}") - - except Exception as e: - print(f"❌ AI engine call failed: {e}") - print(f" - Error type: {type(e).__name__}") - traceback.print_exc() - - except Exception as e: - print(f"❌ AI engine test failed: {e}") - traceback.print_exc() - -async def main(): - """Run all AI service tests.""" - await test_ai_analytics_service() - await test_ai_engine_service() - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_api_database_integration.py b/docs/alwrity_test_scripts/test_api_database_integration.py deleted file mode 100644 index e0e4c729..00000000 --- a/docs/alwrity_test_scripts/test_api_database_integration.py +++ /dev/null @@ -1,512 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for API Database Integration -Verifies that all API endpoints with database integration are working correctly. -""" - -import asyncio -import sys -import os -import requests -import json -from pathlib import Path -from datetime import datetime, timedelta - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.database import init_database, get_db_session -from services.content_planning_db import ContentPlanningDBService -from loguru import logger - -# API base URL -API_BASE_URL = "http://localhost:8000" - -def test_database_initialization(): - """Test database initialization.""" - - print("🗄️ Testing Database Initialization...") - - try: - # Initialize database - init_database() - print("✅ Database initialized successfully") - - # Test database session - db_session = get_db_session() - if db_session: - print("✅ Database session created successfully") - db_session.close() - return True - else: - print("❌ Failed to create database session") - return False - - except Exception as e: - print(f"❌ Database initialization failed: {str(e)}") - return False - -def test_api_health_check(): - """Test API health check endpoints.""" - - print("\n🏥 Testing API Health Checks...") - - # Test content planning health check - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/health") - if response.status_code == 200: - health_data = response.json() - print(f"✅ Content planning health check: {health_data['status']}") - else: - print(f"❌ Content planning health check failed: {response.status_code}") - return False - except Exception as e: - print(f"❌ Content planning health check error: {str(e)}") - return False - - # Test database health check - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/database/health") - if response.status_code == 200: - health_data = response.json() - print(f"✅ Database health check: {health_data['status']}") - else: - print(f"❌ Database health check failed: {response.status_code}") - return False - except Exception as e: - print(f"❌ Database health check error: {str(e)}") - return False - - return True - -def test_content_strategy_api(): - """Test content strategy API endpoints.""" - - print("\n📋 Testing Content Strategy API...") - - # Test 1: Create content strategy - print("\n📝 Test 1: Create Content Strategy") - strategy_data = { - "user_id": 1, - "name": "Test Content Strategy", - "industry": "technology", - "target_audience": { - "demographics": "25-45 years old", - "interests": ["technology", "innovation"] - }, - "content_pillars": [ - {"name": "AI", "description": "Artificial Intelligence content"}, - {"name": "Machine Learning", "description": "ML tutorials and guides"} - ], - "ai_recommendations": { - "strategic_insights": ["Focus on educational content"], - "content_recommendations": ["Create comprehensive guides"] - } - } - - try: - response = requests.post( - f"{API_BASE_URL}/api/content-planning/strategies/", - json=strategy_data - ) - - if response.status_code == 200: - strategy = response.json() - print(f"✅ Content strategy created: {strategy['id']}") - strategy_id = strategy['id'] - else: - print(f"❌ Failed to create content strategy: {response.status_code}") - print(f"Response: {response.text}") - return False - except Exception as e: - print(f"❌ Error creating content strategy: {str(e)}") - return False - - # Test 2: Get content strategy - print("\n📖 Test 2: Get Content Strategy") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}") - - if response.status_code == 200: - strategy = response.json() - print(f"✅ Content strategy retrieved: {strategy['name']}") - else: - print(f"❌ Failed to retrieve content strategy: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error retrieving content strategy: {str(e)}") - return False - - # Test 3: Get user strategies - print("\n👤 Test 3: Get User Content Strategies") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/strategies/?user_id=1") - - if response.status_code == 200: - strategies = response.json() - print(f"✅ Retrieved {len(strategies)} user strategies") - else: - print(f"❌ Failed to get user strategies: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting user strategies: {str(e)}") - return False - - # Test 4: Update content strategy - print("\n✏️ Test 4: Update Content Strategy") - update_data = { - "name": "Updated Test Content Strategy", - "industry": "artificial_intelligence" - } - - try: - response = requests.put( - f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}", - json=update_data - ) - - if response.status_code == 200: - strategy = response.json() - print(f"✅ Content strategy updated: {strategy['name']}") - else: - print(f"❌ Failed to update content strategy: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error updating content strategy: {str(e)}") - return False - - # Test 5: Delete content strategy - print("\n🗑️ Test 5: Delete Content Strategy") - try: - response = requests.delete(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}") - - if response.status_code == 200: - print("✅ Content strategy deleted successfully") - else: - print(f"❌ Failed to delete content strategy: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error deleting content strategy: {str(e)}") - return False - - return True - -def test_calendar_event_api(): - """Test calendar event API endpoints.""" - - print("\n📅 Testing Calendar Event API...") - - # First create a strategy for the event - strategy_data = { - "user_id": 1, - "name": "Test Strategy for Events", - "industry": "technology" - } - - try: - response = requests.post( - f"{API_BASE_URL}/api/content-planning/strategies/", - json=strategy_data - ) - - if response.status_code == 200: - strategy = response.json() - strategy_id = strategy['id'] - else: - print(f"❌ Failed to create test strategy: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error creating test strategy: {str(e)}") - return False - - # Test 1: Create calendar event - print("\n📝 Test 1: Create Calendar Event") - event_data = { - "strategy_id": strategy_id, - "title": "Test Blog Post", - "description": "A comprehensive guide to AI", - "content_type": "blog_post", - "platform": "website", - "scheduled_date": (datetime.utcnow() + timedelta(days=7)).isoformat(), - "ai_recommendations": { - "keywords": ["AI", "machine learning"], - "estimated_performance": "High engagement expected" - } - } - - try: - response = requests.post( - f"{API_BASE_URL}/api/content-planning/calendar-events/", - json=event_data - ) - - if response.status_code == 200: - event = response.json() - print(f"✅ Calendar event created: {event['id']}") - event_id = event['id'] - else: - print(f"❌ Failed to create calendar event: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error creating calendar event: {str(e)}") - return False - - # Test 2: Get calendar event - print("\n📖 Test 2: Get Calendar Event") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/calendar-events/{event_id}") - - if response.status_code == 200: - event = response.json() - print(f"✅ Calendar event retrieved: {event['title']}") - else: - print(f"❌ Failed to retrieve calendar event: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error retrieving calendar event: {str(e)}") - return False - - # Test 3: Get strategy events - print("\n📋 Test 3: Get Strategy Calendar Events") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/calendar-events/?strategy_id={strategy_id}") - - if response.status_code == 200: - events = response.json() - print(f"✅ Retrieved {len(events)} strategy events") - else: - print(f"❌ Failed to get strategy events: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting strategy events: {str(e)}") - return False - - # Clean up - try: - requests.delete(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}") - except: - pass - - return True - -def test_content_gap_analysis_api(): - """Test content gap analysis API endpoints.""" - - print("\n🔍 Testing Content Gap Analysis API...") - - # Test 1: Create content gap analysis - print("\n📝 Test 1: Create Content Gap Analysis") - analysis_data = { - "user_id": 1, - "website_url": "https://example.com", - "competitor_urls": ["https://competitor1.com", "https://competitor2.com"], - "target_keywords": ["AI", "machine learning", "data science"], - "industry": "technology", - "analysis_results": { - "content_gaps": ["Video tutorials", "Case studies"], - "opportunities": ["Educational content", "Expert interviews"] - }, - "recommendations": { - "strategic_insights": ["Focus on educational content"], - "content_recommendations": ["Create comprehensive guides"] - }, - "opportunities": { - "high_priority": ["Video tutorials"], - "medium_priority": ["Case studies"] - } - } - - try: - response = requests.post( - f"{API_BASE_URL}/api/content-planning/gap-analysis/", - json=analysis_data - ) - - if response.status_code == 200: - analysis = response.json() - print(f"✅ Content gap analysis created: {analysis['id']}") - analysis_id = analysis['id'] - else: - print(f"❌ Failed to create content gap analysis: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error creating content gap analysis: {str(e)}") - return False - - # Test 2: Get content gap analysis - print("\n📖 Test 2: Get Content Gap Analysis") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/gap-analysis/{analysis_id}") - - if response.status_code == 200: - analysis = response.json() - print(f"✅ Content gap analysis retrieved: {analysis['website_url']}") - else: - print(f"❌ Failed to retrieve content gap analysis: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error retrieving content gap analysis: {str(e)}") - return False - - # Test 3: Get user analyses - print("\n👤 Test 3: Get User Content Gap Analyses") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/gap-analysis/?user_id=1") - - if response.status_code == 200: - analyses = response.json() - print(f"✅ Retrieved {len(analyses)} user analyses") - else: - print(f"❌ Failed to get user analyses: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting user analyses: {str(e)}") - return False - - return True - -def test_advanced_api_endpoints(): - """Test advanced API endpoints.""" - - print("\n🚀 Testing Advanced API Endpoints...") - - # Create a test strategy first - strategy_data = { - "user_id": 1, - "name": "Advanced Test Strategy", - "industry": "technology" - } - - try: - response = requests.post( - f"{API_BASE_URL}/api/content-planning/strategies/", - json=strategy_data - ) - - if response.status_code == 200: - strategy = response.json() - strategy_id = strategy['id'] - else: - print(f"❌ Failed to create test strategy: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error creating test strategy: {str(e)}") - return False - - # Test 1: Get strategy analytics - print("\n📊 Test 1: Get Strategy Analytics") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}/analytics") - - if response.status_code == 200: - analytics = response.json() - print(f"✅ Strategy analytics retrieved: {analytics['analytics_count']} records") - else: - print(f"❌ Failed to get strategy analytics: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting strategy analytics: {str(e)}") - return False - - # Test 2: Get strategy events - print("\n📅 Test 2: Get Strategy Events") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}/events") - - if response.status_code == 200: - events = response.json() - print(f"✅ Strategy events retrieved: {events['events_count']} events") - else: - print(f"❌ Failed to get strategy events: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting strategy events: {str(e)}") - return False - - # Test 3: Get user recommendations - print("\n💡 Test 3: Get User Recommendations") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/users/1/recommendations") - - if response.status_code == 200: - recommendations = response.json() - print(f"✅ User recommendations retrieved: {recommendations['recommendations_count']} recommendations") - else: - print(f"❌ Failed to get user recommendations: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting user recommendations: {str(e)}") - return False - - # Test 4: Get strategy summary - print("\n📋 Test 4: Get Strategy Summary") - try: - response = requests.get(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}/summary") - - if response.status_code == 200: - summary = response.json() - print(f"✅ Strategy summary retrieved successfully") - else: - print(f"❌ Failed to get strategy summary: {response.status_code}") - return False - except Exception as e: - print(f"❌ Error getting strategy summary: {str(e)}") - return False - - # Clean up - try: - requests.delete(f"{API_BASE_URL}/api/content-planning/strategies/{strategy_id}") - except: - pass - - return True - -def main(): - """Main test function.""" - print("🚀 Starting API Database Integration Tests...") - print("=" * 60) - - # Test 1: Database Initialization - db_init_success = test_database_initialization() - - # Test 2: API Health Checks - health_success = test_api_health_check() - - # Test 3: Content Strategy API - strategy_success = test_content_strategy_api() - - # Test 4: Calendar Event API - event_success = test_calendar_event_api() - - # Test 5: Content Gap Analysis API - analysis_success = test_content_gap_analysis_api() - - # Test 6: Advanced API Endpoints - advanced_success = test_advanced_api_endpoints() - - print("\n" + "=" * 60) - print("📊 Test Results Summary:") - print(f"Database Initialization: {'✅ PASSED' if db_init_success else '❌ FAILED'}") - print(f"API Health Checks: {'✅ PASSED' if health_success else '❌ FAILED'}") - print(f"Content Strategy API: {'✅ PASSED' if strategy_success else '❌ FAILED'}") - print(f"Calendar Event API: {'✅ PASSED' if event_success else '❌ FAILED'}") - print(f"Content Gap Analysis API: {'✅ PASSED' if analysis_success else '❌ FAILED'}") - print(f"Advanced API Endpoints: {'✅ PASSED' if advanced_success else '❌ FAILED'}") - - if db_init_success and health_success and strategy_success and event_success and analysis_success and advanced_success: - print("\n🎉 All API database integration tests passed!") - print("\n✅ API Database Integration Achievements:") - print(" - Database models integrated with API endpoints") - print(" - All CRUD operations working via API") - print(" - Health checks for both services and database") - print(" - Advanced query endpoints functional") - print(" - Error handling and validation working") - print(" - RESTful API design implemented") - return 0 - else: - print("\n⚠️ Some API database integration tests failed. Please check the API server and database configuration.") - return 1 - -if __name__ == "__main__": - exit_code = main() - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_database_integration.py b/docs/alwrity_test_scripts/test_database_integration.py deleted file mode 100644 index bf099c8d..00000000 --- a/docs/alwrity_test_scripts/test_database_integration.py +++ /dev/null @@ -1,637 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for Database Integration -Verifies that all database operations are working correctly. -""" - -import asyncio -import sys -import os -from pathlib import Path -from datetime import datetime - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.database import get_db_session, init_database -from services.content_planning_db import ContentPlanningDBService -from loguru import logger - -async def test_database_initialization(): - """Test database initialization.""" - - print("🗄️ Testing Database Initialization...") - - try: - # Initialize database - init_database() - print("✅ Database initialized successfully") - - # Test database session - db_session = get_db_session() - if db_session: - print("✅ Database session created successfully") - db_session.close() - return True - else: - print("❌ Failed to create database session") - return False - - except Exception as e: - print(f"❌ Database initialization failed: {str(e)}") - return False - -async def test_content_strategy_operations(): - """Test content strategy database operations.""" - - print("\n📋 Testing Content Strategy Operations...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - db_service = ContentPlanningDBService(db_session) - - # Test 1: Create content strategy - print("\n📝 Test 1: Create Content Strategy") - strategy_data = { - 'user_id': 1, - 'name': 'Test Content Strategy', - 'industry': 'technology', - 'target_audience': { - 'demographics': '25-45 years old', - 'interests': ['technology', 'innovation'] - }, - 'content_pillars': ['AI', 'Machine Learning', 'Data Science'], - 'ai_recommendations': { - 'strategic_insights': ['Focus on educational content'], - 'content_recommendations': ['Create comprehensive guides'] - } - } - - try: - strategy = await db_service.create_content_strategy(strategy_data) - if strategy: - print(f"✅ Content strategy created: {strategy.id}") - strategy_id = strategy.id - else: - print("❌ Failed to create content strategy") - return False - except Exception as e: - print(f"❌ Error creating content strategy: {str(e)}") - return False - - # Test 2: Get content strategy - print("\n📖 Test 2: Get Content Strategy") - try: - retrieved_strategy = await db_service.get_content_strategy(strategy_id) - if retrieved_strategy: - print(f"✅ Content strategy retrieved: {retrieved_strategy.name}") - else: - print("❌ Failed to retrieve content strategy") - return False - except Exception as e: - print(f"❌ Error retrieving content strategy: {str(e)}") - return False - - # Test 3: Update content strategy - print("\n✏️ Test 3: Update Content Strategy") - update_data = { - 'name': 'Updated Test Content Strategy', - 'industry': 'artificial_intelligence' - } - - try: - updated_strategy = await db_service.update_content_strategy(strategy_id, update_data) - if updated_strategy: - print(f"✅ Content strategy updated: {updated_strategy.name}") - else: - print("❌ Failed to update content strategy") - return False - except Exception as e: - print(f"❌ Error updating content strategy: {str(e)}") - return False - - # Test 4: Get user strategies - print("\n👤 Test 4: Get User Content Strategies") - try: - user_strategies = await db_service.get_user_content_strategies(1) - print(f"✅ Retrieved {len(user_strategies)} user strategies") - except Exception as e: - print(f"❌ Error getting user strategies: {str(e)}") - return False - - # Test 5: Delete content strategy - print("\n🗑️ Test 5: Delete Content Strategy") - try: - deleted = await db_service.delete_content_strategy(strategy_id) - if deleted: - print("✅ Content strategy deleted successfully") - else: - print("❌ Failed to delete content strategy") - return False - except Exception as e: - print(f"❌ Error deleting content strategy: {str(e)}") - return False - - db_session.close() - return True - -async def test_calendar_event_operations(): - """Test calendar event database operations.""" - - print("\n📅 Testing Calendar Event Operations...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - db_service = ContentPlanningDBService(db_session) - - # First create a strategy for the event - strategy_data = { - 'user_id': 1, - 'name': 'Test Strategy for Events', - 'industry': 'technology' - } - strategy = await db_service.create_content_strategy(strategy_data) - if not strategy: - print("❌ Failed to create test strategy") - return False - - # Test 1: Create calendar event - print("\n📝 Test 1: Create Calendar Event") - event_data = { - 'strategy_id': strategy.id, - 'title': 'Test Blog Post', - 'description': 'A comprehensive guide to AI', - 'content_type': 'blog_post', - 'platform': 'website', - 'scheduled_date': datetime.utcnow(), - 'status': 'draft', - 'ai_recommendations': { - 'keywords': ['AI', 'machine learning'], - 'estimated_performance': 'High engagement expected' - } - } - - try: - event = await db_service.create_calendar_event(event_data) - if event: - print(f"✅ Calendar event created: {event.id}") - event_id = event.id - else: - print("❌ Failed to create calendar event") - return False - except Exception as e: - print(f"❌ Error creating calendar event: {str(e)}") - return False - - # Test 2: Get calendar event - print("\n📖 Test 2: Get Calendar Event") - try: - retrieved_event = await db_service.get_calendar_event(event_id) - if retrieved_event: - print(f"✅ Calendar event retrieved: {retrieved_event.title}") - else: - print("❌ Failed to retrieve calendar event") - return False - except Exception as e: - print(f"❌ Error retrieving calendar event: {str(e)}") - return False - - # Test 3: Get strategy events - print("\n📋 Test 3: Get Strategy Calendar Events") - try: - strategy_events = await db_service.get_strategy_calendar_events(strategy.id) - print(f"✅ Retrieved {len(strategy_events)} strategy events") - except Exception as e: - print(f"❌ Error getting strategy events: {str(e)}") - return False - - # Test 4: Update calendar event - print("\n✏️ Test 4: Update Calendar Event") - update_data = { - 'title': 'Updated Test Blog Post', - 'status': 'scheduled' - } - - try: - updated_event = await db_service.update_calendar_event(event_id, update_data) - if updated_event: - print(f"✅ Calendar event updated: {updated_event.title}") - else: - print("❌ Failed to update calendar event") - return False - except Exception as e: - print(f"❌ Error updating calendar event: {str(e)}") - return False - - # Clean up - await db_service.delete_content_strategy(strategy.id) - db_session.close() - return True - -async def test_content_gap_analysis_operations(): - """Test content gap analysis database operations.""" - - print("\n🔍 Testing Content Gap Analysis Operations...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - db_service = ContentPlanningDBService(db_session) - - # Test 1: Create content gap analysis - print("\n📝 Test 1: Create Content Gap Analysis") - analysis_data = { - 'user_id': 1, - 'website_url': 'https://example.com', - 'competitor_urls': ['https://competitor1.com', 'https://competitor2.com'], - 'target_keywords': ['AI', 'machine learning', 'data science'], - 'analysis_results': { - 'content_gaps': ['Video tutorials', 'Case studies'], - 'opportunities': ['Educational content', 'Expert interviews'] - }, - 'recommendations': { - 'strategic_insights': ['Focus on educational content'], - 'content_recommendations': ['Create comprehensive guides'] - }, - 'opportunities': { - 'high_priority': ['Video tutorials'], - 'medium_priority': ['Case studies'] - } - } - - try: - analysis = await db_service.create_content_gap_analysis(analysis_data) - if analysis: - print(f"✅ Content gap analysis created: {analysis.id}") - analysis_id = analysis.id - else: - print("❌ Failed to create content gap analysis") - return False - except Exception as e: - print(f"❌ Error creating content gap analysis: {str(e)}") - return False - - # Test 2: Get content gap analysis - print("\n📖 Test 2: Get Content Gap Analysis") - try: - retrieved_analysis = await db_service.get_content_gap_analysis(analysis_id) - if retrieved_analysis: - print(f"✅ Content gap analysis retrieved: {retrieved_analysis.website_url}") - else: - print("❌ Failed to retrieve content gap analysis") - return False - except Exception as e: - print(f"❌ Error retrieving content gap analysis: {str(e)}") - return False - - # Test 3: Get user analyses - print("\n👤 Test 3: Get User Content Gap Analyses") - try: - user_analyses = await db_service.get_user_content_gap_analyses(1) - print(f"✅ Retrieved {len(user_analyses)} user analyses") - except Exception as e: - print(f"❌ Error getting user analyses: {str(e)}") - return False - - # Test 4: Update content gap analysis - print("\n✏️ Test 4: Update Content Gap Analysis") - update_data = { - 'website_url': 'https://updated-example.com', - 'analysis_results': { - 'content_gaps': ['Video tutorials', 'Case studies', 'Webinars'], - 'opportunities': ['Educational content', 'Expert interviews', 'Interactive content'] - } - } - - try: - updated_analysis = await db_service.update_content_gap_analysis(analysis_id, update_data) - if updated_analysis: - print(f"✅ Content gap analysis updated: {updated_analysis.website_url}") - else: - print("❌ Failed to update content gap analysis") - return False - except Exception as e: - print(f"❌ Error updating content gap analysis: {str(e)}") - return False - - # Clean up - await db_service.delete_content_gap_analysis(analysis_id) - db_session.close() - return True - -async def test_content_recommendation_operations(): - """Test content recommendation database operations.""" - - print("\n💡 Testing Content Recommendation Operations...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - db_service = ContentPlanningDBService(db_session) - - # Test 1: Create content recommendation - print("\n📝 Test 1: Create Content Recommendation") - recommendation_data = { - 'user_id': 1, - 'recommendation_type': 'blog_post', - 'title': 'Complete Guide to AI Implementation', - 'description': 'A comprehensive guide for implementing AI in business', - 'target_keywords': ['AI implementation', 'business AI', 'AI strategy'], - 'estimated_length': '2000-3000 words', - 'priority': 'high', - 'platforms': ['website', 'linkedin'], - 'estimated_performance': 'High engagement expected', - 'status': 'pending' - } - - try: - recommendation = await db_service.create_content_recommendation(recommendation_data) - if recommendation: - print(f"✅ Content recommendation created: {recommendation.id}") - recommendation_id = recommendation.id - else: - print("❌ Failed to create content recommendation") - return False - except Exception as e: - print(f"❌ Error creating content recommendation: {str(e)}") - return False - - # Test 2: Get content recommendation - print("\n📖 Test 2: Get Content Recommendation") - try: - retrieved_recommendation = await db_service.get_content_recommendation(recommendation_id) - if retrieved_recommendation: - print(f"✅ Content recommendation retrieved: {retrieved_recommendation.title}") - else: - print("❌ Failed to retrieve content recommendation") - return False - except Exception as e: - print(f"❌ Error retrieving content recommendation: {str(e)}") - return False - - # Test 3: Get user recommendations - print("\n👤 Test 3: Get User Content Recommendations") - try: - user_recommendations = await db_service.get_user_content_recommendations(1) - print(f"✅ Retrieved {len(user_recommendations)} user recommendations") - except Exception as e: - print(f"❌ Error getting user recommendations: {str(e)}") - return False - - # Test 4: Update content recommendation - print("\n✏️ Test 4: Update Content Recommendation") - update_data = { - 'title': 'Updated Complete Guide to AI Implementation', - 'status': 'accepted', - 'priority': 'medium' - } - - try: - updated_recommendation = await db_service.update_content_recommendation(recommendation_id, update_data) - if updated_recommendation: - print(f"✅ Content recommendation updated: {updated_recommendation.title}") - else: - print("❌ Failed to update content recommendation") - return False - except Exception as e: - print(f"❌ Error updating content recommendation: {str(e)}") - return False - - # Clean up - await db_service.delete_content_recommendation(recommendation_id) - db_session.close() - return True - -async def test_analytics_operations(): - """Test analytics database operations.""" - - print("\n📊 Testing Analytics Operations...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - db_service = ContentPlanningDBService(db_session) - - # Create test strategy and event for analytics - strategy_data = { - 'user_id': 1, - 'name': 'Test Strategy for Analytics', - 'industry': 'technology' - } - strategy = await db_service.create_content_strategy(strategy_data) - - event_data = { - 'strategy_id': strategy.id, - 'title': 'Test Event for Analytics', - 'content_type': 'blog_post', - 'platform': 'website', - 'scheduled_date': datetime.utcnow(), - 'status': 'published' - } - event = await db_service.create_calendar_event(event_data) - - # Test 1: Create content analytics - print("\n📝 Test 1: Create Content Analytics") - analytics_data = { - 'event_id': event.id, - 'strategy_id': strategy.id, - 'platform': 'website', - 'metrics': { - 'page_views': 1500, - 'unique_visitors': 800, - 'time_on_page': 180, - 'bounce_rate': 0.25, - 'social_shares': 45 - }, - 'performance_score': 8.5, - 'recorded_at': datetime.utcnow() - } - - try: - analytics = await db_service.create_content_analytics(analytics_data) - if analytics: - print(f"✅ Content analytics created: {analytics.id}") - analytics_id = analytics.id - else: - print("❌ Failed to create content analytics") - return False - except Exception as e: - print(f"❌ Error creating content analytics: {str(e)}") - return False - - # Test 2: Get event analytics - print("\n📖 Test 2: Get Event Analytics") - try: - event_analytics = await db_service.get_event_analytics(event.id) - print(f"✅ Retrieved {len(event_analytics)} event analytics") - except Exception as e: - print(f"❌ Error getting event analytics: {str(e)}") - return False - - # Test 3: Get strategy analytics - print("\n📋 Test 3: Get Strategy Analytics") - try: - strategy_analytics = await db_service.get_strategy_analytics(strategy.id) - print(f"✅ Retrieved {len(strategy_analytics)} strategy analytics") - except Exception as e: - print(f"❌ Error getting strategy analytics: {str(e)}") - return False - - # Test 4: Get platform analytics - print("\n🌐 Test 4: Get Platform Analytics") - try: - platform_analytics = await db_service.get_analytics_by_platform('website') - print(f"✅ Retrieved {len(platform_analytics)} platform analytics") - except Exception as e: - print(f"❌ Error getting platform analytics: {str(e)}") - return False - - # Clean up - await db_service.delete_content_strategy(strategy.id) - db_session.close() - return True - -async def test_advanced_operations(): - """Test advanced database operations.""" - - print("\n🚀 Testing Advanced Operations...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - db_service = ContentPlanningDBService(db_session) - - # Create test data - strategy_data = { - 'user_id': 1, - 'name': 'Advanced Test Strategy', - 'industry': 'technology' - } - strategy = await db_service.create_content_strategy(strategy_data) - - # Create multiple events - events_data = [ - { - 'strategy_id': strategy.id, - 'title': 'Event 1', - 'content_type': 'blog_post', - 'platform': 'website', - 'scheduled_date': datetime.utcnow(), - 'status': 'published' - }, - { - 'strategy_id': strategy.id, - 'title': 'Event 2', - 'content_type': 'video', - 'platform': 'youtube', - 'scheduled_date': datetime.utcnow(), - 'status': 'draft' - } - ] - - for event_data in events_data: - await db_service.create_calendar_event(event_data) - - # Test 1: Get strategies with analytics - print("\n📊 Test 1: Get Strategies with Analytics") - try: - strategies_with_analytics = await db_service.get_strategies_with_analytics(1) - print(f"✅ Retrieved {len(strategies_with_analytics)} strategies with analytics") - except Exception as e: - print(f"❌ Error getting strategies with analytics: {str(e)}") - return False - - # Test 2: Get events by status - print("\n📋 Test 2: Get Events by Status") - try: - published_events = await db_service.get_events_by_status(strategy.id, 'published') - draft_events = await db_service.get_events_by_status(strategy.id, 'draft') - print(f"✅ Retrieved {len(published_events)} published events and {len(draft_events)} draft events") - except Exception as e: - print(f"❌ Error getting events by status: {str(e)}") - return False - - # Test 3: Health check - print("\n🏥 Test 3: Database Health Check") - try: - health_status = await db_service.health_check() - print(f"✅ Health check completed: {health_status['status']}") - print(f" - Tables: {len(health_status['tables'])}") - except Exception as e: - print(f"❌ Error in health check: {str(e)}") - return False - - # Clean up - await db_service.delete_content_strategy(strategy.id) - db_session.close() - return True - -async def main(): - """Main test function.""" - print("🚀 Starting Database Integration Tests...") - print("=" * 60) - - # Test 1: Database Initialization - db_init_success = await test_database_initialization() - - # Test 2: Content Strategy Operations - strategy_success = await test_content_strategy_operations() - - # Test 3: Calendar Event Operations - event_success = await test_calendar_event_operations() - - # Test 4: Content Gap Analysis Operations - analysis_success = await test_content_gap_analysis_operations() - - # Test 5: Content Recommendation Operations - recommendation_success = await test_content_recommendation_operations() - - # Test 6: Analytics Operations - analytics_success = await test_analytics_operations() - - # Test 7: Advanced Operations - advanced_success = await test_advanced_operations() - - print("\n" + "=" * 60) - print("📊 Test Results Summary:") - print(f"Database Initialization: {'✅ PASSED' if db_init_success else '❌ FAILED'}") - print(f"Content Strategy Operations: {'✅ PASSED' if strategy_success else '❌ FAILED'}") - print(f"Calendar Event Operations: {'✅ PASSED' if event_success else '❌ FAILED'}") - print(f"Content Gap Analysis Operations: {'✅ PASSED' if analysis_success else '❌ FAILED'}") - print(f"Content Recommendation Operations: {'✅ PASSED' if recommendation_success else '❌ FAILED'}") - print(f"Analytics Operations: {'✅ PASSED' if analytics_success else '❌ FAILED'}") - print(f"Advanced Operations: {'✅ PASSED' if advanced_success else '❌ FAILED'}") - - if (db_init_success and strategy_success and event_success and - analysis_success and recommendation_success and analytics_success and advanced_success): - print("\n🎉 All database integration tests passed!") - print("\n✅ Database Integration Achievements:") - print(" - Database models integrated successfully") - print(" - All CRUD operations working correctly") - print(" - Relationships and foreign keys functional") - print(" - Error handling and rollback mechanisms working") - print(" - Session management and connection handling operational") - print(" - Advanced queries and analytics working") - print(" - Health monitoring and status checks functional") - return 0 - else: - print("\n⚠️ Some database integration tests failed. Please check the database configuration.") - return 1 - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_endpoint_fixes.py b/docs/alwrity_test_scripts/test_endpoint_fixes.py deleted file mode 100644 index 36f2478e..00000000 --- a/docs/alwrity_test_scripts/test_endpoint_fixes.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the endpoint fixes for 422 errors. -""" - -import requests -import json -import sys - -def test_strategies_endpoint(): - """Test the strategies endpoint that was causing 422 errors.""" - try: - print("🧪 Testing strategies endpoint...") - - # Test without user_id (should now work) - response = requests.get("http://localhost:8000/api/content-planning/strategies/", timeout=10) - - if response.status_code == 200: - data = response.json() - if isinstance(data, list) and len(data) > 0: - print("✅ Strategies endpoint: PASSED") - print(f" - Status: {response.status_code}") - print(f" - Found {len(data)} strategies") - return True - else: - print(f"❌ Strategies endpoint: FAILED (Invalid response format: {data})") - return False - else: - print(f"❌ Strategies endpoint: FAILED (Status: {response.status_code})") - return False - - except Exception as e: - print(f"❌ Strategies endpoint: FAILED (Error: {e})") - return False - -def test_gap_analysis_endpoint(): - """Test the gap analysis endpoint that was causing 422 errors.""" - try: - print("🧪 Testing gap analysis endpoint...") - - # Test without user_id (should now work) - response = requests.get("http://localhost:8000/api/content-planning/gap-analysis/", timeout=10) - - if response.status_code == 200: - data = response.json() - if isinstance(data, list) and len(data) > 0: - print("✅ Gap analysis endpoint: PASSED") - print(f" - Status: {response.status_code}") - print(f" - Found {len(data)} analyses") - return True - else: - print(f"❌ Gap analysis endpoint: FAILED (Invalid response format: {data})") - return False - else: - print(f"❌ Gap analysis endpoint: FAILED (Status: {response.status_code})") - return False - - except Exception as e: - print(f"❌ Gap analysis endpoint: FAILED (Error: {e})") - return False - -def test_ai_analytics_endpoint(): - """Test the AI analytics endpoint.""" - try: - print("🧪 Testing AI analytics endpoint...") - - response = requests.get("http://localhost:8000/api/content-planning/ai-analytics/", timeout=10) - - if response.status_code == 200: - data = response.json() - if "insights" in data and "recommendations" in data: - print("✅ AI analytics endpoint: PASSED") - print(f" - Status: {response.status_code}") - print(f" - Found {len(data['insights'])} insights") - print(f" - Found {len(data['recommendations'])} recommendations") - return True - else: - print(f"❌ AI analytics endpoint: FAILED (Missing expected fields)") - return False - else: - print(f"❌ AI analytics endpoint: FAILED (Status: {response.status_code})") - return False - - except Exception as e: - print(f"❌ AI analytics endpoint: FAILED (Error: {e})") - return False - -def main(): - """Run all endpoint tests.""" - print("🧪 Testing Endpoint Fixes") - print("=" * 50) - - tests = [ - test_strategies_endpoint, - test_gap_analysis_endpoint, - test_ai_analytics_endpoint - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All endpoint tests passed! The 422 errors are fixed.") - return 0 - else: - print("⚠️ Some endpoint tests failed. Please check the backend.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_enhanced_strategy_phase1.py b/docs/alwrity_test_scripts/test_enhanced_strategy_phase1.py deleted file mode 100644 index 08ba0001..00000000 --- a/docs/alwrity_test_scripts/test_enhanced_strategy_phase1.py +++ /dev/null @@ -1,589 +0,0 @@ -""" -Test Enhanced Strategy Service - Phase 1 Implementation -Validates the enhanced strategy service with 30+ strategic inputs and AI recommendations. -""" - -import asyncio -from datetime import datetime -from typing import Dict, Any - -# Import models -from models.enhanced_strategy_models import EnhancedContentStrategy, EnhancedAIAnalysisResult, OnboardingDataIntegration - -# Import services -from api.content_planning.services.enhanced_strategy_service import EnhancedStrategyService -from services.enhanced_strategy_db_service import EnhancedStrategyDBService - -class TestEnhancedStrategyPhase1: - """Test class for Enhanced Strategy Service Phase 1 implementation.""" - - def get_sample_strategy_data(self) -> Dict[str, Any]: - """Sample strategy data for testing.""" - return { - 'user_id': 1, - 'name': 'Test Enhanced Strategy', - 'industry': 'technology', - - # Business Context (8 inputs) - 'business_objectives': { - 'primary': 'Increase brand awareness', - 'secondary': ['Lead generation', 'Customer engagement'] - }, - 'target_metrics': { - 'traffic': '50% increase', - 'engagement': '25% improvement', - 'conversions': '15% growth' - }, - 'content_budget': 5000.0, - 'team_size': 3, - 'implementation_timeline': '6 months', - 'market_share': '2.5%', - 'competitive_position': 'challenger', - 'performance_metrics': { - 'current_traffic': 10000, - 'current_engagement': 3.2, - 'current_conversions': 2.1 - }, - - # Audience Intelligence (6 inputs) - 'content_preferences': { - 'formats': ['blog_posts', 'videos', 'infographics'], - 'topics': ['technology', 'business', 'innovation'], - 'tone': 'professional' - }, - 'consumption_patterns': { - 'peak_times': ['9-11 AM', '2-4 PM'], - 'devices': ['desktop', 'mobile'], - 'channels': ['website', 'social_media'] - }, - 'audience_pain_points': [ - 'Complex technology solutions', - 'Limited time for research', - 'Need for practical implementation' - ], - 'buying_journey': { - 'awareness': 'Social media, SEO', - 'consideration': 'Case studies, demos', - 'decision': 'Free trials, consultations' - }, - 'seasonal_trends': { - 'Q1': 'New year planning content', - 'Q2': 'Spring technology updates', - 'Q3': 'Summer optimization', - 'Q4': 'Year-end reviews' - }, - 'engagement_metrics': { - 'avg_time_on_page': 2.5, - 'bounce_rate': 45.2, - 'social_shares': 150 - }, - - # Competitive Intelligence (5 inputs) - 'top_competitors': [ - 'Competitor A', - 'Competitor B', - 'Competitor C' - ], - 'competitor_content_strategies': { - 'Competitor A': 'High-frequency blog posts', - 'Competitor B': 'Video-focused content', - 'Competitor C': 'Whitepaper strategy' - }, - 'market_gaps': [ - 'Interactive content experiences', - 'AI-powered personalization', - 'Industry-specific solutions' - ], - 'industry_trends': [ - 'AI integration', - 'Remote work solutions', - 'Sustainability focus' - ], - 'emerging_trends': [ - 'Voice search optimization', - 'Video-first content', - 'Personalization at scale' - ], - - # Content Strategy (7 inputs) - 'preferred_formats': ['blog_posts', 'videos', 'webinars'], - 'content_mix': { - 'blog_posts': 40, - 'videos': 30, - 'webinars': 20, - 'infographics': 10 - }, - 'content_frequency': 'weekly', - 'optimal_timing': { - 'blog_posts': 'Tuesday 9 AM', - 'videos': 'Thursday 2 PM', - 'social_posts': 'Daily 10 AM' - }, - 'quality_metrics': { - 'readability_score': 8.5, - 'engagement_threshold': 3.0, - 'conversion_target': 2.5 - }, - 'editorial_guidelines': { - 'tone': 'professional', - 'style': 'clear and concise', - 'formatting': 'scannable' - }, - 'brand_voice': { - 'personality': 'innovative', - 'tone': 'authoritative', - 'style': 'informative' - }, - - # Performance & Analytics (4 inputs) - 'traffic_sources': { - 'organic': 45, - 'social': 25, - 'direct': 20, - 'referral': 10 - }, - 'conversion_rates': { - 'overall': 2.1, - 'blog_posts': 1.8, - 'videos': 3.2, - 'webinars': 5.5 - }, - 'content_roi_targets': { - 'target_roi': 300, - 'cost_per_lead': 50, - 'lifetime_value': 500 - }, - 'ab_testing_capabilities': True - } - - def test_enhanced_strategy_model_creation(self): - """Test creating enhanced strategy model with 30+ inputs.""" - sample_strategy_data = self.get_sample_strategy_data() - strategy = EnhancedContentStrategy(**sample_strategy_data) - - # Verify all fields are set - assert strategy.user_id == 1 - assert strategy.name == 'Test Enhanced Strategy' - assert strategy.industry == 'technology' - - # Verify business context fields - assert strategy.business_objectives is not None - assert strategy.target_metrics is not None - assert strategy.content_budget == 5000.0 - assert strategy.team_size == 3 - - # Verify audience intelligence fields - assert strategy.content_preferences is not None - assert strategy.consumption_patterns is not None - assert strategy.audience_pain_points is not None - - # Verify competitive intelligence fields - assert strategy.top_competitors is not None - assert strategy.market_gaps is not None - assert strategy.industry_trends is not None - - # Verify content strategy fields - assert strategy.preferred_formats is not None - assert strategy.content_mix is not None - assert strategy.content_frequency == 'weekly' - - # Verify performance analytics fields - assert strategy.traffic_sources is not None - assert strategy.conversion_rates is not None - assert strategy.ab_testing_capabilities is True - - print("✅ Enhanced strategy model creation test passed") - - def test_completion_percentage_calculation(self): - """Test completion percentage calculation for 30+ inputs.""" - sample_strategy_data = self.get_sample_strategy_data() - strategy = EnhancedContentStrategy(**sample_strategy_data) - - # Calculate completion percentage - completion = strategy.calculate_completion_percentage() - - # Should be high since we provided most fields - assert completion > 80 - assert strategy.completion_percentage > 80 - - print(f"✅ Completion percentage calculation test passed: {completion}%") - - def test_enhanced_strategy_to_dict(self): - """Test enhanced strategy to_dict method.""" - sample_strategy_data = self.get_sample_strategy_data() - strategy = EnhancedContentStrategy(**sample_strategy_data) - strategy_dict = strategy.to_dict() - - # Verify all categories are present - assert 'business_objectives' in strategy_dict - assert 'content_preferences' in strategy_dict - assert 'top_competitors' in strategy_dict - assert 'preferred_formats' in strategy_dict - assert 'traffic_sources' in strategy_dict - - # Verify metadata fields - assert 'completion_percentage' in strategy_dict - assert 'created_at' in strategy_dict - assert 'updated_at' in strategy_dict - - print("✅ Enhanced strategy to_dict test passed") - - def test_ai_analysis_result_model(self): - """Test AI analysis result model creation.""" - analysis_data = { - 'user_id': 1, - 'strategy_id': 1, - 'analysis_type': 'comprehensive_strategy', - 'comprehensive_insights': { - 'strategic_positioning': 'Strong market position', - 'content_pillars': ['Educational', 'Thought Leadership', 'Case Studies'] - }, - 'audience_intelligence': { - 'persona_insights': 'Tech-savvy professionals', - 'engagement_patterns': 'Peak engagement on Tuesdays' - }, - 'competitive_intelligence': { - 'competitor_analysis': 'Identified 3 key competitors', - 'differentiation_opportunities': ['AI integration', 'Personalization'] - }, - 'performance_optimization': { - 'traffic_optimization': 'Focus on organic search', - 'conversion_improvement': 'A/B test landing pages' - }, - 'content_calendar_optimization': { - 'publishing_schedule': 'Tuesday/Thursday posts', - 'content_mix': '40% blog, 30% video, 30% other' - }, - 'processing_time': 2.5, - 'ai_service_status': 'operational' - } - - analysis_result = EnhancedAIAnalysisResult(**analysis_data) - - assert analysis_result.user_id == 1 - assert analysis_result.strategy_id == 1 - assert analysis_result.analysis_type == 'comprehensive_strategy' - assert analysis_result.processing_time == 2.5 - assert analysis_result.ai_service_status == 'operational' - - print("✅ AI analysis result model test passed") - - def test_onboarding_integration_model(self): - """Test onboarding data integration model creation.""" - integration_data = { - 'user_id': 1, - 'strategy_id': 1, - 'website_analysis_data': { - 'writing_style': {'tone': 'professional'}, - 'target_audience': {'demographics': 'professionals'} - }, - 'research_preferences_data': { - 'content_types': ['blog_posts', 'videos'], - 'research_depth': 'comprehensive' - }, - 'auto_populated_fields': { - 'content_preferences': 'website_analysis', - 'target_audience': 'website_analysis', - 'preferred_formats': 'research_preferences' - }, - 'field_mappings': { - 'writing_style.tone': 'brand_voice.personality', - 'content_types': 'preferred_formats' - }, - 'data_quality_scores': { - 'website_analysis': 85.0, - 'research_preferences': 90.0 - }, - 'confidence_levels': { - 'content_preferences': 0.8, - 'target_audience': 0.8, - 'preferred_formats': 0.7 - } - } - - integration = OnboardingDataIntegration(**integration_data) - - assert integration.user_id == 1 - assert integration.strategy_id == 1 - assert integration.website_analysis_data is not None - assert integration.research_preferences_data is not None - assert integration.auto_populated_fields is not None - - print("✅ Onboarding integration model test passed") - - def test_enhanced_strategy_service_initialization(self): - """Test enhanced strategy service initialization.""" - service = EnhancedStrategyService() - - # Verify strategic input fields are defined - assert 'business_context' in service.strategic_input_fields - assert 'audience_intelligence' in service.strategic_input_fields - assert 'competitive_intelligence' in service.strategic_input_fields - assert 'content_strategy' in service.strategic_input_fields - assert 'performance_analytics' in service.strategic_input_fields - - # Verify field counts - total_fields = sum(len(fields) for fields in service.strategic_input_fields.values()) - assert total_fields >= 30 # 30+ strategic inputs - - print(f"✅ Enhanced strategy service initialization test passed: {total_fields} fields") - - def test_specialized_prompt_creation(self): - """Test specialized AI prompt creation.""" - service = EnhancedStrategyService() - - strategy_data = { - 'name': 'Test Strategy', - 'industry': 'technology', - 'business_objectives': 'Increase brand awareness', - 'target_metrics': '50% traffic growth', - 'content_budget': 5000, - 'team_size': 3 - } - - # Test each analysis type - analysis_types = [ - 'comprehensive_strategy', - 'audience_intelligence', - 'competitive_intelligence', - 'performance_optimization', - 'content_calendar_optimization' - ] - - for analysis_type in analysis_types: - prompt = service._create_specialized_prompt(analysis_type, strategy_data, None) - - assert prompt is not None - assert len(prompt) > 0 - assert 'Test Strategy' in prompt - - # Check for either analysis type or relevant keywords - if analysis_type == 'performance_optimization': - assert 'optimization' in prompt.lower() - elif analysis_type == 'content_calendar_optimization': - assert 'optimization' in prompt.lower() - else: - assert analysis_type in prompt or 'analysis' in prompt.lower() - - print("✅ Specialized prompt creation test passed") - - def test_fallback_recommendations(self): - """Test fallback recommendations when AI service fails.""" - service = EnhancedStrategyService() - - analysis_types = [ - 'comprehensive_strategy', - 'audience_intelligence', - 'competitive_intelligence', - 'performance_optimization', - 'content_calendar_optimization' - ] - - for analysis_type in analysis_types: - fallback = service._get_fallback_recommendations(analysis_type) - - assert fallback is not None - assert 'recommendations' in fallback - assert 'insights' in fallback - assert 'metrics' in fallback - assert 'score' in fallback['metrics'] - assert 'confidence' in fallback['metrics'] - - print("✅ Fallback recommendations test passed") - - def test_data_quality_calculation(self): - """Test data quality score calculation.""" - service = EnhancedStrategyService() - - data_sources = { - 'website_analysis': { - 'writing_style': {'tone': 'professional'}, - 'target_audience': {'demographics': 'professionals'}, - 'content_type': {'primary': 'blog_posts'} - }, - 'research_preferences': { - 'content_types': ['blog_posts', 'videos'], - 'research_depth': 'comprehensive' - } - } - - quality_scores = service._calculate_data_quality_scores(data_sources) - - assert 'website_analysis' in quality_scores - assert 'research_preferences' in quality_scores - assert quality_scores['website_analysis'] > 0 - assert quality_scores['research_preferences'] > 0 - - print("✅ Data quality calculation test passed") - - def test_confidence_level_calculation(self): - """Test confidence level calculation for auto-populated fields.""" - service = EnhancedStrategyService() - - auto_populated_fields = { - 'content_preferences': 'website_analysis', - 'target_audience': 'website_analysis', - 'preferred_formats': 'research_preferences' - } - - confidence_levels = service._calculate_confidence_levels(auto_populated_fields) - - assert 'content_preferences' in confidence_levels - assert 'target_audience' in confidence_levels - assert 'preferred_formats' in confidence_levels - - # Verify confidence levels are between 0 and 1 - for field, confidence in confidence_levels.items(): - assert 0 <= confidence <= 1 - - print("✅ Confidence level calculation test passed") - - def test_strategic_scores_calculation(self): - """Test strategic scores calculation from AI recommendations.""" - service = EnhancedStrategyService() - - ai_recommendations = { - 'comprehensive_strategy': { - 'metrics': {'score': 85, 'confidence': 0.9} - }, - 'audience_intelligence': { - 'metrics': {'score': 80, 'confidence': 0.8} - }, - 'competitive_intelligence': { - 'metrics': {'score': 75, 'confidence': 0.7} - } - } - - scores = service._calculate_strategic_scores(ai_recommendations) - - assert 'overall_score' in scores - assert 'content_quality_score' in scores - assert 'engagement_score' in scores - assert 'conversion_score' in scores - assert 'innovation_score' in scores - - # Verify scores are calculated - assert scores['overall_score'] > 0 - - print("✅ Strategic scores calculation test passed") - - def test_market_positioning_extraction(self): - """Test market positioning extraction from AI recommendations.""" - service = EnhancedStrategyService() - - ai_recommendations = { - 'comprehensive_strategy': { - 'metrics': {'score': 85, 'confidence': 0.9} - } - } - - positioning = service._extract_market_positioning(ai_recommendations) - - assert 'industry_position' in positioning - assert 'competitive_advantage' in positioning - assert 'market_share' in positioning - assert 'positioning_score' in positioning - - print("✅ Market positioning extraction test passed") - - def test_competitive_advantages_extraction(self): - """Test competitive advantages extraction from AI recommendations.""" - service = EnhancedStrategyService() - - ai_recommendations = { - 'competitive_intelligence': { - 'metrics': {'score': 80, 'confidence': 0.8} - } - } - - advantages = service._extract_competitive_advantages(ai_recommendations) - - assert isinstance(advantages, list) - assert len(advantages) > 0 - - for advantage in advantages: - assert 'advantage' in advantage - assert 'impact' in advantage - assert 'implementation' in advantage - - print("✅ Competitive advantages extraction test passed") - - def test_strategic_risks_extraction(self): - """Test strategic risks extraction from AI recommendations.""" - service = EnhancedStrategyService() - - ai_recommendations = { - 'comprehensive_strategy': { - 'metrics': {'score': 85, 'confidence': 0.9} - } - } - - risks = service._extract_strategic_risks(ai_recommendations) - - assert isinstance(risks, list) - assert len(risks) > 0 - - for risk in risks: - assert 'risk' in risk - assert 'probability' in risk - assert 'impact' in risk - - print("✅ Strategic risks extraction test passed") - - def test_opportunity_analysis_extraction(self): - """Test opportunity analysis extraction from AI recommendations.""" - service = EnhancedStrategyService() - - ai_recommendations = { - 'comprehensive_strategy': { - 'metrics': {'score': 85, 'confidence': 0.9} - } - } - - opportunities = service._extract_opportunity_analysis(ai_recommendations) - - assert isinstance(opportunities, list) - assert len(opportunities) > 0 - - for opportunity in opportunities: - assert 'opportunity' in opportunity - assert 'potential_impact' in opportunity - assert 'implementation_ease' in opportunity - - print("✅ Opportunity analysis extraction test passed") - -def run_enhanced_strategy_phase1_tests(): - """Run all Phase 1 tests for enhanced strategy service.""" - print("🚀 Starting Enhanced Strategy Phase 1 Tests") - print("=" * 50) - - test_instance = TestEnhancedStrategyPhase1() - - # Run all tests - test_instance.test_enhanced_strategy_model_creation() - test_instance.test_completion_percentage_calculation() - test_instance.test_enhanced_strategy_to_dict() - test_instance.test_ai_analysis_result_model() - test_instance.test_onboarding_integration_model() - test_instance.test_enhanced_strategy_service_initialization() - test_instance.test_specialized_prompt_creation() - test_instance.test_fallback_recommendations() - test_instance.test_data_quality_calculation() - test_instance.test_confidence_level_calculation() - test_instance.test_strategic_scores_calculation() - test_instance.test_market_positioning_extraction() - test_instance.test_competitive_advantages_extraction() - test_instance.test_strategic_risks_extraction() - test_instance.test_opportunity_analysis_extraction() - - print("=" * 50) - print("✅ All Enhanced Strategy Phase 1 Tests Passed!") - print("🎯 Phase 1 Implementation Complete:") - print(" - Enhanced database schema with 30+ input fields ✓") - print(" - Enhanced Strategy Service core implementation ✓") - print(" - 5 specialized AI prompt implementations ✓") - print(" - Onboarding data integration ✓") - print(" - Comprehensive AI recommendations ✓") - -if __name__ == "__main__": - run_enhanced_strategy_phase1_tests() \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_env_check.py b/docs/alwrity_test_scripts/test_env_check.py deleted file mode 100644 index 502382d6..00000000 --- a/docs/alwrity_test_scripts/test_env_check.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to check environment variables and API key loading. -""" - -import os -import sys -from pathlib import Path - -# Add the backend directory to the Python path -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from dotenv import load_dotenv - -def test_environment_loading(): - """Test environment variable loading.""" - print("🔍 Testing environment variable loading...") - - # Check current working directory - print(f"Current working directory: {os.getcwd()}") - - # Check if .env file exists in various locations - possible_env_paths = [ - Path('.env'), # Current directory - Path('../.env'), # Parent directory - Path('../../.env'), # Grandparent directory - Path('../../../.env'), # Great-grandparent directory - Path('backend/.env'), # Backend directory - ] - - print("\n📁 Checking for .env files:") - for env_path in possible_env_paths: - if env_path.exists(): - print(f"✅ Found .env file: {env_path.absolute()}") - else: - print(f"❌ No .env file: {env_path.absolute()}") - - # Try to load .env from different locations - print("\n🔄 Attempting to load .env files:") - for env_path in possible_env_paths: - if env_path.exists(): - print(f"Loading .env from: {env_path.absolute()}") - load_dotenv(env_path) - break - else: - print("⚠️ No .env file found, trying to load from current directory") - load_dotenv() - - # Check environment variables - print("\n🔑 Checking environment variables:") - env_vars_to_check = [ - 'GEMINI_API_KEY', - 'GOOGLE_API_KEY', - 'OPENAI_API_KEY', - 'DATABASE_URL', - 'SECRET_KEY' - ] - - for var in env_vars_to_check: - value = os.getenv(var) - if value: - # Show first few characters for security - masked_value = value[:8] + "..." if len(value) > 8 else "***" - print(f"✅ {var}: {masked_value}") - else: - print(f"❌ {var}: Not set") - - # Test specific Gemini API key loading - print("\n🤖 Testing Gemini API key loading:") - gemini_key = os.getenv('GEMINI_API_KEY') - if gemini_key: - print(f"✅ GEMINI_API_KEY found: {gemini_key[:8]}...") - - # Test if the key looks valid - if len(gemini_key) > 20: - print("✅ API key length looks valid") - else: - print("⚠️ API key seems too short") - else: - print("❌ GEMINI_API_KEY not found") - - # Check alternative names - alternative_keys = ['GOOGLE_API_KEY', 'GEMINI_KEY', 'GOOGLE_AI_API_KEY'] - for alt_key in alternative_keys: - alt_value = os.getenv(alt_key) - if alt_value: - print(f"⚠️ Found alternative key {alt_key}: {alt_value[:8]}...") - - return gemini_key is not None - -def test_gemini_provider_import(): - """Test importing the Gemini provider.""" - print("\n🧪 Testing Gemini provider import...") - - try: - from services.llm_providers.gemini_provider import gemini_structured_json_response - print("✅ Successfully imported gemini_structured_json_response") - return True - except Exception as e: - print(f"❌ Failed to import Gemini provider: {e}") - return False - -def test_ai_service_manager_import(): - """Test importing the AI service manager.""" - print("\n🧪 Testing AI service manager import...") - - try: - from services.ai_service_manager import AIServiceManager - print("✅ Successfully imported AIServiceManager") - - # Try to create an instance - ai_manager = AIServiceManager() - print("✅ Successfully created AIServiceManager instance") - return True - except Exception as e: - print(f"❌ Failed to import/create AI service manager: {e}") - return False - -if __name__ == "__main__": - print("🚀 Starting environment and API key validation tests") - print("=" * 60) - - # Test environment loading - env_ok = test_environment_loading() - - # Test imports - gemini_import_ok = test_gemini_provider_import() - ai_manager_ok = test_ai_service_manager_import() - - print("\n" + "=" * 60) - print("📊 Test Results Summary:") - print(f"Environment loading: {'✅ PASS' if env_ok else '❌ FAIL'}") - print(f"Gemini provider import: {'✅ PASS' if gemini_import_ok else '❌ FAIL'}") - print(f"AI service manager: {'✅ PASS' if ai_manager_ok else '❌ FAIL'}") - - if not env_ok: - print("\n💡 To fix environment issues:") - print("1. Create a .env file in the backend directory") - print("2. Add your GEMINI_API_KEY to the .env file") - print("3. Example: GEMINI_API_KEY=your_actual_api_key_here") - - print("\n" + "=" * 60) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_final_ai_integration.py b/docs/alwrity_test_scripts/test_final_ai_integration.py deleted file mode 100644 index 596cb020..00000000 --- a/docs/alwrity_test_scripts/test_final_ai_integration.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 -""" -Final test to verify real AI integration is working. -""" - -import requests -import json -import sys - -def test_ai_analytics_real_data(): - """Test that AI analytics endpoint returns real AI insights.""" - try: - print("🧪 Testing AI Analytics Real Data") - print("=" * 40) - - response = requests.get("http://localhost:8000/api/content-planning/ai-analytics/", timeout=30) - - if response.status_code == 200: - data = response.json() - - print(f"✅ AI Analytics endpoint: PASSED") - print(f" - Status: {response.status_code}") - print(f" - AI Service Status: {data.get('ai_service_status', 'unknown')}") - print(f" - Total Insights: {data.get('total_insights', 0)}") - print(f" - Total Recommendations: {data.get('total_recommendations', 0)}") - - # Check if we have real AI insights - insights = data.get('insights', []) - if len(insights) > 0: - print(f" - Real AI Insights Found: {len(insights)}") - for i, insight in enumerate(insights[:2]): # Show first 2 insights - print(f" {i+1}. {insight.get('title', 'No title')} ({insight.get('type', 'unknown')})") - print(f" Priority: {insight.get('priority', 'unknown')}") - print(f" Description: {insight.get('description', 'No description')[:80]}...") - else: - print(" - No insights found") - - # Check recommendations - recommendations = data.get('recommendations', []) - if len(recommendations) > 0: - print(f" - Real AI Recommendations Found: {len(recommendations)}") - for i, rec in enumerate(recommendations[:2]): # Show first 2 recommendations - print(f" {i+1}. {rec.get('title', 'No title')} (Confidence: {rec.get('confidence', 0)}%)") - else: - print(" - No recommendations found") - - # Verify it's not mock data - if data.get('ai_service_status') == 'operational': - print("✅ Real AI Integration: CONFIRMED") - return True - else: - print("❌ Still using fallback/mock data") - return False - - else: - print(f"❌ AI Analytics endpoint: FAILED (Status: {response.status_code})") - return False - - except Exception as e: - print(f"❌ AI Analytics test failed: {e}") - return False - -def test_strategies_endpoint(): - """Test that strategies endpoint works without user_id.""" - try: - print("\n🧪 Testing Strategies Endpoint") - print("=" * 35) - - response = requests.get("http://localhost:8000/api/content-planning/strategies/", timeout=10) - - if response.status_code == 200: - data = response.json() - print(f"✅ Strategies endpoint: PASSED") - print(f" - Status: {response.status_code}") - print(f" - Strategies found: {len(data)}") - - if len(data) > 0: - strategy = data[0] - print(f" - Strategy name: {strategy.get('name', 'Unknown')}") - print(f" - Industry: {strategy.get('industry', 'Unknown')}") - print(f" - Content pillars: {len(strategy.get('content_pillars', []))}") - - return True - else: - print(f"❌ Strategies endpoint: FAILED (Status: {response.status_code})") - return False - - except Exception as e: - print(f"❌ Strategies test failed: {e}") - return False - -def test_gap_analysis_endpoint(): - """Test that gap analysis endpoint works without user_id.""" - try: - print("\n🧪 Testing Gap Analysis Endpoint") - print("=" * 35) - - response = requests.get("http://localhost:8000/api/content-planning/gap-analysis/", timeout=10) - - if response.status_code == 200: - data = response.json() - print(f"✅ Gap analysis endpoint: PASSED") - print(f" - Status: {response.status_code}") - print(f" - Analyses found: {len(data)}") - - if len(data) > 0: - analysis = data[0] - print(f" - Website: {analysis.get('website_url', 'Unknown')}") - print(f" - Competitors: {len(analysis.get('competitor_urls', []))}") - print(f" - Keywords: {len(analysis.get('target_keywords', []))}") - - return True - else: - print(f"❌ Gap analysis endpoint: FAILED (Status: {response.status_code})") - return False - - except Exception as e: - print(f"❌ Gap analysis test failed: {e}") - return False - -def main(): - """Run all final tests.""" - print("🧪 Final AI Integration Test") - print("=" * 50) - - tests = [ - test_ai_analytics_real_data, - test_strategies_endpoint, - test_gap_analysis_endpoint - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Final Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 SUCCESS: All endpoints working with real AI integration!") - print("✅ 422 errors fixed") - print("✅ Real AI insights being generated") - print("✅ UI should now show real data instead of mock data") - return 0 - else: - print("⚠️ Some tests failed. Please check the implementation.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_fixes.py b/docs/alwrity_test_scripts/test_fixes.py deleted file mode 100644 index ea6b7bdc..00000000 --- a/docs/alwrity_test_scripts/test_fixes.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the fixes for the Content Planning Dashboard. -""" - -import requests -import json -import sys - -def test_backend_health(): - """Test if the backend is responding.""" - try: - response = requests.get("http://localhost:8000/health", timeout=5) - if response.status_code == 200: - print("✅ Backend health check: PASSED") - return True - else: - print(f"❌ Backend health check: FAILED (Status: {response.status_code})") - return False - except Exception as e: - print(f"❌ Backend health check: FAILED (Error: {e})") - return False - -def test_ai_analytics_endpoint(): - """Test if the AI analytics endpoint is working.""" - try: - response = requests.get("http://localhost:8000/api/content-planning/ai-analytics/", timeout=10) - if response.status_code == 200: - data = response.json() - if 'insights' in data and 'recommendations' in data: - print("✅ AI Analytics endpoint: PASSED") - print(f" - Found {len(data['insights'])} insights") - print(f" - Found {len(data['recommendations'])} recommendations") - return True - else: - print("❌ AI Analytics endpoint: FAILED (Missing expected fields)") - return False - else: - print(f"❌ AI Analytics endpoint: FAILED (Status: {response.status_code})") - return False - except Exception as e: - print(f"❌ AI Analytics endpoint: FAILED (Error: {e})") - return False - -def test_content_planning_health(): - """Test if the content planning health endpoint is working.""" - try: - response = requests.get("http://localhost:8000/api/content-planning/health", timeout=10) - if response.status_code == 200: - data = response.json() - if 'status' in data: - print("✅ Content Planning health check: PASSED") - print(f" - Status: {data['status']}") - return True - else: - print("❌ Content Planning health check: FAILED (Missing status field)") - return False - else: - print(f"❌ Content Planning health check: FAILED (Status: {response.status_code})") - return False - except Exception as e: - print(f"❌ Content Planning health check: FAILED (Error: {e})") - return False - -def main(): - """Run all tests.""" - print("🧪 Testing Content Planning Dashboard Fixes") - print("=" * 50) - - tests = [ - test_backend_health, - test_ai_analytics_endpoint, - test_content_planning_health - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All tests passed! The fixes are working correctly.") - return 0 - else: - print("⚠️ Some tests failed. Please check the backend logs.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_gemini_debug.py b/docs/alwrity_test_scripts/test_gemini_debug.py deleted file mode 100644 index 4493888a..00000000 --- a/docs/alwrity_test_scripts/test_gemini_debug.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -""" -Debug script to test Gemini API and identify the empty response issue. -""" - -import os -import sys -import asyncio -import logging - -# Add current directory to path -sys.path.append('.') - -# Set up logging -logging.basicConfig(level=logging.DEBUG) -logger = logging.getLogger(__name__) - -async def test_gemini_api(): - """Test Gemini API to identify the issue.""" - - # Check if API key is set - api_key = os.getenv('GEMINI_API_KEY') - if not api_key: - logger.error("❌ GEMINI_API_KEY environment variable not set") - return False - - logger.info(f"🔑 Found Gemini API key: {api_key[:10]}...") - - try: - # Test basic API connectivity - from services.llm_providers.gemini_provider import test_gemini_api_key - is_valid, message = await test_gemini_api_key(api_key) - - if is_valid: - logger.info(f"✅ {message}") - else: - logger.error(f"❌ {message}") - return False - - # Test simple text generation - from services.llm_providers.gemini_provider import gemini_pro_text_gen - simple_response = gemini_pro_text_gen("Hello, this is a test. Please respond with 'Test successful'.") - logger.info(f"📝 Simple text response: {simple_response}") - - # Test structured JSON generation with a simple schema - from services.llm_providers.gemini_provider import gemini_structured_json_response - - simple_schema = { - "type": "object", - "properties": { - "message": {"type": "string"}, - "status": {"type": "string"} - } - } - - simple_prompt = "Generate a simple JSON response with a message and status." - - logger.info("🧪 Testing structured JSON generation...") - structured_response = gemini_structured_json_response(simple_prompt, simple_schema) - logger.info(f"📋 Structured response: {structured_response}") - - # Test with the actual autofill schema - from api.content_planning.services.content_strategy.autofill.ai_structured_autofill import AIStructuredAutofillService - - autofill_service = AIStructuredAutofillService() - schema = autofill_service._build_schema() - - logger.info(f"🔧 Autofill schema has {len(schema.get('properties', {}))} properties") - - # Test with a minimal context - test_context = { - 'user_id': 1, - 'website_analysis': { - 'url': 'https://test.com', - 'industry': 'Technology' - } - } - - context_summary = autofill_service._build_context_summary(test_context) - prompt = autofill_service._build_prompt(context_summary) - - logger.info(f"📝 Autofill prompt length: {len(prompt)}") - logger.info(f"📝 Autofill prompt preview: {prompt[:200]}...") - - # Test the actual autofill call - logger.info("🧪 Testing actual autofill generation...") - autofill_result = await autofill_service.generate_autofill_fields(1, test_context) - logger.info(f"📋 Autofill result: {autofill_result}") - - return True - - except Exception as e: - logger.error(f"❌ Error testing Gemini API: {e}") - import traceback - logger.error(f"Traceback: {traceback.format_exc()}") - return False - -if __name__ == "__main__": - success = asyncio.run(test_gemini_api()) - if success: - logger.info("✅ Gemini API test completed successfully") - else: - logger.error("❌ Gemini API test failed") - sys.exit(1) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_gemini_fix.py b/docs/alwrity_test_scripts/test_gemini_fix.py deleted file mode 100644 index f71c75bc..00000000 --- a/docs/alwrity_test_scripts/test_gemini_fix.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the Gemini provider fixes. -""" - -import os -import sys -from pathlib import Path - -# Add the backend directory to the path -sys.path.append(str(Path(__file__).parent / 'backend')) - -from services.llm_providers.gemini_provider import gemini_text_response, gemini_pro_text_gen, test_gemini_api_key - -def test_gemini_text_response(): - """Test the basic text response function.""" - try: - print("🧪 Testing Gemini text response...") - - # Test with a simple prompt - prompt = "Hello, how are you today?" - response = gemini_text_response(prompt, temperature=0.1, max_tokens=50) - - if response and len(response) > 0: - print("✅ Gemini text response: PASSED") - print(f" - Response: {response[:100]}...") - return True - else: - print("❌ Gemini text response: FAILED (Empty response)") - return False - - except Exception as e: - print(f"❌ Gemini text response: FAILED (Error: {e})") - return False - -def test_gemini_pro_text_gen(): - """Test the legacy text generation function.""" - try: - print("🧪 Testing Gemini Pro text generation...") - - # Test with a simple prompt - prompt = "What is the capital of France?" - response = gemini_pro_text_gen(prompt, temperature=0.1, max_tokens=50) - - if response and len(response) > 0 and not response.startswith("Error"): - print("✅ Gemini Pro text generation: PASSED") - print(f" - Response: {response[:100]}...") - return True - else: - print(f"❌ Gemini Pro text generation: FAILED (Response: {response})") - return False - - except Exception as e: - print(f"❌ Gemini Pro text generation: FAILED (Error: {e})") - return False - -async def test_gemini_api_key_validation(): - """Test the API key validation function.""" - try: - print("🧪 Testing Gemini API key validation...") - - # Get API key from environment - api_key = os.getenv('GEMINI_API_KEY') - if not api_key: - print("❌ Gemini API key validation: FAILED (No API key found)") - return False - - # Test the API key - is_valid, message = await test_gemini_api_key(api_key) - - if is_valid: - print("✅ Gemini API key validation: PASSED") - print(f" - Message: {message}") - return True - else: - print(f"❌ Gemini API key validation: FAILED (Message: {message})") - return False - - except Exception as e: - print(f"❌ Gemini API key validation: FAILED (Error: {e})") - return False - -async def main(): - """Run all Gemini tests.""" - print("🧪 Testing Gemini Provider Fixes") - print("=" * 50) - - tests = [ - test_gemini_text_response, - test_gemini_pro_text_gen, - test_gemini_api_key_validation - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test == test_gemini_api_key_validation: - result = await test() - else: - result = test() - - if result: - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All Gemini tests passed! The fixes are working correctly.") - return 0 - else: - print("⚠️ Some Gemini tests failed. Please check the API key and configuration.") - return 1 - -if __name__ == "__main__": - import asyncio - sys.exit(asyncio.run(main())) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_gemini_real.py b/docs/alwrity_test_scripts/test_gemini_real.py deleted file mode 100644 index bb0310e1..00000000 --- a/docs/alwrity_test_scripts/test_gemini_real.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the Gemini provider is working with real API calls. -""" - -import os -import sys -from pathlib import Path - -# Add the backend directory to the path -sys.path.append(str(Path(__file__).parent / 'backend')) - -from services.llm_providers.gemini_provider import gemini_text_response, gemini_pro_text_gen - -def test_gemini_real_call(): - """Test a real Gemini API call.""" - try: - print("🧪 Testing real Gemini API call...") - - # Test with a simple prompt - prompt = "What is the capital of France? Answer in one sentence." - response = gemini_text_response(prompt, temperature=0.1, max_tokens=50) - - if response and len(response) > 0: - print("✅ Real Gemini API call: PASSED") - print(f" - Response: {response}") - return True - else: - print("❌ Real Gemini API call: FAILED (Empty response)") - return False - - except Exception as e: - print(f"❌ Real Gemini API call: FAILED (Error: {e})") - return False - -def test_gemini_pro_real_call(): - """Test the legacy function with real API call.""" - try: - print("🧪 Testing Gemini Pro real API call...") - - # Test with a simple prompt - prompt = "What is 2 + 2? Answer in one word." - response = gemini_pro_text_gen(prompt, temperature=0.1, max_tokens=10) - - if response and len(response) > 0 and not response.startswith("Error"): - print("✅ Gemini Pro real API call: PASSED") - print(f" - Response: {response}") - return True - else: - print(f"❌ Gemini Pro real API call: FAILED (Response: {response})") - return False - - except Exception as e: - print(f"❌ Gemini Pro real API call: FAILED (Error: {e})") - return False - -def main(): - """Run all real API tests.""" - print("🧪 Testing Gemini Provider Real API Calls") - print("=" * 50) - - tests = [ - test_gemini_real_call, - test_gemini_pro_real_call - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All real API tests passed! The Gemini provider is working correctly.") - return 0 - else: - print("⚠️ Some real API tests failed. Please check the API key and configuration.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_gemini_structure.py b/docs/alwrity_test_scripts/test_gemini_structure.py deleted file mode 100644 index dc0a5bd5..00000000 --- a/docs/alwrity_test_scripts/test_gemini_structure.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the Gemini provider structure is correct. -""" - -import os -import sys -from pathlib import Path - -# Add the backend directory to the path -sys.path.append(str(Path(__file__).parent / 'backend')) - -def test_gemini_import(): - """Test that the Gemini provider can be imported without errors.""" - try: - print("🧪 Testing Gemini provider import...") - - # Test import - from services.llm_providers.gemini_provider import ( - gemini_text_response, - gemini_pro_text_gen, - test_gemini_api_key, - gemini_structured_json_response - ) - - print("✅ Gemini provider import: PASSED") - print(" - All functions imported successfully") - return True - - except Exception as e: - print(f"❌ Gemini provider import: FAILED (Error: {e})") - return False - -def test_gemini_function_signatures(): - """Test that the function signatures are correct.""" - try: - print("🧪 Testing Gemini function signatures...") - - from services.llm_providers.gemini_provider import ( - gemini_text_response, - gemini_pro_text_gen, - test_gemini_api_key, - gemini_structured_json_response - ) - - # Test function signatures - import inspect - - # Check gemini_text_response - sig = inspect.signature(gemini_text_response) - expected_params = ['prompt', 'temperature', 'top_p', 'n', 'max_tokens', 'system_prompt'] - actual_params = list(sig.parameters.keys()) - - if all(param in actual_params for param in expected_params): - print("✅ gemini_text_response signature: PASSED") - else: - print(f"❌ gemini_text_response signature: FAILED") - print(f" - Expected: {expected_params}") - print(f" - Actual: {actual_params}") - return False - - # Check gemini_pro_text_gen - sig = inspect.signature(gemini_pro_text_gen) - expected_params = ['prompt', 'temperature', 'top_p', 'top_k', 'max_tokens'] - actual_params = list(sig.parameters.keys()) - - if all(param in actual_params for param in expected_params): - print("✅ gemini_pro_text_gen signature: PASSED") - else: - print(f"❌ gemini_pro_text_gen signature: FAILED") - print(f" - Expected: {expected_params}") - print(f" - Actual: {actual_params}") - return False - - # Check gemini_structured_json_response - sig = inspect.signature(gemini_structured_json_response) - expected_params = ['prompt', 'schema', 'temperature', 'top_p', 'top_k', 'max_tokens', 'system_prompt'] - actual_params = list(sig.parameters.keys()) - - if all(param in actual_params for param in expected_params): - print("✅ gemini_structured_json_response signature: PASSED") - else: - print(f"❌ gemini_structured_json_response signature: FAILED") - print(f" - Expected: {expected_params}") - print(f" - Actual: {actual_params}") - return False - - return True - - except Exception as e: - print(f"❌ Gemini function signatures: FAILED (Error: {e})") - return False - -def test_gemini_api_key_handling(): - """Test that the API key handling is correct.""" - try: - print("🧪 Testing Gemini API key handling...") - - from services.llm_providers.gemini_provider import gemini_text_response - - # Test with no API key (should raise ValueError) - original_key = os.environ.get('GEMINI_API_KEY') - if 'GEMINI_API_KEY' in os.environ: - del os.environ['GEMINI_API_KEY'] - - try: - gemini_text_response("test", max_tokens=10) - print("❌ API key handling: FAILED (Should have raised ValueError)") - return False - except ValueError as e: - if "Gemini API key not found" in str(e): - print("✅ API key handling: PASSED") - print(" - Correctly raises ValueError when API key is missing") - else: - print(f"❌ API key handling: FAILED (Unexpected error: {e})") - return False - finally: - # Restore original key if it existed - if original_key: - os.environ['GEMINI_API_KEY'] = original_key - - return True - - except Exception as e: - print(f"❌ Gemini API key handling: FAILED (Error: {e})") - return False - -def main(): - """Run all structure tests.""" - print("🧪 Testing Gemini Provider Structure") - print("=" * 50) - - tests = [ - test_gemini_import, - test_gemini_function_signatures, - test_gemini_api_key_handling - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All structure tests passed! The Gemini provider is correctly structured.") - print("💡 To test with real API calls, set the GEMINI_API_KEY environment variable.") - return 0 - else: - print("⚠️ Some structure tests failed. Please check the implementation.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_imports.py b/docs/alwrity_test_scripts/test_imports.py deleted file mode 100644 index b15a7e20..00000000 --- a/docs/alwrity_test_scripts/test_imports.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify all imports work correctly. -""" - -import sys -import os - -# Add the current directory to Python path -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -def test_imports(): - """Test all critical imports""" - try: - print("Testing imports...") - - # Test database imports - print("Testing database imports...") - from services.database import init_database, get_db_session - print("✅ Database imports successful") - - # Test model imports - print("Testing model imports...") - from models.monitoring_models import StrategyMonitoringPlan, MonitoringTask - from models.enhanced_strategy_models import EnhancedContentStrategy - print("✅ Model imports successful") - - # Test service imports - print("Testing service imports...") - from services.strategy_service import StrategyService - from services.monitoring_plan_generator import MonitoringPlanGenerator - print("✅ Service imports successful") - - # Test LLM provider imports - print("Testing LLM provider imports...") - from services.llm_providers.anthropic_provider import anthropic_text_response - print("✅ LLM provider imports successful") - - # Test API route imports - print("Testing API route imports...") - from api.content_planning.monitoring_routes import router as monitoring_router - print("✅ API route imports successful") - - print("🎉 All imports successful!") - return True - - except Exception as e: - print(f"❌ Import failed: {e}") - import traceback - traceback.print_exc() - return False - -if __name__ == "__main__": - success = test_imports() - sys.exit(0 if success else 1) diff --git a/docs/alwrity_test_scripts/test_json_compatibility.py b/docs/alwrity_test_scripts/test_json_compatibility.py deleted file mode 100644 index 8d7c6924..00000000 --- a/docs/alwrity_test_scripts/test_json_compatibility.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the JSON compatibility fix. -""" - -import os -import sys -import json -from pathlib import Path - -# Add the backend directory to the path -sys.path.append(str(Path(__file__).parent / 'backend')) - -from services.llm_providers.gemini_provider import gemini_structured_json_response - -def test_json_string_return(): - """Test that the function returns JSON string instead of dict.""" - try: - print("🧪 Testing JSON string return...") - - # Simple schema for testing - test_schema = { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"}, - "city": {"type": "string"} - }, - "required": ["name", "age"] - } - - # Test prompt - prompt = "Create a person profile with name John, age 30, and city New York." - - response = gemini_structured_json_response( - prompt=prompt, - schema=test_schema, - temperature=0.1, - max_tokens=100 - ) - - # Check that response is a JSON string - if isinstance(response, str): - # Try to parse it as JSON - parsed = json.loads(response) - if isinstance(parsed, dict) and "name" in parsed and "age" in parsed: - print("✅ JSON string return: PASSED") - print(f" - Response type: {type(response)}") - print(f" - Parsed content: {parsed}") - return True - else: - print(f"❌ JSON string return: FAILED (Invalid JSON content: {parsed})") - return False - else: - print(f"❌ JSON string return: FAILED (Expected string, got {type(response)})") - return False - - except Exception as e: - print(f"❌ JSON string return: FAILED (Error: {e})") - return False - -def test_json_compatibility(): - """Test that the response can be parsed by calling code.""" - try: - print("🧪 Testing JSON compatibility...") - - # Simple schema for testing - test_schema = { - "type": "object", - "properties": { - "result": {"type": "string"}, - "status": {"type": "string"} - }, - "required": ["result", "status"] - } - - # Test prompt - prompt = "Return a simple result with status success." - - response = gemini_structured_json_response( - prompt=prompt, - schema=test_schema, - temperature=0.1, - max_tokens=50 - ) - - # Simulate what calling code would do - try: - parsed_response = json.loads(response) - if isinstance(parsed_response, dict): - print("✅ JSON compatibility: PASSED") - print(f" - Successfully parsed by calling code") - print(f" - Parsed content: {parsed_response}") - return True - else: - print(f"❌ JSON compatibility: FAILED (Parsed result not dict: {parsed_response})") - return False - except json.JSONDecodeError as e: - print(f"❌ JSON compatibility: FAILED (JSON decode error: {e})") - return False - - except Exception as e: - print(f"❌ JSON compatibility: FAILED (Error: {e})") - return False - -def main(): - """Run all JSON compatibility tests.""" - print("🧪 Testing JSON Compatibility Fix") - print("=" * 50) - - tests = [ - test_json_string_return, - test_json_compatibility - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All JSON compatibility tests passed!") - return 0 - else: - print("⚠️ Some JSON compatibility tests failed.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_onboarding_data.py b/docs/alwrity_test_scripts/test_onboarding_data.py deleted file mode 100644 index e035fd1c..00000000 --- a/docs/alwrity_test_scripts/test_onboarding_data.py +++ /dev/null @@ -1,463 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to validate onboarding data existence in the database. -This script checks if onboarding data exists for test users and validates the data flow. -""" - -import sys -import os -import asyncio -import logging -from datetime import datetime -from typing import Dict, Any, Optional - -# Add the backend directory to the Python path -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from sqlalchemy.orm import Session -from services.database import get_db_session -from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences, APIKey -from models.enhanced_strategy_models import OnboardingDataIntegration -from api.content_planning.services.content_strategy.onboarding.data_integration import OnboardingDataIntegrationService -from api.content_planning.services.content_strategy.autofill.ai_structured_autofill import AIStructuredAutofillService -from services.ai_service_manager import AIServiceManager - -# Configure logging -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.StreamHandler(sys.stdout), - logging.FileHandler('onboarding_test.log') - ] -) -logger = logging.getLogger(__name__) - -class OnboardingDataValidator: - """Validator for onboarding data existence and quality.""" - - def __init__(self): - self.db_session = get_db_session() - self.data_integration_service = OnboardingDataIntegrationService() - self.ai_service = AIStructuredAutofillService() - self.ai_manager = AIServiceManager() - - def test_database_connection(self) -> bool: - """Test database connection.""" - try: - # Simple query to test connection - from sqlalchemy import text - result = self.db_session.execute(text("SELECT 1")) - logger.info("✅ Database connection successful") - return True - except Exception as e: - logger.error(f"❌ Database connection failed: {e}") - return False - - def check_onboarding_sessions(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: - """Check onboarding sessions for given user IDs.""" - if user_ids is None: - user_ids = [1, 2, 3] # Default test user IDs - - results = {} - - for user_id in user_ids: - logger.info(f"🔍 Checking onboarding session for user {user_id}") - - try: - session = self.db_session.query(OnboardingSession).filter( - OnboardingSession.user_id == user_id - ).order_by(OnboardingSession.updated_at.desc()).first() - - if session: - results[user_id] = { - 'session_exists': True, - 'session_id': session.id, - 'status': session.status, - 'progress': session.progress, - 'created_at': session.created_at.isoformat(), - 'updated_at': session.updated_at.isoformat(), - 'data': session.to_dict() if hasattr(session, 'to_dict') else str(session) - } - logger.info(f"✅ Onboarding session found for user {user_id}: {session.status}") - else: - results[user_id] = { - 'session_exists': False, - 'error': 'No onboarding session found' - } - logger.warning(f"❌ No onboarding session found for user {user_id}") - - except Exception as e: - results[user_id] = { - 'session_exists': False, - 'error': str(e) - } - logger.error(f"❌ Error checking onboarding session for user {user_id}: {e}") - - return results - - def check_website_analysis(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: - """Check website analysis data for given user IDs.""" - if user_ids is None: - user_ids = [1, 2, 3] - - results = {} - - for user_id in user_ids: - logger.info(f"🔍 Checking website analysis for user {user_id}") - - try: - # Get onboarding session first - session = self.db_session.query(OnboardingSession).filter( - OnboardingSession.user_id == user_id - ).order_by(OnboardingSession.updated_at.desc()).first() - - if not session: - results[user_id] = { - 'website_analysis_exists': False, - 'error': 'No onboarding session found' - } - continue - - # Get website analysis - website_analysis = self.db_session.query(WebsiteAnalysis).filter( - WebsiteAnalysis.session_id == session.id - ).order_by(WebsiteAnalysis.updated_at.desc()).first() - - if website_analysis: - results[user_id] = { - 'website_analysis_exists': True, - 'analysis_id': website_analysis.id, - 'website_url': website_analysis.website_url, - 'status': website_analysis.status, - 'created_at': website_analysis.created_at.isoformat(), - 'updated_at': website_analysis.updated_at.isoformat(), - 'data_keys': list(website_analysis.to_dict().keys()) if hasattr(website_analysis, 'to_dict') else [] - } - logger.info(f"✅ Website analysis found for user {user_id}: {website_analysis.website_url}") - else: - results[user_id] = { - 'website_analysis_exists': False, - 'error': 'No website analysis found' - } - logger.warning(f"❌ No website analysis found for user {user_id}") - - except Exception as e: - results[user_id] = { - 'website_analysis_exists': False, - 'error': str(e) - } - logger.error(f"❌ Error checking website analysis for user {user_id}: {e}") - - return results - - def check_research_preferences(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: - """Check research preferences data for given user IDs.""" - if user_ids is None: - user_ids = [1, 2, 3] - - results = {} - - for user_id in user_ids: - logger.info(f"🔍 Checking research preferences for user {user_id}") - - try: - # Get onboarding session first - session = self.db_session.query(OnboardingSession).filter( - OnboardingSession.user_id == user_id - ).order_by(OnboardingSession.updated_at.desc()).first() - - if not session: - results[user_id] = { - 'research_preferences_exists': False, - 'error': 'No onboarding session found' - } - continue - - # Get research preferences - research_prefs = self.db_session.query(ResearchPreferences).filter( - ResearchPreferences.session_id == session.id - ).first() - - if research_prefs: - results[user_id] = { - 'research_preferences_exists': True, - 'prefs_id': research_prefs.id, - 'research_depth': research_prefs.research_depth, - 'content_types': research_prefs.content_types, - 'created_at': research_prefs.created_at.isoformat(), - 'updated_at': research_prefs.updated_at.isoformat(), - 'data_keys': list(research_prefs.to_dict().keys()) if hasattr(research_prefs, 'to_dict') else [] - } - logger.info(f"✅ Research preferences found for user {user_id}: {research_prefs.research_depth}") - else: - results[user_id] = { - 'research_preferences_exists': False, - 'error': 'No research preferences found' - } - logger.warning(f"❌ No research preferences found for user {user_id}") - - except Exception as e: - results[user_id] = { - 'research_preferences_exists': False, - 'error': str(e) - } - logger.error(f"❌ Error checking research preferences for user {user_id}: {e}") - - return results - - def check_api_keys(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: - """Check API keys data for given user IDs.""" - if user_ids is None: - user_ids = [1, 2, 3] - - results = {} - - for user_id in user_ids: - logger.info(f"🔍 Checking API keys for user {user_id}") - - try: - # Get onboarding session first - session = self.db_session.query(OnboardingSession).filter( - OnboardingSession.user_id == user_id - ).order_by(OnboardingSession.updated_at.desc()).first() - - if not session: - results[user_id] = { - 'api_keys_exist': False, - 'error': 'No onboarding session found' - } - continue - - # Get API keys - api_keys = self.db_session.query(APIKey).filter( - APIKey.session_id == session.id - ).all() - - if api_keys: - results[user_id] = { - 'api_keys_exist': True, - 'count': len(api_keys), - 'providers': [key.provider for key in api_keys], - 'created_at': api_keys[0].created_at.isoformat() if api_keys else None, - 'updated_at': api_keys[0].updated_at.isoformat() if api_keys else None - } - logger.info(f"✅ API keys found for user {user_id}: {len(api_keys)} keys") - else: - results[user_id] = { - 'api_keys_exist': False, - 'error': 'No API keys found' - } - logger.warning(f"❌ No API keys found for user {user_id}") - - except Exception as e: - results[user_id] = { - 'api_keys_exist': False, - 'error': str(e) - } - logger.error(f"❌ Error checking API keys for user {user_id}: {e}") - - return results - - async def test_data_integration_service(self, user_id: int = 1) -> Dict[str, Any]: - """Test the data integration service.""" - logger.info(f"🔍 Testing data integration service for user {user_id}") - - try: - # Test the process_onboarding_data method - integrated_data = await self.data_integration_service.process_onboarding_data(user_id, self.db_session) - - if integrated_data: - result = { - 'success': True, - 'has_website_analysis': bool(integrated_data.get('website_analysis')), - 'has_research_preferences': bool(integrated_data.get('research_preferences')), - 'has_api_keys_data': bool(integrated_data.get('api_keys_data')), - 'has_onboarding_session': bool(integrated_data.get('onboarding_session')), - 'data_quality': integrated_data.get('data_quality', {}), - 'processing_timestamp': integrated_data.get('processing_timestamp'), - 'context_keys': list(integrated_data.keys()) - } - - logger.info(f"✅ Data integration successful for user {user_id}") - logger.info(f" Website analysis: {result['has_website_analysis']}") - logger.info(f" Research preferences: {result['has_research_preferences']}") - logger.info(f" API keys: {result['has_api_keys_data']}") - logger.info(f" Onboarding session: {result['has_onboarding_session']}") - - return result - else: - logger.error(f"❌ Data integration returned None for user {user_id}") - return {'success': False, 'error': 'No data returned'} - - except Exception as e: - logger.error(f"❌ Data integration failed for user {user_id}: {e}") - return {'success': False, 'error': str(e)} - - async def test_ai_service_configuration(self) -> Dict[str, Any]: - """Test AI service configuration.""" - logger.info("🔍 Testing AI service configuration") - - try: - # Test basic AI service functionality - test_prompt = "Generate a simple test response" - test_schema = { - "type": "OBJECT", - "properties": { - "test_field": {"type": "STRING", "description": "A test field"} - }, - "required": ["test_field"] - } - - # Test the AI service manager - result = await self.ai_manager.execute_structured_json_call( - service_type="STRATEGIC_INTELLIGENCE", - prompt=test_prompt, - schema=test_schema - ) - - if result and not result.get('error'): - logger.info("✅ AI service configuration successful") - return { - 'success': True, - 'ai_service_working': True, - 'test_response': result - } - else: - logger.error(f"❌ AI service test failed: {result.get('error', 'Unknown error')}") - return { - 'success': False, - 'ai_service_working': False, - 'error': result.get('error', 'Unknown error') - } - - except Exception as e: - logger.error(f"❌ AI service configuration test failed: {e}") - return { - 'success': False, - 'ai_service_working': False, - 'error': str(e) - } - - async def test_ai_structured_autofill(self, user_id: int = 1) -> Dict[str, Any]: - """Test the AI structured autofill service.""" - logger.info(f"🔍 Testing AI structured autofill for user {user_id}") - - try: - # First get the context - integrated_data = await self.data_integration_service.process_onboarding_data(user_id, self.db_session) - - if not integrated_data: - logger.error(f"❌ No integrated data available for user {user_id}") - return {'success': False, 'error': 'No integrated data available'} - - # Test the AI structured autofill - result = await self.ai_service.generate_autofill_fields(user_id, integrated_data) - - if result: - meta = result.get('meta', {}) - fields = result.get('fields', {}) - - test_result = { - 'success': True, - 'ai_used': meta.get('ai_used', False), - 'ai_overrides_count': meta.get('ai_overrides_count', 0), - 'success_rate': meta.get('success_rate', 0), - 'attempts': meta.get('attempts', 0), - 'missing_fields': meta.get('missing_fields', []), - 'fields_generated': len(fields), - 'sample_fields': list(fields.keys())[:5] if fields else [] - } - - logger.info(f"✅ AI structured autofill test completed for user {user_id}") - logger.info(f" AI used: {test_result['ai_used']}") - logger.info(f" Fields generated: {test_result['fields_generated']}") - logger.info(f" Success rate: {test_result['success_rate']:.1f}%") - logger.info(f" Attempts: {test_result['attempts']}") - - return test_result - else: - logger.error(f"❌ AI structured autofill returned None for user {user_id}") - return {'success': False, 'error': 'No result returned'} - - except Exception as e: - logger.error(f"❌ AI structured autofill test failed for user {user_id}: {e}") - return {'success': False, 'error': str(e)} - - def print_summary(self, results: Dict[str, Any]): - """Print a summary of all test results.""" - logger.info("\n" + "="*80) - logger.info("📊 ONBOARDING DATA VALIDATION SUMMARY") - logger.info("="*80) - - for test_name, result in results.items(): - logger.info(f"\n🔍 {test_name.upper()}:") - if isinstance(result, dict): - for key, value in result.items(): - if isinstance(value, dict): - logger.info(f" {key}:") - for sub_key, sub_value in value.items(): - logger.info(f" {sub_key}: {sub_value}") - else: - logger.info(f" {key}: {value}") - else: - logger.info(f" {result}") - - logger.info("\n" + "="*80) - - def cleanup(self): - """Clean up database session.""" - if self.db_session: - self.db_session.close() - -async def main(): - """Main test function.""" - logger.info("🚀 Starting onboarding data validation tests") - - validator = OnboardingDataValidator() - - try: - # Test database connection - db_connected = validator.test_database_connection() - if not db_connected: - logger.error("❌ Cannot proceed without database connection") - return - - # Test user IDs to check - test_user_ids = [1, 2, 3] - - # Run all tests - results = { - 'database_connection': db_connected, - 'onboarding_sessions': validator.check_onboarding_sessions(test_user_ids), - 'website_analysis': validator.check_website_analysis(test_user_ids), - 'research_preferences': validator.check_research_preferences(test_user_ids), - 'api_keys': validator.check_api_keys(test_user_ids), - 'data_integration': await validator.test_data_integration_service(1), - 'ai_service_config': await validator.test_ai_service_configuration(), - 'ai_structured_autofill': await validator.test_ai_structured_autofill(1) - } - - # Print summary - validator.print_summary(results) - - # Determine overall status - overall_success = all([ - results['database_connection'], - any(session.get('session_exists', False) for session in results['onboarding_sessions'].values()), - results['data_integration']['success'], - results['ai_service_config']['success'] - ]) - - if overall_success: - logger.info("✅ All critical tests passed!") - else: - logger.error("❌ Some critical tests failed!") - - except Exception as e: - logger.error(f"❌ Test execution failed: {e}") - finally: - validator.cleanup() - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_phase2_ai_integration.py b/docs/alwrity_test_scripts/test_phase2_ai_integration.py deleted file mode 100644 index 0a834590..00000000 --- a/docs/alwrity_test_scripts/test_phase2_ai_integration.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for Phase 2 AI Integration -Verifies that the Keyword Researcher and Competitor Analyzer are working with real AI calls. -""" - -import asyncio -import sys -import os -from pathlib import Path - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.content_gap_analyzer.keyword_researcher import KeywordResearcher -from services.content_gap_analyzer.competitor_analyzer import CompetitorAnalyzer -from loguru import logger - -async def test_keyword_researcher_ai(): - """Test the Keyword Researcher AI integration.""" - - print("🔍 Testing Keyword Researcher AI Integration...") - - # Initialize the Keyword Researcher - keyword_researcher = KeywordResearcher() - - # Test data - test_industry = "Technology" - test_url = "https://example.com" - test_keywords = ["artificial intelligence", "machine learning", "data science"] - - try: - print("\n1. Testing Keyword Analysis...") - keyword_analysis = await keyword_researcher.analyze_keywords(test_industry, test_url, test_keywords) - print(f"✅ Keyword Analysis completed: {len(keyword_analysis.get('insights', []))} insights generated") - - print("\n2. Testing Keyword Expansion...") - keyword_expansion = await keyword_researcher.expand_keywords(test_keywords, test_industry) - print(f"✅ Keyword Expansion completed: {len(keyword_expansion.get('expanded_keywords', []))} keywords expanded") - - print("\n3. Testing Search Intent Analysis...") - intent_analysis = await keyword_researcher.analyze_search_intent(test_keywords) - print(f"✅ Search Intent Analysis completed: {len(intent_analysis.get('intent_categories', {}))} intent categories") - - print("\n4. Testing Content Format Suggestions...") - # Create mock AI insights for testing - mock_ai_insights = { - 'keywords': test_keywords, - 'industry': test_industry, - 'trends': {'ai': 'rising', 'ml': 'stable'} - } - content_formats = await keyword_researcher._suggest_content_formats(mock_ai_insights) - print(f"✅ Content Format Suggestions completed: {len(content_formats)} formats suggested") - - print("\n5. Testing Topic Clustering...") - topic_clusters = await keyword_researcher._create_topic_clusters(mock_ai_insights) - print(f"✅ Topic Clustering completed: {len(topic_clusters.get('topic_clusters', []))} clusters created") - - print("\n🎉 All Keyword Researcher AI Tests Passed!") - return True - - except Exception as e: - print(f"❌ Keyword Researcher AI Test Failed: {str(e)}") - logger.error(f"Keyword Researcher AI test failed: {str(e)}") - return False - -async def test_competitor_analyzer_ai(): - """Test the Competitor Analyzer AI integration.""" - - print("\n🏢 Testing Competitor Analyzer AI Integration...") - - # Initialize the Competitor Analyzer - competitor_analyzer = CompetitorAnalyzer() - - # Test data - test_competitor_urls = [ - "https://competitor1.com", - "https://competitor2.com", - "https://competitor3.com" - ] - test_industry = "Technology" - - try: - print("\n1. Testing Competitor Analysis...") - competitor_analysis = await competitor_analyzer.analyze_competitors(test_competitor_urls, test_industry) - print(f"✅ Competitor Analysis completed: {len(competitor_analysis.get('competitors', []))} competitors analyzed") - - print("\n2. Testing Market Position Evaluation...") - # Create mock competitor data for testing - mock_competitors = [ - { - 'url': 'competitor1.com', - 'analysis': { - 'content_count': 150, - 'avg_quality_score': 8.5, - 'top_keywords': ['AI', 'ML', 'Data Science'] - } - }, - { - 'url': 'competitor2.com', - 'analysis': { - 'content_count': 200, - 'avg_quality_score': 7.8, - 'top_keywords': ['Automation', 'Innovation', 'Tech'] - } - } - ] - market_position = await competitor_analyzer._evaluate_market_position(mock_competitors, test_industry) - print(f"✅ Market Position Evaluation completed: {len(market_position.get('strategic_recommendations', []))} recommendations") - - print("\n3. Testing Content Gap Identification...") - content_gaps = await competitor_analyzer._identify_content_gaps(mock_competitors) - print(f"✅ Content Gap Identification completed: {len(content_gaps)} gaps identified") - - print("\n4. Testing Competitive Insights Generation...") - # Create mock analysis results for testing - mock_analysis_results = { - 'competitors': mock_competitors, - 'market_position': market_position, - 'content_gaps': content_gaps, - 'industry': test_industry - } - competitive_insights = await competitor_analyzer._generate_competitive_insights(mock_analysis_results) - print(f"✅ Competitive Insights Generation completed: {len(competitive_insights)} insights generated") - - print("\n🎉 All Competitor Analyzer AI Tests Passed!") - return True - - except Exception as e: - print(f"❌ Competitor Analyzer AI Test Failed: {str(e)}") - logger.error(f"Competitor Analyzer AI test failed: {str(e)}") - return False - -async def test_ai_fallback_functionality(): - """Test the fallback functionality when AI fails.""" - - print("\n🔄 Testing AI Fallback Functionality...") - - # Initialize services - keyword_researcher = KeywordResearcher() - competitor_analyzer = CompetitorAnalyzer() - - # Test with minimal data to trigger fallback - minimal_data = {'test': 'data'} - - try: - print("Testing Keyword Researcher fallback...") - keyword_result = await keyword_researcher._analyze_keyword_trends("test", []) - - if keyword_result and 'trends' in keyword_result: - print("✅ Keyword Researcher fallback working correctly") - else: - print("❌ Keyword Researcher fallback failed") - return False - - print("Testing Competitor Analyzer fallback...") - competitor_result = await competitor_analyzer._evaluate_market_position([], "test") - - if competitor_result and 'market_leader' in competitor_result: - print("✅ Competitor Analyzer fallback working correctly") - else: - print("❌ Competitor Analyzer fallback failed") - return False - - print("✅ All fallback functionality working correctly") - return True - - except Exception as e: - print(f"❌ Fallback test failed: {str(e)}") - return False - -async def main(): - """Main test function.""" - print("🚀 Starting Phase 2 AI Integration Tests...") - print("=" * 60) - - # Test 1: Keyword Researcher AI Integration - keyword_success = await test_keyword_researcher_ai() - - # Test 2: Competitor Analyzer AI Integration - competitor_success = await test_competitor_analyzer_ai() - - # Test 3: Fallback Functionality - fallback_success = await test_ai_fallback_functionality() - - print("\n" + "=" * 60) - print("📊 Phase 2 Test Results Summary:") - print(f"Keyword Researcher AI: {'✅ PASSED' if keyword_success else '❌ FAILED'}") - print(f"Competitor Analyzer AI: {'✅ PASSED' if competitor_success else '❌ FAILED'}") - print(f"Fallback Functionality: {'✅ PASSED' if fallback_success else '❌ FAILED'}") - - if keyword_success and competitor_success and fallback_success: - print("\n🎉 All Phase 2 tests passed! AI Integration is working correctly.") - print("✅ Phase 2: Advanced AI Features COMPLETED") - return 0 - else: - print("\n⚠️ Some tests failed. Please check the AI configuration.") - return 1 - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_phase3_ai_optimization.py b/docs/alwrity_test_scripts/test_phase3_ai_optimization.py deleted file mode 100644 index 6b39dec9..00000000 --- a/docs/alwrity_test_scripts/test_phase3_ai_optimization.py +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for Phase 3 AI Prompt Optimization -Verifies that the AI Prompt Optimizer is working with advanced prompts and schemas. -""" - -import asyncio -import sys -import os -from pathlib import Path - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.ai_prompt_optimizer import AIPromptOptimizer -from services.content_gap_analyzer.ai_engine_service import AIEngineService -from loguru import logger - -async def test_ai_prompt_optimizer(): - """Test the AI Prompt Optimizer functionality.""" - - print("🔧 Testing AI Prompt Optimizer...") - - # Initialize the AI Prompt Optimizer - ai_optimizer = AIPromptOptimizer() - - # Test 1: Strategic Content Gap Analysis - print("\n📊 Test 1: Strategic Content Gap Analysis") - analysis_data = { - 'target_url': 'example.com', - 'industry': 'technology', - 'serp_opportunities': 25, - 'expanded_keywords_count': 150, - 'competitors_analyzed': 5, - 'content_quality_score': 8.5, - 'competition_level': 'high', - 'dominant_themes': { - 'artificial_intelligence': 0.3, - 'machine_learning': 0.25, - 'data_science': 0.2, - 'automation': 0.15, - 'innovation': 0.1 - }, - 'competitive_landscape': { - 'market_leader': 'competitor1.com', - 'content_leader': 'competitor2.com', - 'quality_leader': 'competitor3.com' - } - } - - try: - result = await ai_optimizer.generate_strategic_content_gap_analysis(analysis_data) - print(f"✅ Strategic content gap analysis completed") - print(f" - Strategic insights: {len(result.get('strategic_insights', []))}") - print(f" - Content recommendations: {len(result.get('content_recommendations', []))}") - print(f" - Keyword strategy: {bool(result.get('keyword_strategy'))}") - except Exception as e: - print(f"❌ Strategic content gap analysis failed: {str(e)}") - return False - - # Test 2: Advanced Market Position Analysis - print("\n🏢 Test 2: Advanced Market Position Analysis") - market_data = { - 'industry': 'technology', - 'competitors': [ - { - 'url': 'competitor1.com', - 'content_score': 8.5, - 'quality_score': 9.0, - 'frequency': 'high' - }, - { - 'url': 'competitor2.com', - 'content_score': 7.8, - 'quality_score': 8.2, - 'frequency': 'medium' - } - ], - 'market_size': 'Large', - 'growth_rate': '15%', - 'key_trends': ['AI adoption', 'Cloud migration', 'Digital transformation'] - } - - try: - result = await ai_optimizer.generate_advanced_market_position_analysis(market_data) - print(f"✅ Advanced market position analysis completed") - print(f" - Market leader: {result.get('market_leader', 'N/A')}") - print(f" - Market gaps: {len(result.get('market_gaps', []))}") - print(f" - Opportunities: {len(result.get('opportunities', []))}") - print(f" - Strategic recommendations: {len(result.get('strategic_recommendations', []))}") - except Exception as e: - print(f"❌ Advanced market position analysis failed: {str(e)}") - return False - - # Test 3: Advanced Keyword Analysis - print("\n🔍 Test 3: Advanced Keyword Analysis") - keyword_data = { - 'industry': 'technology', - 'target_keywords': ['artificial intelligence', 'machine learning', 'data science'], - 'search_volume_data': { - 'artificial intelligence': 50000, - 'machine learning': 35000, - 'data science': 25000 - }, - 'competition_analysis': { - 'artificial intelligence': 'high', - 'machine learning': 'medium', - 'data science': 'low' - }, - 'trend_analysis': { - 'artificial intelligence': 'rising', - 'machine learning': 'stable', - 'data science': 'rising' - } - } - - try: - result = await ai_optimizer.generate_advanced_keyword_analysis(keyword_data) - print(f"✅ Advanced keyword analysis completed") - print(f" - Keyword opportunities: {len(result.get('keyword_opportunities', []))}") - print(f" - Keyword clusters: {len(result.get('keyword_clusters', []))}") - except Exception as e: - print(f"❌ Advanced keyword analysis failed: {str(e)}") - return False - - # Test 4: Health Check - print("\n🏥 Test 4: Health Check") - try: - health_status = await ai_optimizer.health_check() - print(f"✅ Health check completed") - print(f" - Service status: {health_status.get('status')}") - print(f" - Prompts loaded: {health_status.get('prompts_loaded')}") - print(f" - Schemas loaded: {health_status.get('schemas_loaded')}") - print(f" - AI integration: {health_status.get('capabilities', {}).get('ai_integration')}") - except Exception as e: - print(f"❌ Health check failed: {str(e)}") - return False - - return True - -async def test_ai_engine_integration(): - """Test the AI Engine Service integration with prompt optimizer.""" - - print("\n🤖 Testing AI Engine Service Integration...") - - # Initialize the AI Engine Service - ai_engine = AIEngineService() - - # Test 1: Content Gap Analysis with Advanced Prompts - print("\n📊 Test 1: Content Gap Analysis with Advanced Prompts") - analysis_summary = { - 'target_url': 'example.com', - 'industry': 'technology', - 'serp_opportunities': 25, - 'expanded_keywords_count': 150, - 'competitors_analyzed': 5, - 'dominant_themes': { - 'artificial_intelligence': 0.3, - 'machine_learning': 0.25, - 'data_science': 0.2 - } - } - - try: - result = await ai_engine.analyze_content_gaps(analysis_summary) - print(f"✅ Content gap analysis with advanced prompts completed") - print(f" - Strategic insights: {len(result.get('strategic_insights', []))}") - print(f" - Content recommendations: {len(result.get('content_recommendations', []))}") - except Exception as e: - print(f"❌ Content gap analysis failed: {str(e)}") - return False - - # Test 2: Market Position Analysis with Advanced Prompts - print("\n🏢 Test 2: Market Position Analysis with Advanced Prompts") - market_data = { - 'industry': 'technology', - 'competitors': [ - { - 'url': 'competitor1.com', - 'content_score': 8.5, - 'quality_score': 9.0 - }, - { - 'url': 'competitor2.com', - 'content_score': 7.8, - 'quality_score': 8.2 - } - ] - } - - try: - result = await ai_engine.analyze_market_position(market_data) - print(f"✅ Market position analysis with advanced prompts completed") - print(f" - Market leader: {result.get('market_leader', 'N/A')}") - print(f" - Market gaps: {len(result.get('market_gaps', []))}") - print(f" - Strategic recommendations: {len(result.get('strategic_recommendations', []))}") - except Exception as e: - print(f"❌ Market position analysis failed: {str(e)}") - return False - - return True - -async def test_ai_fallback_functionality(): - """Test the fallback functionality when AI fails.""" - - print("\n🛡️ Testing AI Fallback Functionality...") - - # Initialize the AI Prompt Optimizer - ai_optimizer = AIPromptOptimizer() - - # Test with invalid data to trigger fallback - print("\n📊 Test: Fallback for Strategic Content Gap Analysis") - invalid_data = { - 'invalid_field': 'invalid_value' - } - - try: - result = await ai_optimizer.generate_strategic_content_gap_analysis(invalid_data) - print(f"✅ Fallback functionality working") - print(f" - Strategic insights: {len(result.get('strategic_insights', []))}") - print(f" - Content recommendations: {len(result.get('content_recommendations', []))}") - except Exception as e: - print(f"❌ Fallback functionality failed: {str(e)}") - return False - - return True - -async def main(): - """Main test function.""" - print("🚀 Starting Phase 3 AI Prompt Optimization Tests...") - print("=" * 60) - - # Test 1: AI Prompt Optimizer - ai_optimizer_success = await test_ai_prompt_optimizer() - - # Test 2: AI Engine Integration - ai_engine_success = await test_ai_engine_integration() - - # Test 3: Fallback Functionality - fallback_success = await test_ai_fallback_functionality() - - print("\n" + "=" * 60) - print("📊 Test Results Summary:") - print(f"AI Prompt Optimizer: {'✅ PASSED' if ai_optimizer_success else '❌ FAILED'}") - print(f"AI Engine Integration: {'✅ PASSED' if ai_engine_success else '❌ FAILED'}") - print(f"Fallback Functionality: {'✅ PASSED' if fallback_success else '❌ FAILED'}") - - if ai_optimizer_success and ai_engine_success and fallback_success: - print("\n🎉 All Phase 3 tests passed! AI Prompt Optimization is working correctly.") - print("\n✅ Phase 3 Achievements:") - print(" - Advanced AI prompts implemented") - print(" - Comprehensive JSON schemas created") - print(" - Expert-level AI instructions optimized") - print(" - Robust error handling and fallbacks") - print(" - AI engine service integration completed") - return 0 - else: - print("\n⚠️ Some Phase 3 tests failed. Please check the AI configuration.") - return 1 - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_phase4_ai_service_integration.py b/docs/alwrity_test_scripts/test_phase4_ai_service_integration.py deleted file mode 100644 index 52e0c3bf..00000000 --- a/docs/alwrity_test_scripts/test_phase4_ai_service_integration.py +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for Phase 4 AI Service Integration -Verifies that the AI Service Manager is working with centralized management and performance monitoring. -""" - -import asyncio -import sys -import os -from pathlib import Path - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.ai_service_manager import AIServiceManager -from services.content_gap_analyzer.ai_engine_service import AIEngineService -from loguru import logger - -async def test_ai_service_manager(): - """Test the AI Service Manager functionality.""" - - print("🔧 Testing AI Service Manager...") - - # Initialize the AI Service Manager - ai_manager = AIServiceManager() - - # Test 1: Content Gap Analysis - print("\n📊 Test 1: Content Gap Analysis") - analysis_data = { - 'target_url': 'example.com', - 'industry': 'technology', - 'serp_opportunities': 25, - 'expanded_keywords_count': 150, - 'competitors_analyzed': 5, - 'content_quality_score': 8.5, - 'competition_level': 'high', - 'dominant_themes': { - 'artificial_intelligence': 0.3, - 'machine_learning': 0.25, - 'data_science': 0.2, - 'automation': 0.15, - 'innovation': 0.1 - }, - 'competitive_landscape': { - 'market_leader': 'competitor1.com', - 'content_leader': 'competitor2.com', - 'quality_leader': 'competitor3.com' - } - } - - try: - result = await ai_manager.generate_content_gap_analysis(analysis_data) - print(f"✅ Content gap analysis completed") - print(f" - Strategic insights: {len(result.get('strategic_insights', []))}") - print(f" - Content recommendations: {len(result.get('content_recommendations', []))}") - except Exception as e: - print(f"❌ Content gap analysis failed: {str(e)}") - return False - - # Test 2: Market Position Analysis - print("\n🏢 Test 2: Market Position Analysis") - market_data = { - 'industry': 'technology', - 'competitors': [ - { - 'url': 'competitor1.com', - 'content_score': 8.5, - 'quality_score': 9.0, - 'frequency': 'high' - }, - { - 'url': 'competitor2.com', - 'content_score': 7.8, - 'quality_score': 8.2, - 'frequency': 'medium' - } - ], - 'market_size': 'Large', - 'growth_rate': '15%', - 'key_trends': ['AI adoption', 'Cloud migration', 'Digital transformation'] - } - - try: - result = await ai_manager.generate_market_position_analysis(market_data) - print(f"✅ Market position analysis completed") - print(f" - Market leader: {result.get('market_leader', 'N/A')}") - print(f" - Market gaps: {len(result.get('market_gaps', []))}") - print(f" - Opportunities: {len(result.get('opportunities', []))}") - print(f" - Strategic recommendations: {len(result.get('strategic_recommendations', []))}") - except Exception as e: - print(f"❌ Market position analysis failed: {str(e)}") - return False - - # Test 3: Keyword Analysis - print("\n🔍 Test 3: Keyword Analysis") - keyword_data = { - 'industry': 'technology', - 'target_keywords': ['artificial intelligence', 'machine learning', 'data science'], - 'search_volume_data': { - 'artificial intelligence': 50000, - 'machine learning': 35000, - 'data science': 25000 - }, - 'competition_analysis': { - 'artificial intelligence': 'high', - 'machine learning': 'medium', - 'data science': 'low' - }, - 'trend_analysis': { - 'artificial intelligence': 'rising', - 'machine learning': 'stable', - 'data science': 'rising' - } - } - - try: - result = await ai_manager.generate_keyword_analysis(keyword_data) - print(f"✅ Keyword analysis completed") - print(f" - Keyword opportunities: {len(result.get('keyword_opportunities', []))}") - except Exception as e: - print(f"❌ Keyword analysis failed: {str(e)}") - return False - - # Test 4: Performance Metrics - print("\n📈 Test 4: Performance Metrics") - try: - performance_metrics = ai_manager.get_performance_metrics() - print(f"✅ Performance metrics retrieved") - print(f" - Total calls: {performance_metrics.get('total_calls', 0)}") - print(f" - Success rate: {performance_metrics.get('success_rate', 0):.1f}%") - print(f" - Average response time: {performance_metrics.get('average_response_time', 0):.2f}s") - print(f" - Service breakdown: {len(performance_metrics.get('service_breakdown', {}))} services") - except Exception as e: - print(f"❌ Performance metrics failed: {str(e)}") - return False - - # Test 5: Health Check - print("\n🏥 Test 5: Health Check") - try: - health_status = await ai_manager.health_check() - print(f"✅ Health check completed") - print(f" - Service status: {health_status.get('status')}") - print(f" - Prompts loaded: {health_status.get('prompts_loaded')}") - print(f" - Schemas loaded: {health_status.get('schemas_loaded')}") - print(f" - AI integration: {health_status.get('capabilities', {}).get('ai_integration')}") - print(f" - Configuration: {len(health_status.get('configuration', {}))} settings") - except Exception as e: - print(f"❌ Health check failed: {str(e)}") - return False - - return True - -async def test_ai_engine_integration(): - """Test the AI Engine Service integration with AI Service Manager.""" - - print("\n🤖 Testing AI Engine Service Integration...") - - # Initialize the AI Engine Service - ai_engine = AIEngineService() - - # Test 1: Content Gap Analysis with AI Service Manager - print("\n📊 Test 1: Content Gap Analysis with AI Service Manager") - analysis_summary = { - 'target_url': 'example.com', - 'industry': 'technology', - 'serp_opportunities': 25, - 'expanded_keywords_count': 150, - 'competitors_analyzed': 5, - 'dominant_themes': { - 'artificial_intelligence': 0.3, - 'machine_learning': 0.25, - 'data_science': 0.2 - } - } - - try: - result = await ai_engine.analyze_content_gaps(analysis_summary) - print(f"✅ Content gap analysis with AI Service Manager completed") - print(f" - Strategic insights: {len(result.get('strategic_insights', []))}") - print(f" - Content recommendations: {len(result.get('content_recommendations', []))}") - except Exception as e: - print(f"❌ Content gap analysis failed: {str(e)}") - return False - - # Test 2: Market Position Analysis with AI Service Manager - print("\n🏢 Test 2: Market Position Analysis with AI Service Manager") - market_data = { - 'industry': 'technology', - 'competitors': [ - { - 'url': 'competitor1.com', - 'content_score': 8.5, - 'quality_score': 9.0 - }, - { - 'url': 'competitor2.com', - 'content_score': 7.8, - 'quality_score': 8.2 - } - ] - } - - try: - result = await ai_engine.analyze_market_position(market_data) - print(f"✅ Market position analysis with AI Service Manager completed") - print(f" - Market leader: {result.get('market_leader', 'N/A')}") - print(f" - Market gaps: {len(result.get('market_gaps', []))}") - print(f" - Strategic recommendations: {len(result.get('strategic_recommendations', []))}") - except Exception as e: - print(f"❌ Market position analysis failed: {str(e)}") - return False - - return True - -async def test_performance_monitoring(): - """Test the performance monitoring functionality.""" - - print("\n📊 Testing Performance Monitoring...") - - # Initialize the AI Service Manager - ai_manager = AIServiceManager() - - # Make multiple AI calls to generate performance data - print("\n🔄 Making multiple AI calls to generate performance data...") - - test_data = { - 'target_url': 'test.com', - 'industry': 'technology', - 'serp_opportunities': 10, - 'expanded_keywords_count': 50, - 'competitors_analyzed': 3, - 'dominant_themes': {'test': 1.0}, - 'competitive_landscape': {'test': 'test'} - } - - # Make several calls to generate metrics - for i in range(3): - try: - await ai_manager.generate_content_gap_analysis(test_data) - print(f" - Call {i+1} completed") - except Exception as e: - print(f" - Call {i+1} failed: {str(e)}") - - # Test performance metrics - print("\n📈 Testing Performance Metrics...") - try: - metrics = ai_manager.get_performance_metrics() - print(f"✅ Performance metrics analysis:") - print(f" - Total calls: {metrics.get('total_calls', 0)}") - print(f" - Success rate: {metrics.get('success_rate', 0):.1f}%") - print(f" - Average response time: {metrics.get('average_response_time', 0):.2f}s") - - # Service breakdown - service_breakdown = metrics.get('service_breakdown', {}) - print(f" - Service breakdown:") - for service, data in service_breakdown.items(): - print(f" * {service}: {data.get('total_calls', 0)} calls, {data.get('success_rate', 0):.1f}% success") - - except Exception as e: - print(f"❌ Performance metrics failed: {str(e)}") - return False - - return True - -async def test_configuration_management(): - """Test the configuration management functionality.""" - - print("\n⚙️ Testing Configuration Management...") - - # Initialize the AI Service Manager - ai_manager = AIServiceManager() - - # Test configuration access - try: - config = ai_manager.config - print(f"✅ Configuration retrieved:") - print(f" - Max retries: {config.get('max_retries')}") - print(f" - Timeout seconds: {config.get('timeout_seconds')}") - print(f" - Temperature: {config.get('temperature')}") - print(f" - Max tokens: {config.get('max_tokens')}") - print(f" - Enable caching: {config.get('enable_caching')}") - print(f" - Performance monitoring: {config.get('performance_monitoring')}") - print(f" - Fallback enabled: {config.get('fallback_enabled')}") - except Exception as e: - print(f"❌ Configuration test failed: {str(e)}") - return False - - return True - -async def main(): - """Main test function.""" - print("🚀 Starting Phase 4 AI Service Integration Tests...") - print("=" * 70) - - # Test 1: AI Service Manager - ai_manager_success = await test_ai_service_manager() - - # Test 2: AI Engine Integration - ai_engine_success = await test_ai_engine_integration() - - # Test 3: Performance Monitoring - performance_success = await test_performance_monitoring() - - # Test 4: Configuration Management - config_success = await test_configuration_management() - - print("\n" + "=" * 70) - print("📊 Test Results Summary:") - print(f"AI Service Manager: {'✅ PASSED' if ai_manager_success else '❌ FAILED'}") - print(f"AI Engine Integration: {'✅ PASSED' if ai_engine_success else '❌ FAILED'}") - print(f"Performance Monitoring: {'✅ PASSED' if performance_success else '❌ FAILED'}") - print(f"Configuration Management: {'✅ PASSED' if config_success else '❌ FAILED'}") - - if ai_manager_success and ai_engine_success and performance_success and config_success: - print("\n🎉 All Phase 4 tests passed! AI Service Integration is working correctly.") - print("\n✅ Phase 4 Achievements:") - print(" - Centralized AI service management implemented") - print(" - Performance monitoring with metrics tracking") - print(" - Service breakdown by AI type") - print(" - Configuration management with timeout settings") - print(" - Health monitoring and error handling") - print(" - All services integrated with AI Service Manager") - return 0 - else: - print("\n⚠️ Some Phase 4 tests failed. Please check the AI configuration.") - return 1 - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_schema_fixes.py b/docs/alwrity_test_scripts/test_schema_fixes.py deleted file mode 100644 index 9f399b8f..00000000 --- a/docs/alwrity_test_scripts/test_schema_fixes.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the schema validation fixes. -""" - -import os -import sys -from pathlib import Path - -# Add the backend directory to the path -sys.path.append(str(Path(__file__).parent / 'backend')) - -from services.llm_providers.gemini_provider import _clean_schema_for_gemini, _validate_and_fix_schema - -def test_empty_object_fix(): - """Test fixing empty object properties.""" - try: - print("🧪 Testing empty object property fix...") - - # Test schema with empty object properties (like the one causing errors) - test_schema = { - "type": "object", - "properties": { - "trends": { - "type": "object", - "properties": {} # This causes the error - }, - "analysis": { - "type": "object", - "properties": { - "score": {"type": "number"} - } - } - } - } - - # Clean the schema - cleaned_schema = _clean_schema_for_gemini(test_schema) - fixed_schema = _validate_and_fix_schema(cleaned_schema) - - # Check that empty object properties are converted to strings - assert fixed_schema["properties"]["trends"]["type"] == "string" - assert fixed_schema["properties"]["analysis"]["type"] == "object" - assert "score" in fixed_schema["properties"]["analysis"]["properties"] - - print("✅ Empty object property fix: PASSED") - print(f" - Trends type: {fixed_schema['properties']['trends']['type']}") - print(f" - Analysis type: {fixed_schema['properties']['analysis']['type']}") - return True - - except Exception as e: - print(f"❌ Empty object property fix: FAILED (Error: {e})") - return False - -def test_complex_schema_validation(): - """Test complex schema validation.""" - try: - print("🧪 Testing complex schema validation...") - - # Test schema with nested empty objects - test_schema = { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "metrics": { - "type": "object", - "properties": {} # Empty properties - }, - "summary": { - "type": "object", - "properties": { - "total": {"type": "integer"}, - "average": {"type": "number"} - } - } - } - } - } - } - - # Clean and validate the schema - cleaned_schema = _clean_schema_for_gemini(test_schema) - fixed_schema = _validate_and_fix_schema(cleaned_schema) - - # Check that empty nested objects are fixed - assert fixed_schema["properties"]["data"]["properties"]["metrics"]["type"] == "string" - assert fixed_schema["properties"]["data"]["properties"]["summary"]["type"] == "object" - assert "total" in fixed_schema["properties"]["data"]["properties"]["summary"]["properties"] - - print("✅ Complex schema validation: PASSED") - return True - - except Exception as e: - print(f"❌ Complex schema validation: FAILED (Error: {e})") - return False - -def test_unsupported_properties_removal(): - """Test removal of unsupported properties.""" - try: - print("🧪 Testing unsupported properties removal...") - - # Test schema with unsupported properties - test_schema = { - "type": "object", - "properties": { - "title": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "pattern": "^[a-zA-Z0-9 ]+$" - }, - "content": { - "type": "string", - "format": "text" - } - }, - "additionalProperties": False - } - - # Clean the schema - cleaned_schema = _clean_schema_for_gemini(test_schema) - - # Check that unsupported properties are removed - assert "additionalProperties" not in cleaned_schema - assert "minLength" not in cleaned_schema["properties"]["title"] - assert "maxLength" not in cleaned_schema["properties"]["title"] - assert "pattern" not in cleaned_schema["properties"]["title"] - assert "format" not in cleaned_schema["properties"]["content"] - - # Check that supported properties remain - assert "type" in cleaned_schema - assert "properties" in cleaned_schema - - print("✅ Unsupported properties removal: PASSED") - return True - - except Exception as e: - print(f"❌ Unsupported properties removal: FAILED (Error: {e})") - return False - -def main(): - """Run all schema validation tests.""" - print("🧪 Testing Schema Validation Fixes") - print("=" * 50) - - tests = [ - test_empty_object_fix, - test_complex_schema_validation, - test_unsupported_properties_removal - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All schema validation tests passed!") - return 0 - else: - print("⚠️ Some schema validation tests failed.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_service_integration.py b/docs/alwrity_test_scripts/test_service_integration.py deleted file mode 100644 index 43c6f9ae..00000000 --- a/docs/alwrity_test_scripts/test_service_integration.py +++ /dev/null @@ -1,435 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for Phase 3: Service Integration -Verifies that content planning service integrates with database and AI services correctly. -""" - -import asyncio -import sys -import os -from pathlib import Path -from datetime import datetime, timedelta - -# Add the backend directory to the Python path -sys.path.append(str(Path(__file__).parent / "backend")) - -from services.database import init_database, get_db_session -from services.content_planning_service import ContentPlanningService -from services.content_planning_db import ContentPlanningDBService -from loguru import logger - -async def test_database_initialization(): - """Test database initialization.""" - - print("🗄️ Testing Database Initialization...") - - try: - # Initialize database - init_database() - print("✅ Database initialized successfully") - - # Test database session - db_session = get_db_session() - if db_session: - print("✅ Database session created successfully") - db_session.close() - return True - else: - print("❌ Failed to create database session") - return False - - except Exception as e: - print(f"❌ Database initialization failed: {str(e)}") - return False - -async def test_service_initialization(): - """Test content planning service initialization.""" - - print("\n🔧 Testing Service Initialization...") - - try: - # Test service initialization with database session - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - service = ContentPlanningService(db_session) - - if service.db_service: - print("✅ Content planning service initialized with database service") - else: - print("❌ Database service not initialized") - return False - - if service.ai_manager: - print("✅ AI service manager initialized") - else: - print("❌ AI service manager not initialized") - return False - - db_session.close() - return True - - except Exception as e: - print(f"❌ Service initialization failed: {str(e)}") - return False - -async def test_content_strategy_with_ai(): - """Test content strategy creation with AI integration.""" - - print("\n📋 Testing Content Strategy with AI...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - service = ContentPlanningService(db_session) - - # Test 1: Create content strategy with AI - print("\n📝 Test 1: Create Content Strategy with AI") - strategy_data = { - 'user_id': 1, - 'name': 'AI-Enhanced Content Strategy', - 'industry': 'technology', - 'target_audience': { - 'demographics': '25-45 years old', - 'interests': ['technology', 'innovation', 'AI'] - }, - 'content_preferences': { - 'formats': ['blog_posts', 'videos', 'social_media'], - 'frequency': 'weekly', - 'platforms': ['website', 'linkedin', 'youtube'] - } - } - - try: - strategy = await service.create_content_strategy_with_ai( - user_id=strategy_data['user_id'], - strategy_data=strategy_data - ) - - if strategy: - print(f"✅ Content strategy created with AI: {strategy.id}") - strategy_id = strategy.id - else: - print("❌ Failed to create content strategy with AI") - return False - except Exception as e: - print(f"❌ Error creating content strategy with AI: {str(e)}") - return False - - # Test 2: Get content strategy from database - print("\n📖 Test 2: Get Content Strategy from Database") - try: - retrieved_strategy = await service.get_content_strategy( - user_id=strategy_data['user_id'], - strategy_id=strategy_id - ) - - if retrieved_strategy: - print(f"✅ Content strategy retrieved: {retrieved_strategy.name}") - print(f" - Industry: {retrieved_strategy.industry}") - print(f" - AI Recommendations: {len(retrieved_strategy.ai_recommendations) if retrieved_strategy.ai_recommendations else 0} items") - else: - print("❌ Failed to retrieve content strategy") - return False - except Exception as e: - print(f"❌ Error retrieving content strategy: {str(e)}") - return False - - # Test 3: Analyze content strategy with AI - print("\n🤖 Test 3: Analyze Content Strategy with AI") - try: - ai_strategy = await service.analyze_content_strategy_with_ai( - industry='artificial_intelligence', - target_audience={ - 'demographics': '30-50 years old', - 'interests': ['AI', 'machine learning', 'data science'] - }, - business_goals=['thought leadership', 'lead generation'], - content_preferences={ - 'formats': ['blog_posts', 'webinars', 'case_studies'], - 'frequency': 'bi-weekly' - }, - user_id=2 - ) - - if ai_strategy: - print(f"✅ AI-analyzed strategy created: {ai_strategy.id}") - print(f" - Name: {ai_strategy.name}") - print(f" - Industry: {ai_strategy.industry}") - else: - print("❌ Failed to create AI-analyzed strategy") - return False - except Exception as e: - print(f"❌ Error analyzing content strategy with AI: {str(e)}") - return False - - db_session.close() - return True - -async def test_calendar_events_with_ai(): - """Test calendar event creation with AI integration.""" - - print("\n📅 Testing Calendar Events with AI...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - service = ContentPlanningService(db_session) - - # First create a strategy for the events - strategy_data = { - 'user_id': 1, - 'name': 'Test Strategy for Events', - 'industry': 'technology' - } - - try: - strategy = await service.create_content_strategy_with_ai( - user_id=strategy_data['user_id'], - strategy_data=strategy_data - ) - - if not strategy: - print("❌ Failed to create test strategy") - return False - except Exception as e: - print(f"❌ Error creating test strategy: {str(e)}") - return False - - # Test 1: Create calendar event with AI - print("\n📝 Test 1: Create Calendar Event with AI") - event_data = { - 'strategy_id': strategy.id, - 'title': 'AI Marketing Trends 2024', - 'description': 'Comprehensive analysis of AI marketing trends and strategies', - 'content_type': 'blog_post', - 'platform': 'website', - 'scheduled_date': datetime.utcnow() + timedelta(days=7) - } - - try: - event = await service.create_calendar_event_with_ai(event_data) - - if event: - print(f"✅ Calendar event created with AI: {event.id}") - print(f" - Title: {event.title}") - print(f" - Platform: {event.platform}") - print(f" - AI Recommendations: {len(event.ai_recommendations) if event.ai_recommendations else 0} items") - event_id = event.id - else: - print("❌ Failed to create calendar event with AI") - return False - except Exception as e: - print(f"❌ Error creating calendar event with AI: {str(e)}") - return False - - # Test 2: Get calendar events from database - print("\n📖 Test 2: Get Calendar Events from Database") - try: - events = await service.get_calendar_events(strategy_id=strategy.id) - - if events: - print(f"✅ Retrieved {len(events)} calendar events") - for event in events: - print(f" - {event.title} ({event.content_type})") - else: - print("❌ No calendar events found") - return False - except Exception as e: - print(f"❌ Error getting calendar events: {str(e)}") - return False - - # Test 3: Track content performance with AI - print("\n📊 Test 3: Track Content Performance with AI") - try: - performance = await service.track_content_performance_with_ai(event_id) - - if performance: - print(f"✅ Performance tracking completed: {performance['analytics_id']}") - print(f" - Performance Score: {performance['performance_score']}") - print(f" - Engagement Prediction: {performance['engagement_prediction']}") - else: - print("❌ Failed to track content performance") - return False - except Exception as e: - print(f"❌ Error tracking content performance: {str(e)}") - return False - - db_session.close() - return True - -async def test_content_gap_analysis_with_ai(): - """Test content gap analysis with AI integration.""" - - print("\n🔍 Testing Content Gap Analysis with AI...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - service = ContentPlanningService(db_session) - - # Test 1: Analyze content gaps with AI - print("\n📝 Test 1: Analyze Content Gaps with AI") - try: - analysis = await service.analyze_content_gaps_with_ai( - website_url='https://example.com', - competitor_urls=['https://competitor1.com', 'https://competitor2.com'], - user_id=1, - target_keywords=['AI marketing', 'digital transformation', 'content strategy'] - ) - - if analysis: - print(f"✅ Content gap analysis completed: {analysis['analysis_id']}") - print(f" - Stored at: {analysis['stored_at']}") - print(f" - Results: {len(analysis['results']) if analysis['results'] else 0} items") - else: - print("❌ Failed to analyze content gaps with AI") - return False - except Exception as e: - print(f"❌ Error analyzing content gaps with AI: {str(e)}") - return False - - # Test 2: Generate content recommendations with AI - print("\n💡 Test 2: Generate Content Recommendations with AI") - try: - # First create a strategy for recommendations - strategy_data = { - 'user_id': 1, - 'name': 'Recommendation Test Strategy', - 'industry': 'technology' - } - - strategy = await service.create_content_strategy_with_ai( - user_id=strategy_data['user_id'], - strategy_data=strategy_data - ) - - if strategy: - recommendations = await service.generate_content_recommendations_with_ai(strategy.id) - - if recommendations: - print(f"✅ Generated {len(recommendations)} content recommendations") - for i, rec in enumerate(recommendations[:3], 1): - print(f" {i}. {rec.get('title', 'Untitled')} ({rec.get('type', 'content')})") - else: - print("❌ No content recommendations generated") - return False - else: - print("❌ Failed to create strategy for recommendations") - return False - except Exception as e: - print(f"❌ Error generating content recommendations: {str(e)}") - return False - - db_session.close() - return True - -async def test_ai_analytics_storage(): - """Test AI analytics storage functionality.""" - - print("\n📊 Testing AI Analytics Storage...") - - db_session = get_db_session() - if not db_session: - print("❌ No database session available") - return False - - service = ContentPlanningService(db_session) - - # Test 1: Create strategy and verify AI analytics storage - print("\n📝 Test 1: Verify AI Analytics Storage") - try: - strategy_data = { - 'user_id': 1, - 'name': 'Analytics Test Strategy', - 'industry': 'technology', - 'target_audience': {'demographics': '25-45 years old'}, - 'content_preferences': {'formats': ['blog_posts']} - } - - strategy = await service.create_content_strategy_with_ai( - user_id=strategy_data['user_id'], - strategy_data=strategy_data - ) - - if strategy: - print(f"✅ Strategy created with AI analytics: {strategy.id}") - - # Check if AI analytics were stored - db_service = service._get_db_service() - analytics = await db_service.get_strategy_analytics(strategy.id) - - if analytics: - print(f"✅ AI analytics stored: {len(analytics)} records") - for analytic in analytics: - print(f" - Type: {analytic.analysis_type}") - print(f" - Performance Score: {analytic.performance_score}") - else: - print("⚠️ No AI analytics found (this might be expected)") - else: - print("❌ Failed to create strategy for analytics test") - return False - except Exception as e: - print(f"❌ Error testing AI analytics storage: {str(e)}") - return False - - db_session.close() - return True - -async def main(): - """Main test function.""" - print("🚀 Starting Phase 3: Service Integration Tests...") - print("=" * 60) - - # Test 1: Database Initialization - db_init_success = await test_database_initialization() - - # Test 2: Service Initialization - service_init_success = await test_service_initialization() - - # Test 3: Content Strategy with AI - strategy_success = await test_content_strategy_with_ai() - - # Test 4: Calendar Events with AI - events_success = await test_calendar_events_with_ai() - - # Test 5: Content Gap Analysis with AI - analysis_success = await test_content_gap_analysis_with_ai() - - # Test 6: AI Analytics Storage - analytics_success = await test_ai_analytics_storage() - - print("\n" + "=" * 60) - print("📊 Test Results Summary:") - print(f"Database Initialization: {'✅ PASSED' if db_init_success else '❌ FAILED'}") - print(f"Service Initialization: {'✅ PASSED' if service_init_success else '❌ FAILED'}") - print(f"Content Strategy with AI: {'✅ PASSED' if strategy_success else '❌ FAILED'}") - print(f"Calendar Events with AI: {'✅ PASSED' if events_success else '❌ FAILED'}") - print(f"Content Gap Analysis with AI: {'✅ PASSED' if analysis_success else '❌ FAILED'}") - print(f"AI Analytics Storage: {'✅ PASSED' if analytics_success else '❌ FAILED'}") - - if db_init_success and service_init_success and strategy_success and events_success and analysis_success and analytics_success: - print("\n🎉 All Phase 3 service integration tests passed!") - print("\n✅ Phase 3 Service Integration Achievements:") - print(" - Content planning service integrated with database operations") - print(" - AI services integrated with database storage") - print(" - Data persistence for AI results implemented") - print(" - Service database integration tested and functional") - print(" - AI analytics tracking and storage working") - print(" - Comprehensive error handling and logging") - return 0 - else: - print("\n⚠️ Some Phase 3 service integration tests failed. Please check the service configuration.") - return 1 - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) \ No newline at end of file diff --git a/docs/alwrity_test_scripts/test_structured_output.py b/docs/alwrity_test_scripts/test_structured_output.py deleted file mode 100644 index 892e9245..00000000 --- a/docs/alwrity_test_scripts/test_structured_output.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify the structured output functionality. -""" - -import os -import sys -from pathlib import Path - -# Add the backend directory to the path -sys.path.append(str(Path(__file__).parent / 'backend')) - -from services.llm_providers.gemini_provider import gemini_structured_json_response, _clean_schema_for_gemini - -def test_schema_cleaning(): - """Test the schema cleaning function.""" - try: - print("🧪 Testing schema cleaning...") - - # Test schema with unsupported properties - test_schema = { - "type": "object", - "properties": { - "title": {"type": "string", "minLength": 1, "maxLength": 100}, - "description": {"type": "string", "pattern": "^[a-zA-Z0-9 ]+$"}, - "tags": {"type": "array", "items": {"type": "string"}} - }, - "additionalProperties": False, - "required": ["title"] - } - - cleaned_schema = _clean_schema_for_gemini(test_schema) - - # Check that unsupported properties are removed - assert "additionalProperties" not in cleaned_schema - assert "minLength" not in cleaned_schema["properties"]["title"] - assert "maxLength" not in cleaned_schema["properties"]["title"] - assert "pattern" not in cleaned_schema["properties"]["description"] - - # Check that supported properties remain - assert "type" in cleaned_schema - assert "properties" in cleaned_schema - assert "required" in cleaned_schema - - print("✅ Schema cleaning: PASSED") - print(f" - Original schema keys: {list(test_schema.keys())}") - print(f" - Cleaned schema keys: {list(cleaned_schema.keys())}") - return True - - except Exception as e: - print(f"❌ Schema cleaning: FAILED (Error: {e})") - return False - -def test_structured_output(): - """Test structured JSON output.""" - try: - print("🧪 Testing structured JSON output...") - - # Simple schema for testing - test_schema = { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"}, - "city": {"type": "string"} - }, - "required": ["name", "age"] - } - - # Test prompt - prompt = "Create a person profile with name John, age 30, and city New York." - - response = gemini_structured_json_response( - prompt=prompt, - schema=test_schema, - temperature=0.1, - max_tokens=100 - ) - - if isinstance(response, dict) and "name" in response and "age" in response: - print("✅ Structured JSON output: PASSED") - print(f" - Response: {response}") - return True - else: - print(f"❌ Structured JSON output: FAILED (Response: {response})") - return False - - except Exception as e: - print(f"❌ Structured JSON output: FAILED (Error: {e})") - return False - -def main(): - """Run all structured output tests.""" - print("🧪 Testing Structured Output Functionality") - print("=" * 50) - - tests = [ - test_schema_cleaning, - test_structured_output - ] - - passed = 0 - total = len(tests) - - for test in tests: - if test(): - passed += 1 - print() - - print("=" * 50) - print(f"📊 Test Results: {passed}/{total} tests passed") - - if passed == total: - print("🎉 All structured output tests passed!") - return 0 - else: - print("⚠️ Some structured output tests failed.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/docs/debug_wix_oauth.py b/docs/debug_wix_oauth.py deleted file mode 100644 index 8e12dcc8..00000000 --- a/docs/debug_wix_oauth.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -""" -Debug script for Wix OAuth issues -""" - -import requests -import json - -def test_oauth_url(): - """Test the OAuth URL and provide debugging information""" - - print("🔍 Debugging Wix OAuth Configuration") - print("=" * 50) - - # Get the OAuth URL from our backend - try: - response = requests.get("http://localhost:8000/api/wix/test/auth/url") - if response.status_code == 200: - data = response.json() - oauth_url = data['url'] - print(f"✅ OAuth URL generated successfully") - print(f"📋 URL: {oauth_url}") - print() - else: - print(f"❌ Failed to get OAuth URL: {response.status_code}") - return - except Exception as e: - print(f"❌ Error getting OAuth URL: {e}") - return - - # Test the OAuth URL with a HEAD request to see if it's accessible - print("🌐 Testing OAuth URL accessibility...") - try: - head_response = requests.head(oauth_url, timeout=10) - print(f"📊 HEAD Response Status: {head_response.status_code}") - print(f"📋 Response Headers: {dict(head_response.headers)}") - print() - except Exception as e: - print(f"❌ Error testing OAuth URL: {e}") - print() - - # Provide debugging steps - print("🔧 Debugging Steps:") - print("1. Copy this URL and test it directly in your browser:") - print(f" {oauth_url}") - print() - print("2. Check your Wix OAuth app configuration:") - print(" - Go to Wix Dashboard → Settings → Development & integrations → Headless Settings") - print(" - Find your OAuth app with Client ID: 9faf59b5-2984-4d0d-ac75-47c32ab9f1fb") - print(" - Verify these URLs are configured:") - print(" • Allow Authorization Redirect URIs: http://localhost:3000/wix/callback") - print(" • Allow Redirect Domains: localhost:3000") - print(" • Login URL: http://localhost:3000") - print() - print("3. Common issues:") - print(" - App not published/activated") - print(" - URLs not saved properly") - print(" - App in development mode instead of production") - print(" - Missing required permissions") - print() - print("4. Alternative test:") - print(" - Try creating a completely new OAuth app") - print(" - Configure URLs immediately during creation") - print(" - Test with the new Client ID") - -if __name__ == "__main__": - test_oauth_url() diff --git a/frontend/src/components/StoryWriter/Phases/StoryExport.tsx b/frontend/src/components/StoryWriter/Phases/StoryExport.tsx index e916054f..ab4e4f94 100644 --- a/frontend/src/components/StoryWriter/Phases/StoryExport.tsx +++ b/frontend/src/components/StoryWriter/Phases/StoryExport.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Box, Paper, @@ -9,11 +9,16 @@ import { Divider, CircularProgress, LinearProgress, + Tooltip, } from '@mui/material'; import VideoLibraryIcon from '@mui/icons-material/VideoLibrary'; import DownloadIcon from '@mui/icons-material/Download'; import { useStoryWriterState } from '../../../hooks/useStoryWriterState'; import { storyWriterApi } from '../../../services/storyWriterApi'; +import { fetchMediaBlobUrl } from '../../../utils/fetchMediaBlobUrl'; +import { triggerSubscriptionError } from '../../../api/client'; +import SmartDisplayIcon from '@mui/icons-material/SmartDisplay'; +import SceneVideoApproval from '../components/SceneVideoApproval'; interface StoryExportProps { state: ReturnType; @@ -22,8 +27,28 @@ interface StoryExportProps { const StoryExport: React.FC = ({ state }) => { const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); const [videoProgress, setVideoProgress] = useState(0); + const [videoMessage, setVideoMessage] = useState(''); + const [videoBlobUrl, setVideoBlobUrl] = useState(null); + const [isGeneratingHdVideo, setIsGeneratingHdVideo] = useState(false); + const [hdVideoProgress, setHdVideoProgress] = useState(0); + const [hdVideoMessage, setHdVideoMessage] = useState(''); + const [hdVideoPrompts, setHdVideoPrompts] = useState>(new Map()); // Store prompts by scene number + const videoRef = useRef(null); const [error, setError] = useState(null); + // Scene-by-scene approval state + const [approvalModal, setApprovalModal] = useState<{ + open: boolean; + sceneNumber: number; + sceneTitle: string; + videoUrl: string; + promptUsed: string; + } | null>(null); + const [regeneratingScene, setRegeneratingScene] = useState(null); + + // Keep track of the processing function for continuation + const processSceneRef = useRef<((sceneIndex: number) => Promise) | null>(null); + const handleCopyToClipboard = () => { if (state.storyContent) { navigator.clipboard.writeText(state.storyContent); @@ -91,8 +116,8 @@ const StoryExport: React.FC = ({ state }) => { throw new Error('Number of images and audio files must match number of scenes'); } - // Generate video - const response = await storyWriterApi.generateStoryVideo({ + // Start async video generation + const startRes = await storyWriterApi.generateStoryVideoAsync({ scenes: scenes, image_urls: imageUrls, audio_urls: audioUrls, @@ -101,12 +126,42 @@ const StoryExport: React.FC = ({ state }) => { transition_duration: state.videoTransitionDuration, }); - if (response.success && response.video) { - state.setStoryVideo(response.video.video_url); + // Poll task status + const taskId = startRes.task_id; + setVideoMessage(startRes.message || 'Starting video generation...'); + + let done = false; + while (!done) { + await new Promise((r) => setTimeout(r, 1200)); + const status = await storyWriterApi.getTaskStatus(taskId); + setVideoProgress(Math.round(status.progress ?? 0)); + if (status.message) setVideoMessage(status.message); + if (status.status === 'completed') { + done = true; + const result = await storyWriterApi.getTaskResult(taskId); + // result.video exists under result.video + // @ts-ignore – result typing is StoryFullGenerationResponse; our async returns a dict + const video = result.video || (result as any).video; + const videoUrl = video?.video_url; + if (!videoUrl) throw new Error('Video URL missing in result'); + state.setStoryVideo(videoUrl); + // fetch blob for authenticated preview + const blobUrl = await fetchMediaBlobUrl(videoUrl); + setVideoBlobUrl(blobUrl); + setVideoProgress(100); + setVideoMessage('Video generation complete'); state.setError(null); - setVideoProgress(100); - } else { - throw new Error('Failed to generate video'); + // Autoplay and fullscreen + setTimeout(() => { + const v = videoRef.current; + if (v) { + try { v.play().catch(() => {}); } catch {} + try { if (v.requestFullscreen) v.requestFullscreen(); } catch {} + } + }, 300); + } else if (status.status === 'failed') { + throw new Error(status.error || 'Video generation failed'); + } } } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate video'; @@ -117,19 +172,260 @@ const StoryExport: React.FC = ({ state }) => { } }; - const handleDownloadVideo = () => { + const handleDownloadVideo = async () => { if (state.storyVideo) { - const videoUrl = storyWriterApi.getVideoUrl(state.storyVideo); + const blobUrl = await fetchMediaBlobUrl(state.storyVideo); const a = document.createElement('a'); - a.href = videoUrl; + a.href = blobUrl; a.download = `story-video-${Date.now()}.mp4`; document.body.appendChild(a); a.click(); document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + } + }; + + const handleGenerateHdVideo = async () => { + if (!state.outlineScenes || state.outlineScenes.length === 0) { + setError('Please generate a structured outline first'); + return; + } + + const scenes = state.outlineScenes; + const totalScenes = scenes.length; + + // Initialize HD videos map if not exists + if (!state.sceneHdVideos) { + state.setSceneHdVideos(new Map()); + } + + // Clear previous prompts + setHdVideoPrompts(new Map()); + + state.setHdVideoGenerationStatus('generating'); + setIsGeneratingHdVideo(true); + setError(null); + + // Build story context for prompt enhancement + const storyContext = { + persona: state.persona, + story_setting: state.storySetting, + characters: state.characters, + plot_elements: state.plotElements, + writing_style: state.writingStyle, + story_tone: state.storyTone, + narrative_pov: state.narrativePOV, + audience_age_group: state.audienceAgeGroup, + content_rating: state.contentRating, + premise: state.premise || '', + outline: state.outline || '', + story_content: state.storyContent || '', + }; + + // Iterate through scenes one at a time + const processScene = async (sceneIndex: number): Promise => { + if (sceneIndex >= totalScenes) { + // All scenes processed + state.setHdVideoGenerationStatus('completed'); + setIsGeneratingHdVideo(false); + setHdVideoProgress(100); + setHdVideoMessage(`All ${totalScenes} scenes processed`); + + // Show completion message + const approvedCount = state.sceneHdVideos?.size || 0; + setHdVideoMessage(`HD video generation complete! ${approvedCount} of ${totalScenes} scenes approved.`); + return; + } + + const scene = scenes[sceneIndex]; + const sceneNumber = scene.scene_number || sceneIndex + 1; + state.setCurrentHdSceneIndex(sceneIndex); + + setHdVideoProgress(Math.round((sceneIndex / totalScenes) * 100)); + setHdVideoMessage(`Generating HD video for Scene ${sceneNumber}...`); + + try { + // Generate video for current scene + const result = await storyWriterApi.generateHdVideoScene({ + scene_number: sceneNumber, + scene_data: scene, + story_context: storyContext, + all_scenes: scenes, + provider: 'huggingface', + model: 'tencent/HunyuanVideo', + num_frames: 50, + guidance_scale: 7.5, + }); + + // Store prompt for this scene + setHdVideoPrompts((prev) => { + const newPrompts = new Map(prev); + newPrompts.set(sceneNumber, result.prompt_used); + return newPrompts; + }); + + // Show approval modal + state.setHdVideoGenerationStatus('awaiting_approval'); + setApprovalModal({ + open: true, + sceneNumber: sceneNumber, + sceneTitle: scene.title || `Scene ${sceneNumber}`, + videoUrl: result.video_url, + promptUsed: result.prompt_used, + }); + + } catch (err: any) { + // Check if this is a subscription error (429/402) and trigger global subscription modal + const status = err?.response?.status; + if (status === 429 || status === 402) { + const handled = await triggerSubscriptionError(err); + if (handled) { + // Subscription modal is showing, stop processing scenes + setIsGeneratingHdVideo(false); + state.setHdVideoGenerationStatus('idle'); + return; + } + } + + const errorMessage = err.response?.data?.detail || err.message || `Failed to generate HD video for scene ${sceneNumber}`; + setError(errorMessage); + + // On subscription error, stop processing. On other errors, continue to next scene. + if (status !== 429 && status !== 402) { + await processScene(sceneIndex + 1); + } else { + setIsGeneratingHdVideo(false); + state.setHdVideoGenerationStatus('idle'); + } + } + }; + + // Store processScene function in ref for continuation + processSceneRef.current = processScene; + + // Start processing first scene + await processScene(0); + }; + + // Handle approval modal actions + const handleApprove = () => { + if (!approvalModal) return; + + const sceneNumber = approvalModal.sceneNumber; + const hdVideos = state.sceneHdVideos || new Map(); + hdVideos.set(sceneNumber, approvalModal.videoUrl); + state.setSceneHdVideos(new Map(hdVideos)); + + setApprovalModal(null); + + // Continue to next scene + const currentIndex = state.currentHdSceneIndex; + const scenes = state.outlineScenes || []; + if (currentIndex + 1 < scenes.length && processSceneRef.current) { + state.setHdVideoGenerationStatus('generating'); + processSceneRef.current(currentIndex + 1); + } else { + state.setHdVideoGenerationStatus('completed'); + setIsGeneratingHdVideo(false); + const approvedCount = state.sceneHdVideos?.size || 0; + setHdVideoMessage(`HD video generation complete! ${approvedCount} of ${scenes.length} scenes approved.`); + } + }; + + const handleReject = () => { + if (!approvalModal) return; + + // Skip scene and continue to next + setApprovalModal(null); + + const currentIndex = state.currentHdSceneIndex; + const scenes = state.outlineScenes || []; + if (currentIndex + 1 < scenes.length && processSceneRef.current) { + state.setHdVideoGenerationStatus('generating'); + processSceneRef.current(currentIndex + 1); + } else { + state.setHdVideoGenerationStatus('completed'); + setIsGeneratingHdVideo(false); + const approvedCount = state.sceneHdVideos?.size || 0; + setHdVideoMessage(`HD video generation complete! ${approvedCount} of ${scenes.length} scenes approved.`); + } + }; + + const handleRegenerate = async () => { + if (!approvalModal) return; + + const sceneNumber = approvalModal.sceneNumber; + const scenes = state.outlineScenes || []; + const sceneIndex = scenes.findIndex((s: any) => (s.scene_number || 0) === sceneNumber); + const scene = scenes[sceneIndex]; + + if (!scene) return; + + setRegeneratingScene(sceneNumber); + + try { + const storyContext = { + persona: state.persona, + story_setting: state.storySetting, + characters: state.characters, + plot_elements: state.plotElements, + writing_style: state.writingStyle, + story_tone: state.storyTone, + narrative_pov: state.narrativePOV, + audience_age_group: state.audienceAgeGroup, + content_rating: state.contentRating, + premise: state.premise || '', + outline: state.outline || '', + story_content: state.storyContent || '', + }; + + const result = await storyWriterApi.generateHdVideoScene({ + scene_number: sceneNumber, + scene_data: scene, + story_context: storyContext, + all_scenes: scenes, + provider: 'huggingface', + model: 'tencent/HunyuanVideo', + num_frames: 50, + guidance_scale: 7.5, + }); + + // Update prompt for this scene + setHdVideoPrompts((prev) => { + const newPrompts = new Map(prev); + newPrompts.set(sceneNumber, result.prompt_used); + return newPrompts; + }); + + // Update approval modal with new video + setApprovalModal({ + open: true, + sceneNumber: sceneNumber, + sceneTitle: scene.title || `Scene ${sceneNumber}`, + videoUrl: result.video_url, + promptUsed: result.prompt_used, + }); + } catch (err: any) { + // Check if this is a subscription error (429/402) and trigger global subscription modal + const status = err?.response?.status; + if (status === 429 || status === 402) { + const handled = await triggerSubscriptionError(err); + if (handled) { + // Subscription modal is showing, stop here + setRegeneratingScene(null); + return; + } + } + + const errorMessage = err.response?.data?.detail || err.message || 'Failed to regenerate video'; + setError(errorMessage); + } finally { + setRegeneratingScene(null); } }; return ( + <> = ({ state }) => { - Generating video... {videoProgress}% + {videoMessage || 'Generating video...'} {videoProgress}% )} @@ -297,8 +593,9 @@ const StoryExport: React.FC = ({ state }) => { {state.storyVideo && ( )} @@ -364,6 +762,29 @@ const StoryExport: React.FC = ({ state }) => { )} + + {/* Scene Video Approval Modal */} + {approvalModal && state.outlineScenes && ( + { + if (!isGeneratingHdVideo && !regeneratingScene) { + setApprovalModal(null); + state.setHdVideoGenerationStatus('paused'); + } + }} + /> + )} + ); }; diff --git a/frontend/src/components/StoryWriter/Phases/StoryOutline.tsx b/frontend/src/components/StoryWriter/Phases/StoryOutline.tsx index 2f4d981d..09038156 100644 --- a/frontend/src/components/StoryWriter/Phases/StoryOutline.tsx +++ b/frontend/src/components/StoryWriter/Phases/StoryOutline.tsx @@ -579,17 +579,17 @@ const StoryOutline: React.FC = ({ state, onNext }) => { onContinue={handleContinue} /> - ) : ( - state.setOutline(e.target.value)} - label="Story Outline" - sx={{ mb: 3 }} - /> - )} + ) : ( + state.setOutline(e.target.value)} + label="Story Outline" + sx={{ mb: 3 }} + /> + )} = ({ state, onNext }) => { setIsTitleModalOpen(false); }} /> - + ); }; diff --git a/frontend/src/components/StoryWriter/StoryWriter.tsx b/frontend/src/components/StoryWriter/StoryWriter.tsx index 3ca1be77..a6c1871d 100644 --- a/frontend/src/components/StoryWriter/StoryWriter.tsx +++ b/frontend/src/components/StoryWriter/StoryWriter.tsx @@ -142,7 +142,8 @@ export const StoryWriter: React.FC = () => { throw new Error('Number of images and audio files must match number of scenes'); } - const response = await storyWriterApi.generateStoryVideo({ + // Switch to async flow so UI can poll progress messages + const start = await storyWriterApi.generateStoryVideoAsync({ scenes: scenes, image_urls: imageUrls, audio_urls: audioUrls, @@ -151,9 +152,22 @@ export const StoryWriter: React.FC = () => { transition_duration: state.videoTransitionDuration, }); - if (response.success && response.video) { - state.setStoryVideo(response.video.video_url); - state.setError(null); + // Optional: set a lightweight spinner; export page shows detailed progress + let done = false; + while (!done) { + await new Promise((r) => setTimeout(r, 1200)); + const status = await storyWriterApi.getTaskStatus(start.task_id); + if (status.status === 'completed') { + const result = await storyWriterApi.getTaskResult(start.task_id); + // @ts-ignore: async result includes video dict + const video = (result as any).video || (result as any)?.result?.video; + const finalUrl: string | undefined = video?.video_url; + if (finalUrl) state.setStoryVideo(finalUrl); + state.setError(null); + done = true; + } else if (status.status === 'failed') { + throw new Error(status.error || 'Video generation failed'); + } } } catch (err: any) { const status = err?.response?.status; diff --git a/frontend/src/components/StoryWriter/components/AudioPlayerList.tsx b/frontend/src/components/StoryWriter/components/AudioPlayerList.tsx new file mode 100644 index 00000000..73e92d1e --- /dev/null +++ b/frontend/src/components/StoryWriter/components/AudioPlayerList.tsx @@ -0,0 +1,104 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Typography } from '@mui/material'; +import { storyWriterApi } from '../../../services/storyWriterApi'; +import { aiApiClient } from '../../../api/client'; + +interface AudioPlayerListProps { + scenes: any[]; + sceneAudioMap: Map; +} + +export const AudioPlayerList: React.FC = ({ scenes, sceneAudioMap }) => { + const [audioBlobUrls, setAudioBlobUrls] = useState>(new Map()); + + useEffect(() => { + if (!sceneAudioMap || sceneAudioMap.size === 0) { + setAudioBlobUrls((prev) => { + prev.forEach((url) => URL.revokeObjectURL(url)); + return new Map(); + }); + return; + } + + let isMounted = true; + + const loadAudioBlobs = async () => { + const entries = Array.from(sceneAudioMap.entries()); + const blobEntries: Array<[number, string]> = []; + + for (const [sceneNumber, audioPath] of entries) { + if (!audioPath) continue; + try { + const normalizedPath = audioPath.startsWith('/') ? audioPath : `/${audioPath}`; + const response = await aiApiClient.get(normalizedPath, { + responseType: 'blob', + }); + const blobUrl = URL.createObjectURL(response.data); + blobEntries.push([sceneNumber, blobUrl]); + } catch (err) { + console.error('Failed to load audio blob:', err); + } + } + + if (!isMounted) { + blobEntries.forEach(([, url]) => URL.revokeObjectURL(url)); + return; + } + + setAudioBlobUrls((prev) => { + prev.forEach((url) => URL.revokeObjectURL(url)); + return new Map(blobEntries); + }); + }; + + loadAudioBlobs(); + + return () => { + isMounted = false; + setAudioBlobUrls((prev) => { + prev.forEach((url) => URL.revokeObjectURL(url)); + return new Map(); + }); + }; + }, [sceneAudioMap]); + + return ( + + + Audio narration generated for {sceneAudioMap.size} scene(s). Listen to audio for each scene: + + + {scenes.map((scene: any, index: number) => { + const sceneNumber = scene.scene_number || index + 1; + const audioUrl = sceneAudioMap.get(sceneNumber); + if (!audioUrl) return null; + const blobUrl = audioBlobUrls.get(sceneNumber); + + return ( + + + Scene {sceneNumber}: {scene.title || `Scene ${sceneNumber}`} + + + + ); + })} + + + ); +}; + diff --git a/frontend/src/components/StoryWriter/components/AudioSection.tsx b/frontend/src/components/StoryWriter/components/AudioSection.tsx new file mode 100644 index 00000000..fcab425a --- /dev/null +++ b/frontend/src/components/StoryWriter/components/AudioSection.tsx @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import { + Box, + Typography, + Button, + Alert, + LinearProgress, + CircularProgress, + Chip, +} from '@mui/material'; +import VolumeUpIcon from '@mui/icons-material/VolumeUp'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import { useStoryWriterState } from '../../../hooks/useStoryWriterState'; +import { storyWriterApi } from '../../../services/storyWriterApi'; +import { triggerSubscriptionError } from '../../../api/client'; +import { SceneSelection } from './SceneSelection'; +import { AudioPlayerList } from './AudioPlayerList'; + +interface AudioSectionProps { + state: ReturnType; + selectedScenes: Set; + onSelectedScenesChange: (scenes: Set) => void; + showSceneSelection: boolean; + onShowSceneSelectionChange: (show: boolean) => void; + error: string | null; + onError: (error: string | null) => void; +} + +export const AudioSection: React.FC = ({ + state, + selectedScenes, + onSelectedScenesChange, + showSceneSelection, + onShowSceneSelectionChange, + error, + onError, +}) => { + const [isGeneratingAudio, setIsGeneratingAudio] = useState(false); + const [audioProgress, setAudioProgress] = useState(0); + + const hasScenes = state.isOutlineStructured && state.outlineScenes && state.outlineScenes.length > 0; + const narrationEnabled = state.enableNarration; + const hasAudio = narrationEnabled && state.sceneAudio && state.sceneAudio.size > 0; + const canGenerateAudio = hasScenes && selectedScenes.size > 0 && !isGeneratingAudio; + + const handleGenerateAudio = async () => { + if (!state.outlineScenes || state.outlineScenes.length === 0) { + onError('Please generate a structured outline first'); + return; + } + if (!narrationEnabled) { + onError('Narration feature is disabled in Story Setup.'); + return; + } + + if (selectedScenes.size === 0) { + onError('Please select at least one scene to generate audio for'); + return; + } + + setIsGeneratingAudio(true); + onError(null); + setAudioProgress(0); + + try { + const scenesToGenerate = state.outlineScenes.filter((scene: any, index: number) => { + const sceneNumber = scene.scene_number || index + 1; + return selectedScenes.has(sceneNumber); + }); + + const response = await storyWriterApi.generateSceneAudio({ + scenes: scenesToGenerate, + provider: state.audioProvider, + lang: state.audioLang, + slow: state.audioSlow, + rate: state.audioRate, + }); + + if (response.success && response.audio_files) { + const audioMap = new Map(); + response.audio_files.forEach((audio) => { + if (audio.audio_url && !audio.error) { + audioMap.set(audio.scene_number, audio.audio_url); + } + }); + state.setSceneAudio(audioMap); + state.setError(null); + setAudioProgress(100); + } else { + throw new Error('Failed to generate audio'); + } + } catch (err: any) { + console.error('Audio generation failed:', err); + + const status = err?.response?.status; + if (status === 429 || status === 402) { + const handled = await triggerSubscriptionError(err); + if (handled) { + setIsGeneratingAudio(false); + return; + } + } + + const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate audio'; + onError(errorMessage); + state.setError(errorMessage); + } finally { + setIsGeneratingAudio(false); + } + }; + + if (!narrationEnabled) { + return ( + + Narration is disabled in Story Setup. Enable it to generate or listen to audio narration. + + ); + } + + return ( + + + + + + Audio Narration + + {hasAudio && ( + } + label="Generated" + size="small" + color="success" + sx={{ ml: 1 }} + /> + )} + + + + + {hasScenes && state.outlineScenes && ( + + )} + + {isGeneratingAudio && ( + + + + Generating audio for {selectedScenes.size} selected scene + {selectedScenes.size !== 1 ? 's' : ''}... + + + )} + + {hasAudio && state.sceneAudio && state.outlineScenes && ( + + )} + + ); +}; + diff --git a/frontend/src/components/StoryWriter/components/HdVideoSection.tsx b/frontend/src/components/StoryWriter/components/HdVideoSection.tsx new file mode 100644 index 00000000..c055a1be --- /dev/null +++ b/frontend/src/components/StoryWriter/components/HdVideoSection.tsx @@ -0,0 +1,430 @@ +import React, { useState, useRef } from 'react'; +import { + Box, + Typography, + Button, + Alert, + LinearProgress, + Tooltip, +} from '@mui/material'; +import SmartDisplayIcon from '@mui/icons-material/SmartDisplay'; +import { useStoryWriterState } from '../../../hooks/useStoryWriterState'; +import { storyWriterApi } from '../../../services/storyWriterApi'; +import { triggerSubscriptionError } from '../../../api/client'; +import SceneVideoApproval from './SceneVideoApproval'; + +// Simple logger for frontend +const logger = { + error: (message: string, ...args: any[]) => console.error(`[HdVideoSection] ${message}`, ...args), + warn: (message: string, ...args: any[]) => console.warn(`[HdVideoSection] ${message}`, ...args), + info: (message: string, ...args: any[]) => console.info(`[HdVideoSection] ${message}`, ...args), +}; + +interface HdVideoSectionProps { + state: ReturnType; + error: string | null; + onError: (error: string | null) => void; +} + +export const HdVideoSection: React.FC = ({ state, onError }) => { + const [isGeneratingHdVideo, setIsGeneratingHdVideo] = useState(false); + const [hdVideoProgress, setHdVideoProgress] = useState(0); + const [hdVideoMessage, setHdVideoMessage] = useState(''); + const [hdVideoPrompts, setHdVideoPrompts] = useState>(new Map()); + + const [approvalModal, setApprovalModal] = useState<{ + open: boolean; + sceneNumber: number; + sceneTitle: string; + videoUrl: string; + promptUsed: string; + } | null>(null); + const [regeneratingScene, setRegeneratingScene] = useState(null); + + const processSceneRef = useRef<((sceneIndex: number) => Promise) | null>(null); + + const handleGenerateHdVideo = async () => { + if (!state.outlineScenes || state.outlineScenes.length === 0) { + onError('Please generate a structured outline first'); + return; + } + + const scenes = state.outlineScenes; + const totalScenes = scenes.length; + + if (!state.sceneHdVideos) { + state.setSceneHdVideos(new Map()); + } + + setHdVideoPrompts(new Map()); + state.setHdVideoGenerationStatus('generating'); + setIsGeneratingHdVideo(true); + onError(null); + + const storyContext = { + persona: state.persona, + story_setting: state.storySetting, + characters: state.characters, + plot_elements: state.plotElements, + writing_style: state.writingStyle, + story_tone: state.storyTone, + narrative_pov: state.narrativePOV, + audience_age_group: state.audienceAgeGroup, + content_rating: state.contentRating, + premise: state.premise || '', + outline: state.outline || '', + story_content: state.storyContent || '', + }; + + const processScene = async (sceneIndex: number): Promise => { + if (sceneIndex >= totalScenes) { + state.setHdVideoGenerationStatus('completed'); + setIsGeneratingHdVideo(false); + setHdVideoProgress(100); + const approvedCount = state.sceneHdVideos?.size || 0; + setHdVideoMessage(`HD video generation complete! ${approvedCount} of ${totalScenes} scenes approved.`); + return; + } + + const scene = scenes[sceneIndex]; + const sceneNumber = scene.scene_number || sceneIndex + 1; + state.setCurrentHdSceneIndex(sceneIndex); + + setHdVideoProgress(Math.round((sceneIndex / totalScenes) * 100)); + setHdVideoMessage(`Generating HD video for Scene ${sceneNumber}...`); + + try { + const sceneImageUrl = state.sceneImages?.get(sceneNumber); + + const result = await storyWriterApi.generateHdVideoScene({ + scene_number: sceneNumber, + scene_data: scene, + story_context: storyContext, + all_scenes: scenes, + scene_image_url: sceneImageUrl, + provider: 'huggingface', + model: 'tencent/HunyuanVideo', + num_frames: 50, + guidance_scale: 7.5, + }); + + setHdVideoPrompts((prev) => { + const newPrompts = new Map(prev); + newPrompts.set(sceneNumber, result.prompt_used); + return newPrompts; + }); + + state.setHdVideoGenerationStatus('awaiting_approval'); + setApprovalModal({ + open: true, + sceneNumber: sceneNumber, + sceneTitle: scene.title || `Scene ${sceneNumber}`, + videoUrl: result.video_url, + promptUsed: result.prompt_used, + }); + + } catch (err: any) { + // Check if this is a subscription error (429/402) and trigger global subscription modal + const status = err?.response?.status; + if (status === 429 || status === 402) { + const handled = await triggerSubscriptionError(err); + if (handled) { + // Subscription modal is showing, stop processing scenes + setIsGeneratingHdVideo(false); + state.setHdVideoGenerationStatus('idle'); + return; + } + } + + // Extract error message as string (handle both string and object responses) + let errorMessage: string; + if (err.response?.data?.detail) { + const detail = err.response.data.detail; + if (typeof detail === 'string') { + errorMessage = detail; + } else if (typeof detail === 'object' && detail !== null) { + // Handle object response like {error: "...", message: "..."} + errorMessage = detail.message || detail.error || JSON.stringify(detail); + } else { + errorMessage = String(detail); + } + } else { + errorMessage = err.message || `Failed to generate HD video for scene ${sceneNumber}`; + } + onError(errorMessage); + + // CRITICAL: Stop processing on ANY error to prevent wasting money on expensive video API calls + // This is an expensive operation ($0.40/video) - don't continue if there's an error + // Only retry/continue if the user explicitly requests it + logger.error(`[HdVideoSection] Video generation failed for scene ${sceneNumber}: ${errorMessage}`); + logger.error(`[HdVideoSection] Stopping video generation to prevent wasted API calls`); + + setIsGeneratingHdVideo(false); + state.setHdVideoGenerationStatus('idle'); + + // Don't continue to next scene - stop immediately to save money + return; + } + }; + + processSceneRef.current = processScene; + await processScene(0); + }; + + const handleApprove = () => { + if (!approvalModal) return; + + const sceneNumber = approvalModal.sceneNumber; + const hdVideos = state.sceneHdVideos || new Map(); + hdVideos.set(sceneNumber, approvalModal.videoUrl); + state.setSceneHdVideos(new Map(hdVideos)); + + setApprovalModal(null); + + const currentIndex = state.currentHdSceneIndex; + const scenes = state.outlineScenes || []; + if (currentIndex + 1 < scenes.length && processSceneRef.current) { + state.setHdVideoGenerationStatus('generating'); + processSceneRef.current(currentIndex + 1); + } else { + state.setHdVideoGenerationStatus('completed'); + setIsGeneratingHdVideo(false); + const approvedCount = state.sceneHdVideos?.size || 0; + setHdVideoMessage(`HD video generation complete! ${approvedCount} of ${scenes.length} scenes approved.`); + } + }; + + const handleReject = () => { + if (!approvalModal) return; + + setApprovalModal(null); + + const currentIndex = state.currentHdSceneIndex; + const scenes = state.outlineScenes || []; + if (currentIndex + 1 < scenes.length && processSceneRef.current) { + state.setHdVideoGenerationStatus('generating'); + processSceneRef.current(currentIndex + 1); + } else { + state.setHdVideoGenerationStatus('completed'); + setIsGeneratingHdVideo(false); + const approvedCount = state.sceneHdVideos?.size || 0; + setHdVideoMessage(`HD video generation complete! ${approvedCount} of ${scenes.length} scenes approved.`); + } + }; + + const handleRegenerate = async () => { + if (!approvalModal) return; + + const sceneNumber = approvalModal.sceneNumber; + const scenes = state.outlineScenes || []; + const sceneIndex = scenes.findIndex((s: any) => (s.scene_number || 0) === sceneNumber); + const scene = scenes[sceneIndex]; + + if (!scene) return; + + setRegeneratingScene(sceneNumber); + + try { + const storyContext = { + persona: state.persona, + story_setting: state.storySetting, + characters: state.characters, + plot_elements: state.plotElements, + writing_style: state.writingStyle, + story_tone: state.storyTone, + narrative_pov: state.narrativePOV, + audience_age_group: state.audienceAgeGroup, + content_rating: state.contentRating, + premise: state.premise || '', + outline: state.outline || '', + story_content: state.storyContent || '', + }; + + const sceneImageUrl = state.sceneImages?.get(sceneNumber); + + const result = await storyWriterApi.generateHdVideoScene({ + scene_number: sceneNumber, + scene_data: scene, + story_context: storyContext, + all_scenes: scenes, + scene_image_url: sceneImageUrl, + provider: 'huggingface', + model: 'tencent/HunyuanVideo', + num_frames: 50, + guidance_scale: 7.5, + }); + + setHdVideoPrompts((prev) => { + const newPrompts = new Map(prev); + newPrompts.set(sceneNumber, result.prompt_used); + return newPrompts; + }); + + setApprovalModal({ + open: true, + sceneNumber: sceneNumber, + sceneTitle: scene.title || `Scene ${sceneNumber}`, + videoUrl: result.video_url, + promptUsed: result.prompt_used, + }); + } catch (err: any) { + // Check if this is a subscription error (429/402) and trigger global subscription modal + const status = err?.response?.status; + if (status === 429 || status === 402) { + const handled = await triggerSubscriptionError(err); + if (handled) { + // Subscription modal is showing, stop here + setRegeneratingScene(null); + return; + } + } + + // Extract error message as string (handle both string and object responses) + let errorMessage: string; + if (err.response?.data?.detail) { + const detail = err.response.data.detail; + if (typeof detail === 'string') { + errorMessage = detail; + } else if (typeof detail === 'object' && detail !== null) { + // Handle object response like {error: "...", message: "..."} + errorMessage = detail.message || detail.error || JSON.stringify(detail); + } else { + errorMessage = String(detail); + } + } else { + errorMessage = err.message || 'Failed to regenerate video'; + } + onError(errorMessage); + } finally { + setRegeneratingScene(null); + } + }; + + return ( + <> + + + + Generate HD Animation with AI + + + Upgrade this storyboard into a high‑definition AI animation using Hugging Face text‑to‑video models. + Your draft was generated affordably (images + narration). This premium option uses an AI model to render motion. + + + Recommended models: + + + • tencent/HunyuanVideo
+ • Lightricks/LTX-Video
+ • Lightricks/LTX-Video-0.9.8-13B-distilled +
+ + This will generate HD videos for each scene one at a time. You'll review and approve each scene before the next one is generated. + +
+ } + arrow + placement="top" + > + + + + + + {(isGeneratingHdVideo || state.hdVideoGenerationStatus === 'generating' || state.hdVideoGenerationStatus === 'awaiting_approval') && ( + + + + {hdVideoMessage || 'Generating HD video...'} {hdVideoProgress}% + + {state.hdVideoGenerationStatus === 'awaiting_approval' && ( + + ⏸ Awaiting your approval for Scene {state.currentHdSceneIndex + 1} of {state.outlineScenes?.length || 0} + + )} + {state.hdVideoGenerationStatus === 'generating' && ( + + Processing Scene {state.currentHdSceneIndex + 1} of {state.outlineScenes?.length || 0}... + + )} + {state.sceneHdVideos && state.sceneHdVideos.size > 0 && ( + + ✓ {state.sceneHdVideos.size} of {state.outlineScenes?.length || 0} scenes approved + + )} + + {hdVideoPrompts.size > 0 && ( + + + Generated Prompts: + + {Array.from(hdVideoPrompts.entries()) + .sort(([a], [b]) => a - b) + .map(([sceneNum, prompt]) => ( + + + Scene {sceneNum}: + + + {prompt.length > 200 ? `${prompt.substring(0, 200)}...` : prompt} + + + ))} + + )} + + )} + + {state.hdVideoGenerationStatus === 'completed' && ( + + HD video generation complete! {state.sceneHdVideos?.size || 0} of {state.outlineScenes?.length || 0} scenes were approved. + + )} + + + {approvalModal && state.outlineScenes && ( + { + if (!isGeneratingHdVideo && !regeneratingScene) { + setApprovalModal(null); + state.setHdVideoGenerationStatus('paused'); + } + }} + /> + )} + + ); +}; + diff --git a/frontend/src/components/StoryWriter/components/MultimediaSection.tsx b/frontend/src/components/StoryWriter/components/MultimediaSection.tsx index 857ae8bb..d06b73d3 100644 --- a/frontend/src/components/StoryWriter/components/MultimediaSection.tsx +++ b/frontend/src/components/StoryWriter/components/MultimediaSection.tsx @@ -3,52 +3,27 @@ import { Box, Paper, Typography, - Button, Alert, Divider, - LinearProgress, - CircularProgress, - Chip, - FormGroup, - FormControlLabel, - Checkbox, - Collapse, - IconButton, } from '@mui/material'; -import VolumeUpIcon from '@mui/icons-material/VolumeUp'; -import VideoLibraryIcon from '@mui/icons-material/VideoLibrary'; -import DownloadIcon from '@mui/icons-material/Download'; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import { useStoryWriterState } from '../../../hooks/useStoryWriterState'; -import { storyWriterApi } from '../../../services/storyWriterApi'; -import { triggerSubscriptionError, aiApiClient } from '../../../api/client'; +import { AudioSection } from './AudioSection'; +import { VideoSection } from './VideoSection'; interface MultimediaSectionProps { state: ReturnType; } export const MultimediaSection: React.FC = ({ state }) => { - const [isGeneratingAudio, setIsGeneratingAudio] = useState(false); - const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); - const [audioProgress, setAudioProgress] = useState(0); - const [videoProgress, setVideoProgress] = useState(0); const [error, setError] = useState(null); const [selectedScenes, setSelectedScenes] = useState>(new Set()); const [showSceneSelection, setShowSceneSelection] = useState(false); - const [audioBlobUrls, setAudioBlobUrls] = useState>(new Map()); const hasScenes = state.isOutlineStructured && state.outlineScenes && state.outlineScenes.length > 0; - const narrationEnabled = state.enableNarration; - const videoEnabled = state.enableVideoNarration; - const hasAudio = narrationEnabled && state.sceneAudio && state.sceneAudio.size > 0; - const hasVideo = videoEnabled && !!state.storyVideo; - const hasImages = state.sceneImages && state.sceneImages.size > 0; // Initialize selected scenes to all scenes by default useEffect(() => { - if (!narrationEnabled || !state.outlineScenes) { + if (!state.enableNarration || !state.outlineScenes) { setSelectedScenes(new Set()); return; } @@ -60,264 +35,14 @@ export const MultimediaSection: React.FC = ({ state }) = ); return allSceneNumbers; }); - }, [narrationEnabled, state.outlineScenes]); - - const canGenerateAudio = hasScenes && selectedScenes.size > 0 && !isGeneratingAudio; - const canGenerateVideo = hasScenes && hasImages && hasAudio && !isGeneratingVideo; - - const handleSceneSelectionToggle = (sceneNumber: number) => { - setSelectedScenes((prev) => { - const next = new Set(prev); - if (next.has(sceneNumber)) { - next.delete(sceneNumber); - } else { - next.add(sceneNumber); - } - return next; - }); - }; - - const handleSelectAllScenes = () => { - if (hasScenes && state.outlineScenes) { - const allSceneNumbers = new Set( - state.outlineScenes.map((scene: any, index: number) => - scene.scene_number || index + 1 - ) - ); - setSelectedScenes(allSceneNumbers); - } - }; - - const handleDeselectAllScenes = () => { - setSelectedScenes(new Set()); - }; - - // Fetch authenticated audio blobs for playback - useEffect(() => { - const sceneAudioMap = state.sceneAudio; - if (!narrationEnabled || !sceneAudioMap || sceneAudioMap.size === 0) { - setAudioBlobUrls((prev) => { - prev.forEach((url) => URL.revokeObjectURL(url)); - return new Map(); - }); - return; - } - - let isMounted = true; - - const loadAudioBlobs = async () => { - const entries = Array.from(sceneAudioMap.entries()); - const blobEntries: Array<[number, string]> = []; - - for (const [sceneNumber, audioPath] of entries) { - if (!audioPath) continue; - try { - const normalizedPath = audioPath.startsWith('/') ? audioPath : `/${audioPath}`; - const response = await aiApiClient.get(normalizedPath, { - responseType: 'blob', - }); - const blobUrl = URL.createObjectURL(response.data); - blobEntries.push([sceneNumber, blobUrl]); - } catch (err) { - console.error('Failed to load audio blob:', err); - } - } - - if (!isMounted) { - blobEntries.forEach(([, url]) => URL.revokeObjectURL(url)); - return; - } - - setAudioBlobUrls((prev) => { - prev.forEach((url) => URL.revokeObjectURL(url)); - return new Map(blobEntries); - }); - }; - - loadAudioBlobs(); - - return () => { - isMounted = false; - setAudioBlobUrls((prev) => { - prev.forEach((url) => URL.revokeObjectURL(url)); - return new Map(); - }); - }; - }, [state.sceneAudio, narrationEnabled]); - - const handleGenerateAudio = async () => { - if (!state.outlineScenes || state.outlineScenes.length === 0) { - setError('Please generate a structured outline first'); - return; - } - if (!narrationEnabled) { - setError('Narration feature is disabled in Story Setup.'); - return; - } - - if (selectedScenes.size === 0) { - setError('Please select at least one scene to generate audio for'); - return; - } - - setIsGeneratingAudio(true); - setError(null); - setAudioProgress(0); - - try { - // Filter scenes to only selected ones - const scenesToGenerate = state.outlineScenes.filter((scene: any, index: number) => { - const sceneNumber = scene.scene_number || index + 1; - return selectedScenes.has(sceneNumber); - }); - - const response = await storyWriterApi.generateSceneAudio({ - scenes: scenesToGenerate, - provider: state.audioProvider, - lang: state.audioLang, - slow: state.audioSlow, - rate: state.audioRate, - }); - - if (response.success && response.audio_files) { - // Store audio URLs by scene number - const audioMap = new Map(); - response.audio_files.forEach((audio) => { - if (audio.audio_url && !audio.error) { - audioMap.set(audio.scene_number, audio.audio_url); - } - }); - state.setSceneAudio(audioMap); - state.setError(null); - setAudioProgress(100); - } else { - throw new Error('Failed to generate audio'); - } - } catch (err: any) { - console.error('Audio generation failed:', err); - - // Check if this is a subscription error (429/402) - const status = err?.response?.status; - if (status === 429 || status === 402) { - const handled = await triggerSubscriptionError(err); - if (handled) { - setIsGeneratingAudio(false); - return; - } - } - - const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate audio'; - setError(errorMessage); - state.setError(errorMessage); - } finally { - setIsGeneratingAudio(false); - } - }; - - const handleGenerateVideo = async () => { - if (!state.outlineScenes || state.outlineScenes.length === 0) { - setError('Please generate a structured outline first'); - return; - } - if (!videoEnabled) { - setError('Story video feature is disabled in Story Setup.'); - return; - } - - if (!hasImages) { - setError('Please generate images for scenes first'); - return; - } - - if (!hasAudio) { - setError('Please generate audio for scenes first'); - return; - } - - setIsGeneratingVideo(true); - setError(null); - setVideoProgress(0); - - try { - // Prepare image and audio URLs in scene order - const imageUrls: string[] = []; - const audioUrls: string[] = []; - const scenes = state.outlineScenes; - - for (const scene of scenes) { - const sceneNumber = scene.scene_number || scenes.indexOf(scene) + 1; - const imageUrl = state.sceneImages?.get(sceneNumber); - const audioUrl = state.sceneAudio?.get(sceneNumber); - - if (imageUrl && audioUrl) { - imageUrls.push(imageUrl); - audioUrls.push(audioUrl); - } else { - throw new Error(`Missing image or audio for scene ${sceneNumber}`); - } - } - - if (imageUrls.length !== scenes.length || audioUrls.length !== scenes.length) { - throw new Error('Number of images and audio files must match number of scenes'); - } - - setVideoProgress(30); - - // Generate video - const response = await storyWriterApi.generateStoryVideo({ - scenes: scenes, - image_urls: imageUrls, - audio_urls: audioUrls, - story_title: state.storySetting || 'Story', - fps: state.videoFps, - transition_duration: state.videoTransitionDuration, - }); - - if (response.success && response.video) { - state.setStoryVideo(response.video.video_url); - state.setError(null); - setVideoProgress(100); - } else { - throw new Error('Failed to generate video'); - } - } catch (err: any) { - console.error('Video generation failed:', err); - - // Check if this is a subscription error (429/402) - const status = err?.response?.status; - if (status === 429 || status === 402) { - const handled = await triggerSubscriptionError(err); - if (handled) { - setIsGeneratingVideo(false); - return; - } - } - - const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate video'; - setError(errorMessage); - state.setError(errorMessage); - } finally { - setIsGeneratingVideo(false); - } - }; - - const handleDownloadVideo = () => { - if (state.storyVideo) { - const videoUrl = storyWriterApi.getVideoUrl(state.storyVideo); - const a = document.createElement('a'); - a.href = videoUrl; - a.download = `story-video-${Date.now()}.mp4`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - } - }; + }, [state.enableNarration, state.outlineScenes]); if (!hasScenes) { return null; // Don't show if no scenes available } return ( + <> = ({ state }) = )} {/* Audio Section */} - {narrationEnabled ? ( - - - - - - Audio Narration - - {hasAudio && ( - } - label="Generated" - size="small" - color="success" - sx={{ ml: 1 }} - /> - )} - - - - - {hasScenes && state.outlineScenes && ( - - - - Select scenes to generate audio for: - - - - - setShowSceneSelection(!showSceneSelection)} - sx={{ p: 0.5 }} - > - {showSceneSelection ? : } - - - - - - {state.outlineScenes.map((scene: any, index: number) => { - const sceneNumber = scene.scene_number || index + 1; - const hasAudioForScene = state.sceneAudio?.has(sceneNumber); - return ( - handleSceneSelectionToggle(sceneNumber)} - size="small" - /> - } - label={ - - - Scene {sceneNumber}: {scene.title || `Scene ${sceneNumber}`} - - {hasAudioForScene && ( - - )} - - } - /> - ); - })} - - - - )} - - {isGeneratingAudio && ( - - - - Generating audio for {selectedScenes.size} selected scene - {selectedScenes.size !== 1 ? 's' : ''}... - - - )} - - {hasAudio && state.sceneAudio && state.outlineScenes && ( - - - Audio narration generated for {state.sceneAudio.size} scene(s). Listen to audio for each scene: - - - {state.outlineScenes.map((scene: any, index: number) => { - const sceneNumber = scene.scene_number || index + 1; - const audioUrl = state.sceneAudio?.get(sceneNumber); - if (!audioUrl) return null; - const blobUrl = audioBlobUrls.get(sceneNumber); - - return ( - - - Scene {sceneNumber}: {scene.title || `Scene ${sceneNumber}`} - - - - ); - })} - - - )} - - ) : ( - - Narration is disabled in Story Setup. Enable it to generate or listen to audio narration. - - )} + {/* Video Section */} - {videoEnabled ? ( - - - - - - Story Video - - {hasVideo && ( - } - label="Generated" - size="small" - color="success" - sx={{ ml: 1 }} - /> - )} - {!hasVideo && !hasImages && ( - - )} - {!hasVideo && hasImages && !hasAudio && ( - - )} - - - {hasVideo && ( - - )} - - - - - {isGeneratingVideo && ( - - 0 ? 'determinate' : 'indeterminate'} - value={videoProgress} - /> - - Generating video... This may take a few minutes. - - - )} - - {hasVideo && state.storyVideo && ( - - - Video ready! Preview and download below. - - - Your browser does not support the video tag. - - - )} - - ) : ( - - Story video generation is disabled in Story Setup. Enable it to create narrative videos. - - )} + + ); }; diff --git a/frontend/src/components/StoryWriter/components/SceneSelection.tsx b/frontend/src/components/StoryWriter/components/SceneSelection.tsx new file mode 100644 index 00000000..be276f0e --- /dev/null +++ b/frontend/src/components/StoryWriter/components/SceneSelection.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { + Box, + Typography, + Button, + FormGroup, + FormControlLabel, + Checkbox, + Collapse, + IconButton, +} from '@mui/material'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; + +interface SceneSelectionProps { + scenes: any[]; + selectedScenes: Set; + onSelectedScenesChange: (scenes: Set) => void; + sceneAudioMap?: Map | null; + showSceneSelection: boolean; + onShowSceneSelectionChange: (show: boolean) => void; +} + +export const SceneSelection: React.FC = ({ + scenes, + selectedScenes, + onSelectedScenesChange, + sceneAudioMap, + showSceneSelection, + onShowSceneSelectionChange, +}) => { + const handleSceneSelectionToggle = (sceneNumber: number) => { + const next = new Set(selectedScenes); + if (next.has(sceneNumber)) { + next.delete(sceneNumber); + } else { + next.add(sceneNumber); + } + onSelectedScenesChange(next); + }; + + const handleSelectAllScenes = () => { + const allSceneNumbers = new Set( + scenes.map((scene: any, index: number) => scene.scene_number || index + 1) + ); + onSelectedScenesChange(allSceneNumbers); + }; + + const handleDeselectAllScenes = () => { + onSelectedScenesChange(new Set()); + }; + + return ( + + + + Select scenes to generate audio for: + + + + + onShowSceneSelectionChange(!showSceneSelection)} + sx={{ p: 0.5 }} + > + {showSceneSelection ? : } + + + + + + {scenes.map((scene: any, index: number) => { + const sceneNumber = scene.scene_number || index + 1; + const hasAudioForScene = sceneAudioMap?.has(sceneNumber); + return ( + handleSceneSelectionToggle(sceneNumber)} + size="small" + /> + } + label={ + + + Scene {sceneNumber}: {scene.title || `Scene ${sceneNumber}`} + + {hasAudioForScene && ( + + )} + + } + /> + ); + })} + + + + ); +}; + diff --git a/frontend/src/components/StoryWriter/components/SceneVideoApproval.tsx b/frontend/src/components/StoryWriter/components/SceneVideoApproval.tsx new file mode 100644 index 00000000..ec366960 --- /dev/null +++ b/frontend/src/components/StoryWriter/components/SceneVideoApproval.tsx @@ -0,0 +1,216 @@ +import React, { useState, useRef } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + CircularProgress, + Paper, +} from '@mui/material'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CancelIcon from '@mui/icons-material/Cancel'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import { fetchMediaBlobUrl } from '../../../utils/fetchMediaBlobUrl'; + +interface SceneVideoApprovalProps { + open: boolean; + sceneNumber: number; + sceneTitle: string; + totalScenes: number; + videoUrl: string; + promptUsed: string; + onApprove: () => void; + onReject: () => void; + onRegenerate: () => void; + isRegenerating?: boolean; + onClose?: () => void; +} + +const SceneVideoApproval: React.FC = ({ + open, + sceneNumber, + sceneTitle, + totalScenes, + videoUrl, + promptUsed, + onApprove, + onReject, + onRegenerate, + isRegenerating = false, + onClose, +}) => { + const [videoBlobUrl, setVideoBlobUrl] = useState(null); + const [loadingVideo, setLoadingVideo] = useState(true); + const videoRef = useRef(null); + + // Load video when modal opens + React.useEffect(() => { + if (open && videoUrl) { + setLoadingVideo(true); + fetchMediaBlobUrl(videoUrl) + .then((blobUrl) => { + setVideoBlobUrl(blobUrl); + setLoadingVideo(false); + }) + .catch((err) => { + console.error('Failed to load video:', err); + setLoadingVideo(false); + }); + } + + // Cleanup blob URL when modal closes + return () => { + if (videoBlobUrl) { + URL.revokeObjectURL(videoBlobUrl); + setVideoBlobUrl(null); + } + }; + }, [open, videoUrl]); + + const handleClose = () => { + if (onClose && !isRegenerating) { + onClose(); + } + }; + + return ( + + + + Scene {sceneNumber} of {totalScenes}: {sceneTitle} + + + Review the generated HD video and choose an action + + + + + + {/* Video Player */} + + {loadingVideo ? ( + + ) : videoBlobUrl ? ( + + ) : ( + Failed to load video + )} + + + {/* Prompt Used */} + {promptUsed && ( + + + Generated Prompt (for transparency): + + + {promptUsed} + + + )} + + {isRegenerating && ( + + + + Regenerating video for this scene... + + + )} + + + + + + + + + + ); +}; + +export default SceneVideoApproval; + diff --git a/frontend/src/components/StoryWriter/components/VideoSection.tsx b/frontend/src/components/StoryWriter/components/VideoSection.tsx new file mode 100644 index 00000000..0796a9f1 --- /dev/null +++ b/frontend/src/components/StoryWriter/components/VideoSection.tsx @@ -0,0 +1,260 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { + Box, + Typography, + Button, + LinearProgress, + CircularProgress, + Chip, + Alert, +} from '@mui/material'; +import VideoLibraryIcon from '@mui/icons-material/VideoLibrary'; +import DownloadIcon from '@mui/icons-material/Download'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import { useStoryWriterState } from '../../../hooks/useStoryWriterState'; +import { storyWriterApi } from '../../../services/storyWriterApi'; +import { triggerSubscriptionError } from '../../../api/client'; +import { fetchMediaBlobUrl } from '../../../utils/fetchMediaBlobUrl'; +import { HdVideoSection } from './HdVideoSection'; + +interface VideoSectionProps { + state: ReturnType; + error: string | null; + onError: (error: string | null) => void; +} + +export const VideoSection: React.FC = ({ state, error, onError }) => { + const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); + const [videoProgress, setVideoProgress] = useState(0); + const [videoMessage, setVideoMessage] = useState(''); + const [videoBlobUrl, setVideoBlobUrl] = useState(null); + const videoRef = useRef(null); + + const hasScenes = state.isOutlineStructured && state.outlineScenes && state.outlineScenes.length > 0; + const videoEnabled = state.enableVideoNarration; + const hasVideo = videoEnabled && !!state.storyVideo; + const hasImages = state.sceneImages && state.sceneImages.size > 0; + const hasAudio = state.enableNarration && state.sceneAudio && state.sceneAudio.size > 0; + const canGenerateVideo = hasScenes && hasImages && hasAudio && !isGeneratingVideo; + + // Load video blob URL when storyVideo changes + useEffect(() => { + if (state.storyVideo) { + fetchMediaBlobUrl(state.storyVideo).then(setVideoBlobUrl); + } else { + if (videoBlobUrl) { + URL.revokeObjectURL(videoBlobUrl); + setVideoBlobUrl(null); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state.storyVideo]); + + const handleGenerateVideo = async () => { + if (!state.outlineScenes || state.outlineScenes.length === 0) { + onError('Please generate a structured outline first'); + return; + } + if (!videoEnabled) { + onError('Story video feature is disabled in Story Setup.'); + return; + } + + if (!hasImages) { + onError('Please generate images for scenes first'); + return; + } + + if (!hasAudio) { + onError('Please generate audio for scenes first'); + return; + } + + setIsGeneratingVideo(true); + onError(null); + setVideoProgress(0); + setVideoMessage(''); + + try { + const imageUrls: string[] = []; + const audioUrls: string[] = []; + const scenes = state.outlineScenes; + + for (const scene of scenes) { + const sceneNumber = scene.scene_number || scenes.indexOf(scene) + 1; + const imageUrl = state.sceneImages?.get(sceneNumber); + const audioUrl = state.sceneAudio?.get(sceneNumber); + + if (imageUrl && audioUrl) { + imageUrls.push(imageUrl); + audioUrls.push(audioUrl); + } else { + throw new Error(`Missing image or audio for scene ${sceneNumber}`); + } + } + + if (imageUrls.length !== scenes.length || audioUrls.length !== scenes.length) { + throw new Error('Number of images and audio files must match number of scenes'); + } + + const start = await storyWriterApi.generateStoryVideoAsync({ + scenes: scenes, + image_urls: imageUrls, + audio_urls: audioUrls, + story_title: state.storySetting || 'Story', + fps: state.videoFps, + transition_duration: state.videoTransitionDuration, + }); + setVideoMessage(start.message || 'Starting video generation...'); + + const taskId = start.task_id; + let done = false; + while (!done) { + await new Promise((r) => setTimeout(r, 1200)); + const status = await storyWriterApi.getTaskStatus(taskId); + setVideoProgress(Math.round(status.progress ?? 0)); + if (status.message) setVideoMessage(status.message); + if (status.status === 'completed') { + done = true; + const result = await storyWriterApi.getTaskResult(taskId); + const video = (result as any).video || (result as any)?.result?.video; + const finalUrl: string | undefined = video?.video_url; + if (!finalUrl) throw new Error('Video URL not found in result'); + state.setStoryVideo(finalUrl); + const blobUrl = await fetchMediaBlobUrl(finalUrl); + setVideoBlobUrl(blobUrl); + setVideoProgress(100); + setVideoMessage('Video generation complete'); + state.setError(null); + setTimeout(() => { + const v = videoRef.current; + if (v) { + try { v.play().catch(() => {}); } catch {} + try { if (v.requestFullscreen) v.requestFullscreen(); } catch {} + } + }, 300); + } else if (status.status === 'failed') { + throw new Error(status.error || 'Video generation failed'); + } + } + } catch (err: any) { + console.error('Video generation failed:', err); + + const status = err?.response?.status; + if (status === 429 || status === 402) { + const handled = await triggerSubscriptionError(err); + if (handled) { + setIsGeneratingVideo(false); + return; + } + } + + const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate video'; + onError(errorMessage); + state.setError(errorMessage); + } finally { + setIsGeneratingVideo(false); + } + }; + + const handleDownloadVideo = async () => { + if (state.storyVideo) { + const blobUrl = await fetchMediaBlobUrl(state.storyVideo); + const a = document.createElement('a'); + a.href = blobUrl; + a.download = `story-video-${Date.now()}.mp4`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + } + }; + + if (!videoEnabled) { + return ( + + Story video generation is disabled in Story Setup. Enable it to create narrative videos. + + ); + } + + return ( + + + + + + Story Video + + {hasVideo && ( + } + label="Generated" + size="small" + color="success" + sx={{ ml: 1 }} + /> + )} + {!hasVideo && !hasImages && ( + + )} + {!hasVideo && hasImages && !hasAudio && ( + + )} + + + {hasVideo && ( + + )} + + + + + {isGeneratingVideo && ( + + 0 ? 'determinate' : 'indeterminate'} + value={videoProgress} + /> + + {videoMessage || 'Generating video... This may take a few minutes.'} + + + )} + + {hasVideo && state.storyVideo && ( + + + Video ready! Preview and download below. + + + + + + + )} + + ); +}; + diff --git a/frontend/src/components/billing/ComprehensiveAPIBreakdown.tsx b/frontend/src/components/billing/ComprehensiveAPIBreakdown.tsx index 6c9757d7..599d2636 100644 --- a/frontend/src/components/billing/ComprehensiveAPIBreakdown.tsx +++ b/frontend/src/components/billing/ComprehensiveAPIBreakdown.tsx @@ -121,6 +121,13 @@ const API_CATEGORIES = { models: ['stable-diffusion-xl', 'stable-diffusion-3'], pricing: '$0.04 per image generated', use_cases: ['Image generation', 'Art creation', 'Visual content'] + }, + { + name: 'Image Editing', + description: 'AI-powered image editing using natural language prompts', + models: ['Qwen/Qwen-Image-Edit', 'FLUX.1-Kontext-dev'], + pricing: '$0.04 per image edited', + use_cases: ['Image editing', 'Photo manipulation', 'Natural language editing'] } ] }, @@ -160,7 +167,7 @@ const ComprehensiveAPIBreakdown: React.FC = ({ if (['firecrawl'].includes(provider)) { return 'content_processing'; } - if (['stability'].includes(provider)) { + if (['stability', 'image_edit'].includes(provider)) { return 'image_generation'; } return 'llm_models'; // default diff --git a/frontend/src/hooks/useStoryWriterState.ts b/frontend/src/hooks/useStoryWriterState.ts index f1be4258..e4518dc6 100644 --- a/frontend/src/hooks/useStoryWriterState.ts +++ b/frontend/src/hooks/useStoryWriterState.ts @@ -51,6 +51,9 @@ export interface StoryWriterState { sceneImages: Map | null; // Generated image URLs by scene number sceneAudio: Map | null; // Generated audio URLs by scene number storyVideo: string | null; // Generated video URL + sceneHdVideos: Map | null; // Approved HD video URLs by scene number + hdVideoGenerationStatus: 'idle' | 'generating' | 'awaiting_approval' | 'completed' | 'paused'; + currentHdSceneIndex: number; // Which scene is currently being generated/reviewed // Task management currentTaskId: string | null; @@ -100,6 +103,9 @@ const DEFAULT_STATE: Partial = { sceneImages: null, sceneAudio: null, storyVideo: null, + sceneHdVideos: null, + hdVideoGenerationStatus: 'idle', + currentHdSceneIndex: 0, currentTaskId: null, generationProgress: 0, generationMessage: null, @@ -141,6 +147,7 @@ export const useStoryWriterState = () => { ...parsed, sceneImages: parsed.sceneImages ? new Map(parsed.sceneImages) : null, sceneAudio: parsed.sceneAudio ? new Map(parsed.sceneAudio) : null, + sceneHdVideos: parsed.sceneHdVideos ? new Map(parsed.sceneHdVideos) : null, }; return restoredState as StoryWriterState; @@ -185,6 +192,7 @@ export const useStoryWriterState = () => { audienceAgeGroup: validAudienceAgeGroup, sceneImages: persistableState.sceneImages ? Array.from(persistableState.sceneImages.entries()) : null, sceneAudio: persistableState.sceneAudio ? Array.from(persistableState.sceneAudio.entries()) : null, + sceneHdVideos: persistableState.sceneHdVideos ? Array.from(persistableState.sceneHdVideos.entries()) : null, }; localStorage.setItem('story_writer_state', JSON.stringify(serializableState)); @@ -337,6 +345,18 @@ export const useStoryWriterState = () => { setState((prev) => ({ ...prev, storyVideo: video })); }, []); + const setSceneHdVideos = useCallback((videos: Map | null) => { + setState((prev) => ({ ...prev, sceneHdVideos: videos })); + }, []); + + const setHdVideoGenerationStatus = useCallback((status: 'idle' | 'generating' | 'awaiting_approval' | 'completed' | 'paused') => { + setState((prev) => ({ ...prev, hdVideoGenerationStatus: status })); + }, []); + + const setCurrentHdSceneIndex = useCallback((index: number) => { + setState((prev) => ({ ...prev, currentHdSceneIndex: index })); + }, []); + const setIsComplete = useCallback((complete: boolean) => { setState((prev) => ({ ...prev, isComplete: complete })); }, []); @@ -450,6 +470,9 @@ export const useStoryWriterState = () => { setSceneImages, setSceneAudio, setStoryVideo, + setSceneHdVideos, + setHdVideoGenerationStatus, + setCurrentHdSceneIndex, setCurrentTaskId, setGenerationProgress, setGenerationMessage, diff --git a/frontend/src/services/billingService.ts b/frontend/src/services/billingService.ts index 9458ec8e..b9838116 100644 --- a/frontend/src/services/billingService.ts +++ b/frontend/src/services/billingService.ts @@ -185,6 +185,8 @@ function coerceUsageStats(raw: any): UsageStats { metaphor_calls: raw?.limits?.limits?.metaphor_calls ?? 0, firecrawl_calls: raw?.limits?.limits?.firecrawl_calls ?? 0, stability_calls: raw?.limits?.limits?.stability_calls ?? 0, + video_calls: raw?.limits?.limits?.video_calls ?? 0, + image_edit_calls: raw?.limits?.limits?.image_edit_calls ?? 0, gemini_tokens: raw?.limits?.limits?.gemini_tokens ?? 0, openai_tokens: raw?.limits?.limits?.openai_tokens ?? 0, anthropic_tokens: raw?.limits?.limits?.anthropic_tokens ?? 0, diff --git a/frontend/src/services/storyWriterApi.ts b/frontend/src/services/storyWriterApi.ts index 3cc0f55b..d7df0545 100644 --- a/frontend/src/services/storyWriterApi.ts +++ b/frontend/src/services/storyWriterApi.ts @@ -427,6 +427,104 @@ class StoryWriterApi { return response.data; } + /** + * Generate video asynchronously (returns task_id for polling) + */ + async generateStoryVideoAsync( + request: StoryVideoGenerationRequest + ): Promise<{ task_id: string; status: string; message: string }> { + const response = await aiApiClient.post<{ task_id: string; status: string; message: string }>( + "/api/story/generate-video-async", + request + ); + return response.data; + } + + /** + * Generate HD AI animation via Hugging Face text-to-video (server saves and returns url) + */ + async generateHdVideo(payload: { + prompt: string; + provider?: string; + model?: string; + num_frames?: number; + guidance_scale?: number; + num_inference_steps?: number; + negative_prompt?: string; + seed?: number; + }): Promise<{ success: boolean; video_filename: string; video_url: string; provider: string; model: string }> { + // Long-running request - use longRunningApiClient to allow more time + const { longRunningApiClient } = await import("../api/client"); + const response = await longRunningApiClient.post( + "/api/story/hd-video", + { + provider: "huggingface", + ...payload, + } + ); + return response.data; + } + + /** + * Generate HD AI animation asynchronously (returns task_id for polling) + */ + async generateHdVideoAsync(payload: { + prompt: string; + provider?: string; + model?: string; + num_frames?: number; + guidance_scale?: number; + num_inference_steps?: number; + negative_prompt?: string; + seed?: number; + }): Promise<{ task_id: string; status: string; message: string }> { + const response = await aiApiClient.post<{ task_id: string; status: string; message: string }>( + "/api/story/hd-video-async", + { + provider: "huggingface", + ...payload, + } + ); + return response.data; + } + + /** + * Generate HD AI video for a single scene with AI-enhanced prompt + */ + async generateHdVideoScene(payload: { + scene_number: number; + scene_data: StoryScene; + story_context: Record; + all_scenes: StoryScene[]; + scene_image_url?: string; + provider?: string; + model?: string; + num_frames?: number; + guidance_scale?: number; + num_inference_steps?: number; + negative_prompt?: string; + seed?: number; + }): Promise<{ + success: boolean; + scene_number: number; + video_filename: string; + video_url: string; + prompt_used: string; + provider: string; + model: string; + }> { + // Long-running request - use longRunningApiClient to allow more time + const { longRunningApiClient } = await import("../api/client"); + const response = await longRunningApiClient.post( + "/api/story/hd-video-scene", + { + provider: "huggingface", + ...payload, + } + ); + return response.data; + } + /** * Get video URL for a story video file */ diff --git a/frontend/src/types/billing.ts b/frontend/src/types/billing.ts index 3fb55dce..6b2e13f5 100644 --- a/frontend/src/types/billing.ts +++ b/frontend/src/types/billing.ts @@ -49,6 +49,8 @@ export interface SubscriptionLimits { metaphor_calls: number; firecrawl_calls: number; stability_calls: number; + video_calls: number; + image_edit_calls: number; gemini_tokens: number; openai_tokens: number; anthropic_tokens: number; @@ -207,6 +209,8 @@ export const SubscriptionLimitsSchema = z.object({ metaphor_calls: z.number(), firecrawl_calls: z.number(), stability_calls: z.number(), + video_calls: z.number().optional().default(0), + image_edit_calls: z.number().optional().default(0), gemini_tokens: z.number(), openai_tokens: z.number(), anthropic_tokens: z.number(), diff --git a/frontend/src/utils/fetchMediaBlobUrl.ts b/frontend/src/utils/fetchMediaBlobUrl.ts new file mode 100644 index 00000000..b6f76824 --- /dev/null +++ b/frontend/src/utils/fetchMediaBlobUrl.ts @@ -0,0 +1,9 @@ +import { aiApiClient } from "../api/client"; + +export async function fetchMediaBlobUrl(pathOrUrl: string): Promise { + const rel = pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`; + const res = await aiApiClient.get(rel, { responseType: "blob" }); + return URL.createObjectURL(res.data); +} + + diff --git a/scripts/wix_reconsent_helper.py b/scripts/wix_reconsent_helper.py deleted file mode 100644 index b81fe750..00000000 --- a/scripts/wix_reconsent_helper.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Helper script to fetch the Wix OAuth re-consent URL for manual testing. - -This script does NOT change any backend behaviour. It simply calls the -unauthenticated `/api/wix/test/auth/url` endpoint (which already exists for -testing) to retrieve the authorization URL that includes all required scopes -and prints it to the console. Optionally it can open the URL in the default -web browser to start the re-consent flow. - -Usage: - - python scripts/wix_reconsent_helper.py --base-url http://localhost:8000 --open - -Options: - --base-url Base URL where the ALwrity backend is running. Defaults to - http://localhost:8000. - --open If provided, the script will attempt to open the URL in the - system default web browser after fetching it. -""" - -from __future__ import annotations - -import argparse -import sys -import webbrowser -from typing import Optional - -import requests - - -TEST_AUTH_ENDPOINT = "/api/wix/test/auth/url" - - -def fetch_authorization_url(base_url: str) -> str: - """Fetch the Wix authorization URL from the test endpoint.""" - - endpoint = base_url.rstrip("/") + TEST_AUTH_ENDPOINT - try: - response = requests.get(endpoint, timeout=10) - response.raise_for_status() - except requests.RequestException as exc: # pragma: no cover - simple helper - raise SystemExit(f"Failed to fetch authorization URL: {exc}") from exc - - payload = response.json() or {} - url: Optional[str] = payload.get("url") or payload.get("authorization_url") - if not url: - raise SystemExit( - "Test endpoint did not return an authorization URL. " - "Ensure the backend is running and the endpoint is available." - ) - - # Provide a small summary for the caller. - scope = None - if "scope=" in url: - scope = url.split("scope=")[-1].split("&")[0] - - print("✅ Wix authorization URL fetched successfully:\n") - print(url) - if scope: - print("\nScopes requested:") - for item in scope.split(","): - print(f" - {item}") - - return url - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Fetch Wix OAuth re-consent URL") - parser.add_argument( - "--base-url", - default="http://localhost:8000", - help="Base URL for the ALwrity backend (default: http://localhost:8000)", - ) - parser.add_argument( - "--open", - action="store_true", - help="Open the fetched URL in the default web browser", - ) - return parser.parse_args(argv) - - -def main(argv: Optional[list[str]] = None) -> None: - args = parse_args(argv or sys.argv[1:]) - url = fetch_authorization_url(args.base_url) - - if args.open: - print("\nOpening web browser for re-consent...") - webbrowser.open(url) - - -if __name__ == "__main__": # pragma: no cover - script entry point - main()