diff --git a/app/client/src/Container.tsx b/app/client/src/Container.tsx index 7f8cfda5..a35a5fa1 100644 --- a/app/client/src/Container.tsx +++ b/app/client/src/Container.tsx @@ -95,6 +95,12 @@ const pages: MenuItem[] = [ {LABELS[Pages.EXPORTS]} ), }, + { + key: Pages.SETTINGS, + label: ( + {LABELS[Pages.SETTINGS]} + ), + }, // { // key: Pages.TELEMETRY, diff --git a/app/client/src/components/JobStatus/jobStatusIcon.tsx b/app/client/src/components/JobStatus/jobStatusIcon.tsx index c9511a33..42f822b6 100644 --- a/app/client/src/components/JobStatus/jobStatusIcon.tsx +++ b/app/client/src/components/JobStatus/jobStatusIcon.tsx @@ -24,6 +24,12 @@ const IconWrapper = styled.div` } ` +const StyledIconWrapper = styled(IconWrapper)` + svg { + color: #008cff; + } +`; + export default function JobStatusIcon({ status, customTooltipTitles }: JobStatusProps) { const tooltipTitles = {...defaultTooltipTitles, ...customTooltipTitles}; @@ -44,11 +50,11 @@ export default function JobStatusIcon({ status, customTooltipTitles }: JobStatus ; case 'ENGINE_SCHEDULING': return - + ; case 'ENGINE_RUNNING': return - + ; case null: return diff --git a/app/client/src/constants.ts b/app/client/src/constants.ts index b5f201bc..37436c28 100644 --- a/app/client/src/constants.ts +++ b/app/client/src/constants.ts @@ -12,6 +12,7 @@ export const LABELS = { [Pages.EXPORTS]: 'Exports', [Pages.HISTORY]: 'History', [Pages.FEEDBACK]: 'Feedback', + [Pages.SETTINGS]: 'Settings', //[Pages.TELEMETRY]: 'Telemetry', [ModelParameters.TEMPERATURE]: 'Temperature', [ModelParameters.TOP_K]: 'Top K', diff --git a/app/client/src/pages/DataGenerator/Configure.tsx b/app/client/src/pages/DataGenerator/Configure.tsx index 880abe17..1e6dc0d6 100644 --- a/app/client/src/pages/DataGenerator/Configure.tsx +++ b/app/client/src/pages/DataGenerator/Configure.tsx @@ -2,17 +2,23 @@ import endsWith from 'lodash/endsWith'; import isEmpty from 'lodash/isEmpty'; import isFunction from 'lodash/isFunction'; import { FunctionComponent, useEffect, useState } from 'react'; -import { Flex, Form, FormInstance, Input, Select, Typography } from 'antd'; +import { Flex, Form, Input, Select, Typography } from 'antd'; import styled from 'styled-components'; import { File, WorkflowType } from './types'; import { useFetchModels } from '../../api/api'; import { MODEL_PROVIDER_LABELS } from './constants'; import { ModelProviders, ModelProvidersDropdownOpts } from './types'; -import { getWizardModel, getWizardModeType, useWizardCtx } from './utils'; +import { getWizardModeType, useWizardCtx } from './utils'; import FileSelectorButton from './FileSelectorButton'; import UseCaseSelector from './UseCaseSelector'; import { useLocation, useParams } from 'react-router-dom'; import { WizardModeType } from '../../types'; +import get from 'lodash/get'; +import forEach from 'lodash/forEach'; +import { useModelProviders } from '../Settings/hooks'; +import { ModelProviderType } from '../Settings/AddModelProviderButton'; +import { CustomModel } from '../Settings/SettingsPage'; +import filter from 'lodash/filter'; const StepContainer = styled(Flex)` @@ -47,6 +53,8 @@ export const WORKFLOW_OPTIONS = [ export const MODEL_TYPE_OPTIONS: ModelProvidersDropdownOpts = [ { label: MODEL_PROVIDER_LABELS[ModelProviders.BEDROCK], value: ModelProviders.BEDROCK}, { label: MODEL_PROVIDER_LABELS[ModelProviders.CAII], value: ModelProviders.CAII }, + { label: MODEL_PROVIDER_LABELS[ModelProviders.OPENAI], value: ModelProviders.OPENAI }, + { label: MODEL_PROVIDER_LABELS[ModelProviders.GEMINI], value: ModelProviders.GEMINI }, ]; const Configure: FunctionComponent = () => { @@ -54,7 +62,12 @@ const Configure: FunctionComponent = () => { const formData = Form.useWatch((values) => values, form); const location = useLocation(); const { template_name, generate_file_name } = useParams(); + const [models, setModels] = useState([]) const [wizardModeType, setWizardModeType] = useState(getWizardModeType(location)); + const { data } = useFetchModels(); + const customModelPrividersReq = useModelProviders(); + const customModels = get(customModelPrividersReq, 'data.endpoints', []); + console.log('customModels', customModels); useEffect(() => { if (wizardModeType === WizardModeType.DATA_AUGMENTATION) { @@ -77,10 +90,18 @@ const Configure: FunctionComponent = () => { } }, [template_name]); + useEffect(() => { + // set model providers + // set model ids + if (formData && (formData?.inference_type === ModelProviderType.OPENAI || formData?.inference_type === ModelProviderType.GEMINI) && isEmpty(generate_file_name)) { + form.setFieldValue('inference_type', ModelProviders.OPENAI); + } + + }, [customModels, formData]); + // let formData = Form.useWatch((values) => values, form); const { setIsStepValid } = useWizardCtx(); - const { data } = useFetchModels(); const [selectedFiles, setSelectedFiles] = useState( !isEmpty(form.getFieldValue('doc_paths')) ? form.getFieldValue('doc_paths') : []); @@ -104,7 +125,6 @@ const Configure: FunctionComponent = () => { useEffect(() => { - console.log('useEffect 1'); if (formData && formData?.inference_type === undefined && isEmpty(generate_file_name)) { form.setFieldValue('inference_type', ModelProviders.CAII); setTimeout(() => { @@ -155,6 +175,20 @@ const Configure: FunctionComponent = () => { } } + const onModelProviderChange = (value: string) => { + form.setFieldValue('model_id', undefined) + console.log('value', value); + if (ModelProviderType.OPENAI === value) { + const _models = filter(customModels, (model: CustomModel) => model.provider_type === ModelProviderType.OPENAI); + setModels(_models.map((_model: CustomModel) => _model.model_id)); + } else if (ModelProviderType.GEMINI === value) { + const _models = filter(customModels, (model: CustomModel) => model.provider_type === ModelProviderType.GEMINI); + setModels(_models.map((_model: CustomModel) => _model.model_id)); + } + } + console.log('models', models); + + return ( @@ -178,7 +212,7 @@ const Configure: FunctionComponent = () => { > ) : ( - + {formData?.inference_type === ModelProviders.BEDROCK && data?.models?.[ModelProviders.BEDROCK]?.map((model, i) => ( {model} - )} + ))} + {(formData?.inference_type === ModelProviders.OPENAI || formData?.inference_type === ModelProviders.GEMINI) && models?.map((model, i) => ( + + {model} + + ))} )} - {formData?.inference_type === ModelProviders.CAII && ( <> diff --git a/app/client/src/pages/DataGenerator/Examples.tsx b/app/client/src/pages/DataGenerator/Examples.tsx index 9b46f389..4bfbe449 100644 --- a/app/client/src/pages/DataGenerator/Examples.tsx +++ b/app/client/src/pages/DataGenerator/Examples.tsx @@ -132,12 +132,13 @@ const Examples: FunctionComponent = () => { }; const showEmptyState = (workflowType === WorkflowType.FREE_FORM_DATA_GENERATION && - isEmpty(mutation.data) && + isEmpty(mutation.data) && Array.isArray(records) && records.length === 0) || (form.getFieldValue('use_case') === 'custom' && isEmpty(form.getFieldValue('examples'))); + console.log('records', records); return ( {mutation?.isPending || restore_mutation.isPending && } diff --git a/app/client/src/pages/DataGenerator/Finish.tsx b/app/client/src/pages/DataGenerator/Finish.tsx index 60f7d4de..54d4ef4b 100644 --- a/app/client/src/pages/DataGenerator/Finish.tsx +++ b/app/client/src/pages/DataGenerator/Finish.tsx @@ -322,12 +322,14 @@ const Finish = () => { const rawData = genDatasetResp !== null && hasTopics(genDatasetResp) ? getRawData(genDatasetResp) : genDatasetResp?.results + console.log('Finish >> ', isDemo); + return (
<Flex align='center' gap={10}> <CheckCircleIcon style={{ color: '#178718' }}/> - {'Success'} + {isDemo ? 'Success' : 'Job Successfully Started'} </Flex> {isDemo ? ( diff --git a/app/client/src/pages/DataGenerator/FreeFormExampleTable.tsx b/app/client/src/pages/DataGenerator/FreeFormExampleTable.tsx index c93bceba..f905cce2 100644 --- a/app/client/src/pages/DataGenerator/FreeFormExampleTable.tsx +++ b/app/client/src/pages/DataGenerator/FreeFormExampleTable.tsx @@ -48,6 +48,7 @@ interface Props { const FreeFormExampleTable: FunctionComponent = ({ data }) => { const [colDefs, setColDefs] = useState([]); const [rowData, setRowData] = useState([]); + console.log('FreeFormExampleTable', data); useEffect(() => { if (!isEmpty(data)) { diff --git a/app/client/src/pages/DataGenerator/Success.tsx b/app/client/src/pages/DataGenerator/Success.tsx index f7c8e504..e942e3c9 100644 --- a/app/client/src/pages/DataGenerator/Success.tsx +++ b/app/client/src/pages/DataGenerator/Success.tsx @@ -122,7 +122,7 @@ const Success: FC = ({ formData, isDemo = true }) => { <Flex align='center' gap={10}> <CheckCircleIcon style={{ color: '#178718' }}/> - {'Success'} + {isDemo ? 'Success' : 'Job successfully started.'} </Flex> {isDemo ? ( diff --git a/app/client/src/pages/DataGenerator/constants.ts b/app/client/src/pages/DataGenerator/constants.ts index b90946bc..b4f1f058 100644 --- a/app/client/src/pages/DataGenerator/constants.ts +++ b/app/client/src/pages/DataGenerator/constants.ts @@ -5,6 +5,8 @@ export const MODEL_PROVIDER_LABELS = { [ModelProviders.CAII]: 'Cloudera AI Inference Service', [ModelProviders.GOOGLE_GEMINI]: 'Google Gemini', [ModelProviders.AZURE_OPENAI]: 'Azure OpenAI', + [ModelProviders.GEMINI]: 'Gemini', + [ModelProviders.OPENAI]: 'OpenAI' }; export const MIN_SEED_INSTRUCTIONS = 1 diff --git a/app/client/src/pages/DataGenerator/types.ts b/app/client/src/pages/DataGenerator/types.ts index 73c64b20..027bb054 100644 --- a/app/client/src/pages/DataGenerator/types.ts +++ b/app/client/src/pages/DataGenerator/types.ts @@ -19,6 +19,8 @@ export enum ModelProviders { CAII = 'CAII', AZURE_OPENAI = 'AZURE_OPENAI', GOOGLE_GEMINI = 'GOOGLE_GEMINI', + OPENAI = 'openai', + GEMINI = 'gemini', } export type ModelProvidersDropdownOpts = { label: string, value: ModelProviders }[]; diff --git a/app/client/src/pages/Datasets/DatasetsPage.tsx b/app/client/src/pages/Datasets/DatasetsPage.tsx index b36c85d7..2ff5954c 100644 --- a/app/client/src/pages/Datasets/DatasetsPage.tsx +++ b/app/client/src/pages/Datasets/DatasetsPage.tsx @@ -4,7 +4,6 @@ import { Col, Flex, Input, Layout, Row, Table, TableProps, Tooltip, notification import styled from 'styled-components'; import Paragraph from 'antd/es/typography/Paragraph'; import { useDatasets } from '../Home/hooks'; -import { ExportResult } from '../../components/Export/ExportModal'; import { SearchProps } from 'antd/es/input'; import Loading from '../Evaluator/Loading'; import { Dataset } from '../Evaluator/types'; diff --git a/app/client/src/pages/Home/DatasetsTab.tsx b/app/client/src/pages/Home/DatasetsTab.tsx index 7ce2d040..6512dc1a 100644 --- a/app/client/src/pages/Home/DatasetsTab.tsx +++ b/app/client/src/pages/Home/DatasetsTab.tsx @@ -106,10 +106,16 @@ const DatasetsTab: React.FC = ({ hideSearch = false }) => { key: 'job_status', title: 'Status', dataIndex: 'job_status', - width: 80, + width: 140, sorter: sortItemsByKey('job_status'), - render: (status: JobStatus) => + render: (status: JobStatus) => + + {status === 'ENGINE_SCHEDULING' &&
{'Scheduling'}
} + {status === 'ENGINE_RUNNING' &&
{'Running'}
} + {status === 'ENGINE_STOPPED' &&
{'Stopped'}
} + {status === 'ENGINE_SUCCEEDED' &&
{'Success'}
} + {status === 'ENGINE_TIMEDOUT' &&
{'Timeout'}
}
}, { diff --git a/app/client/src/pages/Settings/AddModelProviderButton.tsx b/app/client/src/pages/Settings/AddModelProviderButton.tsx new file mode 100644 index 00000000..4b6b5587 --- /dev/null +++ b/app/client/src/pages/Settings/AddModelProviderButton.tsx @@ -0,0 +1,215 @@ +import { useEffect, useState } from 'react'; +import { PlusCircleOutlined } from '@ant-design/icons'; +import { Alert, Button, Form, Input, Modal, notification, Radio, Select } from 'antd'; +import type { CheckboxGroupProps } from 'antd/es/checkbox'; +import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; +import { useMutation } from "@tanstack/react-query"; +import { addModelProvider } from './hooks'; +import Loading from '../Evaluator/Loading'; + +export enum ModelProviderType { + OPENAI = 'openai', + GEMINI = 'gemini', + CAII = 'caii' +} + + +const modelProviderTypeOptions: CheckboxGroupProps['options'] = [ + { label: 'OpenAI', value: 'openai' }, + // { label: 'CAII', value: 'caii' }, + { label: 'Gemini', value: 'gemini' }, +]; + +const OPENAI_MODELS = [ + "gpt-4.1", // Latest GPT-4.1 series (April 2025) + "gpt-4.1-mini", + "gpt-4.1-nano" +]; + +const OPENAI_MODELS_OPTIONS = OPENAI_MODELS.map((model: string) => ({ + label: model, + value: model +})); + +const GEMINI_MODELS = [ + "gemini-2.5-pro", // June 2025 - most powerful thinking model + "gemini-2.5-flash", // June 2025 - best price-performance + "gemini-2.5-flash-lite" // June 2025 - cost-efficient +]; + +const GEMINI_MODELS_OPTIONS = GEMINI_MODELS.map((model: string) => ({ + label: model, + value: model +})); + +interface Props { + refetch: () => void; +} + +const AddModelProviderButton: React.FC = ({ refetch }) => { + const [form] = Form.useForm(); + const [showModal, setShowModal] = useState(false); + const [models, setModels] = useState(OPENAI_MODELS_OPTIONS); + const mutation = useMutation({ + mutationFn: addModelProvider + }); + + + useEffect(() => { + if (mutation.isError) { + notification.error({ + message: 'Error', + description: `An error occurred while fetching the prompt.\n ${mutation.error}` + }); + } + if (mutation.isSuccess) { + notification.success({ + message: 'Success', + description: `THe model provider has been created successfully!.` + }); + form.resetFields(); + setShowModal(false); + refetch(); + } + }, [mutation.error, mutation.isSuccess]); + + const onCancel = () => { + form.resetFields(); + setShowModal(false); + } + + const onSubmit = async () => { + try { + await form.validateFields(); + const values = form.getFieldsValue(); + + mutation.mutate({ + endpoint_config: { + display_name: values.display_name, + endpoint_id: values.endpoint_id, + model_id: values.model_id, + provider_type: values.provider_type, + api_key: values.api_key, + endpoint_url: values.endpoint_url + } + }); + } catch (error) { + console.error(error); + } + }; + + + const initialValues = { + provider_type: 'openai' + }; + + const onChange = (e: any) => { + const value = get(e, 'target.value'); + if (value === 'openai' && !isEqual(OPENAI_MODELS_OPTIONS, models)) { + setModels(OPENAI_MODELS_OPTIONS); + } else if (value === 'gemini' && !isEqual(GEMINI_MODELS_OPTIONS, models)) { + setModels(GEMINI_MODELS_OPTIONS); + } + } + + return ( + <> + + {showModal && +
+
+
+ {mutation.isPending && } + {mutation.error && ( + {mutation.error instanceof Error ? mutation.error.message : String(mutation.error)}
+ } + /> + )} + + + + + + + + + + + + + + + + + } + + ); +} + +export default AddModelProviderButton; + diff --git a/app/client/src/pages/Settings/EditModelProvider.tsx b/app/client/src/pages/Settings/EditModelProvider.tsx new file mode 100644 index 00000000..0c786a46 --- /dev/null +++ b/app/client/src/pages/Settings/EditModelProvider.tsx @@ -0,0 +1,236 @@ +import { useEffect, useState } from 'react'; +import { PlusCircleOutlined } from '@ant-design/icons'; +import { Alert, Button, Form, Input, Modal, notification, Radio, Select } from 'antd'; +import type { CheckboxGroupProps } from 'antd/es/checkbox'; +import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; +import { useMutation } from "@tanstack/react-query"; +import { addModelProvider, useGetModelProvider } from './hooks'; +import Loading from '../Evaluator/Loading'; +import { CustomModel } from './SettingsPage'; +import isEmpty from 'lodash/isEmpty'; + +export enum ModelProviderType { + OPENAI = 'openai', + GEMINIE = 'gemini', + CAII = 'caii' +} + + +const modelProviderTypeOptions: CheckboxGroupProps['options'] = [ + { label: 'OpenAI', value: 'openai' }, + // { label: 'CAII', value: 'caii' }, + { label: 'Gemini', value: 'gemini' }, +]; + +const OPENAI_MODELS = [ + "gpt-4.1", // Latest GPT-4.1 series (April 2025) + "gpt-4.1-mini", + "gpt-4.1-nano", + "o3", // Latest reasoning models (April 2025) + "o4-mini", + "o3-mini", // January 2025 + "o1", // December 2024 + "gpt-4o", // November 2024 + "gpt-4o-mini", // July 2024 + "gpt-4-turbo", // April 2024 + "gpt-3.5-turbo" // Legacy but still widely used +]; + +const OPENAI_MODELS_OPTIONS = OPENAI_MODELS.map((model: string) => ({ + label: model, + value: model +})); + +const GEMINI_MODELS = [ + "gemini-2.5-pro", // June 2025 - most powerful thinking model + "gemini-2.5-flash", // June 2025 - best price-performance + "gemini-2.5-flash-lite", // June 2025 - cost-efficient + "gemini-2.0-flash", // February 2025 - next-gen features + "gemini-2.0-flash-lite", // February 2025 - low latency + "gemini-1.5-pro", // September 2024 - complex reasoning + "gemini-1.5-flash", // September 2024 - fast & versatile + "gemini-1.5-flash-8b" // October 2024 - lightweight +]; + +const GEMINI_MODELS_OPTIONS = GEMINI_MODELS.map((model: string) => ({ + label: model, + value: model +})); + +interface Props { + refetch: () => void; + onClose: () => void; + model: CustomModel; +} + +const EditModelProvider: React.FC = ({ model, refetch, onClose }) => { + const [form] = Form.useForm(); + const modelProviderReq = useGetModelProvider(model.endpoint_id); + const [models, setModels] = useState(OPENAI_MODELS_OPTIONS); + const mutation = useMutation({ + mutationFn: addModelProvider + }); + + useEffect(() => { + if (!isEmpty(modelProviderReq.data)) { + const endpoint = get(modelProviderReq, 'data.endpoint'); + form.setFieldsValue({ + ...endpoint + }); + } + }, [modelProviderReq.data]); + + + useEffect(() => { + if (mutation.isError) { + notification.error({ + message: 'Error', + description: `An error occurred while fetching the model.\n ${mutation.error}` + }); + } + if (mutation.isSuccess) { + notification.success({ + message: 'Success', + description: `THe model provider has been edited successfully!.` + }); + refetch(); + } + }, [mutation.error, mutation.isSuccess]); + + const onCancel = () => { + form.resetFields(); + onClose(); + } + + const onSubmit = async () => { + try { + await form.validateFields(); + const values = form.getFieldsValue(); + + mutation.mutate({ + endpoint_config: { + display_name: values.display_name, + endpoint_id: values.endpoint_id, + model_id: values.model_id, + provider_type: values.provider_type, + api_key: values.api_key, + endpoint_url: values.endpoint_url + } + }); + } catch (error) { + console.error(error); + } + }; + + + const initialValues = { + provider_type: 'openai' + }; + + const onChange = (e: any) => { + const value = get(e, 'target.value'); + if (value === 'openai' && !isEqual(OPENAI_MODELS_OPTIONS, models)) { + setModels(OPENAI_MODELS_OPTIONS); + } else if (value === 'gemini' && !isEqual(GEMINI_MODELS_OPTIONS, models)) { + setModels(GEMINI_MODELS_OPTIONS); + } + } + + return ( + <> + +
+
+
+ {(mutation.isPending || modelProviderReq.isLoading) && } + {mutation.error && ( + {mutation.error instanceof Error ? mutation.error.message : String(mutation.error)} + } + /> + )} + + + + + + + + + + + + + + + + +
+ + ); +} + +export default EditModelProvider; + diff --git a/app/client/src/pages/Settings/SettingsPage.tsx b/app/client/src/pages/Settings/SettingsPage.tsx new file mode 100644 index 00000000..4913ffd6 --- /dev/null +++ b/app/client/src/pages/Settings/SettingsPage.tsx @@ -0,0 +1,233 @@ +import { Button, Col, Flex, Layout, Modal, notification, Row, Table, Tooltip, Tooltip } from "antd"; +import { Content } from "antd/es/layout/layout"; +import styled from "styled-components"; +import { deleteModelProvider, useModelProviders } from "./hooks"; +import get from "lodash/get"; +import { sortItemsByKey } from "../../utils/sortutils"; +import Paragraph from "antd/es/typography/Paragraph"; +import StyledTitle from "../Evaluator/StyledTitle"; +import Toolbar from "./Toolbar"; +import AddModelProviderButton, { ModelProviderType } from "./AddModelProviderButton"; +import DateTime from "../../components/DateTime/DateTime"; +import { + EditOutlined, + DeleteOutlined + } from '@ant-design/icons'; +import { useMutation } from "@tanstack/react-query"; +import { useState } from "react"; +import EditModelProvider from "./EditModelProvider"; + + + +const StyledContent = styled(Content)` + padding: 24px; + background-color: #f5f7f8; +`; + + +export interface CustomModel { + endpoint_id: string; + display_name: string; + model_id: string; + provider_type: string; + api_key?: string; + cdp_token?: string; + created_at: string +} + +const Container = styled.div` + background-color: #ffffff; + padding: 1rem; + overflow-x: auto; +`; + +const StyledTable = styled(Table)` + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-table-thead > tr > th { + color: #5a656d; + border-bottom: 1px solid #eaebec; + font-weight: 500; + text-align: left; + // background: #ffffff; + border-bottom: 1px solid #eaebec; + transition: background 0.3s ease; + } + .ant-table-row > td.ant-table-cell { + padding: 8px; + padding-left: 16px; + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; + .ant-typography { + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + } + } +`; + +const StyledParagraph = styled(Paragraph)` + font-size: 13px; + font-family: Roboto, -apple-system, 'Segoe UI', sans-serif; + color: #5a656d; +`; + +const StyledButton = styled(Button)` + margin-left: 8px; +`; + +const SettingsPage: React.FC = () => { + const [showModal, setShowModal] = useState(false); + const [model, setModel] = useState(null); + const filteredModelsReq = useModelProviders(); + const customModels = get(filteredModelsReq, 'data.endpoints', []); + + const mutation = useMutation({ + mutationFn: deleteModelProvider + }); + + const onDelete = (model: CustomModel) => { + Modal.confirm({ + content: ( + {`Are you sure you want to delete the model \'${model.display_name}\'`} + ), + onOk: async () => { + try { + mutation.mutate({ + endpoint_id: model.endpoint_id + }) + } catch (error) { + notification.error({ + message: "Error", + description: error instanceof Error ? error.message : String(error), + }); + } + filteredModelsReq.refetch(); + }, + title: 'Confirm' + }); + }; + + const onEdit = (_model: CustomModel) => { + setShowModal(true); + setModel(_model) + + + } + + const modelProvidersColumns = [{ + key: 'display_name', + title: 'Display Name', + dataIndex: 'display_name', + width: 200, + sorter: sortItemsByKey('display_name') + + }, { + key: 'provider_type', + title: 'Provider Type', + dataIndex: 'provider_type', + width: 150, + sorter: sortItemsByKey('provider_type'), + render: (provider_type: string) => { + if (provider_type === 'openai') { + return 'OpenAI'; + } else if (provider_type === ModelProviderType.GEMINI) { + return 'Gemini'; + } else if (provider_type === ModelProviderType.CAII) { + return 'CAII'; + } + return 'N/A' + } + }, { + key: 'model_id', + title: 'Model ID', + dataIndex: 'model_id', + width: 200, + sorter: sortItemsByKey('model_id') + + }, { + key: 'created_at', + title: 'Created At', + dataIndex: 'created_at', + width: 200, + sorter: sortItemsByKey('created_at'), + render: (timestamp: string) => <>{timestamp == null ? 'N/A' : } + + }, { + key: 'endpoint_url', + title: 'Endpoint', + dataIndex: 'endpoint_url', + width: 300, + sorter: sortItemsByKey('endpoint_url'), + render: (endpoint_url: string) => {endpoint_url} + }, { + title: 'Actions', + width: 100, + render: (model: CustomModel) => { + return ( + + + + + + onEdit(model)} + data-event-category="User Action" + data-event="Edit" + > + + + + + ); + + } + }]; + + return ( + + + + {'Settings'} + +
+
+ + + {'Custom Models'} + } + right={ + + + + } + /> + `${row?.endpoint_id}`} + tableLayout="fixed" + columns={modelProvidersColumns} + dataSource={customModels || [] as CustomModel[]} /> + + +
+ {showModal && + setShowModal(false)} />} +
+
+ ); + +} + +export default SettingsPage; \ No newline at end of file diff --git a/app/client/src/pages/Settings/Toolbar.tsx b/app/client/src/pages/Settings/Toolbar.tsx new file mode 100644 index 00000000..579591de --- /dev/null +++ b/app/client/src/pages/Settings/Toolbar.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Row } from 'antd'; +import classNames from 'classnames'; +import styled from 'styled-components'; + +interface Props { + /** Content for the left content area. */ + left?: React.ReactNode; + /** Content for the center content area. */ + center?: React.ReactNode; + /** Content for the right content area. */ + right?: React.ReactNode; + /** Additional class name to add to the underlying Row component. */ + className?: string; + style?: React.CSSProperties; +} + +const StyledRow = styled(Row)` + .altus-toolbar { + & .altus-toolbar-group:not(.ant-row-start) { + flex-grow: 1; + } +} + +`; + +/** + * @deprecated + */ +export default function Toolbar({ left, center, right, className = '', ...otherProps }: Props) { + return ( + + {left && ( + + {left} + + )} + {center && ( + + {center} + + )} + {right && ( + + {right} + + )} + + ); +} diff --git a/app/client/src/pages/Settings/hooks.ts b/app/client/src/pages/Settings/hooks.ts new file mode 100644 index 00000000..4269a73a --- /dev/null +++ b/app/client/src/pages/Settings/hooks.ts @@ -0,0 +1,80 @@ +import { useQuery } from "@tanstack/react-query"; + +const BASE_API_URL = import.meta.env.VITE_AMP_URL; + + +const fetchFilteredModels = async () => { + // const model_filtered_resp = await fetch(`${BASE_API_URL}/model/model_id_filter`, { + const model_filtered_resp = await fetch(`/custom_model_endpoints`, { + method: 'GET', + }); + return await model_filtered_resp.json(); +}; + + +export const deleteModelProvider = async ({ endpoint_id }) => { + const delete_resp = await fetch(`/custom_model_endpoints/${endpoint_id}`, { + method: 'DELETE' + }); + return await delete_resp.json(); +} + +export const getModelProvider = async ({ endpoint_id }) => { + const get_model_resp = await fetch(`/custom_model_endpoints/${endpoint_id}`, { + method: 'GET' + }); + return await get_model_resp.json(); +} + +export const updateModelProvider = async ({ endpoint_id }) => { + const update_model_resp = await fetch(`/custom_model_endpoints/${endpoint_id}`, { + method: 'PUT' + }); + return await update_model_resp.json(); +} + + +export const useModelProviders = () => { + + const { data, isLoading, isError, refetch } = useQuery( + { + queryKey: ['fetchFilteredModels'], + queryFn: () => fetchFilteredModels(), + refetchInterval: 15000 + } + ); + + return { + data, + isLoading, + isError, + refetch + }; +} + +export const addModelProvider = async (params: any) => { + const model_filtered_resp = await fetch(`/add_model_endpoint`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(params) + }); + return await model_filtered_resp.json(); +} + +export const useGetModelProvider = (endpoint_id) => { + + const { data, isLoading, isError, refetch } = useQuery( + { + queryKey: ['getModelProvider'], + queryFn: () => getModelProvider({ endpoint_id }), + refetchInterval: 15000 + } + ); + + return { + data, + isLoading, + isError, + refetch + }; +} \ No newline at end of file diff --git a/app/client/src/routes.tsx b/app/client/src/routes.tsx index 9a0e4cc2..db393328 100644 --- a/app/client/src/routes.tsx +++ b/app/client/src/routes.tsx @@ -12,6 +12,7 @@ import EvaluationDetailsPage from "./pages/EvaluationDetails/EvaluationDetailsPa import DatasetsPage from "./pages/Datasets/DatasetsPage"; import EvaluationsPage from "./pages/Evaluations/EvaluationsPage"; import ExportsPage from "./pages/Exports/ExportsPage"; +import SettingsPage from "./pages/Settings/SettingsPage"; //import TelemetryDashboard from "./components/TelemetryDashboard"; @@ -108,6 +109,13 @@ const router = createBrowserRouter([ errorElement: , loader: async () => null }, + { + path: Pages.SETTINGS, + element: , + errorElement: , + loader: async () => null + }, + // { // path: `telemetry`, // element: , diff --git a/app/client/src/types.ts b/app/client/src/types.ts index 900b81ab..414fdbd0 100644 --- a/app/client/src/types.ts +++ b/app/client/src/types.ts @@ -10,7 +10,8 @@ export enum Pages { EXPORTS = 'exports', WELCOME = 'welcome', FEEDBACK = 'feedback', - UPGRADE = 'upgrade' + UPGRADE = 'upgrade', + SETTINGS = 'settings' //TELEMETRY = 'telemetry' }