AI Story Writer Backend Migration Complete, Frontend UI Components Added
This commit is contained in:
@@ -696,15 +696,15 @@ async def generate_scene_audio(
|
||||
audio_filename = result.get("audio_filename") or ""
|
||||
|
||||
audio_models.append(
|
||||
StoryAudioResult(
|
||||
scene_number=result.get("scene_number", 0),
|
||||
scene_title=result.get("scene_title", "Untitled"),
|
||||
StoryAudioResult(
|
||||
scene_number=result.get("scene_number", 0),
|
||||
scene_title=result.get("scene_title", "Untitled"),
|
||||
audio_filename=audio_filename,
|
||||
audio_url=audio_url,
|
||||
provider=result.get("provider", "unknown"),
|
||||
file_size=result.get("file_size", 0),
|
||||
error=result.get("error")
|
||||
)
|
||||
provider=result.get("provider", "unknown"),
|
||||
file_size=result.get("file_size", 0),
|
||||
error=result.get("error")
|
||||
)
|
||||
)
|
||||
|
||||
return StoryAudioGenerationResponse(
|
||||
|
||||
@@ -438,6 +438,45 @@ def llm_text_gen(prompt: str, system_prompt: Optional[str] = None, json_struct:
|
||||
current_tokens_before = 0
|
||||
new_tokens = 0
|
||||
|
||||
# Determine tracked tokens (after any safety capping)
|
||||
tracked_tokens_input = min(tokens_input, tokens_total)
|
||||
tracked_tokens_output = max(tokens_total - tracked_tokens_input, 0)
|
||||
|
||||
# Calculate and persist cost for this call
|
||||
try:
|
||||
cost_info = pricing.calculate_api_cost(
|
||||
provider=provider_enum,
|
||||
model_name=model,
|
||||
tokens_input=tracked_tokens_input,
|
||||
tokens_output=tracked_tokens_output,
|
||||
request_count=1
|
||||
)
|
||||
cost_total = cost_info.get('cost_total', 0.0) or 0.0
|
||||
except Exception as cost_error:
|
||||
cost_total = 0.0
|
||||
logger.error(f"[llm_text_gen] ❌ Failed to calculate API cost: {cost_error}", exc_info=True)
|
||||
|
||||
if cost_total > 0:
|
||||
logger.debug(f"[llm_text_gen] 💰 Calculated cost for {provider_name}: ${cost_total:.6f}")
|
||||
update_costs_query = text(f"""
|
||||
UPDATE usage_summaries
|
||||
SET {provider_name}_cost = COALESCE({provider_name}_cost, 0) + :cost,
|
||||
total_cost = COALESCE(total_cost, 0) + :cost
|
||||
WHERE user_id = :user_id AND billing_period = :period
|
||||
""")
|
||||
db_track.execute(update_costs_query, {
|
||||
'cost': cost_total,
|
||||
'user_id': user_id,
|
||||
'period': current_period
|
||||
})
|
||||
|
||||
# Keep ORM object in sync for logging/debugging
|
||||
current_provider_cost = getattr(summary, f"{provider_name}_cost", 0.0) or 0.0
|
||||
setattr(summary, f"{provider_name}_cost", current_provider_cost + cost_total)
|
||||
summary.total_cost = (summary.total_cost or 0.0) + cost_total
|
||||
else:
|
||||
logger.debug(f"[llm_text_gen] 💰 Cost calculation returned $0 for {provider_name} (tokens_input={tracked_tokens_input}, tokens_output={tracked_tokens_output})")
|
||||
|
||||
# Update totals using SQL UPDATE
|
||||
old_total_calls = summary.total_calls or 0
|
||||
old_total_tokens = summary.total_tokens or 0
|
||||
@@ -717,6 +756,39 @@ def llm_text_gen(prompt: str, system_prompt: Optional[str] = None, json_struct:
|
||||
current_tokens_before = 0
|
||||
new_tokens = 0
|
||||
|
||||
# Determine tracked tokens after any safety capping
|
||||
tracked_tokens_input = min(tokens_input, tokens_total)
|
||||
tracked_tokens_output = max(tokens_total - tracked_tokens_input, 0)
|
||||
|
||||
# Calculate and persist cost for this fallback call
|
||||
cost_total = 0.0
|
||||
try:
|
||||
cost_info = pricing.calculate_api_cost(
|
||||
provider=provider_enum,
|
||||
model_name=fallback_model,
|
||||
tokens_input=tracked_tokens_input,
|
||||
tokens_output=tracked_tokens_output,
|
||||
request_count=1
|
||||
)
|
||||
cost_total = cost_info.get('cost_total', 0.0) or 0.0
|
||||
except Exception as cost_error:
|
||||
logger.error(f"[llm_text_gen] ❌ Failed to calculate fallback cost: {cost_error}", exc_info=True)
|
||||
|
||||
if cost_total > 0:
|
||||
update_costs_query = text(f"""
|
||||
UPDATE usage_summaries
|
||||
SET {provider_name}_cost = COALESCE({provider_name}_cost, 0) + :cost,
|
||||
total_cost = COALESCE(total_cost, 0) + :cost
|
||||
WHERE user_id = :user_id AND billing_period = :period
|
||||
""")
|
||||
db_track.execute(update_costs_query, {
|
||||
'cost': cost_total,
|
||||
'user_id': user_id,
|
||||
'period': current_period
|
||||
})
|
||||
setattr(summary, f"{provider_name}_cost", (getattr(summary, f"{provider_name}_cost", 0.0) or 0.0) + cost_total)
|
||||
summary.total_cost = (summary.total_cost or 0.0) + cost_total
|
||||
|
||||
# Update totals (using potentially capped tokens_total from safety check)
|
||||
summary.total_calls = (summary.total_calls or 0) + 1
|
||||
summary.total_tokens = (summary.total_tokens or 0) + tokens_total
|
||||
|
||||
@@ -72,11 +72,11 @@ class StoryAudioGenerationService:
|
||||
logger.info(f"[StoryAudioGeneration] Generated audio using gTTS: {output_path}")
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
logger.error("[StoryAudioGeneration] gTTS not installed. Install with: pip install gtts")
|
||||
except ImportError as e:
|
||||
logger.error(f"[StoryAudioGeneration] gTTS not installed. ImportError: {e}. Install with: pip install gtts")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"[StoryAudioGeneration] Error generating audio with gTTS: {e}")
|
||||
logger.error(f"[StoryAudioGeneration] Error generating audio with gTTS: {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
def _generate_audio_pyttsx3(
|
||||
|
||||
@@ -72,8 +72,40 @@ class StoryVideoGenerationService:
|
||||
|
||||
# Import MoviePy
|
||||
try:
|
||||
from moviepy.editor import ImageClip, AudioFileClip, concatenate_videoclips, CompositeVideoClip
|
||||
except ImportError:
|
||||
# MoviePy v2.x exposes classes at top-level (moviepy.ImageClip, etc)
|
||||
from moviepy import ImageClip, AudioFileClip, concatenate_videoclips
|
||||
except Exception as _imp_err:
|
||||
# Detailed diagnostics to help users fix environment issues
|
||||
try:
|
||||
import sys as _sys
|
||||
import platform as _platform
|
||||
import importlib
|
||||
mv = None
|
||||
imv = None
|
||||
ff_path = "unresolved"
|
||||
try:
|
||||
mv = importlib.import_module("moviepy")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
imv = importlib.import_module("imageio")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import imageio_ffmpeg as _iff
|
||||
ff_path = _iff.get_ffmpeg_exe()
|
||||
except Exception:
|
||||
pass
|
||||
logger.error(
|
||||
"[StoryVideoGeneration] MoviePy import failed. "
|
||||
f"py={_sys.executable} plat={_platform.platform()} "
|
||||
f"moviepy_ver={getattr(mv,'__version__', 'NA')} "
|
||||
f"imageio_ver={getattr(imv,'__version__', 'NA')} "
|
||||
f"ffmpeg_path={ff_path} err={_imp_err}"
|
||||
)
|
||||
except Exception:
|
||||
# best-effort diagnostics
|
||||
pass
|
||||
logger.error("[StoryVideoGeneration] MoviePy not installed. Install with: pip install moviepy imageio imageio-ffmpeg")
|
||||
raise RuntimeError("MoviePy is not installed. Please install it to generate videos.")
|
||||
|
||||
@@ -182,8 +214,38 @@ class StoryVideoGenerationService:
|
||||
|
||||
# Import MoviePy
|
||||
try:
|
||||
from moviepy.editor import ImageClip, AudioFileClip, concatenate_videoclips, CompositeVideoClip
|
||||
except ImportError:
|
||||
from moviepy import ImageClip, AudioFileClip, concatenate_videoclips
|
||||
except Exception as _imp_err:
|
||||
# Detailed diagnostics to help users fix environment issues
|
||||
try:
|
||||
import sys as _sys
|
||||
import platform as _platform
|
||||
import importlib
|
||||
mv = None
|
||||
imv = None
|
||||
ff_path = "unresolved"
|
||||
try:
|
||||
mv = importlib.import_module("moviepy")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
imv = importlib.import_module("imageio")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import imageio_ffmpeg as _iff
|
||||
ff_path = _iff.get_ffmpeg_exe()
|
||||
except Exception:
|
||||
pass
|
||||
logger.error(
|
||||
"[StoryVideoGeneration] MoviePy import failed. "
|
||||
f"py={_sys.executable} plat={_platform.platform()} "
|
||||
f"moviepy_ver={getattr(mv,'__version__', 'NA')} "
|
||||
f"imageio_ver={getattr(imv,'__version__', 'NA')} "
|
||||
f"ffmpeg_path={ff_path} err={_imp_err}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.error("[StoryVideoGeneration] MoviePy not installed. Install with: pip install moviepy imageio imageio-ffmpeg")
|
||||
raise RuntimeError("MoviePy is not installed. Please install it to generate videos.")
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/story_audio/scene_4_Gravity_s_Gentle_Pull_496a7494.mp3
Normal file
BIN
backend/story_audio/scene_4_Gravity_s_Gentle_Pull_496a7494.mp3
Normal file
Binary file not shown.
BIN
backend/story_audio/scene_4_Gravity_s_Gentle_Pull_a1f7e80d.mp3
Normal file
BIN
backend/story_audio/scene_4_Gravity_s_Gentle_Pull_a1f7e80d.mp3
Normal file
Binary file not shown.
BIN
backend/story_audio/scene_4_Gravity_s_Gentle_Pull_fdd8a3cb.mp3
Normal file
BIN
backend/story_audio/scene_4_Gravity_s_Gentle_Pull_fdd8a3cb.mp3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/story_audio/scene_6_The_Birth_of_a_New_Star_0ac0e570.mp3
Normal file
BIN
backend/story_audio/scene_6_The_Birth_of_a_New_Star_0ac0e570.mp3
Normal file
Binary file not shown.
BIN
backend/story_audio/scene_6_The_Birth_of_a_New_Star_245475df.mp3
Normal file
BIN
backend/story_audio/scene_6_The_Birth_of_a_New_Star_245475df.mp3
Normal file
Binary file not shown.
BIN
backend/story_audio/scene_6_The_Birth_of_a_New_Star_80b3f63a.mp3
Normal file
BIN
backend/story_audio/scene_6_The_Birth_of_a_New_Star_80b3f63a.mp3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user