Skip to content

Commit afd5776

Browse files
author
Keivan Vosoughi
committed
Add Edit Modal
1 parent ecb8576 commit afd5776

File tree

4 files changed

+284
-26
lines changed

4 files changed

+284
-26
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import { useEffect, useState } from 'react';
2+
import { PlusCircleOutlined } from '@ant-design/icons';
3+
import { Alert, Button, Form, Input, Modal, notification, Radio, Select } from 'antd';
4+
import type { CheckboxGroupProps } from 'antd/es/checkbox';
5+
import get from 'lodash/get';
6+
import isEqual from 'lodash/isEqual';
7+
import { useMutation } from "@tanstack/react-query";
8+
import { addModelProvider, useGetModelProvider } from './hooks';
9+
import Loading from '../Evaluator/Loading';
10+
import { CustomModel } from './SettingsPage';
11+
import isEmpty from 'lodash/isEmpty';
12+
13+
export enum ModelProviderType {
14+
OPENAI = 'openai',
15+
GEMINIE = 'gemini',
16+
CAII = 'caii'
17+
}
18+
19+
20+
const modelProviderTypeOptions: CheckboxGroupProps<string>['options'] = [
21+
{ label: 'OpenAI', value: 'openai' },
22+
// { label: 'CAII', value: 'caii' },
23+
{ label: 'Gemini', value: 'gemini' },
24+
];
25+
26+
const OPENAI_MODELS = [
27+
"gpt-4.1", // Latest GPT-4.1 series (April 2025)
28+
"gpt-4.1-mini",
29+
"gpt-4.1-nano",
30+
"o3", // Latest reasoning models (April 2025)
31+
"o4-mini",
32+
"o3-mini", // January 2025
33+
"o1", // December 2024
34+
"gpt-4o", // November 2024
35+
"gpt-4o-mini", // July 2024
36+
"gpt-4-turbo", // April 2024
37+
"gpt-3.5-turbo" // Legacy but still widely used
38+
];
39+
40+
const OPENAI_MODELS_OPTIONS = OPENAI_MODELS.map((model: string) => ({
41+
label: model,
42+
value: model
43+
}));
44+
45+
const GEMINI_MODELS = [
46+
"gemini-2.5-pro", // June 2025 - most powerful thinking model
47+
"gemini-2.5-flash", // June 2025 - best price-performance
48+
"gemini-2.5-flash-lite", // June 2025 - cost-efficient
49+
"gemini-2.0-flash", // February 2025 - next-gen features
50+
"gemini-2.0-flash-lite", // February 2025 - low latency
51+
"gemini-1.5-pro", // September 2024 - complex reasoning
52+
"gemini-1.5-flash", // September 2024 - fast & versatile
53+
"gemini-1.5-flash-8b" // October 2024 - lightweight
54+
];
55+
56+
const GEMINI_MODELS_OPTIONS = GEMINI_MODELS.map((model: string) => ({
57+
label: model,
58+
value: model
59+
}));
60+
61+
interface Props {
62+
refetch: () => void;
63+
model: CustomModel;
64+
}
65+
66+
const EditModelProvider: React.FC<Props> = ({ model, refetch }) => {
67+
const [form] = Form.useForm();
68+
const modelProviderReq = useGetModelProvider(model.endpoint_id);
69+
console.log('modelProviderReq', modelProviderReq);
70+
const [models, setModels] = useState(OPENAI_MODELS_OPTIONS);
71+
const mutation = useMutation({
72+
mutationFn: addModelProvider
73+
});
74+
75+
useEffect(() => {
76+
if (!isEmpty(modelProviderReq.data)) {
77+
console.log('-------->', modelProviderReq.data);
78+
}
79+
80+
}, [modelProviderReq.data]);
81+
82+
83+
useEffect(() => {
84+
if (mutation.isError) {
85+
notification.error({
86+
message: 'Error',
87+
description: `An error occurred while fetching the model.\n ${mutation.error}`
88+
});
89+
}
90+
if (mutation.isSuccess) {
91+
notification.success({
92+
message: 'Success',
93+
description: `THe model provider has been edited successfully!.`
94+
});
95+
setShowModal(false);
96+
refetch();
97+
}
98+
}, [mutation.error, mutation.isSuccess]);
99+
100+
const onCancel = () => {
101+
form.resetFields();
102+
setShowModal(false);
103+
}
104+
105+
const onSubmit = async () => {
106+
try {
107+
await form.validateFields();
108+
const values = form.getFieldsValue();
109+
console.log('values', values);
110+
mutation.mutate({
111+
endpoint_config: {
112+
display_name: values.display_name,
113+
endpoint_id: values.endpoint_id,
114+
model_id: values.model_id,
115+
provider_type: values.provider_type,
116+
api_key: values.api_key,
117+
endpoint_url: values.endpoint_url
118+
}
119+
});
120+
} catch (error) {
121+
console.error(error);
122+
}
123+
};
124+
125+
126+
const initialValues = {
127+
provider_type: 'openai'
128+
};
129+
130+
const onChange = (e: any) => {
131+
console.log('onChange', e);
132+
const value = get(e, 'target.value');
133+
console.log('value:', value);
134+
if (value === 'openai' && !isEqual(OPENAI_MODELS_OPTIONS, models)) {
135+
setModels(OPENAI_MODELS_OPTIONS);
136+
} else if (value === 'gemini' && !isEqual(GEMINI_MODELS_OPTIONS, models)) {
137+
setModels(GEMINI_MODELS_OPTIONS);
138+
}
139+
}
140+
141+
return (
142+
<>
143+
{showModal && <Modal
144+
visible
145+
okText={`Edit`}
146+
title={`Edit Model Provider`}
147+
onCancel={onCancel}
148+
onOk={onSubmit}
149+
width={800}
150+
>
151+
<Form form={form} layout="vertical" initialValues={initialValues}>
152+
<br />
153+
<br />
154+
{(mutation.isPending || modelProviderReq.isLoading) && <Loading />}
155+
{mutation.error && (
156+
<Alert
157+
type="error"
158+
message="Error Occurred"
159+
description={
160+
<div>{mutation.error instanceof Error ? mutation.error.message : String(mutation.error)}</div>
161+
}
162+
/>
163+
)}
164+
<Form.Item name="provider_type">
165+
<Radio.Group
166+
block
167+
options={modelProviderTypeOptions}
168+
defaultValue="openai"
169+
optionType="button"
170+
buttonStyle="solid"
171+
style={{ width: '40%' }}
172+
onChange={onChange}
173+
/>
174+
</Form.Item>
175+
<Form.Item
176+
name="display_name"
177+
label="Display Name"
178+
rules={[
179+
{
180+
required: true,
181+
message: 'This field is required.'
182+
}
183+
]}>
184+
<Input />
185+
</Form.Item>
186+
<Form.Item
187+
name="endpoint_id"
188+
label="Endpoint ID"
189+
rules={[
190+
{
191+
required: true,
192+
message: 'This field is required.'
193+
}
194+
]}>
195+
<Input />
196+
</Form.Item>
197+
<Form.Item
198+
name="model_id"
199+
label="Model"
200+
rules={[
201+
{
202+
required: true,
203+
message: 'This field is required.'
204+
}
205+
]}>
206+
<Select options={models} />
207+
</Form.Item>
208+
<Form.Item
209+
name="endpoint_url"
210+
label="Endpoint URL"
211+
rules={[
212+
{
213+
required: true,
214+
message: 'This field is required.'
215+
}
216+
]}>
217+
<Input />
218+
</Form.Item>
219+
<Form.Item
220+
name="api_key"
221+
label="API Key"
222+
rules={[
223+
{
224+
required: true,
225+
message: 'This field is required.'
226+
}
227+
]}>
228+
<Input.Password />
229+
</Form.Item>
230+
</Form>
231+
</Modal>}
232+
</>
233+
);
234+
}
235+
236+
export default EditModelProvider;
237+

