From 625dd550d39f01dcf182e2832bf56aa6cfcb4ab0 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Mon, 20 Apr 2026 12:55:25 +0530 Subject: [PATCH] Fix production issues: add matplotlib dep, fix get_db calls, resolve ESLint - Add matplotlib>=3.7.0 to requirements-podcast.txt (B-roll requires it) - Fix research.py and exa_provider.py using get_session_for_user() instead of broken Depends(get_db) - Fix BrollInfoPanel.tsx: call useScriptEditor hook unconditionally - Add debug logging to avatar endpoint for troubleshooting --- .../onboarding_utils/step4_asset_routes.py | 4 +++ backend/api/podcast/handlers/research.py | 26 ++++++++------- backend/requirements-podcast.txt | 1 + .../blog_writer/research/exa_provider.py | 7 ++-- backend/services/podcast/broll_composer.py | 2 +- .../ScriptEditor/parts/BrollInfoPanel.tsx | 33 ++++++++----------- 6 files changed, 40 insertions(+), 33 deletions(-) diff --git a/backend/api/onboarding_utils/step4_asset_routes.py b/backend/api/onboarding_utils/step4_asset_routes.py index 86845961..33675c50 100644 --- a/backend/api/onboarding_utils/step4_asset_routes.py +++ b/backend/api/onboarding_utils/step4_asset_routes.py @@ -86,6 +86,8 @@ async def get_latest_avatar( try: user_id = _extract_user_id(current_user) + logger.info(f"[latest-avatar] Looking for avatar for user_id: {user_id}") + # Search for assets that are either: # 1. Saved with source_module=BRAND_AVATAR_GENERATOR (new) # 2. Saved with source_module=STORY_WRITER but have metadata category='brand_avatar' (legacy) @@ -100,6 +102,8 @@ async def get_latest_avatar( ]) ).order_by(desc(ContentAsset.created_at)).limit(50).all() + logger.info(f"[latest-avatar] Found {len(candidates)} candidate(s)") + asset = None for candidate in candidates: # Check for direct match (new assets) diff --git a/backend/api/podcast/handlers/research.py b/backend/api/podcast/handlers/research.py index 143364a0..bfe327a6 100644 --- a/backend/api/podcast/handlers/research.py +++ b/backend/api/podcast/handlers/research.py @@ -63,6 +63,7 @@ def _build_research_cost_estimate( raw_content: str, sources_count: int, provider_result: Dict[str, Any], + user_id: str = "default", ) -> PodcastCostEst: # Fallback defaults mirror current catalog defaults. exa_per_request = 0.005 @@ -70,17 +71,19 @@ def _build_research_cost_estimate( gemini_out_token = 0.0000006 try: - db = next(get_db()) - try: - pricing_service = PricingService(db) - exa_per_request = _get_price_from_catalog( - pricing_service, APIProvider.EXA, "exa-search", "cost_per_request", exa_per_request - ) - gemini_pricing = pricing_service.get_pricing_for_provider_model(APIProvider.GEMINI, "gemini-2.5-flash") or {} - gemini_in_token = float(gemini_pricing.get("cost_per_input_token") or gemini_in_token) - gemini_out_token = float(gemini_pricing.get("cost_per_output_token") or gemini_out_token) - finally: - db.close() + from services.database import get_session_for_user + db = get_session_for_user(user_id) + if db: + try: + pricing_service = PricingService(db) + exa_per_request = _get_price_from_catalog( + pricing_service, APIProvider.EXA, "exa-search", "cost_per_request", exa_per_request + ) + gemini_pricing = pricing_service.get_pricing_for_provider_model(APIProvider.GEMINI, "gemini-2.5-flash") or {} + gemini_in_token = float(gemini_pricing.get("cost_per_input_token") or gemini_in_token) + gemini_out_token = float(gemini_pricing.get("cost_per_output_token") or gemini_out_token) + finally: + db.close() except Exception as pricing_err: logger.warning(f"[Podcast Research] Failed loading pricing catalog; using defaults: {pricing_err}") @@ -431,6 +434,7 @@ QUALITY STANDARDS: raw_content=raw_content, sources_count=len(sources_payload), provider_result=result if isinstance(result, dict) else {}, + user_id=user_id, ), search_type=result.get("search_type") if isinstance(result, dict) else None, provider=result.get("provider", "exa") if isinstance(result, dict) else "exa", diff --git a/backend/requirements-podcast.txt b/backend/requirements-podcast.txt index 013c0adb..6cdfb7c1 100644 --- a/backend/requirements-podcast.txt +++ b/backend/requirements-podcast.txt @@ -47,6 +47,7 @@ pandas>=2.0.0 # Image/media for podcast Pillow>=10.0.0 +matplotlib>=3.7.0 huggingface_hub>=1.1.4 # TTS for podcast diff --git a/backend/services/blog_writer/research/exa_provider.py b/backend/services/blog_writer/research/exa_provider.py index 11857444..4330a23a 100644 --- a/backend/services/blog_writer/research/exa_provider.py +++ b/backend/services/blog_writer/research/exa_provider.py @@ -314,11 +314,14 @@ class ExaResearchProvider(BaseProvider): def track_exa_usage(self, user_id: str, cost: float): """Track Exa API usage after successful call.""" - from services.database import get_db + from services.database import get_session_for_user from services.subscription import PricingService from sqlalchemy import text - db = next(get_db()) + db = get_session_for_user(user_id) + if not db: + logger.warning(f"[track_exa_usage] Could not get DB session for user {user_id}") + return try: pricing_service = PricingService(db) current_period = pricing_service.get_current_billing_period(user_id) diff --git a/backend/services/podcast/broll_composer.py b/backend/services/podcast/broll_composer.py index 1281ca22..1fb62901 100644 --- a/backend/services/podcast/broll_composer.py +++ b/backend/services/podcast/broll_composer.py @@ -8,11 +8,11 @@ import numpy as np from pathlib import Path from dataclasses import dataclass, field from typing import Optional +from PIL import Image, ImageDraw, ImageFont import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib.patches as mpatches -from PIL import Image, ImageDraw, ImageFont from moviepy import ( VideoFileClip, ImageClip, CompositeVideoClip, concatenate_videoclips, diff --git a/frontend/src/components/PodcastMaker/ScriptEditor/parts/BrollInfoPanel.tsx b/frontend/src/components/PodcastMaker/ScriptEditor/parts/BrollInfoPanel.tsx index c27c1f8c..3a0d4bd7 100644 --- a/frontend/src/components/PodcastMaker/ScriptEditor/parts/BrollInfoPanel.tsx +++ b/frontend/src/components/PodcastMaker/ScriptEditor/parts/BrollInfoPanel.tsx @@ -14,27 +14,22 @@ interface BrollInfoPanelProps { } export const BrollInfoPanel: React.FC = (props) => { - let contextValue: ReturnType | null = null; - try { - contextValue = useScriptEditor(); - } catch { - contextValue = null; - } + const ctx = useScriptEditor(); const { - activeScript, - generatingChartId, - generateChartPreviews, - regenerateChart, - removeChart, - scenesWithCharts - } = contextValue ?? {}; + activeScript: ctxActiveScript, + generatingChartId: ctxGeneratingChartId, + generateChartPreviews: ctxGenerateChartPreviews, + regenerateChart: ctxRegenerateChart, + removeChart: ctxRemoveChart, + scenesWithCharts: ctxScenesWithCharts + } = ctx; - const resolvedActiveScript = props.activeScript ?? activeScript; - const resolvedGeneratingChartId = props.generatingChartId ?? generatingChartId; - const resolvedGenerateChartPreviews = props.generateChartPreviews ?? generateChartPreviews; - const resolvedRegenerateChart = props.regenerateChart ?? regenerateChart; - const resolvedRemoveChart = props.removeChart ?? removeChart; + const resolvedActiveScript = props.activeScript ?? ctxActiveScript; + const resolvedGeneratingChartId = props.generatingChartId ?? ctxGeneratingChartId; + const resolvedGenerateChartPreviews = props.generateChartPreviews ?? ctxGenerateChartPreviews; + const resolvedRegenerateChart = props.regenerateChart ?? ctxRegenerateChart; + const resolvedRemoveChart = props.removeChart ?? ctxRemoveChart; if (!resolvedActiveScript || resolvedActiveScript.scenes.length === 0) { return null; @@ -42,7 +37,7 @@ export const BrollInfoPanel: React.FC = (props) => { const scenesWithData = resolvedActiveScript.scenes.filter(s => s.chart_data && Object.keys(s.chart_data).length > 0); const hasChartData = scenesWithData.length > 0; - const resolvedScenesWithCharts = props.scenesWithCharts ?? scenesWithCharts ?? scenesWithData.length; + const resolvedScenesWithCharts = props.scenesWithCharts ?? ctxScenesWithCharts ?? scenesWithData.length; return (