Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure idempotency when generating audiocast #18

Merged
merged 9 commits into from
Nov 27, 2024
22 changes: 11 additions & 11 deletions api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
anthropic

asyncio
elevenlabs
fastapi
uvicorn
gunicorn
uvloop
fastapi-utilities
ffmpeg-python

firebase-admin
google-api-python-client
google-auth
google-cloud-storage
google-generativeai
gunicorn

asyncio
httpx
redis[hiredis]

anthropic
elevenlabs
openai

pycairo
pydantic
pydub
pypdf[crypto]
python-dotenv
ffmpeg-python
python-multipart
python-slugify

ruff
seewav
setuptools
uvicorn
uvloop
redis[hiredis]

async-web-search

lxml
beautifulsoup4
pypdf[crypto]
async-web-search
9 changes: 9 additions & 0 deletions api/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,12 @@ async def save_uploaded_files_endpoint(files: list[UploadFile], sessionId: str =
"""
result = await UploadedFiles(session_id=sessionId)._save_sources(files)
return result


@app.delete("/delete-session/{sessionId}")
def delete_session_endpoint(sessionId: str):
"""
Delete audiocast session
"""
SessionManager._delete_session(sessionId)
return "Deleted"
5 changes: 5 additions & 0 deletions api/src/utils/generate_audiocast.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from fastapi import BackgroundTasks, HTTPException

from src.services.storage import StorageManager
Expand Down Expand Up @@ -36,6 +38,9 @@ def post_generate_audio(
waveform_utils.run_all()
except Exception as e:
print(f"Error in generate_audiocast background_tasks: {str(e)}")
finally:
if os.path.exists(audio_path):
os.remove(audio_path)


async def generate_audiocast(request: GenerateAudioCastRequest, background_tasks: BackgroundTasks):
Expand Down
4 changes: 4 additions & 0 deletions api/src/utils/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,7 @@ def on_snapshot(doc_snapshot, _changes, _read_time):
callback(info)

return doc_ref.on_snapshot(on_snapshot)

@staticmethod
def _delete_session(doc_id: str):
return DBManager()._delete_document(collections["audiora_sessions"], doc_id)
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"test": "npm run test:integration && npm run test:unit"
},
"dependencies": {
"bits-ui": "^0.21.16",
"canvas-confetti": "^1.9.3",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
Expand Down Expand Up @@ -55,6 +54,7 @@
"@types/ramda": "^0.30.1",
"@types/throttle-debounce": "^5.0.2",
"autoprefixer": "^10.4.20",
"bits-ui": "^0.21.16",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
Expand Down
10 changes: 8 additions & 2 deletions app/src/lib/components/ChatContainer.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<script>
import { getSessionContext } from '@/stores/sessionContext.svelte';
import ChatBoxContainer from './ChatBoxContainer.svelte';
import { isfinalResponse } from '@/utils/session.utils';

export let searchTerm = '';
export let disableTextInput = false;
const { sessionCompleted$, fetchingSource$, audioSource$ } = getSessionContext();

const { sessionCompleted$, fetchingSource$, audioSource$, session$ } = getSessionContext();

let navLoading = false;

$: chats = $session$?.chats || [];
$: hasFinalResponse = chats.some(isfinalResponse);
</script>

<div
Expand All @@ -18,7 +24,7 @@
<div class="h-24"></div>
</div>

{#if !$sessionCompleted$ && !$fetchingSource$ && !$audioSource$}
{#if !hasFinalResponse && !$sessionCompleted$ && !$fetchingSource$ && !$audioSource$}
<ChatBoxContainer
bind:searchTerm
loading={navLoading}
Expand Down
4 changes: 2 additions & 2 deletions app/src/lib/components/ExampleCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@

const { addChatItem, startSession } = getSessionContext();

function handleClick() {
async function handleClick() {
startSession(category);
addChatItem({ id: uuid(), content, role: 'user', loading: false });
goto(href);
return goto(href, { invalidateAll: true, replaceState: true });
}
</script>

Expand Down
4 changes: 2 additions & 2 deletions app/src/lib/components/ManageAudioSourceDrawer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@

<Drawer.Root {snapPoints} bind:activeSnapPoint direction="bottom" dismissible shouldScaleBackground>
<Drawer.Trigger>
<Button variant="ghost" class="bg-gray-800 text-base py-6 w-full hover:bg-gray-700 text-white"
>Manage Audiocast Sources
<Button variant="ghost" class="bg-gray-800 text-base py-6 w-full hover:bg-gray-700 text-white">
Manage Sources
</Button>
</Drawer.Trigger>

Expand Down
2 changes: 1 addition & 1 deletion app/src/lib/components/RenderExamples.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 pb-4">
{#each contentExamplesDict as [category, content]}
{@const href = `/chat/${sessionId}?category=${category}`}
{@const href = `/chat/${sessionId}?category=${category}&chat`}
<ExampleCard {content} {href} {category} />
{/each}
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/src/lib/components/RootNav.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import Logo from './Logo.svelte';
import SearchSlideSheet from './SlideSheet.svelte';
import SearchSidebar from './Sidebar.svelte';
import Sidebar from './Sidebar.svelte';
import { Button } from './ui/button';
import { getAppContext } from '$lib/stores/appContext.svelte';
import { invalidateAll } from '$app/navigation';
Expand All @@ -22,7 +22,7 @@
<svelte-fragment>
{#if $openSettingsDrawer$}
{#key sessionId}
<SearchSidebar on:clickItem={() => ($openSettingsDrawer$ = false)} />
<Sidebar on:clickItem={() => ($openSettingsDrawer$ = false)} />
{/key}
{/if}
</svelte-fragment>
Expand Down
106 changes: 69 additions & 37 deletions app/src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,58 +1,78 @@
<script context="module">
<script lang="ts" context="module">
import { SESSION_KEY } from '@/stores/sessionContext.svelte';

const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const last24Hrs = Date.now() - ONE_DAY_MS;
const last7Days = Date.now() - 4 * ONE_DAY_MS;

export function getSessionItems() {
return Object.entries(localStorage)
.filter(([key]) => key.startsWith(SESSION_KEY))
.map(
([key, value]) =>
[key.replace(`${SESSION_KEY}_`, ''), JSON.parse(value) as Session] as const
)
.filter(([_, v]) => Boolean(v));
}

function getSidebarItems(sessionItems: (readonly [string, Session])[]) {
return sessionItems
.filter(([_, item]) => item.chats.length)
.map(([sessionId, session]) => ({
sessionId,
title: session.title || 'Untitled',
nonce: session.nonce,
category: session.category,
completed: session.completed
}))
.sort((a, b) => b.nonce - a.nonce);
}
</script>

<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { browser } from '$app/environment';
import { type Session, SESSION_KEY, getSessionContext } from '../stores/sessionContext.svelte';
import SearchSidebarItem from './SidebarItem.svelte';
import { getSessionContext, type Session } from '../stores/sessionContext.svelte';
import SidebarItem from './SidebarItem.svelte';
import { getAppContext } from '../stores/appContext.svelte';
import HeadphoneOff from 'lucide-svelte/icons/headphone-off';
import cs from 'clsx';
import { page } from '$app/stores';
import NewAudiocastButton from './NewAudiocastButton.svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { env } from '@env';

const dispatch = createEventDispatcher<{ clickItem: void }>();

const { openSettingsDrawer$ } = getAppContext();

$: ({ session$ } = getSessionContext());
const { session$, refreshSidebar$ } = getSessionContext();

$: sessionItems = browser || $session$ ? getSessionItems() : [];
$: sidebarItems = getSidebarItems(sessionItems);

$: sidebarItems = sessionItems
.filter(([_, item]) => item.chats.length)
.map(([sessionId, session]) => ({
sessionId,
title: session.title || 'Untitled',
nonce: session.nonce,
category: session.category,
completed: session.completed
}))
.sort((a, b) => b.nonce - a.nonce);
$: if ($refreshSidebar$) sidebarItems = getSidebarItems(getSessionItems());

$: inLast24Hrs = sidebarItems.filter((i) => i.nonce > last24Hrs);
$: inLast7Days = sidebarItems.filter((i) => i.nonce < last24Hrs && i.nonce > last7Days);
$: inLast30Days = sidebarItems.filter((i) => i.nonce < last24Hrs && i.nonce < last7Days);

function getSessionItems() {
return Object.entries(localStorage)
.filter(([key]) => key.startsWith(SESSION_KEY))
.map(
([key, value]) =>
[key.replace(`${SESSION_KEY}_`, ''), JSON.parse(value) as Session] as const
)
.filter(([_, v]) => Boolean(v));
}

function dispatchClickItem() {
dispatch('clickItem');
}

$: rootPath = $page.url.pathname === '/';

function deleteSession(sessionId: string) {
return async () => {
localStorage.removeItem(`${SESSION_KEY}_${sessionId}`);
sidebarItems = getSidebarItems(getSessionItems());

void fetch(`${env.API_BASE_URL}/delete-session/${sessionId}`, {
method: 'DELETE'
}).catch(() => void 0);

return goto('/', { invalidateAll: true, replaceState: true });
};
}
</script>

<div
Expand All @@ -63,7 +83,7 @@
style="transition: width 0.3s cubic-bezier(0.34, 1.47, 0.64, 1), padding 0.3s ease;"
>
<nav
class={cs('flex w-full flex-col gap-x-2 lg:gap-x-0 lg:gap-y-1', {
class={cs('flex w-full flex-col gap-x-2 pt-2 lg:gap-x-0 lg:gap-y-1', {
'opacity-100': $openSettingsDrawer$,
'opacity-0': !$openSettingsDrawer$
})}
Expand All @@ -84,23 +104,35 @@
{/if}

<div class="flex w-full flex-col gap-y-1.5 pt-2" class:hidden={!inLast24Hrs.length}>
<div class="px-2 text-sm font-medium">Today</div>
{#each inLast24Hrs as item}
<SearchSidebarItem {item} on:click={dispatchClickItem} />
<div class="px-2 text-sm font-semibold">Today</div>
{#each inLast24Hrs as item (item.sessionId)}
<SidebarItem
{item}
on:click={dispatchClickItem}
on:deleteSession={deleteSession(item.sessionId)}
/>
{/each}
</div>

<div class="flex w-full flex-col gap-y-1.5 pt-6" class:hidden={!inLast7Days.length}>
<div class="px-2 text-sm font-medium">Last 7 days</div>
{#each inLast7Days as item}
<SearchSidebarItem {item} on:click={dispatchClickItem} />
<div class="px-2 text-sm font-semibold">Last 7 days</div>
{#each inLast7Days as item (item.sessionId)}
<SidebarItem
{item}
on:click={dispatchClickItem}
on:deleteSession={deleteSession(item.sessionId)}
/>
{/each}
</div>

<div class="flex w-full flex-col gap-y-1.5 pt-6" class:hidden={!inLast30Days.length}>
<div class="px-2 text-sm font-medium">Last month</div>
{#each inLast30Days as item}
<SearchSidebarItem {item} on:click={dispatchClickItem} />
<div class="px-2 text-sm font-semibold">Last month</div>
{#each inLast30Days as item (item.sessionId)}
<SidebarItem
{item}
on:click={dispatchClickItem}
on:deleteSession={deleteSession(item.sessionId)}
/>
{/each}
</div>
<div class="block h-20"></div>
Expand Down
16 changes: 8 additions & 8 deletions app/src/lib/components/SidebarItem.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" context="module">
import type { ContentCategory } from '@/utils/types';
export type SearchSidebarItem = {
export type SidebarItemModel = {
title: string;
nonce: number;
sessionId: string;
Expand All @@ -15,13 +15,11 @@
import { page } from '$app/stores';
import { Button } from './ui/button';
import { cn } from '@/utils/ui.utils';
import SidebarItemActions from './SidebarItemActions.svelte';

export let item: SearchSidebarItem;
export let item: SidebarItemModel;

const [send, receive] = crossfade({
duration: 250,
easing: cubicInOut
});
const [send, receive] = crossfade({ duration: 250, easing: cubicInOut });

$: href = item.completed
? `/audiocast/${item.sessionId}`
Expand All @@ -32,7 +30,7 @@
<Button
{href}
variant="ghost"
class="relative no-underline group hover:no-underline justify-start py-6 flex items-center px-2 hover:bg-gray-700 bg-gray-900"
class="relative no-underline group justify-between hover:no-underline py-6 flex items-center px-2 hover:bg-gray-700 bg-gray-900"
data-sveltekit-noscroll
on:click
>
Expand All @@ -52,7 +50,9 @@
{item.title}
</div>

<div class="text-[10px] px-2 hidden group-hover:block absolute bottom-0 right-0 text-sky-200">
<div class="text-[10px] px-2 hidden group-hover:block absolute bottom-0 left-0 text-sky-200">
{item.category}
</div>

<SidebarItemActions on:deleteSession />
</Button>
Loading
Loading