Skip to content

Commit

Permalink
expose endpoint to get audiocast signed-url
Browse files Browse the repository at this point in the history
  • Loading branch information
nwaughachukwuma committed Nov 11, 2024
1 parent 080f115 commit 43a8cfd
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 26 deletions.
18 changes: 17 additions & 1 deletion api/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

from fastapi import BackgroundTasks, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi_utilities import add_timer_middleware

from src.services.storage import StorageManager
from src.utils.chat_request import chat_request
from src.utils.chat_utils import (
SessionChatItem,
Expand Down Expand Up @@ -89,3 +90,18 @@ async def generate_audiocast_endpoint(
async def get_audiocast_endpoint(session_id: str):
result = get_audiocast(session_id)
return result


@app.get("/get-signed-url", response_model=str)
async def get_signed_url_endpoint(blobname: str):
"""
Get signed URL for generated audiocast
"""
url = StorageManager().get_signed_url(blobname=blobname)
return JSONResponse(
content=url,
headers={
"Content-Type": "application/json",
"Cache-Control": "public, max-age=86390, immutable",
},
)
15 changes: 14 additions & 1 deletion api/src/services/storage.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import os
from dataclasses import dataclass
from io import BytesIO
Expand All @@ -8,7 +9,7 @@
from google.cloud import storage
from pydub import AudioSegment

from api.src.env_var import BUCKET_NAME
from src.env_var import BUCKET_NAME

storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)
Expand Down Expand Up @@ -92,3 +93,15 @@ def download_from_gcs(self, filename: str):

blob.download_to_filename(tmp_file_path)
return tmp_file_path

def get_signed_url(self, blobname, expiration=datetime.timedelta(days=1)):
"""get a signed URL for a blob"""
blob = bucket.blob(blobname)
if not blob.exists():
raise Exception(f"Blob {blobname} does not exist")

return blob.generate_signed_url(
version="v4",
expiration=expiration,
method="GET",
)
29 changes: 29 additions & 0 deletions app/src/lib/components/RenderAudiocast.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script context="module">
const BLOB_BASE_URI = 'audiora/assets';
</script>

<script lang="ts">
import { env } from '@env';
import Spinner from './Spinner.svelte';
export let sessionId: String;
async function getSignedURL() {
const blobname = `${BLOB_BASE_URI}/${sessionId}`;
return fetch(`${env.API_BASE_URL}/get-signed-url?blobname=${blobname}`).then<string>((res) => {
if (res.ok) return res.json();
throw new Error('Failed to get signed Audiocast URL');
});
}
</script>

{#await getSignedURL()}
<Spinner />
{:then audioURL}
<audio controls class="w-full animate-fade-in block">
<source src={audioURL} type="audio/mpeg" />
Your browser does not support the audio element.
</audio>
{:catch error}
<div>{String(error)}</div>
{/await}
98 changes: 74 additions & 24 deletions app/src/routes/audiocast/[sessionId=sessionId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
source_content: string;
created_at?: string;
};
function parseScript(script: string) {
const matches = [...script.matchAll(/<(Speaker\d+)>(.*?)<\/Speaker\d+>/gs)];
return matches.map(([, speaker, content]) => `**${speaker}**: ${content}`).join('\n\n');
}
</script>

<script lang="ts">
import Spinner from '@/components/Spinner.svelte';
import { getSessionContext } from '@/stores/sessionContext.svelte';
import type { ContentCategory } from '@/utils/types';
import { env } from '@env';
import { parse } from 'marked';
import RenderAudiocast from '@/components/RenderAudiocast.svelte';
import * as Accordion from '@/components/ui/accordion';
const { session$ } = getSessionContext();
Expand All @@ -21,30 +29,72 @@
if (generating) return;
generating = true;
// return fetch(`${env.API_BASE_URL}/audiocast/generate`, {
// method: 'POST',
// body: JSON.stringify({ sessionId, summary, category }),
// headers: { 'Content-Type': 'application/json' }
// })
// .then<GenerateAudiocastResponse>((res) => {
// if (res.ok) return res.json();
// throw new Error('Failed to generate audiocast');
// })
// .finally(() => (generating = false));
return fetch(`${env.API_BASE_URL}/audiocast/generate`, {
method: 'POST',
body: JSON.stringify({ sessionId, summary, category }),
headers: { 'Content-Type': 'application/json' }
})
.then<GenerateAudiocastResponse>((res) => {
if (res.ok) return res.json();
throw new Error('Failed to generate audiocast');
})
.finally(() => (generating = false));
}
async function getAudiocast(sessionId: string, category: ContentCategory, summary: string) {
return fetch(`${env.API_BASE_URL}/audiocast/${sessionId}`).then<GenerateAudiocastResponse>(
(res) => {
if (res.status === 404) return generateAudiocast(sessionId, category, summary);
else if (res.ok) return res.json();
throw new Error('Failed to get audiocast');
}
);
}
</script>

{#if !$session$?.summary}
<Spinner />
{:else}
{@const { summary, category } = $session$}
{#await generateAudiocast($session$.id, category, summary)}
<Spinner />
{:then data}
<pre>
{JSON.stringify(data, null, 2)}
</pre>
{:catch error}
<div>{String(error)}</div>
{/await}
{/if}
<div
class="mx-auto flex h-full w-full max-w-full flex-col items-center overflow-hidden px-2 sm:max-w-xl sm:px-1 lg:max-w-3xl"
>
{#if $session$?.summary}
{@const { summary, category } = $session$}
{#await getAudiocast($session$.id, category, summary)}
<div class="-mt-16 flex h-full w-full items-center justify-center sm:-mt-24">
<Spinner />
</div>
{:then data}
{@const script = data.script}
{@const sourceContent = data.source_content}
<div class="flex w-full flex-col gap-y-3">
<RenderAudiocast sessionId={$session$.id} />

<Accordion.Root>
<Accordion.Item value="item-1">
<Accordion.Trigger>Audio Transcript</Accordion.Trigger>
<Accordion.Content>
<div class="flex w-full flex-col gap-y-3">
{#await parse(parseScript(script)) then parsedContent}
{@html parsedContent}
{/await}
</div>
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>

<Accordion.Root>
<Accordion.Item value="item-2">
<Accordion.Trigger>Source Content</Accordion.Trigger>
<Accordion.Content>
<div class="flex w-full flex-col gap-y-3">
{#await parse(sourceContent) then parsedContent}
{@html parsedContent}
{/await}
</div>
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
</div>
{:catch error}
<div>{String(error)}</div>
{/await}
{/if}
</div>

0 comments on commit 43a8cfd

Please sign in to comment.