From 68b1c47b4796ca123aa5d9a0524fa1a84c9084c5 Mon Sep 17 00:00:00 2001 From: Chukwuma Nwaugha Date: Wed, 27 Nov 2024 22:40:21 +0000 Subject: [PATCH] Ensure idempotency when generating audiocast (#18) * improved cleanup and abstraction * remove session_id key prop from sidebar to fix issue with scrolling to top * add cleanup for generated audio files; ensure temporary files are removed after processing * install alert-dialog component * wip: use a sidebar_item_action * refine sidebar item and item action * create sessionItems$ store value * fix issue with sidebar state when audiocast is added or removed * implement delete audiocast session on the server --- api/requirements.txt | 22 ++-- api/src/main.py | 9 ++ api/src/utils/generate_audiocast.py | 5 + api/src/utils/session_manager.py | 4 + app/package.json | 2 +- app/src/lib/components/ChatContainer.svelte | 10 +- app/src/lib/components/ExampleCard.svelte | 4 +- .../components/ManageAudioSourceDrawer.svelte | 4 +- app/src/lib/components/RenderExamples.svelte | 2 +- app/src/lib/components/RootNav.svelte | 4 +- app/src/lib/components/Sidebar.svelte | 106 ++++++++++++------ app/src/lib/components/SidebarItem.svelte | 16 +-- .../lib/components/SidebarItemActions.svelte | 46 ++++++++ app/src/lib/components/SlideSheet.svelte | 2 +- .../alert-dialog/alert-dialog-action.svelte | 21 ++++ .../alert-dialog/alert-dialog-cancel.svelte | 21 ++++ .../alert-dialog/alert-dialog-content.svelte | 28 +++++ .../alert-dialog-description.svelte | 16 +++ .../alert-dialog/alert-dialog-footer.svelte | 16 +++ .../alert-dialog/alert-dialog-header.svelte | 13 +++ .../alert-dialog/alert-dialog-overlay.svelte | 21 ++++ .../alert-dialog/alert-dialog-portal.svelte | 9 ++ .../ui/alert-dialog/alert-dialog-title.svelte | 14 +++ .../lib/components/ui/alert-dialog/index.ts | 40 +++++++ app/src/lib/stores/sessionContext.svelte.ts | 9 +- app/src/lib/utils/session.utils.ts | 13 +++ app/src/routes/+layout.svelte | 6 +- app/src/routes/+page.svelte | 6 +- .../chat/[sessionId=sessionId]/+page.svelte | 15 +-- pnpm-lock.yaml | 6 +- 30 files changed, 398 insertions(+), 92 deletions(-) create mode 100644 app/src/lib/components/SidebarItemActions.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte create mode 100644 app/src/lib/components/ui/alert-dialog/index.ts create mode 100644 app/src/lib/utils/session.utils.ts diff --git a/api/requirements.txt b/api/requirements.txt index f10ddd5..1f015fd 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -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] \ No newline at end of file +async-web-search \ No newline at end of file diff --git a/api/src/main.py b/api/src/main.py index 4d88349..3f142d9 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -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" diff --git a/api/src/utils/generate_audiocast.py b/api/src/utils/generate_audiocast.py index 5ba3530..2adfe1e 100644 --- a/api/src/utils/generate_audiocast.py +++ b/api/src/utils/generate_audiocast.py @@ -1,3 +1,5 @@ +import os + from fastapi import BackgroundTasks, HTTPException from src.services.storage import StorageManager @@ -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): diff --git a/api/src/utils/session_manager.py b/api/src/utils/session_manager.py index 37f57e5..704779e 100644 --- a/api/src/utils/session_manager.py +++ b/api/src/utils/session_manager.py @@ -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) diff --git a/app/package.json b/app/package.json index 02aaad1..1e69c2d 100644 --- a/app/package.json +++ b/app/package.json @@ -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", @@ -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", diff --git a/app/src/lib/components/ChatContainer.svelte b/app/src/lib/components/ChatContainer.svelte index 8def7c8..14ab83e 100644 --- a/app/src/lib/components/ChatContainer.svelte +++ b/app/src/lib/components/ChatContainer.svelte @@ -1,11 +1,17 @@
- {#if !$sessionCompleted$ && !$fetchingSource$ && !$audioSource$} + {#if !hasFinalResponse && !$sessionCompleted$ && !$fetchingSource$ && !$audioSource$} diff --git a/app/src/lib/components/ManageAudioSourceDrawer.svelte b/app/src/lib/components/ManageAudioSourceDrawer.svelte index 4e9df0e..4181045 100644 --- a/app/src/lib/components/ManageAudioSourceDrawer.svelte +++ b/app/src/lib/components/ManageAudioSourceDrawer.svelte @@ -77,8 +77,8 @@ - diff --git a/app/src/lib/components/RenderExamples.svelte b/app/src/lib/components/RenderExamples.svelte index fb4f041..6a3e3ea 100644 --- a/app/src/lib/components/RenderExamples.svelte +++ b/app/src/lib/components/RenderExamples.svelte @@ -29,7 +29,7 @@
{#each contentExamplesDict as [category, content]} - {@const href = `/chat/${sessionId}?category=${category}`} + {@const href = `/chat/${sessionId}?category=${category}&chat`} {/each}
diff --git a/app/src/lib/components/RootNav.svelte b/app/src/lib/components/RootNav.svelte index dc00bf4..99df60e 100644 --- a/app/src/lib/components/RootNav.svelte +++ b/app/src/lib/components/RootNav.svelte @@ -1,7 +1,7 @@
-
Last 7 days
- {#each inLast7Days as item} - +
Last 7 days
+ {#each inLast7Days as item (item.sessionId)} + {/each}
-
Last month
- {#each inLast30Days as item} - +
Last month
+ {#each inLast30Days as item (item.sessionId)} + {/each}
diff --git a/app/src/lib/components/SidebarItem.svelte b/app/src/lib/components/SidebarItem.svelte index 848c3f5..bd6eb49 100644 --- a/app/src/lib/components/SidebarItem.svelte +++ b/app/src/lib/components/SidebarItem.svelte @@ -1,6 +1,6 @@ + + + +{#if openDialog} + + + + Delete audiocast? + + This action cannot be undone. This will permanently delete your audiocast data. + + + + + (openDialog = false)}>Cancel + + Continue + + + + +{/if} diff --git a/app/src/lib/components/SlideSheet.svelte b/app/src/lib/components/SlideSheet.svelte index 14837fb..19d4772 100644 --- a/app/src/lib/components/SlideSheet.svelte +++ b/app/src/lib/components/SlideSheet.svelte @@ -19,7 +19,7 @@ - Audiocasts + Audiocasts diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..51dbd6a --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..88b2cf7 --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..b6efc0d --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..972cc63 --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..e843c0d --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,16 @@ + + +
+ +
diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..88bf04b --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,13 @@ + + +
+ +
diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..659bb81 --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,21 @@ + + + diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte new file mode 100644 index 0000000..e227219 --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/app/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..594fe5e --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/lib/components/ui/alert-dialog/index.ts b/app/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..be56dd7 --- /dev/null +++ b/app/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,40 @@ +import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; + +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Portal from "./alert-dialog-portal.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; + +const Root = AlertDialogPrimitive.Root; +const Trigger = AlertDialogPrimitive.Trigger; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, +}; diff --git a/app/src/lib/stores/sessionContext.svelte.ts b/app/src/lib/stores/sessionContext.svelte.ts index ad62b72..21cd590 100644 --- a/app/src/lib/stores/sessionContext.svelte.ts +++ b/app/src/lib/stores/sessionContext.svelte.ts @@ -1,9 +1,11 @@ +import { browser } from '$app/environment'; +import { page } from '$app/stores'; 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'; -import { derived, writable } from 'svelte/store'; +import { derived, get, writable } from 'svelte/store'; const CONTEXT_KEY = {}; export const SESSION_KEY = 'AUDIOCAST_SESSION'; @@ -26,13 +28,15 @@ export type Session = { }; export function setSessionContext(sessionId: string) { - const session$ = persisted(`${SESSION_KEY}_${sessionId}`, null); const sessionId$ = writable(sessionId); + const session$ = persisted(`${SESSION_KEY}_${sessionId}`, null); const sessionCompleted$ = derived(session$, ($session) => !!$session?.completed); const fetchingSource$ = writable(false); const audioSource$ = persisted(`AUDIOCAST_SOURCE_${sessionId}`, ''); + const refreshSidebar$ = derived(page, ({ url }) => browser && url.searchParams.has('chat')); + return setContext(CONTEXT_KEY, { session$, sessionId$, @@ -41,6 +45,7 @@ export function setSessionContext(sessionId: string) { audioSource$, customSources$: getCustomSources$(sessionId), sessionModel$: getSession$(sessionId), + refreshSidebar$, startSession: (category: ContentCategory) => { session$.set({ id: sessionId, diff --git a/app/src/lib/utils/session.utils.ts b/app/src/lib/utils/session.utils.ts new file mode 100644 index 0000000..b53bb47 --- /dev/null +++ b/app/src/lib/utils/session.utils.ts @@ -0,0 +1,13 @@ +import type { ChatItem } from '@/stores/sessionContext.svelte'; + +export const FINAL_RESPONSE_PREFIX = 'Ok, thanks for clarifying!'; +export const FINAL_RESPONSE_SUFFIX = + 'Please click the button below to start generating the audiocast.'; + +export const isfinalResponse = (v: ChatItem) => v.content.includes(FINAL_RESPONSE_SUFFIX); + +export function getSummary(content: string) { + const replacePrefixRegex = new RegExp(FINAL_RESPONSE_PREFIX, 'gi'); + const replaceSuffixRegex = new RegExp(FINAL_RESPONSE_SUFFIX, 'gi'); + return content.replace(replacePrefixRegex, '').replace(replaceSuffixRegex, '').trim(); +} diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte index 4683387..4ac0741 100644 --- a/app/src/routes/+layout.svelte +++ b/app/src/routes/+layout.svelte @@ -7,7 +7,7 @@ import { Toaster } from '$lib/components/ui/sonner'; import { setSessionContext } from '@/stores/sessionContext.svelte'; import RootNav from '@/components/RootNav.svelte'; - import SearchSidebar from '@/components/Sidebar.svelte'; + import Sidebar from '@/components/Sidebar.svelte'; import { page } from '$app/stores'; import Spinner from '@/components/Spinner.svelte'; import { setAppContext } from '@/stores/appContext.svelte'; @@ -39,9 +39,7 @@
{#if browser} {#key sessionId} diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 79f6d6b..201dd39 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -21,14 +21,14 @@ searchTerm = ''; } - function continueChat(category: ContentCategory) { + async function continueChat(category: ContentCategory) { startSession(category); const content = `${selectContent}\nCategory: ${category} `; addChatItem({ id: uuid(), content, role: 'user', loading: false }); - const href = `/chat/${sessionId}?category=${category}`; - goto(href); + const href = `/chat/${sessionId}?category=${category}&chat`; + return goto(href, { invalidateAll: true, replaceState: true }); } diff --git a/app/src/routes/chat/[sessionId=sessionId]/+page.svelte b/app/src/routes/chat/[sessionId=sessionId]/+page.svelte index 006806a..b4e30e8 100644 --- a/app/src/routes/chat/[sessionId=sessionId]/+page.svelte +++ b/app/src/routes/chat/[sessionId=sessionId]/+page.svelte @@ -1,15 +1,3 @@ - -