mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-31 19:45:15 +02:00
feat: add public podcast endpoints and player support
This commit is contained in:
parent
98991d2ed4
commit
b8338d8643
2 changed files with 72 additions and 17 deletions
|
|
@ -49,12 +49,37 @@ async def clone_public_chat(
|
|||
"""
|
||||
Clone a public chat snapshot to the user's account.
|
||||
|
||||
Single-phase clone: creates thread and copies messages in one request.
|
||||
Creates thread and copies messages.
|
||||
Requires authentication.
|
||||
"""
|
||||
return await clone_from_snapshot(session, share_token, user)
|
||||
|
||||
|
||||
@router.get("/{share_token}/podcasts/{podcast_id}")
|
||||
async def get_public_podcast(
|
||||
share_token: str,
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Get podcast details from a public chat snapshot.
|
||||
|
||||
No authentication required - the share_token provides access.
|
||||
Returns podcast info including transcript.
|
||||
"""
|
||||
podcast_info = await get_snapshot_podcast(session, share_token, podcast_id)
|
||||
|
||||
if not podcast_info:
|
||||
raise HTTPException(status_code=404, detail="Podcast not found")
|
||||
|
||||
return {
|
||||
"id": podcast_info.get("original_id"),
|
||||
"title": podcast_info.get("title"),
|
||||
"status": "ready",
|
||||
"podcast_transcript": podcast_info.get("transcript"),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{share_token}/podcasts/{podcast_id}/stream")
|
||||
async def stream_public_podcast(
|
||||
share_token: str,
|
||||
|
|
|
|||
|
|
@ -173,7 +173,18 @@ function AudioLoadingState({ title }: { title: string }) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Podcast Player Component - Fetches audio and transcript with authentication
|
||||
* Get public share token from URL if in public view.
|
||||
* Returns null if not in a public view.
|
||||
*/
|
||||
function getPublicShareToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
const match = window.location.pathname.match(/^\/public\/([^/]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Podcast Player Component - Fetches audio and transcript
|
||||
* Automatically uses public endpoint when viewing a public chat snapshot.
|
||||
*/
|
||||
function PodcastPlayer({
|
||||
podcastId,
|
||||
|
|
@ -217,30 +228,49 @@ function PodcastPlayer({
|
|||
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60s timeout
|
||||
|
||||
try {
|
||||
// Fetch audio blob and podcast details in parallel
|
||||
const [audioResponse, rawPodcastDetails] = await Promise.all([
|
||||
authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts/${podcastId}/audio`,
|
||||
{ method: "GET", signal: controller.signal }
|
||||
),
|
||||
baseApiService.get<unknown>(`/api/v1/podcasts/${podcastId}`),
|
||||
]);
|
||||
// Check if we're in a public view
|
||||
const shareToken = getPublicShareToken();
|
||||
|
||||
if (!audioResponse.ok) {
|
||||
throw new Error(`Failed to load audio: ${audioResponse.status}`);
|
||||
let audioBlob: Blob;
|
||||
let rawPodcastDetails: unknown = null;
|
||||
|
||||
if (shareToken) {
|
||||
// Public view - use public endpoints (baseApiService handles no-auth for /api/v1/public/)
|
||||
const [blob, details] = await Promise.all([
|
||||
baseApiService.getBlob(`/api/v1/public/${shareToken}/podcasts/${podcastId}/stream`),
|
||||
baseApiService.get(`/api/v1/public/${shareToken}/podcasts/${podcastId}`),
|
||||
]);
|
||||
audioBlob = blob;
|
||||
rawPodcastDetails = details;
|
||||
} else {
|
||||
// Authenticated view - fetch audio and details in parallel
|
||||
const [audioResponse, details] = await Promise.all([
|
||||
authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts/${podcastId}/audio`,
|
||||
{ method: "GET", signal: controller.signal }
|
||||
),
|
||||
baseApiService.get<unknown>(`/api/v1/podcasts/${podcastId}`),
|
||||
]);
|
||||
|
||||
if (!audioResponse.ok) {
|
||||
throw new Error(`Failed to load audio: ${audioResponse.status}`);
|
||||
}
|
||||
|
||||
audioBlob = await audioResponse.blob();
|
||||
rawPodcastDetails = details;
|
||||
}
|
||||
|
||||
const audioBlob = await audioResponse.blob();
|
||||
|
||||
// Create object URL from blob
|
||||
const objectUrl = URL.createObjectURL(audioBlob);
|
||||
objectUrlRef.current = objectUrl;
|
||||
setAudioSrc(objectUrl);
|
||||
|
||||
// Parse and validate podcast details, then set transcript
|
||||
const podcastDetails = parsePodcastDetails(rawPodcastDetails);
|
||||
if (podcastDetails.podcast_transcript) {
|
||||
setTranscript(podcastDetails.podcast_transcript);
|
||||
if (rawPodcastDetails) {
|
||||
const podcastDetails = parsePodcastDetails(rawPodcastDetails);
|
||||
if (podcastDetails.podcast_transcript) {
|
||||
setTranscript(podcastDetails.podcast_transcript);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue