Skip to content

Commit

Permalink
Merge branch 'ensure-idempotency-when-generating-audiocast' into ensu…
Browse files Browse the repository at this point in the history
…re-idempotency-alt
  • Loading branch information
nwaughachukwuma committed Nov 27, 2024
2 parents c85c12f + bcabb28 commit 330e58f
Show file tree
Hide file tree
Showing 22 changed files with 352 additions and 53 deletions.
9 changes: 9 additions & 0 deletions api/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,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"
4 changes: 4 additions & 0 deletions api/src/utils/generate_audiocast.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import os

from fastapi import BackgroundTasks, HTTPException

Expand Down Expand Up @@ -49,6 +50,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 @@ -149,3 +149,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
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
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
102 changes: 67 additions & 35 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 { 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 Down Expand Up @@ -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}
<SidebarItem {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}
<SidebarItem {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}
<SidebarItem {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
12 changes: 6 additions & 6 deletions app/src/lib/components/SidebarItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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: 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>
46 changes: 46 additions & 0 deletions app/src/lib/components/SidebarItemActions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts">
import * as AlertDialog from './ui/alert-dialog';
import { Trash } from 'lucide-svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{ deleteSession: void }>();
let openDialog = false;
function dispatchDeleteSession() {
dispatch('deleteSession');
openDialog = false;
}
</script>

<div class="hidden h-full z-50 group-hover:flex items-center p-1">
<button
class="text-gray-400 hover:text-red-500"
on:click|stopPropagation|preventDefault={() => (openDialog = true)}
>
<Trash class="w-4 h-4 inline" />
</button>
</div>

{#if openDialog}
<AlertDialog.Root open portal="body" closeOnOutsideClick closeOnEscape>
<AlertDialog.Content class="border-neutral-800 overflow-hidden">
<AlertDialog.Header>
<AlertDialog.Title>Delete audiocast?</AlertDialog.Title>
<AlertDialog.Description>
This action cannot be undone. This will permanently delete your audiocast data.
</AlertDialog.Description>
</AlertDialog.Header>

<AlertDialog.Footer>
<AlertDialog.Cancel on:click={() => (openDialog = false)}>Cancel</AlertDialog.Cancel>
<AlertDialog.Action
class="bg-red-700 text-red-100 hover:bg-red-600"
on:click={dispatchDeleteSession}
>
Continue
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
{/if}
21 changes: 21 additions & 0 deletions app/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils/ui.utils.js";
type $$Props = AlertDialogPrimitive.ActionProps;
type $$Events = AlertDialogPrimitive.ActionEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>

<AlertDialogPrimitive.Action
class={cn(buttonVariants(), className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Action>
21 changes: 21 additions & 0 deletions app/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils/ui.utils.js";
type $$Props = AlertDialogPrimitive.CancelProps;
type $$Events = AlertDialogPrimitive.CancelEvents;
let className: $$Props["class"] = undefined;
export { className as class };
</script>

<AlertDialogPrimitive.Cancel
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Cancel>
28 changes: 28 additions & 0 deletions app/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import * as AlertDialog from "./index.js";
import { cn, flyAndScale } from "$lib/utils/ui.utils.js";
type $$Props = AlertDialogPrimitive.ContentProps;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>

<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Content>
</AlertDialog.Portal>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils/ui.utils.js";
type $$Props = AlertDialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>

<AlertDialogPrimitive.Description
class={cn("text-muted-foreground text-sm", className)}
{...$$restProps}
>
<slot />
</AlertDialogPrimitive.Description>
Loading

0 comments on commit 330e58f

Please sign in to comment.