app/client/src/pages/Settings/ModelProvidersTable.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

app/client/src/pages/Settings/SettingsPage.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
DeleteOutlined
1515
} from '@ant-design/icons';
1616
import { useMutation } from "@tanstack/react-query";
17+
import { useState } from "react";
18+
import EditModelProvider from "./EditModelProvider";
1719

1820

1921

@@ -75,6 +77,8 @@ const StyledButton = styled(Button)`
7577
`;
7678

7779
const SettingsPage: React.FC = () => {
80+
const [showModal, setShowModal] = useState(false);
81+
const [model, setModel] = useState<CustomModel | null>(null);
7882
const filteredModelsReq = useModelProviders();
7983
console.log('filteredModelsReq', filteredModelsReq);
8084
const customModels = get(filteredModelsReq, 'data.endpoints', []);
@@ -105,7 +109,12 @@ const SettingsPage: React.FC = () => {
105109
});
106110
};
107111

108-
const onEdit = () => {}
112+
const onEdit = (_model: CustomModel) => {
113+
setShowModal(true);
114+
setModel(_model)
115+
116+
117+
}
109118

110119
const modelProvidersColumns = [{
111120
key: 'display_name',
@@ -174,7 +183,7 @@ const SettingsPage: React.FC = () => {
174183
<StyledButton
175184
type="link"
176185
key={`${model.endpoint_id}-deploy`}
177-
onClick={onEdit}
186+
onClick={() => onEdit(model)}
178187
data-event-category="User Action"
179188
data-event="Edit"
180189
>
@@ -187,9 +196,6 @@ const SettingsPage: React.FC = () => {
187196
}
188197
}];
189198

190-
191-
const onAdd = () => {};
192-
193199
return (
194200
<Layout>
195201
<StyledContent>
@@ -218,6 +224,7 @@ const SettingsPage: React.FC = () => {
218224
</Col>
219225
</Row>
220226
</Container>
227+
{showModal && <EditModelProvider model={model as CustomModel} refetch={filteredModelsReq.refetch} />}
221228
</StyledContent>
222229
</Layout>
223230
);

app/client/src/pages/Settings/hooks.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,25 @@ const fetchFilteredModels = async () => {
1212
};
1313

1414

15-
export const deleteModelProvider = async ({ model_id, provider_type }) => {
16-
const model_filtered_resp = await fetch(`/custom_model_endpoints/${model_id}/${provider_type}`, {
15+
export const deleteModelProvider = async ({ endpoint_id }) => {
16+
const delete_resp = await fetch(`/custom_model_endpoints/${endpoint_id}`, {
1717
method: 'DELETE'
1818
});
19-
return await model_filtered_resp.json();
19+
return await delete_resp.json();
20+
}
21+
22+
export const getModelProvider = async ({ endpoint_id }) => {
23+
const get_model_resp = await fetch(`/custom_model_endpoints/${endpoint_id}`, {
24+
method: 'GET'
25+
});
26+
return await get_model_resp.json();
27+
}
28+
29+
export const updateModelProvider = async ({ endpoint_id }) => {
30+
const update_model_resp = await fetch(`/custom_model_endpoints/${endpoint_id}`, {
31+
method: 'PUT'
32+
});
33+
return await update_model_resp.json();
2034
}
2135

2236

@@ -47,6 +61,24 @@ export const addModelProvider = async (params: any) => {
4761
return await model_filtered_resp.json();
4862
}
4963

64+
export const useGetModelProvider = (endpoint_id) => {
65+
66+
const { data, isLoading, isError, refetch } = useQuery(
67+
{
68+
queryKey: ['getModelProvider'],
69+
queryFn: () => getModelProvider({ endpoint_id }),
70+
refetchInterval: 15000
71+
}
72+
);
73+
74+
return {
75+
data,
76+
isLoading,
77+
isError,
78+
refetch
79+
};
80+
}
81+
5082
// {
5183
// "endpoint_config": {
5284
// "api_key": "sk-...",

0 commit comments

Comments
 (0)