diff --git a/src/leapfrogai_ui/src/app.css b/src/leapfrogai_ui/src/app.css index 1afa8ffdd..257949064 100644 --- a/src/leapfrogai_ui/src/app.css +++ b/src/leapfrogai_ui/src/app.css @@ -6,7 +6,7 @@ --header-height: 3rem; --message-input-height: 2.5rem; --sidebar-width: 255px; - scrollbar-color: #4b5563 #1f2937; + scrollbar-color: #4b5563 transparent; } /* Override TailwindCSS default Preflight styles for lists in messages */ diff --git a/src/leapfrogai_ui/src/lib/components/ChatFileUpload.svelte b/src/leapfrogai_ui/src/lib/components/ChatFileUpload.svelte index 73356ee1a..6aff9bd5f 100644 --- a/src/leapfrogai_ui/src/lib/components/ChatFileUpload.svelte +++ b/src/leapfrogai_ui/src/lib/components/ChatFileUpload.svelte @@ -145,8 +145,8 @@ }} accept={ACCEPTED_DOC_AND_AUDIO_FILE_TYPES} disabled={uploadingFiles} - class="remove-btn-style flex rounded-lg p-1.5 text-gray-500 hover:bg-inherit dark:hover:bg-inherit" + class="remove-btn-style flex rounded-lg p-1.5 hover:bg-inherit dark:hover:bg-inherit dark:focus:ring-0" > - + Attach file diff --git a/src/leapfrogai_ui/src/lib/components/FileChatActions.svelte b/src/leapfrogai_ui/src/lib/components/FileChatActions.svelte index 09ea90518..c64df772a 100644 --- a/src/leapfrogai_ui/src/lib/components/FileChatActions.svelte +++ b/src/leapfrogai_ui/src/lib/components/FileChatActions.svelte @@ -122,7 +122,6 @@ } return; } - // save translation response let responseMessage; try { @@ -139,7 +138,7 @@ await handleGeneralError(toastError); responseMessage = await saveMessage({ thread_id: threadId, - content: 'There was an error translating the file', + content: 'There was an error processing the file', role: 'assistant', metadata: { wasTranscriptionOrTranslation: 'true' diff --git a/src/leapfrogai_ui/src/lib/components/IconButton.svelte b/src/leapfrogai_ui/src/lib/components/IconButton.svelte index d3b58089f..0dfeacb8b 100644 --- a/src/leapfrogai_ui/src/lib/components/IconButton.svelte +++ b/src/leapfrogai_ui/src/lib/components/IconButton.svelte @@ -1,10 +1,22 @@ - diff --git a/src/leapfrogai_ui/src/lib/components/ImportExport.svelte b/src/leapfrogai_ui/src/lib/components/ImportExport.svelte index 89d5fae98..5fb377bea 100644 --- a/src/leapfrogai_ui/src/lib/components/ImportExport.svelte +++ b/src/leapfrogai_ui/src/lib/components/ImportExport.svelte @@ -1,6 +1,6 @@ @@ -36,16 +38,30 @@ {...$$restProps} class="sr-only" /> - + {#if unstyled} + + {:else} + + {/if} diff --git a/src/leapfrogai_ui/src/lib/components/LFSidebar.svelte b/src/leapfrogai_ui/src/lib/components/LFSidebar.svelte index 55104e25e..baeaf6fe3 100644 --- a/src/leapfrogai_ui/src/lib/components/LFSidebar.svelte +++ b/src/leapfrogai_ui/src/lib/components/LFSidebar.svelte @@ -57,33 +57,39 @@ data-testid="sidebar" class="sidebar-height flex w-[var(--sidebar-width)] border-r border-gray-700 dark:bg-gray-800 " > - + -
-
-
+
{#each organizedThreads as category} {#if category.threads.length > 0} - + {#each category.threads as thread (thread.id)} {/each} {/if} {/each} -
- +
+
diff --git a/src/leapfrogai_ui/src/lib/components/LFSidebarDropdownItem.svelte b/src/leapfrogai_ui/src/lib/components/LFSidebarDropdownItem.svelte index 7f0206739..65af0a85b 100644 --- a/src/leapfrogai_ui/src/lib/components/LFSidebarDropdownItem.svelte +++ b/src/leapfrogai_ui/src/lib/components/LFSidebarDropdownItem.svelte @@ -1,27 +1,28 @@ -
  • +
  • (hovered = true)} + on:mouseout={() => (hovered = false)} + on:focus + on:blur +> {#if editMode} { await threadsStore.changeThread(threadId); }} + aria-label={label} class={twMerge( active ? activeClass : sClass, 'truncate', 'flex-grow', 'cursor-pointer', + 'justify-between', $$props.class )} > -

    +

    {label}

    + - - { popperOpen = !popperOpen; }} - >
    - - -
    + Edit + { + e.stopPropagation(); + deleteModalOpen = true; + }}>Delete + {/if}
  • @@ -162,7 +169,7 @@ It adds a "three-dot" menu button with Popover, and delete confirmation Modal >
    -

    +

    Are you sure you want to delete your {label.substring(0, MAX_LABEL_SIZE)} chat?

    diff --git a/src/leapfrogai_ui/src/lib/components/Message.svelte b/src/leapfrogai_ui/src/lib/components/Message.svelte index 3b6455efe..85a449398 100644 --- a/src/leapfrogai_ui/src/lib/components/Message.svelte +++ b/src/leapfrogai_ui/src/lib/components/Message.svelte @@ -209,6 +209,7 @@ tabindex="0" > + Edit Message {/if} {#if message.role !== 'user'} @@ -240,6 +241,7 @@ tabindex="0" > + Regenerate Message {/if}
    diff --git a/src/leapfrogai_ui/src/lib/components/PoweredByDU.svelte b/src/leapfrogai_ui/src/lib/components/PoweredByDU.svelte index 2edf3ee2a..3e036063e 100644 --- a/src/leapfrogai_ui/src/lib/components/PoweredByDU.svelte +++ b/src/leapfrogai_ui/src/lib/components/PoweredByDU.svelte @@ -3,7 +3,7 @@ import DefenseUnicorns from '$assets/DefenseUnicorns.png'; -
    +
    Doug Powered By Defense Unicorns diff --git a/src/leapfrogai_ui/src/lib/components/SelectAssistantDropdown.svelte b/src/leapfrogai_ui/src/lib/components/SelectAssistantDropdown.svelte index c700c4fe6..a91fe35b5 100644 --- a/src/leapfrogai_ui/src/lib/components/SelectAssistantDropdown.svelte +++ b/src/leapfrogai_ui/src/lib/components/SelectAssistantDropdown.svelte @@ -3,14 +3,14 @@ import { assistantsStore } from '$stores'; import { NO_SELECTED_ASSISTANT_ID } from '$constants'; import { Button, Dropdown, DropdownItem } from 'flowbite-svelte'; - import { CheckOutline, ChevronDownOutline, UserSettingsOutline } from 'flowbite-svelte-icons'; + import { CheckOutline, ChevronDownOutline, UserAddSolid } from 'flowbite-svelte-icons'; let assistantSelectDropdownOpen = false; $: selectedAssistantName = $assistantsStore.assistants.find( (assistant) => assistant.id === $assistantsStore.selectedAssistantId - )?.name || 'Select assistant...'; + )?.name || 'No Assistant'; const handleSelectAssistant = (e) => { e.preventDefault(); @@ -27,31 +27,39 @@ }; - - -
    -
    {#each $assistantsStore.assistants as assistant (assistant.id)} {assistant.name.length > 20 ? `${assistant.name.slice(0, 20)}...` : assistant.name} {#if $assistantsStore.selectedAssistantId === assistant.id} @@ -59,19 +67,19 @@ {/if} {/each} -
    - { - assistantsStore.setSelectedAssistantId(NO_SELECTED_ASSISTANT_ID); - assistantSelectDropdownOpen = false; + +
    +
    diff --git a/src/leapfrogai_ui/src/lib/stores/toast.ts b/src/leapfrogai_ui/src/lib/stores/toast.ts index 7a2568776..3362452a0 100644 --- a/src/leapfrogai_ui/src/lib/stores/toast.ts +++ b/src/leapfrogai_ui/src/lib/stores/toast.ts @@ -21,7 +21,8 @@ const createToastsStore = () => { const newToast: ToastNotificationProps = { id, ...toastDefaults, - ...toast + ...toast, + timeout: toast.kind === 'error' ? -1 : toastDefaults.timeout }; update((old) => ({ ...old, diff --git a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/+page.svelte b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/+page.svelte index a9c359274..cc854680a 100644 --- a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/+page.svelte +++ b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/+page.svelte @@ -23,7 +23,7 @@ ERROR_SAVING_MSG_TOAST } from '$constants/toastMessages'; import SelectAssistantDropdown from '$components/SelectAssistantDropdown.svelte'; - import { PaperPlaneOutline, StopOutline } from 'flowbite-svelte-icons'; + import { ArrowUpOutline, StopOutline } from 'flowbite-svelte-icons'; import type { FileMetadata, LFFile } from '$lib/types/files'; import UploadedFileCards from '$components/UploadedFileCards.svelte'; import ChatFileUploadForm from '$components/ChatFileUpload.svelte'; @@ -359,13 +359,13 @@ {/if}
    -
    -
    +
    +
    0 && 'py-4' )} > @@ -377,8 +377,7 @@ > - -
    +
    {#if !assistantMode} {/if} @@ -393,31 +392,15 @@ maxRows={10} innerWrappedClass="p-px bg-white dark:bg-gray-700" /> - {#if !$isLoading && $status !== 'in_progress'} - + Send message - - Send message - {:else} - Cancel message + Cancel message {/if}
    diff --git a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/ChatPageWithToast.test.svelte b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/ChatPageWithToast.test.svelte index 0936665cf..0406985a5 100644 --- a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/ChatPageWithToast.test.svelte +++ b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/ChatPageWithToast.test.svelte @@ -1,12 +1,9 @@
    - +
    diff --git a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage.test.ts b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage.test.ts index 21857b0e8..eea671ab2 100644 --- a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage.test.ts +++ b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage.test.ts @@ -21,17 +21,10 @@ import { mockOpenAI } from '../../../../../vitest-setup'; import { ERROR_GETTING_AI_RESPONSE_TOAST, ERROR_SAVING_MSG_TOAST } from '$constants/toastMessages'; import { faker } from '@faker-js/faker'; -import type { LFThread } from '$lib/types/threads'; -import type { LFAssistant } from '$lib/types/assistants'; import { delay } from '$helpers/chatHelpers'; import { mockGetFiles } from '$lib/mocks/file-mocks'; import { threadsStore } from '$stores'; -type LayoutServerLoad = { - threads: LFThread[]; - assistants: LFAssistant[]; -} | null; -let data: LayoutServerLoad; const question = 'What is AI?'; const assistant1 = getFakeAssistant(); @@ -71,7 +64,7 @@ describe('when there is an active thread selected', () => { }); it('it renders all the messages', async () => { - render(ChatPage, { data }); + render(ChatPage); for (let i = 0; i < fakeThreads[0].messages!.length; i++) { await screen.findByText(getMessageText(fakeThreads[0].messages![i])); @@ -79,7 +72,7 @@ describe('when there is an active thread selected', () => { }); test('the send button is disabled when there is no text in the input', () => { - render(ChatPage, { data }); + render(ChatPage); const submitBtn = screen.getByTestId('send message'); expect(submitBtn).toHaveProperty('disabled', true); }); @@ -88,7 +81,7 @@ describe('when there is an active thread selected', () => { mockChatCompletion(); mockNewMessage(); - const { getByTestId } = render(ChatPage, { data }); + const { getByTestId } = render(ChatPage); const input = getByTestId('chat-input') as HTMLInputElement; const submitBtn = getByTestId('send message'); @@ -109,7 +102,7 @@ describe('when there is an active thread selected', () => { mockNewMessage(); mockChatCompletion({ delayTime }); - const { getByTestId } = render(ChatPage, { data }); + const { getByTestId } = render(ChatPage); const input = getByTestId('chat-input') as HTMLInputElement; const submitBtn = getByTestId('send message'); @@ -125,7 +118,7 @@ describe('when there is an active thread selected', () => { }); it('disables the send button if the message length is too long', async () => { - const { getByTestId } = render(ChatPage, { data }); + const { getByTestId } = render(ChatPage); const input = getByTestId('chat-input') as HTMLInputElement; const submitBtn = getByTestId('send message'); const limitText = faker.string.alpha({ length: 101 }); @@ -139,7 +132,7 @@ describe('when there is an active thread selected', () => { it.skip('displays a toast error notification when there is an error with the AI response', async () => { mockChatCompletionError(); mockNewMessage(); - const { getByTestId } = render(ChatPageWithToast, { data }); + const { getByTestId } = render(ChatPageWithToast); const input = getByTestId('chat-input') as HTMLInputElement; const submitBtn = getByTestId('send message'); @@ -152,7 +145,7 @@ describe('when there is an active thread selected', () => { it('displays an error message when there is an error saving the response', async () => { mockNewMessageError(); - const { getByTestId } = render(ChatPageWithToast, { data }); + const { getByTestId } = render(ChatPageWithToast); const input = getByTestId('chat-input') as HTMLInputElement; const submitBtn = getByTestId('send message'); @@ -173,7 +166,7 @@ describe('when there is an active thread selected', () => { }); mockNewMessage(); - const { getByTestId } = render(ChatPageWithToast, { data }); + const { getByTestId } = render(ChatPageWithToast); const input = getByTestId('chat-input') as HTMLInputElement; const submitBtn = getByTestId('send message'); @@ -190,7 +183,7 @@ describe('when there is an active thread selected', () => { it('has a button to go to the assistants management page in the assistant dropdown', async () => { const goToSpy = vi.spyOn(navigation, 'goto'); - const { getByRole, getByTestId } = render(ChatPage, { data }); + const { getByRole, getByTestId } = render(ChatPage); const assistantSelect = getByTestId('assistants-select-btn'); await userEvent.click(assistantSelect); diff --git a/src/leapfrogai_ui/tests/chat.test.ts b/src/leapfrogai_ui/tests/chat.test.ts index e4d6c1fb3..f1b181055 100644 --- a/src/leapfrogai_ui/tests/chat.test.ts +++ b/src/leapfrogai_ui/tests/chat.test.ts @@ -57,7 +57,7 @@ test('it saves in progress responses when interrupted by changing threads', asyn await sendMessage(page, uniqueLongMessagePrompt); await expect(messages).toHaveCount(2); - await page.getByText('New Chat').click(); + await page.getByText('New chat').click(); await expect(messages).toHaveCount(0); await page.getByText(uniqueLongMessagePrompt).click(); // switch back to original thread await expect(messages).toHaveCount(2); diff --git a/src/leapfrogai_ui/tests/helpers/threadHelpers.ts b/src/leapfrogai_ui/tests/helpers/threadHelpers.ts index 12c81ffae..6fa2fea47 100644 --- a/src/leapfrogai_ui/tests/helpers/threadHelpers.ts +++ b/src/leapfrogai_ui/tests/helpers/threadHelpers.ts @@ -4,6 +4,8 @@ import type { Profile } from '$lib/types/profile'; import { supabase } from './helpers'; export const clickToDeleteThread = async (page: Page, label: string) => { + const threads = page.getByTestId('threads'); + await threads.getByText(label).hover(); await page.getByTestId(`thread-menu-btn-${label}`).click(); await page.getByRole('button', { name: /delete/i }).click(); const deleteBtns = await page.getByRole('button', { name: /delete/i }).all(); diff --git a/src/leapfrogai_ui/tests/sidebar.test.ts b/src/leapfrogai_ui/tests/sidebar.test.ts index ce95521c4..1d5ad8b98 100644 --- a/src/leapfrogai_ui/tests/sidebar.test.ts +++ b/src/leapfrogai_ui/tests/sidebar.test.ts @@ -33,6 +33,8 @@ test('can edit thread labels', async ({ page, openAIClient }) => { await sendMessage(page, newMessage1); await expect(messages).toHaveCount(2); + const threads = page.getByTestId('threads'); + await threads.getByText(newMessage1).hover(); const threadMenuBtn = page.getByTestId(`thread-menu-btn-${newMessage1}`); await threadMenuBtn.click(); @@ -60,7 +62,7 @@ test('Can switch threads', async ({ page, openAIClient }) => { await waitForResponseToComplete(page); await expect(messages).toHaveCount(4); - await page.getByText('New Chat').click(); + await page.getByText('New chat').click(); await expect(messages).toHaveCount(0); await sendMessage(page, newMessage3); await waitForResponseToComplete(page);