From ca19c1fabb607764edf5760e13adb12363bd01bc Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Tue, 6 Aug 2024 13:34:02 -0600 Subject: [PATCH 01/34] init --- src/leapfrogai_ui/playwright.config.ts | 2 +- src/leapfrogai_ui/src/lib/mocks/api-key-mocks.ts | 2 +- .../chat/(dashboard)/[[thread_id]]/chatpage.test.ts | 1 + .../(dashboard)/[[thread_id]]/chatpage_no_thread.test.ts | 1 + .../src/routes/chat/(settings)/api-keys/+page.server.ts | 2 +- .../src/routes/chat/(settings)/api-keys/+page.svelte | 8 +++----- .../(settings)/file-management/file-management.test.ts | 2 ++ src/leapfrogai_ui/testUtils/fakeData/index.ts | 1 + src/leapfrogai_ui/tests/rag.test.ts | 4 ++-- 9 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/leapfrogai_ui/playwright.config.ts b/src/leapfrogai_ui/playwright.config.ts index 4d5025365..cab13e526 100644 --- a/src/leapfrogai_ui/playwright.config.ts +++ b/src/leapfrogai_ui/playwright.config.ts @@ -1,6 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -import dotenv from 'dotenv'; +import * as dotenv from 'dotenv'; dotenv.config(); diff --git a/src/leapfrogai_ui/src/lib/mocks/api-key-mocks.ts b/src/leapfrogai_ui/src/lib/mocks/api-key-mocks.ts index bf01f2eb9..4dbca50cb 100644 --- a/src/leapfrogai_ui/src/lib/mocks/api-key-mocks.ts +++ b/src/leapfrogai_ui/src/lib/mocks/api-key-mocks.ts @@ -46,7 +46,7 @@ export const mockCreateApiKey = (api_key = `lfai_${faker.string.uuid()}`) => { const reqJson = (await request.json()) as NewApiKeyInput; const key: APIKeyRow = { id: faker.string.uuid(), - name: reqJson.name, + name: reqJson.name!, api_key, created_at: new Date().getTime(), expires_at: reqJson.expires_at, 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 c6ccff59b..ca3d5f1d2 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 @@ -60,6 +60,7 @@ describe('when there is an active thread selected', () => { mockOpenAI.setMessages(allMessages); mockOpenAI.setFiles(files); + // @ts-expect-error: full mocking of load function params not necessary and is overcomplicated data = await load({ fetch: global.fetch, depends: vi.fn(), diff --git a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage_no_thread.test.ts b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage_no_thread.test.ts index b441aee01..71242a2b2 100644 --- a/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage_no_thread.test.ts +++ b/src/leapfrogai_ui/src/routes/chat/(dashboard)/[[thread_id]]/chatpage_no_thread.test.ts @@ -35,6 +35,7 @@ describe('when there is NO active thread selected', () => { mockOpenAI.setMessages(allMessages); mockOpenAI.setFiles(files); + // @ts-expect-error: full mocking of load function params not necessary and is overcomplicated data = await load({ params: {}, fetch: global.fetch, diff --git a/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.server.ts b/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.server.ts index 4f919caa9..1cc33e4e8 100644 --- a/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.server.ts +++ b/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.server.ts @@ -38,7 +38,7 @@ export const load: PageServerLoad = async ({ depends, locals: { session } }) => key.expires_at = key.expires_at * 1000; }); - return { title: 'LeapfrogAI - API Keys', form, keys: keys ?? [] }; + return { title: 'LeapfrogAI - API Keys', form, apiKeys: keys ?? [] }; }; export const actions: Actions = { diff --git a/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.svelte b/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.svelte index 8c4b4ad62..53bb50342 100644 --- a/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.svelte +++ b/src/leapfrogai_ui/src/routes/chat/(settings)/api-keys/+page.svelte @@ -17,19 +17,17 @@ import { formatDate } from '$helpers/dates.js'; import { formatKeyShort } from '$helpers/apiKeyHelpers'; import type { APIKeyRow } from '$lib/types/apiKeys'; - import type { PageServerData } from './$types'; import { filterTable } from '$lib/utils/tables'; import { tableStyles } from '$lib/styles/tables'; import DeleteApiKeyModal from '$components/modals/DeleteApiKeyModal.svelte'; import CreateApiKeyModal from '$components/modals/CreateApiKeyModal.svelte'; - export let data: PageServerData; + export let data; - // TODO - data.keys not getting type inference - https://github.com/defenseunicorns/leapfrogai/issues/856 $: filteredItems = searchTerm !== '' - ? filterTable(data.keys, FILTER_KEYS, searchTerm.toLowerCase()) - : [...data.keys]; + ? filterTable(data.apiKeys, FILTER_KEYS, searchTerm.toLowerCase()) + : [...data.apiKeys]; $: totalItems = filteredItems.length; $: startRange = currentPosition + 1; $: endRange = Math.min(currentPosition + itemsPerPage, totalItems); diff --git a/src/leapfrogai_ui/src/routes/chat/(settings)/file-management/file-management.test.ts b/src/leapfrogai_ui/src/routes/chat/(settings)/file-management/file-management.test.ts index 13f61da3c..0a0f6987d 100644 --- a/src/leapfrogai_ui/src/routes/chat/(settings)/file-management/file-management.test.ts +++ b/src/leapfrogai_ui/src/routes/chat/(settings)/file-management/file-management.test.ts @@ -26,6 +26,7 @@ describe('file management', () => { let searchbox: HTMLElement; beforeEach(async () => { + // @ts-expect-error: full mocking of load function params not necessary and is overcomplicated const data = await load(); form = await superValidate(yup(filesSchema)); @@ -182,6 +183,7 @@ describe('table pagination', () => { let searchbox: HTMLElement; beforeEach(async () => { + // @ts-expect-error: full mocking of load function params not necessary and is overcomplicated const data = await load(); form = await superValidate(yup(filesSchema)); diff --git a/src/leapfrogai_ui/testUtils/fakeData/index.ts b/src/leapfrogai_ui/testUtils/fakeData/index.ts index 8ddb72873..c1ca2383b 100644 --- a/src/leapfrogai_ui/testUtils/fakeData/index.ts +++ b/src/leapfrogai_ui/testUtils/fakeData/index.ts @@ -263,6 +263,7 @@ export const getFakeOpenAIMessage = ({ }; }; +// @ts-expect-error: status is a deprecated field, not including it here export const getFakeFileObject = (): FileObject => ({ id: `file_${faker.string.uuid()}`, bytes: 64, diff --git a/src/leapfrogai_ui/tests/rag.test.ts b/src/leapfrogai_ui/tests/rag.test.ts index 1c904808d..2c51c853b 100644 --- a/src/leapfrogai_ui/tests/rag.test.ts +++ b/src/leapfrogai_ui/tests/rag.test.ts @@ -14,7 +14,7 @@ import { import { faker } from '@faker-js/faker'; import { createAssistantWithApi, - deleteAssistant, + deleteAssistantCard, deleteAssistantWithApi } from './helpers/assistantHelpers'; import { getSimpleMathQuestion, loadChatPage } from './helpers/helpers'; @@ -143,7 +143,7 @@ test('while creating an assistant, it can upload new files and save the assistan // Cleanup expect(page.waitForURL('/chat/assistants-management')); - await deleteAssistant(assistantInput.name, page); + await deleteAssistantCard(assistantInput.name, page); await deleteTestFilesWithApi(openAIClient); deleteFixtureFile(filename); await deleteFileByName(filename, openAIClient); From 6e9be0f3a538a6de5ec69dc4b7400c32a480a093 Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Tue, 6 Aug 2024 18:01:37 -0600 Subject: [PATCH 02/34] Fix rag e2e --- src/leapfrogai_ui/playwright.config.ts | 2 +- .../components/AssistantFileDropdown.svelte | 3 +- src/leapfrogai_ui/tests/global.setup.ts | 15 +++++- src/leapfrogai_ui/tests/rag.test.ts | 48 ++++++++++--------- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/leapfrogai_ui/playwright.config.ts b/src/leapfrogai_ui/playwright.config.ts index cab13e526..4d5025365 100644 --- a/src/leapfrogai_ui/playwright.config.ts +++ b/src/leapfrogai_ui/playwright.config.ts @@ -1,6 +1,6 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; -import * as dotenv from 'dotenv'; +import dotenv from 'dotenv'; dotenv.config(); diff --git a/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte b/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte index 3c903f0a9..1532144e6 100644 --- a/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte +++ b/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte @@ -81,10 +81,11 @@ > -
+
{#each $filesStore.files?.map( (file) => ({ id: file.id, text: file.filename }) ) as file (file.id)}
  • handleClick(file.id)} checked={$filesStore.selectedAssistantFileIds.includes(file.id)} class="overflow-hidden text-ellipsis whitespace-nowrap">{file.text} { +setup('authenticate', async ({ page, openAIClient }) => { + console.log('setting up...'); + deleteAllGeneratedFixtureFiles(); + await deleteAllTestFilesWithApi(openAIClient); + await deleteAllAssistants(openAIClient); + await deleteAllTestThreadsWithApi(openAIClient); + await deleteAssistantAvatars(); + await deleteAllTestAPIKeys(); + console.log('set up complete'); + await page.goto('/'); // go to the home page await delay(2000); // allow page to fully hydrate if (process.env.PUBLIC_DISABLE_KEYCLOAK === 'true') { diff --git a/src/leapfrogai_ui/tests/rag.test.ts b/src/leapfrogai_ui/tests/rag.test.ts index 2c51c853b..ada97cc38 100644 --- a/src/leapfrogai_ui/tests/rag.test.ts +++ b/src/leapfrogai_ui/tests/rag.test.ts @@ -15,7 +15,8 @@ import { faker } from '@faker-js/faker'; import { createAssistantWithApi, deleteAssistantCard, - deleteAssistantWithApi + deleteAssistantWithApi, + editAssistantCard } from './helpers/assistantHelpers'; import { getSimpleMathQuestion, loadChatPage } from './helpers/helpers'; import { sendMessage, waitForResponseToComplete } from './helpers/threadHelpers'; @@ -36,9 +37,10 @@ test('can edit an assistant and attach files to it', async ({ page, openAIClient await page.goto(`/chat/assistants-management/edit/${assistant.id}`); - await page.getByRole('button', { name: 'Open menu' }).click(); - await page.getByLabel('Choose an item').locator('label').nth(1).click(); - await page.getByLabel('Choose an item').locator('label').nth(2).click(); + 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(); await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Assistant Updated')).toBeVisible(); @@ -61,18 +63,19 @@ test('can create a new assistant and attach files to it', async ({ page, openAIC await page.goto(`/chat/assistants-management/new`); await page.getByLabel('name').fill(assistantInput.name); - await page.getByLabel('description').fill(assistantInput.description); + await page.getByLabel('tagline').fill(assistantInput.description); await page.getByPlaceholder("You'll act as...").fill(assistantInput.instructions); - await page.getByRole('button', { name: 'Open menu' }).click(); - await page.getByLabel('Choose an item').locator('label').nth(1).click(); - await page.getByLabel('Choose an item').locator('label').nth(2).click(); + 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(); await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Assistant Created')).toBeVisible(); // Cleanup expect(page.waitForURL('/chat/assistants-management')); - await deleteAssistant(assistantInput.name, page); + await deleteAssistantCard(assistantInput.name, page); await deleteFileWithApi(uploadedFile1.id, openAIClient); await deleteFileWithApi(uploadedFile2.id, openAIClient); deleteFixtureFile(filename1); @@ -90,21 +93,20 @@ test('it can edit an assistant and remove a file', async ({ page, openAIClient } await page.goto(`/chat/assistants-management/edit/${assistant.id}`); // Create assistant with files - await page.getByRole('button', { name: 'Open menu' }).click(); - await page.getByLabel('Choose an item').locator('label').nth(1).click(); - await page.getByLabel('Choose an item').locator('label').nth(2).click(); + 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(); await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Assistant Updated')).toBeVisible(); - await page - .getByTestId(`assistant-card-${assistant.name}`) - .getByTestId('assistant-card-dropdown') - .click(); - await page.getByRole('menuitem', { name: 'Edit' }).click(); + await editAssistantCard(assistant.name!, page); + await page.waitForURL('/chat/assistants-management/edit/**/*'); // Deselect - await page.locator('.bx--file-close').first().click(); + await page.getByTestId('file-select-dropdown-btn').click(); + await fileSelectContainer.getByTestId(`${uploadedFile1.id}-checkbox`).uncheck(); await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Assistant Updated')).toBeVisible(); @@ -127,10 +129,10 @@ test('while creating an assistant, it can upload new files and save the assistan await page.goto('/chat/assistants-management/new'); await page.getByLabel('name').fill(assistantInput.name); - await page.getByLabel('description').fill(assistantInput.description); + await page.getByLabel('tagline').fill(assistantInput.description); await page.getByPlaceholder("You'll act as...").fill(assistantInput.instructions); - await page.getByRole('button', { name: 'Open menu' }).click(); + await page.getByTestId('file-select-dropdown-btn').click(); await uploadFile(page, filename, 'Upload new data source'); const saveBtn = await page.getByRole('button', { name: 'Save' }); @@ -159,7 +161,7 @@ test('while editing an assistant, it can upload new files and save the assistant const assistant = await createAssistantWithApi({ openAIClient }); await page.goto(`/chat/assistants-management/edit/${assistant.id}`); - await page.getByRole('button', { name: 'Open menu' }).click(); + await page.getByTestId('file-select-dropdown-btn').click(); await uploadFile(page, filename, 'Upload new data source'); const saveBtn = await page.getByRole('button', { name: 'Save' }); @@ -191,7 +193,7 @@ test('it displays a failed toast and temporarily failed uploader item when a the await page.goto('/chat/assistants-management/new'); - await page.getByRole('button', { name: 'Open menu' }).click(); + await page.getByTestId('file-select-dropdown-btn').click(); await uploadFile(page, filename, 'Upload new data source'); await expect(page.getByText(`Upload Failed`)).toBeVisible(); @@ -213,7 +215,7 @@ test('it displays an uploading indicator temporarily when uploading a file', asy await page.goto('/chat/assistants-management/new'); - await page.getByRole('button', { name: 'Open menu' }).click(); + await page.getByTestId('file-select-dropdown-btn').click(); await uploadFile(page, filename, 'Upload new data source'); await expect(page.getByTestId(`${filename}-uploading-uploader-item`)).toBeVisible(); From 539c15f678dcdf08f946c83317a6f1321327a4b5 Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Wed, 7 Aug 2024 08:42:41 -0600 Subject: [PATCH 03/34] move e2e setup steps to after authentication --- src/leapfrogai_ui/tests/global.setup.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/leapfrogai_ui/tests/global.setup.ts b/src/leapfrogai_ui/tests/global.setup.ts index 6d4fe4a9b..aac6d13f9 100644 --- a/src/leapfrogai_ui/tests/global.setup.ts +++ b/src/leapfrogai_ui/tests/global.setup.ts @@ -9,15 +9,6 @@ import { deleteAllTestAPIKeys } from './helpers/apiHelpers'; const authFile = 'playwright/.auth/user.json'; setup('authenticate', async ({ page, openAIClient }) => { - console.log('setting up...'); - deleteAllGeneratedFixtureFiles(); - await deleteAllTestFilesWithApi(openAIClient); - await deleteAllAssistants(openAIClient); - await deleteAllTestThreadsWithApi(openAIClient); - await deleteAssistantAvatars(); - await deleteAllTestAPIKeys(); - console.log('set up complete'); - await page.goto('/'); // go to the home page await delay(2000); // allow page to fully hydrate if (process.env.PUBLIC_DISABLE_KEYCLOAK === 'true') { @@ -77,4 +68,13 @@ setup('authenticate', async ({ page, openAIClient }) => { // End of authentication steps. await page.context().storageState({ path: authFile }); + + console.log('setting up...'); + deleteAllGeneratedFixtureFiles(); + await deleteAllTestFilesWithApi(openAIClient); + await deleteAllAssistants(openAIClient); + await deleteAllTestThreadsWithApi(openAIClient); + await deleteAssistantAvatars(); + await deleteAllTestAPIKeys(); + console.log('set up complete'); }); From 127364916e6e72585cf4a4655449d52940f61e2f Mon Sep 17 00:00:00 2001 From: Andrew Risse Date: Wed, 7 Aug 2024 10:13:11 -0600 Subject: [PATCH 04/34] init --- .../src/lib/components/Citation.svelte | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/leapfrogai_ui/src/lib/components/Citation.svelte b/src/leapfrogai_ui/src/lib/components/Citation.svelte index c5931ed1e..b6f6511af 100644 --- a/src/leapfrogai_ui/src/lib/components/Citation.svelte +++ b/src/leapfrogai_ui/src/lib/components/Citation.svelte @@ -3,9 +3,13 @@ import { toastStore } from '$stores'; import type { FileObject } from 'openai/resources/files'; import { ArrowUpOutline } from 'flowbite-svelte-icons'; + import { onDestroy } from 'svelte'; export let file: FileObject; export let index: string; + let expanded = false; + let url: string; + const handleClick = async () => { if (browser) { const res = await fetch(`/api/files/${file.id}`); @@ -19,31 +23,41 @@ } const blob = await res.blob(); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = file.filename; - document.body.appendChild(a); - a.click(); - - if (res.headers.get('content-type') === 'application/pdf') { - // Open in a new tab - window.open(url, '_blank'); - } + url = window.URL.createObjectURL(blob); + // const a = document.createElement('a'); + // a.style.display = 'none'; + // a.href = url; + // a.download = file.filename; + // document.body.appendChild(a); + // a.click(); + // + // if (res.headers.get('content-type') === 'application/pdf') { + // // Open in a new tab + // window.open(url, '_blank'); + // } - window.URL.revokeObjectURL(url); // avoid memory leaks + // window.URL.revokeObjectURL(url); // avoid memory leaks } }; + + onDestroy(() => { + if (url) window.URL.revokeObjectURL(url); // avoid memory leaks + }); +{#if expanded && url} +