diff --git a/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.module.css b/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.module.css index 39e1a4894..2e8527c3d 100644 --- a/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.module.css +++ b/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.module.css @@ -1,7 +1,3 @@ -.FormLayout { - width: 800px; -} - .GoldenQAHelper { margin-top: 8px; } @@ -18,7 +14,9 @@ border: 1px dashed #7cb87c; border-radius: 4px; padding: 12px 16px; - font-size: 14px; + font-family: monospace; + font-size: 13px; + color: #333; } .CSVFormatLabel { @@ -47,11 +45,38 @@ .GoldenQaRow { display: flex; - align-items: flex-start; gap: 12px; + align-items: center; } .UploadGoldenQaButton { - margin-top: 40px; white-space: nowrap; + border-radius: 20px !important; + flex-shrink: 0; + height: 40px; +} + +.GoldenQaLeft { + width: 420px; + flex-shrink: 0; + min-width: 0; +} + +.GoldenQaRight { + display: flex; + align-items: center; +} + +.GoldenQaHelperText { + margin-top: 6px; +} + +.HiddenFileInput { + display: none; +} + +.SectionDividerLabel { + font-weight: 700; + font-size: 20px; + color: #191c1a; } diff --git a/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.test.tsx b/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.test.tsx index 4db1737c3..b526cc78c 100644 --- a/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.test.tsx +++ b/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.test.tsx @@ -7,21 +7,57 @@ import { createGoldenQaCustomSuccessMock, createGoldenQaSuccessMock, getAIEvaluationCreateMocks, + getAssistantConfigVersionsEmptyMock, + getAssistantConfigVersionsErrorMock, + getAssistantConfigVersionsLoadingMock, + getAssistantConfigVersionsMultipleNamesMock, + getAssistantConfigVersionsMock, + getCreateEvaluationSuccessMock, + getListAiEvaluationsMock, } from 'mocks/AIEvaluations'; -import AIEvaluationCreate, { DUMMY_CREATE, DUMMY_GET_ITEM } from './AIEvaluationCreate'; +import { setNotification } from 'common/notification'; +import AIEvaluationCreate from './AIEvaluationCreate'; -const defaultMocks = getAIEvaluationCreateMocks(DUMMY_GET_ITEM, DUMMY_CREATE); +vi.mock('common/notification', () => ({ + setNotification: vi.fn(), + setErrorMessage: vi.fn(), +})); + +const defaultMocks = getAIEvaluationCreateMocks(); const wrapper = (mocks: any[] = defaultMocks) => ( } /> + Chat Page} /> ); +const fillAndSubmitForm = async (evaluationName = 'test_evaluation') => { + await waitFor(() => { + expect(screen.getByText('Create AI Evaluation')).toBeInTheDocument(); + }); + + fireEvent.change(screen.getByPlaceholderText('Give a unique name for the evaluation experiment'), { + target: { value: evaluationName }, + }); + + const assistantDropdown = screen.getAllByTestId('dropdown')[1]; + const selectTrigger = + assistantDropdown.querySelector('[role="combobox"]') ?? assistantDropdown.querySelector('button'); + fireEvent.mouseDown(selectTrigger!); + + await waitFor(() => { + expect(screen.getByRole('option', { name: 'Test Assistant (Version 2)' })).toBeInTheDocument(); + }); + fireEvent.click(screen.getByRole('option', { name: 'Test Assistant (Version 2)' })); + + fireEvent.click(screen.getByText('Run Evaluation')); +}; + describe('AIEvaluationCreate', () => { beforeEach(() => { vi.clearAllMocks(); @@ -70,9 +106,9 @@ describe('AIEvaluationCreate', () => { screen.getByText(/Select the Golden QA dataset from the existing list or upload a new set/) ).toBeInTheDocument(); expect(screen.getByText('Expected CSV Format:')).toBeInTheDocument(); - expect(screen.getByText('Question,Answer')).toBeInTheDocument(); - expect(screen.getByText('What is the capital of France?,Paris')).toBeInTheDocument(); - expect(screen.getByText('Click here for the template CSV')).toBeInTheDocument(); + expect(screen.getByText('Question, Answer')).toBeInTheDocument(); + expect(screen.getByText('{"What Is X"},{"Answer"}')).toBeInTheDocument(); + expect(screen.getByText('Click Here For The Template Csv')).toBeInTheDocument(); }); test('renders Upload Golden QA button', async () => { @@ -150,10 +186,10 @@ describe('AIEvaluationCreate', () => { render(wrapper()); await waitFor(() => { - expect(screen.getByPlaceholderText('Give a unique name for the evaluation experiment.')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Give a unique name for the evaluation experiment')).toBeInTheDocument(); }); - const nameInput = screen.getByPlaceholderText('Give a unique name for the evaluation experiment.'); + const nameInput = screen.getByPlaceholderText('Give a unique name for the evaluation experiment'); fireEvent.change(nameInput, { target: { value: 'valid_name' } }); fireEvent.click(screen.getByText('Run Evaluation')); @@ -162,43 +198,53 @@ describe('AIEvaluationCreate', () => { }); }); - test('shows validation error for invalid evaluation name pattern', async () => { + test('shows validation error when evaluation name is cleared after being typed', async () => { render(wrapper()); await waitFor(() => { - expect(screen.getByPlaceholderText('Give a unique name for the evaluation experiment.')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Give a unique name for the evaluation experiment')).toBeInTheDocument(); }); - const nameInput = screen.getByPlaceholderText('Give a unique name for the evaluation experiment.'); - fireEvent.change(nameInput, { target: { value: 'invalid name with spaces' } }); - fireEvent.click(screen.getByText('Run Evaluation')); + const nameInput = screen.getByPlaceholderText('Give a unique name for the evaluation experiment'); + fireEvent.change(nameInput, { target: { value: 'some_name' } }); + fireEvent.change(nameInput, { target: { value: '' } }); + fireEvent.blur(nameInput); await waitFor(() => { - expect(screen.getByText('Invalid evaluation name')).toBeInTheDocument(); + expect(screen.getByText('Evaluation name is required')).toBeInTheDocument(); }); }); - test('accepts valid evaluation name with alphanumeric, underscore and hyphen', async () => { + test('accepts any non-empty evaluation name', async () => { render(wrapper()); await waitFor(() => { - expect(screen.getByPlaceholderText('Give a unique name for the evaluation experiment.')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Give a unique name for the evaluation experiment')).toBeInTheDocument(); }); - const nameInput = screen.getByPlaceholderText('Give a unique name for the evaluation experiment.'); + const nameInput = screen.getByPlaceholderText('Give a unique name for the evaluation experiment'); fireEvent.change(nameInput, { target: { value: 'valid_evaluation-name123' } }); expect((nameInput as HTMLInputElement).value).toBe('valid_evaluation-name123'); }); - test('shows assistant helper text', async () => { - render(wrapper()); + test('shows assistant options from query using assistantName and versionNumber', async () => { + render(wrapper([...defaultMocks])); await waitFor(() => { expect(screen.getByText('Create AI Evaluation')).toBeInTheDocument(); }); - expect(screen.getByText("This list includes all assistants and versions you've created.")).toBeInTheDocument(); + const dropdowns = screen.getAllByTestId('dropdown'); + const assistantDropdown = dropdowns[1]; + const selectTrigger = + assistantDropdown.querySelector('[role="combobox"]') ?? assistantDropdown.querySelector('button'); + fireEvent.mouseDown(selectTrigger!); + + await waitFor(() => { + expect(screen.getByText('Test Assistant (Version 2)')).toBeInTheDocument(); + expect(screen.getByText('Test Assistant (Version 1)')).toBeInTheDocument(); + }); }); test('Run Evaluation submit button is visible and enabled', async () => { @@ -211,6 +257,137 @@ describe('AIEvaluationCreate', () => { }); }); + test('shows Fetching assistants... while assistant config versions are loading', async () => { + const mocks = [getAssistantConfigVersionsLoadingMock]; + render(wrapper(mocks)); + + await waitFor(() => { + expect(screen.getByText('Create AI Evaluation')).toBeInTheDocument(); + }); + + const dropdowns = screen.getAllByTestId('dropdown'); + const assistantDropdown = dropdowns[1]; + const selectTrigger = + assistantDropdown.querySelector('[role="combobox"]') ?? assistantDropdown.querySelector('button'); + fireEvent.mouseDown(selectTrigger!); + + await waitFor(() => { + expect(screen.getByText('Fetching assistants...')).toBeInTheDocument(); + }); + }); + + test('shows error notification when fetching assistant config versions fails', async () => { + render(wrapper([getAssistantConfigVersionsErrorMock])); + + await waitFor(() => { + expect(setNotification).toHaveBeenCalledWith('Failed to fetch assistants', 'warning'); + }); + }); + + test('shows No assistants available when assistant config versions list is empty', async () => { + const mocks = [getAssistantConfigVersionsEmptyMock]; + render(wrapper(mocks)); + + await waitFor(() => { + expect(screen.getByText('Create AI Evaluation')).toBeInTheDocument(); + }); + + const dropdowns = screen.getAllByTestId('dropdown'); + const assistantDropdown = dropdowns[1]; + const selectTrigger = + assistantDropdown.querySelector('[role="combobox"]') ?? assistantDropdown.querySelector('button'); + fireEvent.mouseDown(selectTrigger!); + + await waitFor(() => { + expect(screen.getByText('No assistants available')).toBeInTheDocument(); + }); + }); + + test('renders assistant options in the order of their version numbers', async () => { + render(wrapper([getAssistantConfigVersionsMultipleNamesMock])); + + await waitFor(() => { + expect(screen.getByText('Create AI Evaluation')).toBeInTheDocument(); + }); + + const dropdowns = screen.getAllByTestId('dropdown'); + const assistantDropdown = dropdowns[1]; + const selectTrigger = + assistantDropdown.querySelector('[role="combobox"]') ?? assistantDropdown.querySelector('button'); + fireEvent.mouseDown(selectTrigger!); + + await waitFor(() => { + const options = screen.getAllByRole('option'); + expect(options[0]).toHaveTextContent('Alpha Assistant (Version 1)'); + expect(options[1]).toHaveTextContent('Beta Assistant (Version 1)'); + expect(options[2]).toHaveTextContent('Beta Assistant (Version 2)'); + }); + }); + + test('shows all assistant config versions with correct labels for multiple assistant names', async () => { + const mocks = [getAssistantConfigVersionsMultipleNamesMock]; + render(wrapper(mocks)); + + await waitFor(() => { + expect(screen.getByText('Create AI Evaluation')).toBeInTheDocument(); + }); + + const dropdowns = screen.getAllByTestId('dropdown'); + const assistantDropdown = dropdowns[1]; + const selectTrigger = + assistantDropdown.querySelector('[role="combobox"]') ?? assistantDropdown.querySelector('button'); + fireEvent.mouseDown(selectTrigger!); + + await waitFor(() => { + expect(screen.getByText('Alpha Assistant (Version 1)')).toBeInTheDocument(); + expect(screen.getByText('Beta Assistant (Version 1)')).toBeInTheDocument(); + expect(screen.getByText('Beta Assistant (Version 2)')).toBeInTheDocument(); + }); + }); + + test('calls createEvaluation with correct parameters when form is submitted', async () => { + let capturedVariables: any = null; + const captureMock = { + request: { query: getCreateEvaluationSuccessMock.request.query }, + variableMatcher: (vars: any) => { + capturedVariables = vars; + return true; + }, + result: { + data: { + createEvaluation: { + __typename: 'EvaluationResult', + evaluation: { __typename: 'CreateEvaluationResult', status: 'queued' }, + errors: null, + }, + }, + }, + }; + render(wrapper([getListAiEvaluationsMock, getAssistantConfigVersionsMock, captureMock])); + + await fillAndSubmitForm('test_evaluation'); + + await waitFor(() => { + expect(capturedVariables?.input).toMatchObject({ + datasetId: 0, + experimentName: 'test_evaluation', + configId: 'kaapi-uuid-a1', + configVersion: '1', + }); + }); + }); + + test('shows success notification and navigates to chat on successful submission', async () => { + render(wrapper([getListAiEvaluationsMock, getAssistantConfigVersionsMock, getCreateEvaluationSuccessMock])); + + await fillAndSubmitForm(); + + await waitFor(() => { + expect(setNotification).toHaveBeenCalledWith('AI evaluation created successfully!'); + expect(screen.getByText('Chat Page')).toBeInTheDocument(); + }); + }); + test('back button is present and navigates away when clicked', async () => { render(wrapper()); @@ -284,8 +461,8 @@ describe('AIEvaluationCreate', () => { render( wrapper([ ...defaultMocks, - createGoldenQaCustomSuccessMock('first_qa', 1), - createGoldenQaCustomSuccessMock('second_qa', 1), + createGoldenQaCustomSuccessMock('first_qa', 1, '100'), + createGoldenQaCustomSuccessMock('second_qa', 1, '200'), ]) ); diff --git a/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.tsx b/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.tsx index 59c30854f..c0650c79a 100644 --- a/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.tsx +++ b/src/containers/AIEvals/AIEvaluationCreate/AIEvaluationCreate.tsx @@ -1,35 +1,17 @@ -import { gql } from '@apollo/client'; +import { useQuery } from '@apollo/client'; import { Button } from 'components/UI/Form/Button/Button'; import { Dropdown } from 'components/UI/Form/Dropdown/Dropdown'; import { Input } from 'components/UI/Form/Input/Input'; import { FormLayout } from 'containers/Form/FormLayout'; -import React, { useRef, useState } from 'react'; +import { CREATE_EVALUATION } from 'graphql/mutations/AIEvaluations'; +import { GET_ASSISTANT_CONFIG_VERSIONS } from 'graphql/queries/Assistant'; +import { useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router'; import * as Yup from 'yup'; +import { setNotification } from 'common/notification'; +import { UploadGoldenQaDialog } from 'containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog'; import styles from './AIEvaluationCreate.module.css'; -import { UploadGoldenQaDialog } from './UploadGoldenQaDialog'; - -// Dummy GraphQL documents until backend supports get/update/delete for AI evaluations (exported for tests) -export const DUMMY_GET_ITEM = gql` - query DummyAiEvalGet { - __typename - } -`; -export const DUMMY_UPDATE = gql` - mutation DummyAiEvalUpdate { - __typename - } -`; -export const DUMMY_DELETE = gql` - mutation DummyAiEvalDelete { - __typename - } -`; -export const DUMMY_CREATE = gql` - mutation DummyAiEvalCreate { - __typename - } -`; const goldenQAHelperContent = (
@@ -38,36 +20,46 @@ const goldenQAHelperContent = ( evaluation on.

-
Expected CSV Format:
-
Question,Answer
-
{'What is the capital of France?,Paris'}
+
Expected CSV Format:
+
Question, Answer
+
{'{"What Is X"},{"Answer"}'}
- Click here for the template CSV + Click Here For The Template Csv
); const GoldenQaField = (props: any) => { - const { onUploadGoldenQaClick, form, ...dropdownProps } = props; + const { onUploadGoldenQaClick, form, helperText, ...dropdownProps } = props; return ( -
- - +
+
+
+ +
+
+ +
+
+ {helperText &&
{helperText}
}
); }; +const SectionDivider = (_props: any) => null; + export default function AIEvaluationCreate() { - const [goldenQADatasets, setGoldenQADatasets] = useState([]); + const navigate = useNavigate(); + const [goldenQADatasets, setGoldenQADatasets] = useState>([]); const [showUploadGoldenQaDialog, setShowUploadGoldenQaDialog] = useState(false); const [selectedGoldenQaFileName, setSelectedGoldenQaFileName] = useState(null); const [selectedGoldenQaFile, setSelectedGoldenQaFile] = useState(null); @@ -76,20 +68,37 @@ export default function AIEvaluationCreate() { const goldenQaOptions = goldenQADatasets.length === 0 ? [{ id: '0', label: 'No Golden QA available, upload one first' }] - : goldenQADatasets.map((name) => ({ id: name, label: name })); - const assistantOptions = [{ id: '', label: 'Pick your assistant & version to evaluate' }]; + : goldenQADatasets.map(({ datasetId, name }) => ({ id: datasetId, label: name })); + + const { data: versionsData, loading: versionsLoading, error: versionsError } = useQuery( + GET_ASSISTANT_CONFIG_VERSIONS, + { variables: { filter: {} }, fetchPolicy: 'network-only' } + ); + + useEffect(() => { + if (versionsError) { + setNotification(versionsError.message, 'warning'); + } + }, [versionsError]); + + const assistantOptions = versionsLoading + ? [{ id: '', label: 'Fetching assistants...' }] + : versionsData?.assistantConfigVersions?.length > 0 + ? versionsData.assistantConfigVersions.map((v: any) => ({ + id: v.id, + label: `${v.assistantName} (Version ${v.versionNumber})`, + })) + : [{ id: '', label: 'No assistants available' }]; const validationSchema = Yup.object().shape({ - evaluationName: Yup.string() - .required('Evaluation name is required') - .matches(/^[a-zA-Z0-9_-]+$/, 'Invalid evaluation name'), + evaluationName: Yup.string().required('Evaluation name is required'), goldenQaId: Yup.string().required('Please select a Golden QA dataset'), assistantId: Yup.string().required('Please select an AI Assistant'), }); - const [states, setStates] = useState<{ evaluationName: string; goldenQaId: string; assistantId: string }>({ + const [states, setStates] = useState<{ evaluationName: string; goldenQaId: number; assistantId: string }>({ evaluationName: '', - goldenQaId: '0', + goldenQaId: 0, assistantId: '', }); @@ -110,10 +119,9 @@ export default function AIEvaluationCreate() { setShowUploadGoldenQaDialog(true); }; - const handleUploadGoldenQaProceed = (values: { name: string }) => { - // Placeholder for when backend mutation response needs to be used further. New upload at top. - setGoldenQADatasets((prev) => [values.name, ...prev]); - setStates((prev) => ({ ...prev, goldenQaId: values.name })); + const handleUploadGoldenQaProceed = (values: { datasetId: number; name: string }) => { + setGoldenQADatasets((prev) => [{ datasetId: values.datasetId, name: values.name }, ...prev]); + setStates((prev) => ({ ...prev, goldenQaId: values.datasetId })); setShowUploadGoldenQaDialog(false); }; @@ -121,30 +129,45 @@ export default function AIEvaluationCreate() { { component: GoldenQaField, name: 'goldenQaId', - label: 'Select Golden QA', + label: Select Golden QA, options: goldenQaOptions, - placeholder: '', helperText: goldenQAHelperContent, onUploadGoldenQaClick: handleUploadGoldenQaButtonClick, }, + { + component: SectionDivider, + name: '__evaluationDetailsDivider', + label: Evaluation Details, + placeholder: '', + }, { component: Input, name: 'evaluationName', type: 'text', - label: 'Evaluation Name*', - placeholder: 'Give a unique name for the evaluation experiment.', + label: Evaluation Name*, + placeholder: 'Give a unique name for the evaluation experiment', }, { component: Dropdown, name: 'assistantId', - label: 'AI Assistant*', + label: AI Assistant*, options: assistantOptions, - helperText: "This list includes all assistants and versions you've created.", + placeholder: '', }, ]; const dialogMessage = 'This action cannot be undone.'; + const handleSetPayload = (payload: any) => { + const selectedVersion = versionsData?.assistantConfigVersions?.find((v: any) => v.id === payload.assistantId); + return { + datasetId: payload.goldenQaId, + experimentName: payload.evaluationName, + configId: selectedVersion?.kaapiUuid, + configVersion: selectedVersion?.id, + }; + }; + return (
navigate('/chat')} /> {showUploadGoldenQaDialog && selectedGoldenQaFileName && ( @@ -197,4 +222,4 @@ export default function AIEvaluationCreate() { )}
); -}; +} diff --git a/src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.module.css b/src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.module.css similarity index 100% rename from src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.module.css rename to src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.module.css diff --git a/src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.test.tsx b/src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.test.tsx similarity index 99% rename from src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.test.tsx rename to src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.test.tsx index c523db273..360e0573a 100644 --- a/src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.test.tsx +++ b/src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.test.tsx @@ -190,6 +190,7 @@ describe('UploadGoldenQaDialog', () => { expect(notificationSpy).toHaveBeenCalledWith('Golden QA uploaded successfully', 'success'); }); expect(onProceed).toHaveBeenCalledWith({ + datasetId: 123, name: 'golden_qa', duplicationFactor: 1, }); @@ -215,6 +216,7 @@ describe('UploadGoldenQaDialog', () => { await waitFor(() => { expect(onProceed).toHaveBeenCalledWith({ + datasetId: 456, name: 'my_custom_name', duplicationFactor: 3, }); diff --git a/src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.tsx b/src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.tsx similarity index 96% rename from src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.tsx rename to src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.tsx index daea71a4b..4dd35961d 100644 --- a/src/containers/AIEvals/AIEvaluationCreate/UploadGoldenQaDialog.tsx +++ b/src/containers/AIEvals/UploadGoldenQaDialog/UploadGoldenQaDialog.tsx @@ -13,7 +13,7 @@ export interface UploadGoldenQaDialogProps { fileName: string; file: File | null; onClose: () => void; - onProceed: (values: { name: string; duplicationFactor: number }) => void; + onProceed: (values: { datasetId: number; name: string; duplicationFactor: number }) => void; } export const UploadGoldenQaDialog = ({ open, fileName, file, onClose, onProceed }: UploadGoldenQaDialogProps) => { @@ -94,6 +94,7 @@ export const UploadGoldenQaDialog = ({ open, fileName, file, onClose, onProceed setNotification('Golden QA uploaded successfully', 'success'); onProceed({ + datasetId: parseInt(goldenQa.datasetId, 10), name: goldenQa.name, duplicationFactor: goldenQa.duplication_factor, }); diff --git a/src/graphql/mutations/AIEvaluations.ts b/src/graphql/mutations/AIEvaluations.ts index 8d919c105..eb063b606 100644 --- a/src/graphql/mutations/AIEvaluations.ts +++ b/src/graphql/mutations/AIEvaluations.ts @@ -1,9 +1,23 @@ import { gql } from '@apollo/client'; +export const CREATE_EVALUATION = gql` + mutation createEvaluation($input: EvaluationInput!) { + createEvaluation(input: $input) { + evaluation { + status + } + errors { + message + } + } + } +`; + export const CREATE_GOLDEN_QA = gql` mutation CreateGoldenQa($input: GoldenQaInput!) { createGoldenQa(input: $input) { goldenQa { + datasetId name } errors { diff --git a/src/graphql/queries/AIEvaluations.ts b/src/graphql/queries/AIEvaluations.ts new file mode 100644 index 000000000..40e1c5c12 --- /dev/null +++ b/src/graphql/queries/AIEvaluations.ts @@ -0,0 +1,24 @@ +import { gql } from '@apollo/client'; + +export const COUNT_AI_EVALUATIONS = gql` + query countAiEvaluations($filter: AiEvaluationFilter) { + countAiEvaluations(filter: $filter) + } +`; + +export const LIST_AI_EVALUATIONS = gql` + query AiEvaluations($filter: AiEvaluationFilter, $opts: Opts) { + aiEvaluations(filter: $filter, opts: $opts) { + id + name + status + failureReason + results + kaapiEvaluationId + datasetId + assistantConfigVersionId + insertedAt + updatedAt + } + } +`; diff --git a/src/graphql/queries/Assistant.ts b/src/graphql/queries/Assistant.ts index 5fc9cab76..8266762cf 100644 --- a/src/graphql/queries/Assistant.ts +++ b/src/graphql/queries/Assistant.ts @@ -1,6 +1,5 @@ import { gql } from '@apollo/client'; - /** @deprecated Use FILTER_ASSISTANTS instead */ export const GET_ASSISTANTS = gql` query Assistants($filter: AssistantFilter, $opts: Opts) { @@ -37,6 +36,21 @@ export const GET_ASSISTANTS_COUNT = gql` } `; +export const GET_ASSISTANT_CONFIG_VERSIONS = gql` + query AssistantConfigVersions { + assistantConfigVersions { + id + assistantId + versionNumber + description + model + status + assistantName + kaapiUuid + } + } +`; + export const GET_ASSISTANT = gql` query Assistant($assistantId: ID!) { assistant(id: $assistantId) { diff --git a/src/mocks/AIEvaluations.ts b/src/mocks/AIEvaluations.ts index cfe38b090..073b81d21 100644 --- a/src/mocks/AIEvaluations.ts +++ b/src/mocks/AIEvaluations.ts @@ -1,20 +1,140 @@ -import { CREATE_GOLDEN_QA } from 'graphql/mutations/AIEvaluations'; +import { CREATE_EVALUATION, CREATE_GOLDEN_QA } from 'graphql/mutations/AIEvaluations'; +import { LIST_AI_EVALUATIONS } from 'graphql/queries/AIEvaluations'; +import { GET_ASSISTANT_CONFIG_VERSIONS } from 'graphql/queries/Assistant'; -export const getDummyGetItemMock = (query: unknown) => ({ - request: { query }, - result: { data: { __typename: 'Query' } }, -}); +export const getListAiEvaluationsMock = { + request: { query: LIST_AI_EVALUATIONS, variables: { filter: {}, opts: {} } }, + result: { data: { aiEvaluations: [] } }, +}; + +const evaluationSuccessResult = { + data: { + createEvaluation: { + __typename: 'EvaluationResult', + evaluation: { __typename: 'CreateEvaluationResult', status: 'queued' }, + errors: null, + }, + }, +}; + +export const getCreateEvaluationMock = { + request: { query: CREATE_EVALUATION }, + result: evaluationSuccessResult, +}; -export const getDummyCreateMock = (query: unknown) => ({ - request: { query }, - result: { data: { __typename: 'Mutation' } }, +export const getCreateEvaluationWithVariablesMock = (input: { + datasetId: number; + experimentName: string; + configId: string; + configVersion: string; +}) => ({ + request: { query: CREATE_EVALUATION, variables: { input } }, + result: evaluationSuccessResult, }); -export const getAIEvaluationCreateMocks = (getItemQuery: unknown, createQuery: unknown) => [ - getDummyGetItemMock(getItemQuery), - getDummyCreateMock(createQuery), +export const getCreateEvaluationSuccessMock = { + request: { query: CREATE_EVALUATION }, + variableMatcher: () => true, + result: evaluationSuccessResult, +}; + +export const getAIEvaluationCreateMocks = () => [ + getListAiEvaluationsMock, + getCreateEvaluationMock, + getAssistantConfigVersionsMock, ]; +export const getAssistantConfigVersionsMock = { + request: { query: GET_ASSISTANT_CONFIG_VERSIONS, variables: { filter: {} } }, + result: { + data: { + assistantConfigVersions: [ + { + __typename: 'AssistantConfigVersion', + id: '1', + assistantId: 'a1', + assistantName: 'Test Assistant', + versionNumber: 2, + description: 'v2 config', + model: 'gpt-4', + status: 'ACTIVE', + kaapiUuid: 'kaapi-uuid-a1', + }, + { + __typename: 'AssistantConfigVersion', + id: '2', + assistantId: 'a1', + assistantName: 'Test Assistant', + versionNumber: 1, + description: 'v1 config', + model: 'gpt-4', + status: 'ACTIVE', + kaapiUuid: 'kaapi-uuid-a1-v1', + }, + ], + }, + }, +}; + +export const getAssistantConfigVersionsErrorMock = { + request: { query: GET_ASSISTANT_CONFIG_VERSIONS, variables: { filter: {} } }, + error: new Error('Failed to fetch assistants'), +}; + +export const getAssistantConfigVersionsEmptyMock = { + request: { query: GET_ASSISTANT_CONFIG_VERSIONS, variables: { filter: {} } }, + result: { data: { assistantConfigVersions: [] } }, +}; + +export const getAssistantConfigVersionsLoadingMock = { + request: { query: GET_ASSISTANT_CONFIG_VERSIONS, variables: { filter: {} } }, + result: { data: { assistantConfigVersions: [] } }, + delay: Infinity, +}; + +export const getAssistantConfigVersionsMultipleNamesMock = { + request: { query: GET_ASSISTANT_CONFIG_VERSIONS, variables: { filter: {} } }, + result: { + data: { + assistantConfigVersions: [ + { + __typename: 'AssistantConfigVersion', + id: '1', + assistantId: 'a1', + assistantName: 'Alpha Assistant', + versionNumber: 1, + description: 'v1', + model: 'gpt-4', + status: 'ACTIVE', + kaapiUuid: 'kaapi-alpha-v1', + }, + { + __typename: 'AssistantConfigVersion', + id: '2', + assistantId: 'a2', + assistantName: 'Beta Assistant', + versionNumber: 1, + description: 'v3', + model: 'gpt-4', + status: 'ACTIVE', + kaapiUuid: 'kaapi-beta-v3', + }, + { + __typename: 'AssistantConfigVersion', + id: '3', + assistantId: 'a2', + assistantName: 'Beta Assistant', + versionNumber: 2, + description: 'v1', + model: 'gpt-4', + status: 'ACTIVE', + kaapiUuid: 'kaapi-beta-v1', + }, + ], + }, + }, +}; + export const createGoldenQaSuccessMock = { request: { query: CREATE_GOLDEN_QA }, variableMatcher: () => true, @@ -22,7 +142,7 @@ export const createGoldenQaSuccessMock = { data: { createGoldenQa: { __typename: 'CreateGoldenQaPayload', - goldenQa: { __typename: 'GoldenQa', name: 'golden_qa', duplication_factor: 1 }, + goldenQa: { __typename: 'GoldenQa', datasetId: '123', name: 'golden_qa', duplication_factor: 1 }, errors: null, }, }, @@ -49,14 +169,14 @@ export const createGoldenQaNetworkErrorMock = { error: new Error('Network error'), }; -export const createGoldenQaCustomSuccessMock = (name: string, duplicationFactor: number) => ({ +export const createGoldenQaCustomSuccessMock = (name: string, duplicationFactor: number, datasetId = '456') => ({ request: { query: CREATE_GOLDEN_QA }, variableMatcher: () => true, result: { data: { createGoldenQa: { __typename: 'CreateGoldenQaPayload', - goldenQa: { __typename: 'GoldenQa', name, duplication_factor: duplicationFactor }, + goldenQa: { __typename: 'GoldenQa', datasetId, name, duplication_factor: duplicationFactor }, errors: null, }, },