diff --git a/surfsense_web/components/tool-ui/generate-podcast.tsx b/surfsense_web/components/tool-ui/generate-podcast.tsx index 3b4c75a35..e11273e41 100644 --- a/surfsense_web/components/tool-ui/generate-podcast.tsx +++ b/surfsense_web/components/tool-ui/generate-podcast.tsx @@ -51,7 +51,7 @@ function PodcastGeneratingState({ title }: { title: string }) { {/* Animated rings */} -
+

{title}

@@ -113,7 +113,15 @@ function AudioLoadingState({ title }: { title: string }) { } /** - * Podcast Player Component - Fetches audio with authentication + * Transcript entry type from the database + */ +interface TranscriptEntry { + speaker_id: number; + dialog: string; +} + +/** + * Podcast Player Component - Fetches audio and transcript with authentication */ function PodcastPlayer({ podcastId, @@ -127,6 +135,7 @@ function PodcastPlayer({ durationMs?: number; }) { const [audioSrc, setAudioSrc] = useState(null); + const [transcript, setTranscript] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const objectUrlRef = useRef(null); @@ -140,8 +149,8 @@ function PodcastPlayer({ }; }, []); - // Fetch audio with authentication - const loadAudio = useCallback(async () => { + // Fetch audio and podcast details (including transcript) + const loadPodcast = useCallback(async () => { setIsLoading(true); setError(null); @@ -156,35 +165,43 @@ function PodcastPlayer({ const timeoutId = setTimeout(() => controller.abort(), 60000); // 60s timeout try { - // Fetch audio blob with authentication - const response = await podcastsApiService.loadPodcast({ - request: { id: podcastId }, - controller, - }); + // Fetch audio blob and podcast details in parallel + const [audioBlob, podcastDetails] = await Promise.all([ + podcastsApiService.loadPodcast({ + request: { id: podcastId }, + controller, + }), + podcastsApiService.getPodcastById(podcastId), + ]); // Create object URL from blob - const objectUrl = URL.createObjectURL(response); + const objectUrl = URL.createObjectURL(audioBlob); objectUrlRef.current = objectUrl; setAudioSrc(objectUrl); + + // Set transcript from podcast details + if (podcastDetails?.podcast_transcript) { + setTranscript(podcastDetails.podcast_transcript as TranscriptEntry[]); + } } finally { clearTimeout(timeoutId); } } catch (err) { - console.error("Error loading podcast audio:", err); + console.error("Error loading podcast:", 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"); + setError(err instanceof Error ? err.message : "Failed to load podcast"); } } finally { setIsLoading(false); } }, [podcastId]); - // Load audio when component mounts + // Load podcast when component mounts useEffect(() => { - loadAudio(); - }, [loadAudio]); + loadPodcast(); + }, [loadPodcast]); if (isLoading) { return ; @@ -204,6 +221,24 @@ function PodcastPlayer({ durationMs={durationMs} className="w-full" /> + {/* Transcript section */} + {transcript && transcript.length > 0 && ( +
+ + View transcript ({transcript.length} entries) + +
+ {transcript.map((entry, idx) => ( +
+ + Speaker {entry.speaker_id + 1}: + {" "} + {entry.dialog} +
+ ))} +
+
+ )}
); } @@ -219,7 +254,6 @@ function PodcastTaskPoller({ title: string; }) { const [taskStatus, setTaskStatus] = useState({ status: "processing" }); - const [pollCount, setPollCount] = useState(0); const pollingRef = useRef(null); // Set active podcast state when this component mounts @@ -253,9 +287,8 @@ function PodcastTaskPoller({ } } catch (err) { console.error("Error polling task status:", err); - // Don't stop polling on network errors, just increment count + // Don't stop polling on network errors, continue polling } - setPollCount((prev) => prev + 1); }; // Initial poll diff --git a/surfsense_web/lib/apis/podcasts-api.service.ts b/surfsense_web/lib/apis/podcasts-api.service.ts index 346c984af..0defff7d4 100644 --- a/surfsense_web/lib/apis/podcasts-api.service.ts +++ b/surfsense_web/lib/apis/podcasts-api.service.ts @@ -62,6 +62,13 @@ class PodcastsApiService { ); }; + /** + * Get a podcast by its ID (includes full transcript) + */ + getPodcastById = async (podcastId: number) => { + return baseApiService.get(`/api/v1/podcasts/${podcastId}`, podcast); + }; + generatePodcast = async (request: GeneratePodcastRequest) => { // Validate the request const parsedRequest = generatePodcastRequest.safeParse(request);