From 293743a6f5ef7438fec0aaf75625955b11a5dc9e Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Mon, 25 Nov 2024 23:12:37 +0000 Subject: [PATCH] Render session info in UI (#16) * add accordion item for creating custom source items * add shadcn-card component * wip: add form for custom user source * cleanup custom source componentn * add shadcn input component * add components to get custom source by website url and copy/paste * add logic for extracting pdf and page content from url * wip: add component for rendering pdf content * define id field on URLContent * fetch custom sources from specified links - supports .pdf and any public webpage * retain formatting in web and pdf content; improve page content rendering * cleanup * wip: add logic to store and fetch custom sources * add endpoint to fetch custom sources for a given audiocast session * create a component for rendering audio_souces - ai-generated custom * setup firebase and rxjs * add a safe_to_dict method to db_manager * use generate-custom-source endpoint when adding custom source - this allows saving the custom source to firestore * fetch custom sources on mount page/drawer * read custom_sources from firebase using the js_sdk * add firebase.json and optimize_deps * parse firebase_config env variable in vite.config * define .firebaserc * update custom_source model; add toast notification * consolidate and cleanup * wip: create workflow to store copy/paste source * add useful helpers to CustomSourceManager * add endpoint and request logic to add copy/paste source * improve the layouts of copypast_source and website_url_source forms * refactor and cleanup * use improved module names * finalize endpoint to store uploaded content * improve class and module names * add ui logic for uploading dropped files * upload dragged&dropped file as form_data; allow up to 10mb * add a title field on source_content model * ensure the user cannot create more than 5 custom sources * allow uploading a file using the file dialog * cleanup * write source_content to db after it's generated * add user provided custom sources to the tts_prompt * specify the correct interface for _get_custom_sources * improve the prompt to exclude extraneous information in user-provided sources * add FIREBASE_CONFIG to env * remove firebase_config from env_vars * create and expose a sesion_model observable * render session info when generating the audiocast * annonate user specified audiocast type with the category * update session metadata directly outside of background_task --- api/src/main.py | 7 +-- api/src/utils/audiocast_utils.py | 7 +-- api/src/utils/generate_audiocast.py | 28 +++++------ api/src/utils/generate_audiocast_source.py | 8 ++-- api/src/utils/get_audiocast.py | 9 +--- .../lib/components/AudiocastPageHeader.svelte | 16 +++++++ .../AudiocastPageSkeletonLoader.svelte | 4 +- app/src/lib/db/db.session.ts | 17 +++++++ app/src/lib/stores/sessionContext.svelte.ts | 2 + app/src/lib/utils/types.ts | 21 +++++++++ app/src/routes/+page.svelte | 4 +- .../[sessionId=sessionId]/+layout.svelte | 5 +- .../[sessionId=sessionId]/+page.svelte | 47 ++++++++++--------- .../chat/[sessionId=sessionId]/+page.svelte | 7 +-- 14 files changed, 112 insertions(+), 70 deletions(-) create mode 100644 app/src/lib/components/AudiocastPageHeader.svelte create mode 100644 app/src/lib/db/db.session.ts diff --git a/api/src/main.py b/api/src/main.py index ae7297b..47672f5 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -111,11 +111,8 @@ def get_audiocast_endpoint(session_id: str): @app.post("/generate-audiocast-source", response_model=str) -async def generate_audiocast_source_endpoint( - request: GenerateAudiocastSource, - background_tasks: BackgroundTasks, -): - source_content = await generate_audiocast_source(request, background_tasks) +async def generate_audiocast_source_endpoint(request: GenerateAudiocastSource): + source_content = await generate_audiocast_source(request) if not source_content: raise HTTPException(status_code=500, detail="Failed to generate source content") return source_content diff --git a/api/src/utils/audiocast_utils.py b/api/src/utils/audiocast_utils.py index 97b6326..089611c 100644 --- a/api/src/utils/audiocast_utils.py +++ b/api/src/utils/audiocast_utils.py @@ -1,8 +1,8 @@ -from typing import List, TypedDict +from typing import TypedDict from pydantic import BaseModel -from src.utils.chat_utils import ContentCategory, SessionChatItem +from src.utils.chat_utils import ContentCategory class GenerateAudioCastRequest(BaseModel): @@ -14,9 +14,6 @@ class GenerateAudioCastRequest(BaseModel): class GenerateAudioCastResponse(BaseModel): script: str source_content: str - chats: List[SessionChatItem] - category: ContentCategory - title: str | None created_at: str | None diff --git a/api/src/utils/generate_audiocast.py b/api/src/utils/generate_audiocast.py index 6c1b02e..62a3575 100644 --- a/api/src/utils/generate_audiocast.py +++ b/api/src/utils/generate_audiocast.py @@ -60,7 +60,20 @@ async def generate_audiocast(request: GenerateAudioCastRequest, background_tasks db = SessionManager(session_id, category) def update_session_info(info: str): - background_tasks.add_task(db._update_info, info) + db._update_info(info) + + session_data = SessionManager.data(session_id) + source_content = session_data.metadata.source if session_data and session_data.metadata else None + + if not source_content: + update_session_info("Generating source content...") + source_content = await generate_audiocast_source( + GenerateAudiocastSource( + sessionId=session_id, + category=category, + preferenceSummary=summary, + ), + ) session_data = SessionManager.data(session_id) source_content = session_data.metadata.source if session_data and session_data.metadata else None @@ -73,7 +86,6 @@ def update_session_info(info: str): category=category, preferenceSummary=summary, ), - background_tasks, ) if not source_content: @@ -104,20 +116,8 @@ def update_session_info(info: str): audio_script, ) - session_data = SessionManager.data(session_id) - if not session_data: - raise HTTPException( - status_code=404, - detail=f"Failed to get audiocast from the DB for session_id: {session_id}", - ) - - title = session_data.metadata.title if session_data.metadata and session_data.metadata.title else "Untitled" - return GenerateAudioCastResponse( script=audio_script, source_content=source_content, created_at=datetime.now().strftime("%Y-%m-%d %H:%M"), - chats=session_data.chats, - title=title, - category=session_data.category, ) diff --git a/api/src/utils/generate_audiocast_source.py b/api/src/utils/generate_audiocast_source.py index c80df70..3372fbf 100644 --- a/api/src/utils/generate_audiocast_source.py +++ b/api/src/utils/generate_audiocast_source.py @@ -1,4 +1,3 @@ -from fastapi import BackgroundTasks from pydantic import BaseModel from src.utils.audiocast_request import GenerateSourceContent @@ -14,7 +13,7 @@ class GenerateAudiocastSource(BaseModel): preferenceSummary: str -async def generate_audiocast_source(request: GenerateAudiocastSource, background_tasks: BackgroundTasks): +async def generate_audiocast_source(request: GenerateAudiocastSource): """ Generate audiocast source material based on user preferences. """ @@ -28,11 +27,10 @@ async def generate_audiocast_source(request: GenerateAudiocastSource, background @use_cache_manager(cache_key) async def _handler(): db = SessionManager(session_id, category) - background_tasks.add_task(db._update_info, "Generating source content...") - + db._update_info("Generating source content...") generator = GenerateSourceContent(category, preference_summary) source_content = await generator._run() - background_tasks.add_task(db._update_source, source_content) + db._update_source(source_content) return source_content diff --git a/api/src/utils/get_audiocast.py b/api/src/utils/get_audiocast.py index 3bb96b1..f26601c 100644 --- a/api/src/utils/get_audiocast.py +++ b/api/src/utils/get_audiocast.py @@ -35,11 +35,4 @@ def get_audiocast(session_id: str): if session_data.created_at: created_at = datetime.fromisoformat(session_data.created_at).strftime("%Y-%m-%d %H:%M") - return GenerateAudioCastResponse( - script=transcript, - source_content=source, - created_at=created_at, - chats=session_data.chats, - title=title, - category=session_data.category, - ) + return GenerateAudioCastResponse(script=transcript, source_content=source, created_at=created_at) diff --git a/app/src/lib/components/AudiocastPageHeader.svelte b/app/src/lib/components/AudiocastPageHeader.svelte new file mode 100644 index 0000000..80a4348 --- /dev/null +++ b/app/src/lib/components/AudiocastPageHeader.svelte @@ -0,0 +1,16 @@ + + +
+
+ + {session.category} + + + {#if session.metadata?.title} +

{session.metadata.title}

+ {/if} +
+
diff --git a/app/src/lib/components/AudiocastPageSkeletonLoader.svelte b/app/src/lib/components/AudiocastPageSkeletonLoader.svelte index 56abd3d..797466f 100644 --- a/app/src/lib/components/AudiocastPageSkeletonLoader.svelte +++ b/app/src/lib/components/AudiocastPageSkeletonLoader.svelte @@ -1,6 +1,4 @@ -
- -
+
diff --git a/app/src/lib/db/db.session.ts b/app/src/lib/db/db.session.ts new file mode 100644 index 0000000..28117ed --- /dev/null +++ b/app/src/lib/db/db.session.ts @@ -0,0 +1,17 @@ +import type { DocumentReference } from 'firebase/firestore'; +import { distinctUntilChanged, shareReplay, of, catchError, switchMap, startWith } from 'rxjs'; +import { equals } from 'ramda'; +import { dbRefs } from '@/services/firebase'; +import { docData } from './db.utils'; +import type { SessionModel } from '@/utils/types'; + +export const getSession$ = (sessionId: string) => { + const ref = dbRefs.docRef('audiora_sessions', sessionId) as DocumentReference; + return docData(ref).pipe( + switchMap((v) => of(v)), + distinctUntilChanged((a, b) => equals(a, b)), + shareReplay(1), + catchError(() => of(null)), + startWith(null) + ); +}; diff --git a/app/src/lib/stores/sessionContext.svelte.ts b/app/src/lib/stores/sessionContext.svelte.ts index 6ef8864..ad62b72 100644 --- a/app/src/lib/stores/sessionContext.svelte.ts +++ b/app/src/lib/stores/sessionContext.svelte.ts @@ -1,4 +1,5 @@ import { getCustomSources$ } from '@/db/db.customSources'; +import { getSession$ } from '@/db/db.session'; import type { ContentCategory } from '@/utils/types'; import { setContext, getContext } from 'svelte'; import { persisted } from 'svelte-persisted-store'; @@ -39,6 +40,7 @@ export function setSessionContext(sessionId: string) { fetchingSource$, audioSource$, customSources$: getCustomSources$(sessionId), + sessionModel$: getSession$(sessionId), startSession: (category: ContentCategory) => { session$.set({ id: sessionId, diff --git a/app/src/lib/utils/types.ts b/app/src/lib/utils/types.ts index 9153d84..9ef54b4 100644 --- a/app/src/lib/utils/types.ts +++ b/app/src/lib/utils/types.ts @@ -7,3 +7,24 @@ export type ContentCategory = | 'voicenote' | 'interview' | 'soundbite'; + +export interface ChatMetadata { + source: string; + transcript: string; + info?: string; + title: string; +} + +export interface SessionChatItem { + id: string; + role: 'user' | 'assistant'; + content: string; +} + +export interface SessionModel { + id: string; + category: ContentCategory; + chats: Array; + metadata?: ChatMetadata; + created_at?: string; +} diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 708f953..79f6d6b 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -23,7 +23,9 @@ function continueChat(category: ContentCategory) { startSession(category); - addChatItem({ id: uuid(), content: selectContent, role: 'user', loading: false }); + + const content = `${selectContent}\nCategory: ${category} `; + addChatItem({ id: uuid(), content, role: 'user', loading: false }); const href = `/chat/${sessionId}?category=${category}`; goto(href); diff --git a/app/src/routes/audiocast/[sessionId=sessionId]/+layout.svelte b/app/src/routes/audiocast/[sessionId=sessionId]/+layout.svelte index 9d16bed..ebfad72 100644 --- a/app/src/routes/audiocast/[sessionId=sessionId]/+layout.svelte +++ b/app/src/routes/audiocast/[sessionId=sessionId]/+layout.svelte @@ -1,9 +1,10 @@ + {#if $session$ && !$session$.completed}
Generate Audiocast
-{:else} +{:else if $sessionModel$} {/if} diff --git a/app/src/routes/audiocast/[sessionId=sessionId]/+page.svelte b/app/src/routes/audiocast/[sessionId=sessionId]/+page.svelte index f788fe6..8a9b004 100644 --- a/app/src/routes/audiocast/[sessionId=sessionId]/+page.svelte +++ b/app/src/routes/audiocast/[sessionId=sessionId]/+page.svelte @@ -9,9 +9,6 @@ type GenerateAudiocastResponse = { script: string; source_content: string; - chats: Array>; - category: ContentCategory; - title: ChatMetadata['title']; created_at?: string; }; @@ -22,7 +19,7 @@
+ {#if sessionModel} + + {/if} + {#await getAudiocast(sessionId)}
{#if generating} -

- Generating your audiocast...Please wait -

+
+

+ Generating your audiocast...Please wait +

+ + {#if sessionModel?.metadata?.info} +

+ Status: {sessionModel.metadata.info} +

+ {/if} +
{/if}
{:then data}
-
- - {data.category} - - - {#if data.title} -

{data.title}

- {/if} -
-
{:catch error}
Error: {String(error)}
diff --git a/app/src/routes/chat/[sessionId=sessionId]/+page.svelte b/app/src/routes/chat/[sessionId=sessionId]/+page.svelte index 2245e1d..12e1adc 100644 --- a/app/src/routes/chat/[sessionId=sessionId]/+page.svelte +++ b/app/src/routes/chat/[sessionId=sessionId]/+page.svelte @@ -58,12 +58,7 @@ } async function chatRequest(uItem: ChatItem) { - const aItem = addChatItem({ - id: uuid(), - content: '', - role: 'assistant', - loading: true - }); + const aItem = addChatItem({ id: uuid(), content: '', role: 'assistant', loading: true }); return fetch(`${env.API_BASE_URL}/chat/${sessionId}`, { method: 'POST',