feat: enhance video presentation agent with parallel theme assignment and watermarking

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-03-21 23:02:09 -07:00
parent 0fe5e034fe
commit d90b6d35ce
9 changed files with 123 additions and 197 deletions

View file

@ -4,60 +4,15 @@ Podcast generation tool for the SurfSense agent.
This module provides a factory function for creating the generate_podcast tool
that submits a Celery task for background podcast generation. The frontend
polls for completion and auto-updates when the podcast is ready.
Duplicate request prevention:
- Only one podcast can be generated at a time per search space
- Uses Redis to track active podcast tasks
- Returns a friendly message if a podcast is already being generated
"""
from typing import Any
import redis
from langchain_core.tools import tool
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import config
from app.db import Podcast, PodcastStatus
# Redis connection for tracking active podcast tasks
# Defaults to the Celery broker when REDIS_APP_URL is not set
REDIS_URL = config.REDIS_APP_URL
_redis_client: redis.Redis | None = None
def get_redis_client() -> redis.Redis:
"""Get or create Redis client for podcast task tracking."""
global _redis_client
if _redis_client is None:
_redis_client = redis.from_url(REDIS_URL, decode_responses=True)
return _redis_client
def _redis_key(search_space_id: int) -> str:
return f"podcast:generating:{search_space_id}"
def get_generating_podcast_id(search_space_id: int) -> int | None:
"""Get the podcast ID currently being generated for this search space."""
try:
client = get_redis_client()
value = client.get(_redis_key(search_space_id))
return int(value) if value else None
except Exception:
return None
def set_generating_podcast(search_space_id: int, podcast_id: int) -> None:
"""Mark a podcast as currently generating for this search space."""
try:
client = get_redis_client()
client.setex(_redis_key(search_space_id), 1800, str(podcast_id))
except Exception as e:
print(
f"[generate_podcast] Warning: Could not set generating podcast in Redis: {e}"
)
def create_generate_podcast_tool(
search_space_id: int,
@ -109,18 +64,6 @@ def create_generate_podcast_tool(
- message: Status message (or "error" field if status is failed)
"""
try:
generating_podcast_id = get_generating_podcast_id(search_space_id)
if generating_podcast_id:
print(
f"[generate_podcast] Blocked duplicate request. Generating podcast: {generating_podcast_id}"
)
return {
"status": PodcastStatus.GENERATING.value,
"podcast_id": generating_podcast_id,
"title": podcast_title,
"message": "A podcast is already being generated. Please wait for it to complete.",
}
podcast = Podcast(
title=podcast_title,
status=PodcastStatus.PENDING,
@ -142,8 +85,6 @@ def create_generate_podcast_tool(
user_prompt=user_prompt,
)
set_generating_podcast(search_space_id, podcast.id)
print(f"[generate_podcast] Created podcast {podcast.id}, task: {task.id}")
return {

View file

@ -4,70 +4,15 @@ Video presentation generation tool for the SurfSense agent.
This module provides a factory function for creating the generate_video_presentation
tool that submits a Celery task for background video presentation generation.
The frontend polls for completion and auto-updates when the presentation is ready.
Duplicate request prevention:
- Only one video presentation can be generated at a time per search space
- Uses Redis to track active video presentation tasks
- Validates the Redis marker against actual DB status to avoid stale locks
"""
from typing import Any
import redis
from langchain_core.tools import tool
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import config
from app.db import VideoPresentation, VideoPresentationStatus
REDIS_URL = config.REDIS_APP_URL
_redis_client: redis.Redis | None = None
def get_redis_client() -> redis.Redis:
"""Get or create Redis client for video presentation task tracking."""
global _redis_client
if _redis_client is None:
_redis_client = redis.from_url(REDIS_URL, decode_responses=True)
return _redis_client
def _redis_key(search_space_id: int) -> str:
return f"video_presentation:generating:{search_space_id}"
def get_generating_video_presentation_id(search_space_id: int) -> int | None:
"""Get the video presentation ID currently being generated for this search space."""
try:
client = get_redis_client()
value = client.get(_redis_key(search_space_id))
return int(value) if value else None
except Exception:
return None
def clear_generating_video_presentation(search_space_id: int) -> None:
"""Clear the generating marker (used when we detect a stale lock)."""
try:
client = get_redis_client()
client.delete(_redis_key(search_space_id))
except Exception:
pass
def set_generating_video_presentation(
search_space_id: int, video_presentation_id: int
) -> None:
"""Mark a video presentation as currently generating for this search space."""
try:
client = get_redis_client()
client.setex(_redis_key(search_space_id), 1800, str(video_presentation_id))
except Exception as e:
print(
f"[generate_video_presentation] Warning: Could not set generating video presentation in Redis: {e}"
)
def create_generate_video_presentation_tool(
search_space_id: int,
@ -97,33 +42,6 @@ def create_generate_video_presentation_tool(
user_prompt: Optional style/tone instructions.
"""
try:
generating_id = get_generating_video_presentation_id(search_space_id)
if generating_id:
result = await db_session.execute(
select(VideoPresentation).filter(
VideoPresentation.id == generating_id
)
)
existing = result.scalars().first()
if existing and existing.status == VideoPresentationStatus.GENERATING:
print(
f"[generate_video_presentation] Blocked duplicate — "
f"presentation {generating_id} is actively generating"
)
return {
"status": VideoPresentationStatus.GENERATING.value,
"video_presentation_id": generating_id,
"title": video_title,
"message": "A video presentation is already being generated. Please wait for it to complete.",
}
print(
f"[generate_video_presentation] Stale Redis lock for presentation {generating_id} "
f"(status={existing.status if existing else 'not found'}). Clearing and proceeding."
)
clear_generating_video_presentation(search_space_id)
video_pres = VideoPresentation(
title=video_title,
status=VideoPresentationStatus.PENDING,
@ -145,8 +63,6 @@ def create_generate_video_presentation_tool(
user_prompt=user_prompt,
)
set_generating_video_presentation(search_space_id, video_pres.id)
print(
f"[generate_video_presentation] Created video presentation {video_pres.id}, task: {task.id}"
)