From ffaf867f607f8f64b5a6ce73539b1c27a8372664 Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Thu, 26 Sep 2024 16:33:16 -0600 Subject: [PATCH 1/4] init --- .../src/lib/constants/toastMessages.ts | 2 +- .../src/routes/api/chat/assistants/+server.ts | 1 - .../(dashboard)/[[thread_id]]/+page.svelte | 51 ++++++++++---- src/leapfrogai_ui/tests/chat.test.ts | 66 +++++++++++++++++++ 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/src/leapfrogai_ui/src/lib/constants/toastMessages.ts b/src/leapfrogai_ui/src/lib/constants/toastMessages.ts index e431348a5..5bcadadc8 100644 --- a/src/leapfrogai_ui/src/lib/constants/toastMessages.ts +++ b/src/leapfrogai_ui/src/lib/constants/toastMessages.ts @@ -19,7 +19,7 @@ export const ERROR_GETTING_ASSISTANT_MSG_TOAST = ( ): ToastData => ({ kind: 'error', title: 'Error', - subtitle: 'Error getting Assistant Response', + subtitle: 'Error getting assistant response', ...override }); diff --git a/src/leapfrogai_ui/src/routes/api/chat/assistants/+server.ts b/src/leapfrogai_ui/src/routes/api/chat/assistants/+server.ts index b5152cc50..20558f455 100644 --- a/src/leapfrogai_ui/src/routes/api/chat/assistants/+server.ts +++ b/src/leapfrogai_ui/src/routes/api/chat/assistants/+server.ts @@ -46,7 +46,6 @@ export const POST: RequestHandler = async ({ request, locals: { session } }) => throw new Error('assistant_id is not set'); })() }); - // forward run status would stream message deltas let runResult = await forwardStream(runStream); 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 f082615c5..0e67f0e27 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 @@ -76,6 +76,8 @@ resetFiles(); // attachment of files w/assistants disabled } + $: if ($assistantError) handleAssistantResponseError(); + /** END REACTIVE STATE **/ const resetFiles = () => { @@ -121,16 +123,45 @@ const handleCompletedAssistantResponse = async () => { if (componentHasMounted && $status === 'awaiting_message') { - const assistantResponseId = $assistantMessages[$assistantMessages.length - 1].id; + if ($assistantError) return; + if (latestAssistantMessage.role === 'user') { + await handleAssistantResponseError(); + return; + } + + const assistantResponseId = latestAssistantMessage.id; const messageRes = await fetch( `/api/messages?thread_id=${$page.params.thread_id}&message_id=${assistantResponseId}` ); + if (!messageRes.ok) { + //useAssistants onError hook will handle this + return; + } + const message = await messageRes.json(); - await threadsStore.addMessageToStore(message); - threadsStore.setStreamingMessage(null); + if (message && !getMessageText(message)) { + // error with response(empty response)/timeout + await handleAssistantResponseError(); + } else { + await threadsStore.addMessageToStore(message); + threadsStore.setStreamingMessage(null); + } } }; + const handleAssistantResponseError = async () => { + toastStore.addToast({ + ...ERROR_GETTING_ASSISTANT_MSG_TOAST() + }); + if (latestAssistantMessage.role === 'assistant') { + await threadsStore.deleteMessage($page.params.thread_id, latestAssistantMessage.id); + threadsStore.removeMessageFromStore($page.params.thread_id, latestAssistantMessage.id); + $assistantMessages = [...$assistantMessages.splice(-1)]; + } + threadsStore.setStreamingMessage(null); + await threadsStore.setSendingBlocked(false); + }; + /** useChat - streams messages with the /api/chat route**/ const { input: chatInput, @@ -180,19 +211,11 @@ submitMessage: submitAssistantMessage, stop: assistantStop, setMessages: setAssistantMessages, - append: assistantAppend + append: assistantAppend, + error: assistantError } = useAssistant({ api: '/api/chat/assistants', - threadId: data.thread?.id, - onError: async (e) => { - // ignore this error b/c it is expected on cancel - if (e.message !== 'BodyStreamBuffer was aborted') { - toastStore.addToast({ - ...ERROR_GETTING_ASSISTANT_MSG_TOAST() - }); - } - await threadsStore.setSendingBlocked(false); - } + threadId: data.thread?.id }); const sendAssistantMessage = async (e: SubmitEvent | KeyboardEvent) => { diff --git a/src/leapfrogai_ui/tests/chat.test.ts b/src/leapfrogai_ui/tests/chat.test.ts index e4d6c1fb3..bd82e320c 100644 --- a/src/leapfrogai_ui/tests/chat.test.ts +++ b/src/leapfrogai_ui/tests/chat.test.ts @@ -7,6 +7,7 @@ import { waitForResponseToComplete } from './helpers/threadHelpers'; import { loadChatPage } from './helpers/navigationHelpers'; +import { ERROR_GETTING_ASSISTANT_MSG_TOAST } from '$constants/toastMessages'; const newMessage1 = getSimpleMathQuestion(); const newMessage2 = getSimpleMathQuestion(); @@ -197,3 +198,68 @@ test('it can chat with an assistant that doesnt have files', async ({ page, open await deleteActiveThread(page, openAIClient); await deleteAssistantWithApi(assistant.id, openAIClient); }); + +// Note - these error cases do not test all edge cases. ex. completed response comes back empty, /chat/assistants +// partially completes then fails, stream fails, etc... +test('displays an error toast if /chat/assistants throws while getting a response from an assistant', async ({ + page, + openAIClient +}) => { + const assistant = await createAssistantWithApi({ openAIClient }); + await loadChatPage(page); + + const assistantDropdown = page.getByTestId('assistants-select-btn'); + await assistantDropdown.click(); + await page.getByText(assistant!.name!).click(); + + await page.route('*/**/chat/assistants', async (route) => { + await route.abort('failed'); + }); + await sendMessage(page, newMessage1); + + await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); + const messages = await page.getByTestId('message').all(); + expect(messages).toHaveLength(0); +}); + +test('displays an error toast if /chat/assistants returns a 500 when getting a response from an assistant 2', async ({ + page, + openAIClient +}) => { + const assistant = await createAssistantWithApi({ openAIClient }); + await loadChatPage(page); + + const assistantDropdown = page.getByTestId('assistants-select-btn'); + await assistantDropdown.click(); + await page.getByText(assistant!.name!).click(); + + await page.route('*/**/chat/assistants', async (route) => { + await route.fulfill({ status: 500 }); + }); + await sendMessage(page, newMessage1); + + await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); + const messages = await page.getByTestId('message').all(); + expect(messages).toHaveLength(1); +}); + +test('displays an error toast if /chat/assistants returns a 200 with no body when getting a response from an assistant 3', async ({ + page, + openAIClient +}) => { + const assistant = await createAssistantWithApi({ openAIClient }); + await loadChatPage(page); + + const assistantDropdown = page.getByTestId('assistants-select-btn'); + await assistantDropdown.click(); + await page.getByText(assistant!.name!).click(); + + await page.route('*/**/chat/assistants', async (route) => { + await route.fulfill({ status: 200 }); + }); + await sendMessage(page, newMessage1); + + await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); + const messages = await page.getByTestId('message').all(); + expect(messages).toHaveLength(0); +}); From 24ac12f9e86a77ef4fe7759b49dcd5ec563a4ad6 Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Mon, 30 Sep 2024 08:52:01 -0600 Subject: [PATCH 2/4] fix flaky e2e --- .../tests/assistant-progress.test.ts | 53 +++++++++++++------ src/leapfrogai_ui/tests/chat.test.ts | 6 +-- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/leapfrogai_ui/tests/assistant-progress.test.ts b/src/leapfrogai_ui/tests/assistant-progress.test.ts index b59bc8f66..b414b6ce1 100644 --- a/src/leapfrogai_ui/tests/assistant-progress.test.ts +++ b/src/leapfrogai_ui/tests/assistant-progress.test.ts @@ -9,6 +9,7 @@ import { uploadFileWithApi } from './helpers/fileHelpers'; import { loadNewAssistantPage } from './helpers/navigationHelpers'; +import type { FileObject } from 'openai/resources/files'; // 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. Here we test that the @@ -18,12 +19,17 @@ test('when creating an assistant with files, an assistant progress toast is disp openAIClient }) => { const assistantInput = getFakeAssistantInput(); - const filename1 = `${faker.word.noun()}-test.pdf`; - const filename2 = `${faker.word.noun()}-test.pdf`; - await createPDF({ filename: filename1 }); - await createPDF({ filename: filename2 }); - const uploadedFile1 = await uploadFileWithApi(filename1, 'application/pdf', openAIClient); - const uploadedFile2 = await uploadFileWithApi(filename2, 'application/pdf', openAIClient); + const numFiles = 2; + const filenames: string[] = []; + const uploadedFiles: FileObject[] = []; + + for (let i = 0; i < numFiles; i++) { + const filename = `${faker.word.noun()}-test.pdf`; + filenames.push(filename); + await createPDF({ filename }); + const uploadedFile = await uploadFileWithApi(filename, 'application/pdf', openAIClient); + uploadedFiles.push(uploadedFile); + } await loadNewAssistantPage(page); @@ -33,19 +39,36 @@ test('when creating an assistant with files, an assistant progress toast is disp await page.getByTestId('file-select-dropdown-btn').click(); const fileSelectContainer = page.getByTestId('file-select-container'); - await fileSelectContainer.getByTestId(`${uploadedFile1.id}-checkbox`).check(); - await fileSelectContainer.getByTestId(`${uploadedFile2.id}-checkbox`).check(); + for (const file of uploadedFiles) { + await fileSelectContainer.getByTestId(`${file.id}-checkbox`).check(); + } await page.getByRole('button', { name: 'Save' }).click(); - await page.waitForURL('/chat/assistants-management'); - await expect(page.getByTestId(`file-${uploadedFile1.id}-vector-in-progress`)).toBeVisible(); - await expect(page.getByTestId(`file-${uploadedFile2.id}-vector-pending`)).toBeVisible(); + const inProgressSelector = `file-${uploadedFiles[0].id}-vector-in-progress`; + const completedSelector = `file-${uploadedFiles[0].id}-vector-completed`; + + // Second file is pending + await expect(page.getByTestId(`file-${uploadedFiles[1].id}-vector-pending`)).toBeVisible(); + + // Check for either "in-progress" or "completed" state for the first file, it can happen really fast so this prevents + // a flaky test + const progressToast = await page.waitForSelector( + `[data-testid="${inProgressSelector}"], [data-testid="${completedSelector}"]`, + { + timeout: 30000 + } + ); + expect(progressToast).toBeTruthy(); + + await page.waitForURL('/chat/assistants-management'); // cleanup - deleteFixtureFile(filename1); - deleteFixtureFile(filename2); + for (const filename of filenames) { + deleteFixtureFile(filename); + } + for (const file of uploadedFiles) { + await deleteFileWithApi(file.id, openAIClient); + } await deleteAssistantCard(assistantInput.name, page); - await deleteFileWithApi(uploadedFile1.id, openAIClient); - await deleteFileWithApi(uploadedFile2.id, openAIClient); }); diff --git a/src/leapfrogai_ui/tests/chat.test.ts b/src/leapfrogai_ui/tests/chat.test.ts index bd82e320c..1f4cf6313 100644 --- a/src/leapfrogai_ui/tests/chat.test.ts +++ b/src/leapfrogai_ui/tests/chat.test.ts @@ -222,7 +222,7 @@ test('displays an error toast if /chat/assistants throws while getting a respons expect(messages).toHaveLength(0); }); -test('displays an error toast if /chat/assistants returns a 500 when getting a response from an assistant 2', async ({ +test('displays an error toast if /chat/assistants returns a 500 when getting a response from an assistant', async ({ page, openAIClient }) => { @@ -239,11 +239,9 @@ test('displays an error toast if /chat/assistants returns a 500 when getting a r await sendMessage(page, newMessage1); await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); - const messages = await page.getByTestId('message').all(); - expect(messages).toHaveLength(1); }); -test('displays an error toast if /chat/assistants returns a 200 with no body when getting a response from an assistant 3', async ({ +test('displays an error toast if /chat/assistants returns a 200 with no body when getting a response from an assistant', async ({ page, openAIClient }) => { From 3bd06b82d151986735a704f29ae4c22fe3c369e8 Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Mon, 30 Sep 2024 09:56:10 -0600 Subject: [PATCH 3/4] replace message with error message --- .../src/lib/components/Message.svelte | 9 ++- src/leapfrogai_ui/src/lib/constants/errors.ts | 2 + .../src/lib/helpers/chatHelpers.ts | 8 +++ .../(dashboard)/[[thread_id]]/+page.svelte | 20 ++++++ src/leapfrogai_ui/tests/assistants.test.ts | 66 +++++++++++++++++++ src/leapfrogai_ui/tests/chat.test.ts | 64 ------------------ 6 files changed, 103 insertions(+), 66 deletions(-) diff --git a/src/leapfrogai_ui/src/lib/components/Message.svelte b/src/leapfrogai_ui/src/lib/components/Message.svelte index 0af165b1f..d377286bb 100644 --- a/src/leapfrogai_ui/src/lib/components/Message.svelte +++ b/src/leapfrogai_ui/src/lib/components/Message.svelte @@ -59,7 +59,10 @@ }); let assistantImage = isRunAssistantMessage(message) - ? getAssistantImage($page.data.assistants || [], message.assistant_id!) + ? getAssistantImage( + $page.data.assistants || [], + message.assistant_id || message.metadata?.assistant_id + ) : null; let messageIsHovered = false; @@ -166,7 +169,9 @@ >
- {message.role === 'user' ? 'You' : getAssistantName(message.assistant_id)} + {message.role === 'user' + ? 'You' + : getAssistantName(message.assistant_id || message.metadata?.assistant_id)}
{#if fileMetadata}
diff --git a/src/leapfrogai_ui/src/lib/constants/errors.ts b/src/leapfrogai_ui/src/lib/constants/errors.ts index e26224e6f..a34bd5906 100644 --- a/src/leapfrogai_ui/src/lib/constants/errors.ts +++ b/src/leapfrogai_ui/src/lib/constants/errors.ts @@ -1,2 +1,4 @@ export const FILE_CONTEXT_TOO_LARGE_ERROR_MSG = 'Error: Upload fewer or smaller files'; export const ERROR_UPLOADING_FILE_MSG = 'Error uploading file'; +export const ASSISTANT_ERROR_MSG = + "I'm sorry but I've experienced an error. Please try again, or contact support."; diff --git a/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts b/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts index 72db4dd58..cafcc7c98 100644 --- a/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts +++ b/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts @@ -263,3 +263,11 @@ export const getCitations = (message: OpenAIMessage, files: FileObject[]) => { } return []; }; + +export const refetchThread = async (threadId: string) => { + const res = await fetch(`/api/threads/${threadId}`); + if (res.ok) { + const thread = await res.json(); + threadsStore.updateThread(thread); + } +}; \ No newline at end of file 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 0e67f0e27..45f5d5bb2 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 @@ -13,6 +13,7 @@ import { twMerge } from 'tailwind-merge'; import { isRunAssistantMessage, + refetchThread, resetMessages, saveMessage, stopThenSave @@ -29,6 +30,8 @@ import ChatFileUploadForm from '$components/ChatFileUpload.svelte'; import FileChatActions from '$components/FileChatActions.svelte'; import LFCarousel from '$components/LFCarousel.svelte'; + import { ASSISTANT_ERROR_MSG } from '$constants/errors'; + import { delay } from 'msw'; export let data; @@ -149,7 +152,22 @@ } }; + const createAssistantErrorResponse = async () => { + await delay(1000); // ensure error response timestamp is after user's msg + const newMessage = await saveMessage({ + thread_id: data.thread.id, + content: ASSISTANT_ERROR_MSG, + role: 'assistant', + metadata: { + assistant_id: latestAssistantMessage.assistant_id || $threadsStore.selectedAssistantId + } + }); + + await threadsStore.addMessageToStore(newMessage); + }; + const handleAssistantResponseError = async () => { + await refetchThread($page.params.thread_id); // if there was an error in the stream, we need to re-fetch to get the user's msg from the db toastStore.addToast({ ...ERROR_GETTING_ASSISTANT_MSG_TOAST() }); @@ -158,6 +176,8 @@ threadsStore.removeMessageFromStore($page.params.thread_id, latestAssistantMessage.id); $assistantMessages = [...$assistantMessages.splice(-1)]; } + await createAssistantErrorResponse(); + threadsStore.setStreamingMessage(null); await threadsStore.setSendingBlocked(false); }; diff --git a/src/leapfrogai_ui/tests/assistants.test.ts b/src/leapfrogai_ui/tests/assistants.test.ts index 9a003704d..8c1500862 100644 --- a/src/leapfrogai_ui/tests/assistants.test.ts +++ b/src/leapfrogai_ui/tests/assistants.test.ts @@ -16,6 +16,10 @@ import { loadChatPage, loadNewAssistantPage } from './helpers/navigationHelpers'; +import { ERROR_GETTING_ASSISTANT_MSG_TOAST } from '$constants/toastMessages'; +import { ASSISTANT_ERROR_MSG } from '$constants/errors'; + +const newMessage1 = getSimpleMathQuestion(); test('it navigates to the assistants page', async ({ page }) => { await loadChatPage(page); @@ -295,3 +299,65 @@ test('it can delete assistants', async ({ page, openAIClient }) => { await expect(page.getByText(`${assistant.name} Assistant deleted.`)).toBeVisible(); }); + +// Note - these error cases do not test all edge cases. ex. completed response comes back empty, /chat/assistants +// partially completes then fails, stream fails, etc... +test('displays an error toast if /chat/assistants throws while getting a response from an assistant', async ({ + page, + openAIClient +}) => { + const assistant = await createAssistantWithApi({ openAIClient }); + await loadChatPage(page); + + const assistantDropdown = page.getByTestId('assistants-select-btn'); + await assistantDropdown.click(); + await page.getByText(assistant!.name!).click(); + + await page.route('*/**/chat/assistants', async (route) => { + await route.abort('failed'); + }); + await sendMessage(page, newMessage1); + + await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); + await expect(page.getByText(ASSISTANT_ERROR_MSG)).toBeVisible(); +}); + +test('displays an error toast if /chat/assistants returns a 500 when getting a response from an assistant', async ({ + page, + openAIClient +}) => { + const assistant = await createAssistantWithApi({ openAIClient }); + await loadChatPage(page); + + const assistantDropdown = page.getByTestId('assistants-select-btn'); + await assistantDropdown.click(); + await page.getByText(assistant!.name!).click(); + + await page.route('*/**/chat/assistants', async (route) => { + await route.fulfill({ status: 500 }); + }); + await sendMessage(page, newMessage1); + + await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); + await expect(page.getByText(ASSISTANT_ERROR_MSG)).toBeVisible(); +}); + +test('displays an error toast if /chat/assistants returns a 200 with no body when getting a response from an assistant', async ({ + page, + openAIClient +}) => { + const assistant = await createAssistantWithApi({ openAIClient }); + await loadChatPage(page); + + const assistantDropdown = page.getByTestId('assistants-select-btn'); + await assistantDropdown.click(); + await page.getByText(assistant!.name!).click(); + + await page.route('*/**/chat/assistants', async (route) => { + await route.fulfill({ status: 200 }); + }); + await sendMessage(page, newMessage1); + + await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); + await expect(page.getByText(ASSISTANT_ERROR_MSG)).toBeVisible(); +}); diff --git a/src/leapfrogai_ui/tests/chat.test.ts b/src/leapfrogai_ui/tests/chat.test.ts index 1f4cf6313..e4d6c1fb3 100644 --- a/src/leapfrogai_ui/tests/chat.test.ts +++ b/src/leapfrogai_ui/tests/chat.test.ts @@ -7,7 +7,6 @@ import { waitForResponseToComplete } from './helpers/threadHelpers'; import { loadChatPage } from './helpers/navigationHelpers'; -import { ERROR_GETTING_ASSISTANT_MSG_TOAST } from '$constants/toastMessages'; const newMessage1 = getSimpleMathQuestion(); const newMessage2 = getSimpleMathQuestion(); @@ -198,66 +197,3 @@ test('it can chat with an assistant that doesnt have files', async ({ page, open await deleteActiveThread(page, openAIClient); await deleteAssistantWithApi(assistant.id, openAIClient); }); - -// Note - these error cases do not test all edge cases. ex. completed response comes back empty, /chat/assistants -// partially completes then fails, stream fails, etc... -test('displays an error toast if /chat/assistants throws while getting a response from an assistant', async ({ - page, - openAIClient -}) => { - const assistant = await createAssistantWithApi({ openAIClient }); - await loadChatPage(page); - - const assistantDropdown = page.getByTestId('assistants-select-btn'); - await assistantDropdown.click(); - await page.getByText(assistant!.name!).click(); - - await page.route('*/**/chat/assistants', async (route) => { - await route.abort('failed'); - }); - await sendMessage(page, newMessage1); - - await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); - const messages = await page.getByTestId('message').all(); - expect(messages).toHaveLength(0); -}); - -test('displays an error toast if /chat/assistants returns a 500 when getting a response from an assistant', async ({ - page, - openAIClient -}) => { - const assistant = await createAssistantWithApi({ openAIClient }); - await loadChatPage(page); - - const assistantDropdown = page.getByTestId('assistants-select-btn'); - await assistantDropdown.click(); - await page.getByText(assistant!.name!).click(); - - await page.route('*/**/chat/assistants', async (route) => { - await route.fulfill({ status: 500 }); - }); - await sendMessage(page, newMessage1); - - await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); -}); - -test('displays an error toast if /chat/assistants returns a 200 with no body when getting a response from an assistant', async ({ - page, - openAIClient -}) => { - const assistant = await createAssistantWithApi({ openAIClient }); - await loadChatPage(page); - - const assistantDropdown = page.getByTestId('assistants-select-btn'); - await assistantDropdown.click(); - await page.getByText(assistant!.name!).click(); - - await page.route('*/**/chat/assistants', async (route) => { - await route.fulfill({ status: 200 }); - }); - await sendMessage(page, newMessage1); - - await expect(page.getByText(ERROR_GETTING_ASSISTANT_MSG_TOAST().title)).toBeVisible(); - const messages = await page.getByTestId('message').all(); - expect(messages).toHaveLength(0); -}); From 644f0f8b0d60b0688ea8e77cd80083ac696a5a2a Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Mon, 30 Sep 2024 09:57:37 -0600 Subject: [PATCH 4/4] lint --- src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts b/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts index cafcc7c98..ef5961ea5 100644 --- a/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts +++ b/src/leapfrogai_ui/src/lib/helpers/chatHelpers.ts @@ -270,4 +270,4 @@ export const refetchThread = async (threadId: string) => { const thread = await res.json(); threadsStore.updateThread(thread); } -}; \ No newline at end of file +};