-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): persist file upload status (#967)
* Notify user of successful file upload even when leaving file-management page * Display special toast when vectorizing files for an assistant with those files' progress
- Loading branch information
1 parent
38e9705
commit cb0650d
Showing
42 changed files
with
940 additions
and
295 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/leapfrogai_ui/src/lib/components/AssistantProgressToast.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<!-- | ||
Note - fully testing the assistant progress toast has proven difficult with Playwright. Sometimes the websocket | ||
connection for the Supabase realtime listeners works, and sometimes it does not. Due to the dynamic nature of | ||
how this component updates in realtime, unit testing is limited. | ||
There is an issue in the backlog to re-address at some point: | ||
TODO - https://github.com/defenseunicorns/leapfrogai/issues/981 | ||
--> | ||
|
||
<script lang="ts"> | ||
import { P } from 'flowbite-svelte'; | ||
import type { ToastKind, ToastNotificationProps } from '$lib/types/toast'; | ||
import AssistantProgressToastContent from '$components/AssistantProgressToastContent.svelte'; | ||
import ToastOverride from '$components/ToastOverride.svelte'; | ||
import { getColor, getIconComponent } from '$helpers/toastHelpers'; | ||
import { onMount } from 'svelte'; | ||
import { toastStore } from '$stores'; | ||
import { FILE_VECTOR_TIMEOUT_MSG_TOAST } from '$constants/toastMessages'; | ||
export let toast: ToastNotificationProps; | ||
// Processing timeout | ||
export let timeout: number = 5 * 60 * 1000; | ||
let { id, subtitle, kind } = toast; | ||
let timeoutId: number; | ||
$: color = getColor(kind); | ||
function getAssistantVariantTitle(toastKind: ToastKind) { | ||
switch (toastKind) { | ||
case 'success': | ||
return 'Assistant Updated'; | ||
case 'info': | ||
return 'Updating Assistant Files'; | ||
case 'warning': | ||
return 'Updating Assistant Files'; | ||
case 'error': | ||
return 'Error Updating Assistant'; | ||
default: | ||
return 'Updating Assistant Files'; | ||
} | ||
} | ||
// If the files are still processing after x minutes, dismiss the toast and | ||
// pop a new error toast | ||
onMount(() => { | ||
timeoutId = setTimeout(() => { | ||
toastStore.addToast(FILE_VECTOR_TIMEOUT_MSG_TOAST()); | ||
toastStore.dismissToast(id); | ||
}, timeout); | ||
return () => { | ||
clearTimeout(timeoutId); | ||
}; | ||
}); | ||
</script> | ||
|
||
<ToastOverride {color} align={false} data-testid="assistant-progress-toast"> | ||
<svelte:fragment slot="icon"> | ||
<svelte:component this={getIconComponent(kind)} class="h-5 w-5" /> | ||
<span class="sr-only">Toast icon</span> | ||
</svelte:fragment> | ||
<div class="flex flex-col"> | ||
{getAssistantVariantTitle(kind)} | ||
{#if subtitle} | ||
<P size="xs">{subtitle}</P> | ||
{/if} | ||
|
||
<AssistantProgressToastContent | ||
toastId={id} | ||
fileIds={toast.fileIds} | ||
vectorStoreId={toast.vectorStoreId} | ||
on:statusChange={(e) => { | ||
kind = e.detail; | ||
}} | ||
/> | ||
|
||
<P size="xs">{`Time stamp [${new Date().toLocaleTimeString()}]`}</P> | ||
</div> | ||
</ToastOverride> |
38 changes: 38 additions & 0 deletions
38
src/leapfrogai_ui/src/lib/components/AssistantProgressToast.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
Note - fully testing the assistant progress toast has proven difficult with Playwright. Sometimes the websocket | ||
connection for the Supabase realtime listeners works, and sometimes it does not. Due to the dynamic nature of | ||
how this component updates in realtime, unit testing is limited. | ||
There is an issue in the backlog to re-address at some point: | ||
TODO - https://github.com/defenseunicorns/leapfrogai/issues/981 | ||
*/ | ||
|
||
import AssistantProgressToast from '$components/AssistantProgressToast.svelte'; | ||
import { render, screen } from '@testing-library/svelte'; | ||
import filesStore from '$stores/filesStore'; | ||
import { getFakeFiles } from '$testUtils/fakeData'; | ||
import { convertFileObjectToFileRows } from '$helpers/fileHelpers'; | ||
import { delay } from 'msw'; | ||
import { vi } from 'vitest'; | ||
import { toastStore } from '$stores'; | ||
|
||
describe('AssistantProgressToast', () => { | ||
it('is auto dismissed after a specified timeout', async () => { | ||
const dismissToastSpy = vi.spyOn(toastStore, 'dismissToast'); | ||
const files = getFakeFiles({ numFiles: 2 }); | ||
const toastId = '1'; | ||
const toast: ToastNotificationProps = { | ||
id: toastId, | ||
kind: 'info', | ||
title: '', | ||
fileIds: files.map((file) => file.id), | ||
vectorStoreId: '123' | ||
}; | ||
filesStore.setFiles(convertFileObjectToFileRows(files)); | ||
|
||
const timeout = 10; //10ms | ||
render(AssistantProgressToast, { timeout, toast }); //10ms timeout | ||
await screen.findByText('Updating Assistant Files'); | ||
await delay(timeout + 1); | ||
expect(dismissToastSpy).toHaveBeenCalledWith(toastId); | ||
}); | ||
}); |
87 changes: 87 additions & 0 deletions
87
src/leapfrogai_ui/src/lib/components/AssistantProgressToastContent.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<script lang="ts"> | ||
import { Spinner } from 'flowbite-svelte'; | ||
import { filesStore, toastStore } from '$stores'; | ||
import { CheckOutline, ClockOutline, CloseCircleOutline } from 'flowbite-svelte-icons'; | ||
import { createEventDispatcher, onMount } from 'svelte'; | ||
import vectorStatusStore from '$stores/vectorStatusStore'; | ||
export let toastId: string; | ||
export let vectorStoreId: string; | ||
export let fileIds: string[]; | ||
// Auto dismiss toast after success | ||
export let successTimeout: number = 5000; | ||
const dispatch = createEventDispatcher(); | ||
let completedTimeoutId: number; | ||
$: filesToDisplay = $filesStore.files.filter((file) => fileIds.includes(file.id)); | ||
$: allCompleted = | ||
filesToDisplay.length === 0 | ||
? true | ||
: filesToDisplay.length > 0 && | ||
filesToDisplay.every( | ||
(file) => | ||
$vectorStatusStore[file.id] && | ||
$vectorStatusStore[file.id][vectorStoreId] === 'completed' | ||
); | ||
$: errorStatus = filesToDisplay.some( | ||
(file) => $vectorStatusStore[file.id] && $vectorStatusStore[file.id][vectorStoreId] === 'failed' | ||
); | ||
$: if (errorStatus) { | ||
dispatch('statusChange', 'error'); | ||
} | ||
// Auto dismiss success toast after x seconds | ||
$: if (allCompleted) { | ||
dispatch('statusChange', 'success'); | ||
completedTimeoutId = setTimeout(() => { | ||
toastStore.dismissToast(toastId); | ||
}, successTimeout); | ||
} | ||
onMount(() => { | ||
return () => { | ||
clearTimeout(completedTimeoutId); | ||
}; | ||
}); | ||
</script> | ||
|
||
<div class="flex max-h-36 flex-col overflow-y-auto"> | ||
{#if allCompleted} | ||
<div class="text-green-500">File Processing Complete</div> | ||
{:else} | ||
{#each filesToDisplay as file} | ||
<div class="flex items-center justify-between py-1"> | ||
<div class="max-w-32 truncate">{file?.filename}</div> | ||
|
||
{#if !$vectorStatusStore[file.id] || !$vectorStatusStore[file.id][vectorStoreId]} | ||
<ClockOutline | ||
data-testid={`file-${file.id}-vector-pending`} | ||
color="orange" | ||
class="me-2" | ||
/> | ||
{:else if $vectorStatusStore[file.id][vectorStoreId] === 'in_progress'} | ||
<Spinner | ||
data-testid={`file-${file.id}-vector-in-progress`} | ||
class="me-2.5" | ||
size="4" | ||
color="white" | ||
/> | ||
{:else if $vectorStatusStore[file.id][vectorStoreId] === 'completed'} | ||
<CheckOutline | ||
data-testid={`file-${file.id}-vector-completed`} | ||
color="green" | ||
class="me-2" | ||
/> | ||
{:else if $vectorStatusStore[file.id][vectorStoreId] === 'failed' || $vectorStatusStore[file.id][vectorStoreId] === 'cancelled'} | ||
<CloseCircleOutline | ||
data-testid={`file-${file.id}-vector-in-failed`} | ||
color="red" | ||
class="me-2" | ||
/> | ||
{/if} | ||
</div> | ||
{/each} | ||
{/if} | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.