Skip to content

Commit

Permalink
Render session info in UI (#16)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nwaughachukwuma authored Nov 25, 2024
1 parent f278a2d commit 293743a
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 70 deletions.
7 changes: 2 additions & 5 deletions api/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions api/src/utils/audiocast_utils.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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


Expand Down
28 changes: 14 additions & 14 deletions api/src/utils/generate_audiocast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -73,7 +86,6 @@ def update_session_info(info: str):
category=category,
preferenceSummary=summary,
),
background_tasks,
)

if not source_content:
Expand Down Expand Up @@ -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,
)
8 changes: 3 additions & 5 deletions api/src/utils/generate_audiocast_source.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from fastapi import BackgroundTasks
from pydantic import BaseModel

from src.utils.audiocast_request import GenerateSourceContent
Expand All @@ -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.
"""
Expand All @@ -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

Expand Down
9 changes: 1 addition & 8 deletions api/src/utils/get_audiocast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
16 changes: 16 additions & 0 deletions app/src/lib/components/AudiocastPageHeader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import type { SessionModel } from '@/utils/types';
export let session: SessionModel;
</script>

<div class="flex w-full px-4 flex-col gap-y-3 sm:max-w-xl lg:max-w-3xl max-w-full">
<div class="mb-4 flex flex-col gap-y-2">
<span class="capitalize bg-gray-800 text-gray-300 w-fit py-1 px-3 rounded-md">
{session.category}
</span>

{#if session.metadata?.title}
<h1 class="text-2xl font-semibold text-sky-200">{session.metadata.title}</h1>
{/if}
</div>
</div>
4 changes: 1 addition & 3 deletions app/src/lib/components/AudiocastPageSkeletonLoader.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<div class="w-full max-w-3xl mx-auto p-6">
<!-- {/* Title skeleton */} -->
<div class="h-8 w-48 bg-gray-800 rounded animate-pulse mb-8" />
<div class="w-full max-w-3xl mx-auto p-6 px-4">

<!-- {/* Player container */} -->
<div class="w-full rounded-lg bg-gray-800 p-4 mb-4">
Expand Down
17 changes: 17 additions & 0 deletions app/src/lib/db/db.session.ts
Original file line number Diff line number Diff line change
@@ -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<SessionModel>;
return docData(ref).pipe(
switchMap((v) => of(v)),
distinctUntilChanged((a, b) => equals(a, b)),
shareReplay(1),
catchError(() => of(null)),
startWith(null)
);
};
2 changes: 2 additions & 0 deletions app/src/lib/stores/sessionContext.svelte.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -39,6 +40,7 @@ export function setSessionContext(sessionId: string) {
fetchingSource$,
audioSource$,
customSources$: getCustomSources$(sessionId),
sessionModel$: getSession$(sessionId),
startSession: (category: ContentCategory) => {
session$.set({
id: sessionId,
Expand Down
21 changes: 21 additions & 0 deletions app/src/lib/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SessionChatItem>;
metadata?: ChatMetadata;
created_at?: string;
}
4 changes: 3 additions & 1 deletion app/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions app/src/routes/audiocast/[sessionId=sessionId]/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script>
import { getSessionContext } from '@/stores/sessionContext.svelte';
import { Button } from '@/components/ui/button';
const { session$ } = getSessionContext();
const { session$, sessionModel$ } = getSessionContext();
</script>

<!-- TODO: Use only the DB references -->
{#if $session$ && !$session$.completed}
<div
class="w-full mx-auto px-4 md:max-w-xl h-full flex flex-col gap-3 items-center justify-center"
Expand All @@ -18,6 +19,6 @@
>Generate Audiocast</Button
>
</div>
{:else}
{:else if $sessionModel$}
<slot />
{/if}
47 changes: 26 additions & 21 deletions app/src/routes/audiocast/[sessionId=sessionId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
type GenerateAudiocastResponse = {
script: string;
source_content: string;
chats: Array<Omit<ChatItem, 'loading'>>;
category: ContentCategory;
title: ChatMetadata['title'];
created_at?: string;
};
Expand All @@ -22,7 +19,7 @@
</script>

<script lang="ts">
import { getSessionContext, type ChatItem } from '@/stores/sessionContext.svelte';
import { getSessionContext } from '@/stores/sessionContext.svelte';
import type { ContentCategory } from '@/utils/types';
import { env } from '@env';
import { parse } from 'marked';
Expand All @@ -33,12 +30,15 @@
import AudiocastPageChat from '@/components/AudiocastPageChat.svelte';
import AudiocastPageSkeletonLoader from '@/components/AudiocastPageSkeletonLoader.svelte';
import RenderAudioSources from '@/components/RenderAudioSources.svelte';
import AudiocastPageHeader from '@/components/AudiocastPageHeader.svelte';
const { session$, customSources$ } = getSessionContext();
const { customSources$, session$, sessionModel$ } = getSessionContext();
let generating = false;
$: sessionId = $page.params.sessionId;
$: sessionModel = $sessionModel$;
$: $customSources$;
$: $customSources$;
Expand Down Expand Up @@ -73,28 +73,30 @@
</script>

<div class="mx-auto flex h-full w-full pb-40 overflow-auto mt-6 flex-col items-center px-2 sm:px-1">
{#if sessionModel}
<AudiocastPageHeader session={sessionModel} />
{/if}

{#await getAudiocast(sessionId)}
<div class="flex flex-col w-full items-center justify-center -mt-6">
<AudiocastPageSkeletonLoader />

{#if generating}
<p class="mt-4 py-2 px-4 bg-sky-600/20 animate-pulse text-sky-300 rounded-sm">
Generating your audiocast...Please wait
</p>
<div class="flex mt-4 flex-col gap-y-3 w-full items-center">
<p class="py-2 px-4 bg-sky-600/20 animate-pulse text-sky-300 rounded-sm">
Generating your audiocast...Please wait
</p>

{#if sessionModel?.metadata?.info}
<p class="py-2 px-4 animate-pulse text-gray-400 rounded-sm">
Status: {sessionModel.metadata.info}
</p>
{/if}
</div>
{/if}
</div>
{:then data}
<div class="flex w-full px-4 flex-col gap-y-3 sm:max-w-xl lg:max-w-3xl max-w-full">
<div class="mb-4 flex flex-col gap-y-2">
<span class="capitalize bg-gray-800 text-gray-300 w-fit py-1 px-3 rounded-md">
{data.category}
</span>

{#if data.title}
<h1 class="text-2xl font-semibold text-sky-200">{data.title}</h1>
{/if}
</div>

<RenderMedia filename={sessionId} let:uri>
<audio controls class="w-full animate-fade-in block">
<source src={uri} type="audio/mpeg" />
Expand Down Expand Up @@ -133,9 +135,12 @@
<RenderAudioSources aiSource={data.source_content} />
</Accordion.Root>

<AudiocastPageAction {sessionId} sessionTitle={data.title || 'Untitled'} on:showChats>
<AudiocastPageChat slot="chats-button" chats={data.chats} />
</AudiocastPageAction>
{#if sessionModel}
{@const sessionTitle = sessionModel.metadata?.title || 'Untitled'}
<AudiocastPageAction {sessionId} {sessionTitle} on:showChats>
<AudiocastPageChat slot="chats-button" chats={sessionModel.chats} />
</AudiocastPageAction>
{/if}
</div>
{:catch error}
<div>Error: {String(error)}</div>
Expand Down
7 changes: 1 addition & 6 deletions app/src/routes/chat/[sessionId=sessionId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 293743a

Please sign in to comment.