"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { AlertCircleIcon, Loader2Icon, MicIcon } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { Audio } from "@/components/tool-ui/audio"; import { podcastsApiService } from "@/lib/apis/podcasts-api.service"; /** * Type definitions for the generate_podcast tool */ interface GeneratePodcastArgs { source_content: string; podcast_title?: string; user_prompt?: string; } interface GeneratePodcastResult { status: "success" | "error"; podcast_id?: number; title?: string; transcript?: string; duration_ms?: number; transcript_entries?: number; error?: string; } /** * Loading state component shown while podcast is being generated */ function PodcastGeneratingState({ title }: { title: string }) { return (
{/* Animated rings */}

{title}

Generating podcast... This may take a few minutes
); } /** * Error state component shown when podcast generation fails */ function PodcastErrorState({ title, error }: { title: string; error: string }) { return (

{title}

Failed to generate podcast

{error}

); } /** * Audio loading state component */ function AudioLoadingState({ title }: { title: string }) { return (

{title}

Loading audio...
); } /** * Podcast Player Component - Fetches audio with authentication */ function PodcastPlayer({ podcastId, title, description, durationMs, transcript, transcriptEntries, }: { podcastId: number; title: string; description: string; durationMs?: number; transcript?: string; transcriptEntries?: number; }) { const [audioSrc, setAudioSrc] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const objectUrlRef = useRef(null); // Cleanup object URL on unmount useEffect(() => { return () => { if (objectUrlRef.current) { URL.revokeObjectURL(objectUrlRef.current); } }; }, []); // Fetch audio with authentication const loadAudio = useCallback(async () => { setIsLoading(true); setError(null); try { // Revoke previous object URL if exists if (objectUrlRef.current) { URL.revokeObjectURL(objectUrlRef.current); objectUrlRef.current = null; } const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 60000); // 60s timeout try { // Fetch audio blob with authentication const response = await podcastsApiService.loadPodcast({ request: { id: podcastId }, controller, }); // Create object URL from blob const objectUrl = URL.createObjectURL(response); objectUrlRef.current = objectUrl; setAudioSrc(objectUrl); } finally { clearTimeout(timeoutId); } } catch (err) { console.error("Error loading podcast audio:", err); if (err instanceof DOMException && err.name === "AbortError") { setError("Request timed out. Please try again."); } else { setError(err instanceof Error ? err.message : "Failed to load audio"); } } finally { setIsLoading(false); } }, [podcastId]); // Load audio when component mounts useEffect(() => { loadAudio(); }, [loadAudio]); if (isLoading) { return ; } if (error || !audioSrc) { return ; } return (
); } /** * Generate Podcast Tool UI Component * * This component is registered with assistant-ui to render custom UI * when the generate_podcast tool is called by the agent. * * It fetches the podcast audio with authentication (like the old system) * and displays it using the Audio component. */ export const GeneratePodcastToolUI = makeAssistantToolUI< GeneratePodcastArgs, GeneratePodcastResult >({ toolName: "generate_podcast", render: function GeneratePodcastUI({ args, result, status }) { const title = args.podcast_title || "SurfSense Podcast"; // Loading state - podcast is being generated if (status.type === "running" || status.type === "requires-action") { return ; } // Incomplete/cancelled state if (status.type === "incomplete") { if (status.reason === "cancelled") { return (

Podcast generation cancelled

); } if (status.reason === "error") { return ( ); } } // No result yet if (!result) { return ; } // Error result if (result.status === "error") { return ; } // Success - need podcast_id to fetch with auth if (!result.podcast_id) { return ; } // Render the podcast player (handles auth fetch internally) return ( ); }, });