- Fix text selection menu not showing: wire contentRef via inputRef on multiline TextField
- Fix blog title not truncating: add min-w-0 for flex item overflow
- Fix outline generation 500: escape curly braces in f-string prompt template
- Fix content generation 'NoneType not callable': replace SessionLocal() with get_session_for_user(), add db param to MediumBlogGenerator, fix signature mismatch in database_task_manager
- Fix writing assistant suggest 500: add auth + user_id to API endpoint and service, replace sync requests with httpx.AsyncClient
- Fix hallucination detector 404: explicitly include router in main.py and app.py
- Fix missing error_data in task failure responses
- Hide CopilotKit web inspector button
- Remove hardcoded fallback suggestions from SmartTypingAssist
- Fix stale closure refs in SmartTypingAssist handleTypingChange
- Add two-column editor layout, stats bar, section hover menu
- Various subscription, billing, and research module improvements
Backend:
- product_image_service.py: Replaced direct wavespeed_client.generate_image()
with generate_image() from main_image_generation (unified entry point)
- This ensures subscription pre-flight validation (_validate_image_operation)
and usage tracking (_track_image_operation_usage) are enforced
- Removed _generate_image_with_retry method and WaveSpeedClient dependency
- Animation/video/avatar services already route through ImageStudioManager - no changes needed
Frontend:
- useProductMarketing.ts: Added formatError() helper for 402/429 detection
across all 8 API operations
- useCampaignCreator.ts: Added formatError() helper for 402/429 detection
across all 13 API operations
- All error messages now surface subscription limits with upgrade prompts
Backend:
- New POST /api/image-studio/save-to-library endpoint
Saves generated base64 images to workspace disk and creates ContentAsset
record for the unified asset library. Returns asset_id, file_url, filename.
Frontend:
- Added saveImageToLibrary() to useImageStudio hook
- CreateStudio auto-saves generated images to asset library after creation
- All 8 API operations now use _formatErrorMessage() helper
for 402/429 subscription limit errors with upgrade prompts
instead of generic error messages
Extracted 2 endpoint groups into separate sub-router modules:
- edit.py: 4 endpoints (POST /edit/process, GET /edit/operations, GET /edit/models, POST /edit/recommend)
- face_swap.py: 3 endpoints (POST /face-swap/process, GET /face-swap/models, POST /face-swap/recommend)
All 33 routes preserved (10 extracted in B1, 7 extracted in B2, 16 remaining in legacy).
Extracted 4 endpoint groups into separate sub-router modules:
- health.py: 1 endpoint (GET /health)
- upscale.py: 1 endpoint (POST /upscale)
- control.py: 2 endpoints (POST /control/process, GET /control/operations)
- social.py: 2 endpoints (POST /social/optimize, GET /social/platforms/{platform}/formats)
__init__.py now composes these sub-routers into the legacy router.
All 33 routes preserved. No functional changes.
- Created routers/image_studio/models.py with all 40 Pydantic models
- Created routers/image_studio/deps.py with get_studio_manager() and _require_user_id()
- Renamed old monolithic image_studio.py -> image_studio_router.py
- Updated __init__.py to re-export the router for backward compatibility
- Old file now imports models and deps from new modules (no inline definitions)
Backward compatibility: from routers.image_studio import router still works.
Route count unchanged: 33 routes, prefix /api/image-studio.
Changes:
1. helpers.py (_track_image_operation_usage): Map provider name to DB columns
dynamically (stability→stability_calls, wavespeed→wavespeed_calls, etc.)
instead of hardcoding stability_calls/stability_cost.
2. upscale_service.py: Added _track_image_operation_usage() call after
successful Stability upscale completion.
3. control_service.py: Added _track_image_operation_usage() call after
successful Stability control operation completion.
4. edit_service.py: Added _track_image_operation_usage() call after
successful Stability edit operation (remove_background, inpaint,
outpaint, search_replace, search_recolor, relight).
Previously only Create Studio and Face Swap tracked usage. Now all five
studios correctly decrement subscription limits.
The podcast B-roll composer imports matplotlib for chart rendering, so adding it to backend requirements prevents import failures in fresh setups.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Move voice clone cache from module-level memory to localStorage
so it survives page refresh and works across browser tabs
- VoiceAvatarPlaceholder now syncs clone result to localStorage
immediately after creation (both design and clone paths)
- usePodcastProjectState auto-merges voice clone cache into project
knobs when loading a project (fills gap for projects created
before voice clone or when voice clone was created after)
- CreateModal now detects voice clone IDs by prefix (vc_*) not
just by VOICE_CLONE_ID constant, fixing the mismatch where
VoiceSelector passes the actual clone ID but CreateModal
expected the placeholder ID
- AudioRegenerateModal is intentionally per-scene override and
does not write back to knobs (by design)
- trends.py handler added for podcast topic trend analysis
Frontend Changes:
- Add scene numbering badge (1/N) next to scene titles
- Add inline status chips (Complete, Audio, Image, Voice, Why Script)
- Professional AI-like gradient styling for all chips with shadows
- Remove Script Editor header and 'Why This Script Format?' collapsible
- Move Voice and Why Script info to per-scene chips
- Make scene section mobile-responsive (responsive layout, button sizing)
- Rename 'B-Roll Charts' to 'Podcast Charts' with accordion (collapsed by default)
- Add sceneIndex prop to SceneEditor for scene numbering
- Enhanced accessibility with keyboard navigation and focus states
Backend Changes:
- Audio handler improvements
- B-roll handler enhancements
- Script handler updates
- B-roll composer and service improvements
- Removed temporary broll_temp files
Technical:
- Full mobile responsiveness for scene cards
- Gradient chip styling: vibrant colors with white text and shadows
- Non-breaking approval/generation flow preserved
- TypeScript compatibility maintained
- Restore auth on assets_serving.py using get_current_user_with_query_token
(supports ?token= query param for <audio> elements)
- Add proper MIME type detection on asset serving (fixes NotSupportedError)
- Use storage_paths for path resolution in assets_serving.py
- VoiceSelector: append auth token to preview URLs for /api/ endpoints
- VoiceAvatarPlaceholder: add authenticatedAudioUrl state with async token
resolution so <audio> elements get ?token= query param
- TestPersonaModal: same auth token pattern for voice preview audio
- Fix voice clone preview saved as .wav regardless of actual format (MP3/WebM
content from WaveSpeed was saved with .wav extension causing NotSupportedError)
- Add detect_audio_format() and ensure_audio_extension() to media_utils
- Fix assets_serving.py: use storage_paths for root resolution, add proper
MIME types to FileResponse, add auth via query token for <audio> elements
- Fix assets_serving.py: add path traversal security check
- Fix step4_asset_routes.py: use get_user_workspace() instead of WORKSPACE_DIR,
detect actual audio format before saving preview
- Fix get_db() in database.py: raise HTTPException(401) instead of raw Exception,
catch engine creation failures with HTTPException(503)
- Fix avatar.py: add auth error handling, diagnostic logging for path resolution,
graceful DB save degradation
- Upgrade utils/storage_paths.py with robust find_repo_root() (env var override + validation + fallback)
- Remove broken _find_root() from podcast/constants.py, import from storage_paths instead
- Fix ROOT_DIR resolving to backend/ instead of project root (caused avatar upload 500s on Render.com)
- Fix video_combination_service.py default output dir (was writing to data/media instead of workspace)
- Add deprecation comments to global data/media constants in media_utils.py
- Pass user_id through resolve_media_path for tenant-scoped podcast resolution
- Add ALWRITY_ROOT_DIR env var support for explicit production overrides
- Log warning when get_podcast_media_dir called without user_id
- Use OperationButton with cost display for scene action buttons
- Voice clone integration: When user selects voice clone in Write phase,
backend uses their uploaded voice sample + scene script text to generate
audio via qwen3/minimax/cosyvoice voice clone APIs
- Multi-tenant workspace storage: All podcast assets (audio, video, images,
charts) now use workspace-specific directories per user
- Chart preview improvements: Card-based B-Roll charts UI with thumbnails,
takeaway text, and action buttons; public endpoint for image serving
- Voice clone caching: In-memory LRU cache for voice samples (avoids
re-downloading per scene); frontend caches voice clone metadata
- Thread pool for voice clone: Audio generation uses ThreadPoolExecutor to
avoid blocking the FastAPI event loop
- Auto-detect voice clone IDs (vc_*, MY_VOICE_CLONE) to route correctly
- DB fallback for voice sample URL: Fetches from ContentAsset if not passed
- Fixed API URL resolution for chart previews
- Fixed GlassyCard DOM warnings for motion props
- Fixed ScriptGenerationProgressView syntax error
- Fixed usePodcastWorkflow scriptData reference
The save_file_safely function may add a UUID suffix to the filename if there's
a collision, but the preview URL was being constructed using the original
filename instead of the actual saved filename. Now uses saved_preview_path.name.