feat: enhance podcast generation with duplicate request prevention and improved UI feedback

This commit is contained in:
Anish Sarkar 2025-12-21 20:07:04 +05:30
parent e79e1187b2
commit 783ee9c154
6 changed files with 269 additions and 7 deletions

View file

@ -4,13 +4,67 @@ Podcast generation tool for the new chat 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
"""
import os
from typing import Any
import redis
from langchain_core.tools import tool
from sqlalchemy.ext.asyncio import AsyncSession
# Redis connection for tracking active podcast tasks
# Uses the same Redis instance as Celery
REDIS_URL = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0")
_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 get_active_podcast_key(search_space_id: int) -> str:
"""Generate Redis key for tracking active podcast task."""
return f"podcast:active:{search_space_id}"
def get_active_podcast_task(search_space_id: int) -> str | None:
"""Check if there's an active podcast task for this search space."""
try:
client = get_redis_client()
return client.get(get_active_podcast_key(search_space_id))
except Exception:
# If Redis is unavailable, allow the request (fail open)
return None
def set_active_podcast_task(search_space_id: int, task_id: str) -> None:
"""Mark a podcast task as active for this search space."""
try:
client = get_redis_client()
# Set with 30-minute expiry as safety net (podcast should complete before this)
client.setex(get_active_podcast_key(search_space_id), 1800, task_id)
except Exception as e:
print(f"[generate_podcast] Warning: Could not set active task in Redis: {e}")
def clear_active_podcast_task(search_space_id: int) -> None:
"""Clear the active podcast task for this search space."""
try:
client = get_redis_client()
client.delete(get_active_podcast_key(search_space_id))
except Exception as e:
print(f"[generate_podcast] Warning: Could not clear active task in Redis: {e}")
def create_generate_podcast_tool(
search_space_id: int,
@ -49,6 +103,10 @@ def create_generate_podcast_tool(
The tool will start generating a podcast in the background.
The podcast will be available once generation completes.
IMPORTANT: Only one podcast can be generated at a time. If a podcast
is already being generated, this tool will return a message asking
the user to wait.
Args:
source_content: The text content to convert into a podcast.
This can be a summary, research findings, or any text
@ -59,11 +117,23 @@ def create_generate_podcast_tool(
Returns:
A dictionary containing:
- status: "processing" (task submitted) or "error"
- task_id: The Celery task ID for polling status
- status: "processing" (task submitted), "already_generating", or "error"
- task_id: The Celery task ID for polling status (if processing)
- title: The podcast title
- message: Status message for the user
"""
try:
# Check if a podcast is already being generated for this search space
active_task_id = get_active_podcast_task(search_space_id)
if active_task_id:
print(f"[generate_podcast] Blocked duplicate request. Active task: {active_task_id}")
return {
"status": "already_generating",
"task_id": active_task_id,
"title": podcast_title,
"message": "A podcast is already being generated. Please wait for it to complete before requesting another one.",
}
# Import Celery task here to avoid circular imports
from app.tasks.celery_tasks.podcast_tasks import (
generate_content_podcast_task,
@ -78,6 +148,9 @@ def create_generate_podcast_tool(
user_prompt=user_prompt,
)
# Mark this task as active
set_active_podcast_task(search_space_id, task.id)
print(f"[generate_podcast] Submitted Celery task: {task.id}")
# Return immediately with task_id for polling