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
This commit is contained in:
ajaysi
2026-04-20 12:55:25 +05:30
parent 7f7279f903
commit 625dd550d3
6 changed files with 40 additions and 33 deletions

View File

@@ -86,6 +86,8 @@ async def get_latest_avatar(
try: try:
user_id = _extract_user_id(current_user) 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: # Search for assets that are either:
# 1. Saved with source_module=BRAND_AVATAR_GENERATOR (new) # 1. Saved with source_module=BRAND_AVATAR_GENERATOR (new)
# 2. Saved with source_module=STORY_WRITER but have metadata category='brand_avatar' (legacy) # 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() ).order_by(desc(ContentAsset.created_at)).limit(50).all()
logger.info(f"[latest-avatar] Found {len(candidates)} candidate(s)")
asset = None asset = None
for candidate in candidates: for candidate in candidates:
# Check for direct match (new assets) # Check for direct match (new assets)

View File

@@ -63,6 +63,7 @@ def _build_research_cost_estimate(
raw_content: str, raw_content: str,
sources_count: int, sources_count: int,
provider_result: Dict[str, Any], provider_result: Dict[str, Any],
user_id: str = "default",
) -> PodcastCostEst: ) -> PodcastCostEst:
# Fallback defaults mirror current catalog defaults. # Fallback defaults mirror current catalog defaults.
exa_per_request = 0.005 exa_per_request = 0.005
@@ -70,17 +71,19 @@ def _build_research_cost_estimate(
gemini_out_token = 0.0000006 gemini_out_token = 0.0000006
try: try:
db = next(get_db()) from services.database import get_session_for_user
try: db = get_session_for_user(user_id)
pricing_service = PricingService(db) if db:
exa_per_request = _get_price_from_catalog( try:
pricing_service, APIProvider.EXA, "exa-search", "cost_per_request", exa_per_request pricing_service = PricingService(db)
) exa_per_request = _get_price_from_catalog(
gemini_pricing = pricing_service.get_pricing_for_provider_model(APIProvider.GEMINI, "gemini-2.5-flash") or {} pricing_service, APIProvider.EXA, "exa-search", "cost_per_request", exa_per_request
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) gemini_pricing = pricing_service.get_pricing_for_provider_model(APIProvider.GEMINI, "gemini-2.5-flash") or {}
finally: gemini_in_token = float(gemini_pricing.get("cost_per_input_token") or gemini_in_token)
db.close() gemini_out_token = float(gemini_pricing.get("cost_per_output_token") or gemini_out_token)
finally:
db.close()
except Exception as pricing_err: except Exception as pricing_err:
logger.warning(f"[Podcast Research] Failed loading pricing catalog; using defaults: {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, raw_content=raw_content,
sources_count=len(sources_payload), sources_count=len(sources_payload),
provider_result=result if isinstance(result, dict) else {}, provider_result=result if isinstance(result, dict) else {},
user_id=user_id,
), ),
search_type=result.get("search_type") if isinstance(result, dict) else None, search_type=result.get("search_type") if isinstance(result, dict) else None,
provider=result.get("provider", "exa") if isinstance(result, dict) else "exa", provider=result.get("provider", "exa") if isinstance(result, dict) else "exa",

View File

@@ -47,6 +47,7 @@ pandas>=2.0.0
# Image/media for podcast # Image/media for podcast
Pillow>=10.0.0 Pillow>=10.0.0
matplotlib>=3.7.0
huggingface_hub>=1.1.4 huggingface_hub>=1.1.4
# TTS for podcast # TTS for podcast

View File

@@ -314,11 +314,14 @@ class ExaResearchProvider(BaseProvider):
def track_exa_usage(self, user_id: str, cost: float): def track_exa_usage(self, user_id: str, cost: float):
"""Track Exa API usage after successful call.""" """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 services.subscription import PricingService
from sqlalchemy import text 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: try:
pricing_service = PricingService(db) pricing_service = PricingService(db)
current_period = pricing_service.get_current_billing_period(user_id) current_period = pricing_service.get_current_billing_period(user_id)

View File

@@ -8,11 +8,11 @@ import numpy as np
from pathlib import Path from pathlib import Path
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional from typing import Optional
from PIL import Image, ImageDraw, ImageFont
import matplotlib import matplotlib
matplotlib.use("Agg") matplotlib.use("Agg")
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.patches as mpatches import matplotlib.patches as mpatches
from PIL import Image, ImageDraw, ImageFont
from moviepy import ( from moviepy import (
VideoFileClip, ImageClip, CompositeVideoClip, VideoFileClip, ImageClip, CompositeVideoClip,
concatenate_videoclips, concatenate_videoclips,

View File

@@ -14,27 +14,22 @@ interface BrollInfoPanelProps {
} }
export const BrollInfoPanel: React.FC<BrollInfoPanelProps> = (props) => { export const BrollInfoPanel: React.FC<BrollInfoPanelProps> = (props) => {
let contextValue: ReturnType<typeof useScriptEditor> | null = null; const ctx = useScriptEditor();
try {
contextValue = useScriptEditor();
} catch {
contextValue = null;
}
const { const {
activeScript, activeScript: ctxActiveScript,
generatingChartId, generatingChartId: ctxGeneratingChartId,
generateChartPreviews, generateChartPreviews: ctxGenerateChartPreviews,
regenerateChart, regenerateChart: ctxRegenerateChart,
removeChart, removeChart: ctxRemoveChart,
scenesWithCharts scenesWithCharts: ctxScenesWithCharts
} = contextValue ?? {}; } = ctx;
const resolvedActiveScript = props.activeScript ?? activeScript; const resolvedActiveScript = props.activeScript ?? ctxActiveScript;
const resolvedGeneratingChartId = props.generatingChartId ?? generatingChartId; const resolvedGeneratingChartId = props.generatingChartId ?? ctxGeneratingChartId;
const resolvedGenerateChartPreviews = props.generateChartPreviews ?? generateChartPreviews; const resolvedGenerateChartPreviews = props.generateChartPreviews ?? ctxGenerateChartPreviews;
const resolvedRegenerateChart = props.regenerateChart ?? regenerateChart; const resolvedRegenerateChart = props.regenerateChart ?? ctxRegenerateChart;
const resolvedRemoveChart = props.removeChart ?? removeChart; const resolvedRemoveChart = props.removeChart ?? ctxRemoveChart;
if (!resolvedActiveScript || resolvedActiveScript.scenes.length === 0) { if (!resolvedActiveScript || resolvedActiveScript.scenes.length === 0) {
return null; return null;
@@ -42,7 +37,7 @@ export const BrollInfoPanel: React.FC<BrollInfoPanelProps> = (props) => {
const scenesWithData = resolvedActiveScript.scenes.filter(s => s.chart_data && Object.keys(s.chart_data).length > 0); const scenesWithData = resolvedActiveScript.scenes.filter(s => s.chart_data && Object.keys(s.chart_data).length > 0);
const hasChartData = scenesWithData.length > 0; const hasChartData = scenesWithData.length > 0;
const resolvedScenesWithCharts = props.scenesWithCharts ?? scenesWithCharts ?? scenesWithData.length; const resolvedScenesWithCharts = props.scenesWithCharts ?? ctxScenesWithCharts ?? scenesWithData.length;
return ( return (
<Paper <Paper