mirror of
https://github.com/csunny/DB-GPT.git
synced 2026-01-29 21:49:35 +00:00
feat(datasource): Database connection Renewal (#2359)
Co-authored-by: aries_ckt <916701291@qq.com> Co-authored-by: Fangyin Cheng <staneyffer@gmail.com> Co-authored-by: ‘yanzhiyonggit config --global user.email git config --global user.name ‘yanzhiyong <zhiyong.yan@clife.cn>
This commit is contained in:
@@ -50,7 +50,7 @@ import {
|
||||
} from '@/types/knowledge';
|
||||
import { BaseModelParams, IModelData, StartModelParams, SupportModel } from '@/types/model';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { GET, POST } from '.';
|
||||
import { DELETE, GET, POST, PUT } from '.';
|
||||
|
||||
/** App */
|
||||
export const postScenes = () => {
|
||||
@@ -69,25 +69,25 @@ export const addUser = (data: UserParam) => {
|
||||
|
||||
/** Database Page */
|
||||
export const getDbList = () => {
|
||||
return GET<null, DbListResponse>('/api/v1/chat/db/list');
|
||||
return GET<null, DbListResponse>('/api/v2/serve/datasources');
|
||||
};
|
||||
export const getDbSupportType = () => {
|
||||
return GET<null, DbSupportTypeResponse>('/api/v1/chat/db/support/type');
|
||||
return GET<null, DbSupportTypeResponse>('/api/v2/serve/datasource-types');
|
||||
};
|
||||
export const postDbDelete = (dbName: string) => {
|
||||
return POST(`/api/v1/chat/db/delete?db_name=${dbName}`);
|
||||
export const postDbDelete = (id: string) => {
|
||||
return DELETE(`/api/v2/serve/datasources/${id}`);
|
||||
};
|
||||
export const postDbEdit = (data: PostDbParams) => {
|
||||
return POST<PostDbParams, null>('/api/v1/chat/db/edit', data);
|
||||
return PUT<PostDbParams, null>('/api/v2/serve/datasources', data);
|
||||
};
|
||||
export const postDbAdd = (data: PostDbParams) => {
|
||||
return POST<PostDbParams, null>('/api/v1/chat/db/add', data);
|
||||
return POST<PostDbParams, null>('/api/v2/serve/datasources', data);
|
||||
};
|
||||
export const postDbTestConnect = (data: PostDbParams) => {
|
||||
return POST<PostDbParams, null>('/api/v1/chat/db/test/connect', data);
|
||||
return POST<PostDbParams, null>('/api/v2/serve/datasources/test-connection', data);
|
||||
};
|
||||
export const postDbRefresh = (data: PostDbRefreshParams) => {
|
||||
return POST<PostDbRefreshParams, boolean>('/api/v1/chat/db/refresh', data);
|
||||
return POST<PostDbRefreshParams, boolean>(`/api/v2/serve/datasources/${data.id}/refresh`);
|
||||
};
|
||||
|
||||
/** Chat Page */
|
||||
@@ -360,6 +360,7 @@ export const unPublishApp = (app_code: string) => {
|
||||
};
|
||||
export const addOmcDB = (params: Record<string, string>) => {
|
||||
return POST<Record<string, any>, []>('/api/v1/chat/db/add', params);
|
||||
// return POST<Record<string, any>, []>('/api/v2/serve/datasources', params);
|
||||
};
|
||||
|
||||
export const getAppInfo = (data: GetAppInfoParams) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SupportModelParams } from '@/types/model';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { Checkbox, Form, FormInstance, Input, InputNumber, Select } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import NestedFormFields from './nested-form-fields';
|
||||
@@ -7,7 +7,7 @@ interface ParamValues {
|
||||
[key: string]: string | number | boolean | Record<string, any>;
|
||||
}
|
||||
|
||||
function ModelParams({ params, form }: { params: Array<SupportModelParams> | null; form: FormInstance<any> }) {
|
||||
function ConfigurableForm({ params, form }: { params: Array<ConfigurableParams> | null; form: FormInstance<any> }) {
|
||||
useEffect(() => {
|
||||
if (params) {
|
||||
const initialValues: ParamValues = {};
|
||||
@@ -53,6 +53,7 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
});
|
||||
return normalized;
|
||||
};
|
||||
|
||||
// Override the original submit method of the form
|
||||
const originalSubmit = form.submit;
|
||||
form.submit = () => {
|
||||
@@ -62,12 +63,12 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
originalSubmit.call(form);
|
||||
};
|
||||
|
||||
function renderItem(param: SupportModelParams) {
|
||||
function renderItem(param: ConfigurableParams) {
|
||||
if (param.nested_fields) {
|
||||
return (
|
||||
<NestedFormFields
|
||||
parentName={param.param_name}
|
||||
fields={param.nested_fields as Record<string, SupportModelParams[]>}
|
||||
fields={param.nested_fields as Record<string, ConfigurableParams[]>}
|
||||
form={form}
|
||||
/>
|
||||
);
|
||||
@@ -75,8 +76,10 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
|
||||
const type = param.param_type.toLowerCase();
|
||||
const isFixed = param.ext_metadata?.tags?.includes('fixed');
|
||||
const isPrivacy = param.ext_metadata?.tags?.includes('privacy');
|
||||
|
||||
if (type === 'str' || type === 'string') {
|
||||
// Handle dropdown selection box
|
||||
if (param.valid_values) {
|
||||
return (
|
||||
<Select disabled={isFixed}>
|
||||
@@ -88,20 +91,30 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle password input box
|
||||
if (isPrivacy) {
|
||||
return <Input.Password disabled={isFixed} autoComplete='new-password' placeholder='请输入密码' />;
|
||||
}
|
||||
|
||||
// Handle normal text input box
|
||||
return <Input disabled={isFixed} />;
|
||||
}
|
||||
|
||||
if (type === 'int' || type === 'integer' || type === 'number' || type === 'float') {
|
||||
return <InputNumber className='w-full' disabled={isFixed} />;
|
||||
}
|
||||
|
||||
if (type === 'bool' || type === 'boolean') {
|
||||
return <Checkbox disabled={isFixed} />;
|
||||
}
|
||||
|
||||
return <Input disabled={isFixed} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
{params?.map((param: SupportModelParams) => (
|
||||
{params?.map((param: ConfigurableParams) => (
|
||||
<Form.Item
|
||||
key={param.param_name}
|
||||
label={<p className='whitespace-normal overflow-wrap-break-word'>{param.label || param.param_name}</p>}
|
||||
@@ -122,4 +135,4 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelParams;
|
||||
export default ConfigurableForm;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SupportModelParams } from '@/types/model';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { Checkbox, Form, FormInstance, Input, InputNumber, Select } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface NestedFormFieldsProps {
|
||||
parentName: string;
|
||||
fields: Record<string, SupportModelParams[]>;
|
||||
fields: Record<string, ConfigurableParams[]>;
|
||||
form: FormInstance;
|
||||
}
|
||||
const NestedFormFields: React.FC<NestedFormFieldsProps> = ({ parentName, fields, form }) => {
|
||||
@@ -39,7 +39,7 @@ const NestedFormFields: React.FC<NestedFormFieldsProps> = ({ parentName, fields,
|
||||
});
|
||||
};
|
||||
|
||||
const renderFormItem = (param: SupportModelParams) => {
|
||||
const renderFormItem = (param: ConfigurableParams) => {
|
||||
const type = param.param_type.toLowerCase();
|
||||
// Use the complete field path
|
||||
const fieldPath = [parentName, param.param_name];
|
||||
149
web/components/database/database-form.tsx
Normal file
149
web/components/database/database-form.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { apiInterceptors, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { DBOption, DBType } from '@/types/db';
|
||||
import { Button, Form, Input, Select, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ConfigurableForm from '../common/configurable-form';
|
||||
|
||||
const { Option } = Select;
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface DatabaseFormProps {
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
dbTypeList: DBOption[];
|
||||
editValue?: string;
|
||||
choiceDBType?: DBType;
|
||||
getFromRenderData?: ConfigurableParams[];
|
||||
dbNames?: string[];
|
||||
description?: string; // Add description prop
|
||||
}
|
||||
|
||||
function DatabaseForm({
|
||||
onCancel,
|
||||
onSuccess,
|
||||
dbTypeList,
|
||||
editValue,
|
||||
choiceDBType,
|
||||
getFromRenderData,
|
||||
dbNames = [],
|
||||
description = '', // Default value for description
|
||||
}: DatabaseFormProps) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<DBType | undefined>(choiceDBType);
|
||||
const [params, setParams] = useState<Array<ConfigurableParams> | null>(getFromRenderData || null);
|
||||
console.log('dbTypeList', dbTypeList);
|
||||
console.log('editValue', editValue);
|
||||
console.log('choiceDBType', choiceDBType);
|
||||
|
||||
useEffect(() => {
|
||||
if (choiceDBType) {
|
||||
setSelectedType(choiceDBType);
|
||||
}
|
||||
}, [choiceDBType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editValue && getFromRenderData) {
|
||||
setParams(getFromRenderData);
|
||||
// set description
|
||||
form.setFieldValue('description', description);
|
||||
}
|
||||
}, [editValue, getFromRenderData, description, form]);
|
||||
|
||||
const handleTypeChange = (value: DBType) => {
|
||||
setSelectedType(value);
|
||||
form.resetFields(['params']);
|
||||
|
||||
const selectedDBType = dbTypeList.find(type => type.value === value);
|
||||
if (selectedDBType?.parameters) {
|
||||
setParams(selectedDBType.parameters);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (formValues: any) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
console.log('dbNames:', dbNames);
|
||||
|
||||
// Check if database name is duplicated
|
||||
// if (!editValue && dbNames.includes(values.database)) {
|
||||
// message.error(t('database_name_exists'));
|
||||
// return;
|
||||
// }
|
||||
|
||||
const { description, type, ...values } = formValues;
|
||||
|
||||
const data = {
|
||||
type: selectedType,
|
||||
params: values,
|
||||
description: description || '',
|
||||
};
|
||||
|
||||
// If in edit mode, add id
|
||||
if (editValue) {
|
||||
data.id = editValue;
|
||||
}
|
||||
|
||||
console.log('Form submitted:', data);
|
||||
|
||||
const [testErr] = await apiInterceptors(postDbTestConnect(data));
|
||||
if (testErr) return;
|
||||
const [err] = await apiInterceptors((editValue ? postDbEdit : postDbAdd)(data));
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success(t(editValue ? 'update_success' : 'create_success'));
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to submit form:', error);
|
||||
message.error(t(editValue ? 'update_failed' : 'create_failed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
layout='vertical'
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
type: selectedType,
|
||||
}}
|
||||
>
|
||||
<FormItem
|
||||
label={t('database_type')}
|
||||
name='type'
|
||||
rules={[{ required: true, message: t('please_select_database_type') }]}
|
||||
>
|
||||
<Select placeholder={t('select_database_type')} onChange={handleTypeChange} disabled={!!editValue}>
|
||||
{dbTypeList.map(type => (
|
||||
<Option key={type.value} value={type.value} disabled={type.disabled}>
|
||||
{type.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
{params && <ConfigurableForm params={params} form={form} />}
|
||||
|
||||
<FormItem label={t('description')} name='description'>
|
||||
<Input.TextArea rows={2} placeholder={t('input_description')} />
|
||||
</FormItem>
|
||||
|
||||
<div className='flex justify-end space-x-4 mt-6'>
|
||||
<Button onClick={onCancel}>{t('cancel')}</Button>
|
||||
<Button type='primary' htmlType='submit' loading={loading}>
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default DatabaseForm;
|
||||
@@ -1,217 +1,54 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { addOmcDB, apiInterceptors, getSupportDBList, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||
import { isFileDb } from '@/pages/construct/database';
|
||||
import { DBOption, DBType, DbListResponse, PostDbParams } from '@/types/db';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { Button, Form, Input, InputNumber, Modal, Select, Spin, Tooltip, message } from 'antd';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { DBOption, DBType } from '@/types/db';
|
||||
import { Modal } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatabaseForm from './database-form';
|
||||
|
||||
type DBItem = DbListResponse[0] & { db_arn?: string };
|
||||
|
||||
interface Props {
|
||||
dbTypeList: DBOption[];
|
||||
interface NewFormDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
dbTypeList: DBOption[];
|
||||
editValue?: string;
|
||||
choiceDBType?: DBType;
|
||||
editValue?: DBItem;
|
||||
dbNames: string[];
|
||||
onSuccess?: () => void;
|
||||
onClose?: () => void;
|
||||
getFromRenderData?: ConfigurableParams[];
|
||||
dbNames?: string[];
|
||||
dbTypeData?: DBOption[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClose, onSuccess }: Props) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
function FormDialog({
|
||||
open,
|
||||
onClose,
|
||||
onSuccess,
|
||||
dbTypeList,
|
||||
editValue,
|
||||
choiceDBType,
|
||||
getFromRenderData,
|
||||
dbNames,
|
||||
description,
|
||||
}: NewFormDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm<DBItem>();
|
||||
const dbType = Form.useWatch('db_type', form);
|
||||
const [omcDBList, setOmcDBList] = useState([]);
|
||||
const [omcListLoading, setOmcListLoading] = useState(false);
|
||||
const fileDb = useMemo(() => isFileDb(dbTypeList, dbType), [dbTypeList, dbType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (choiceDBType) {
|
||||
form.setFieldValue('db_type', choiceDBType);
|
||||
}
|
||||
}, [choiceDBType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editValue) {
|
||||
form.setFieldsValue({ ...editValue });
|
||||
if (editValue.db_type === 'omc') {
|
||||
form.setFieldValue('db_arn', editValue.db_path);
|
||||
}
|
||||
}
|
||||
}, [editValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const onFinish = async (val: DBItem) => {
|
||||
const { db_host, db_path, db_port, db_type, ...params } = val;
|
||||
setLoading(true);
|
||||
|
||||
if (db_type === 'omc') {
|
||||
const item = omcDBList?.find((item: any) => item.arn === val.db_name) as any;
|
||||
|
||||
try {
|
||||
const [err] = await apiInterceptors(
|
||||
addOmcDB({
|
||||
db_type: 'omc',
|
||||
file_path: val.db_arn || '',
|
||||
comment: val.comment,
|
||||
db_name: item?.dbName || val.db_name,
|
||||
}),
|
||||
);
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success('success');
|
||||
onSuccess?.();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (!editValue && dbNames.some(item => item === params.db_name)) {
|
||||
message.error('The database already exists!');
|
||||
return;
|
||||
}
|
||||
const data: PostDbParams = {
|
||||
db_host: fileDb ? undefined : db_host,
|
||||
db_port: fileDb ? undefined : db_port,
|
||||
db_type: db_type,
|
||||
file_path: fileDb ? db_path : undefined,
|
||||
...params,
|
||||
};
|
||||
try {
|
||||
const [testErr] = await apiInterceptors(postDbTestConnect(data));
|
||||
if (testErr) return;
|
||||
const [err] = await apiInterceptors((editValue ? postDbEdit : postDbAdd)(data));
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success('success');
|
||||
onSuccess?.();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { run: fetchOmcList } = useDebounceFn(
|
||||
async (name: string) => {
|
||||
setOmcListLoading(true);
|
||||
const [_, data = []] = (await apiInterceptors(getSupportDBList(name))) as any;
|
||||
setOmcListLoading(false);
|
||||
|
||||
setOmcDBList(data.map((item: any) => ({ ...item, label: item.dbName, value: item.arn })));
|
||||
},
|
||||
{
|
||||
wait: 500,
|
||||
},
|
||||
);
|
||||
|
||||
const lockDBType = useMemo(() => !!editValue || !!choiceDBType, [editValue, choiceDBType]);
|
||||
return (
|
||||
<Modal
|
||||
title={t(editValue ? 'edit_database' : 'add_database')}
|
||||
open={open}
|
||||
width={800}
|
||||
title={editValue ? t('Edit') : t('create_database')}
|
||||
maskClosable={false}
|
||||
footer={null}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={800}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} className='pt-2' labelCol={{ span: 6 }} labelAlign='left' onFinish={onFinish}>
|
||||
<Form.Item name='db_type' label='DB Type' className='mb-6' rules={[{ required: true }]}>
|
||||
<Select aria-readonly={lockDBType} disabled={lockDBType} options={dbTypeList} />
|
||||
</Form.Item>
|
||||
{form.getFieldValue('db_type') === 'omc' ? (
|
||||
<Form.Item name='db_name' label='DB Name' className='mb-6' rules={[{ required: true }]}>
|
||||
<Select
|
||||
optionRender={(option, { index }) => {
|
||||
const item = omcDBList[index] as any;
|
||||
return (
|
||||
<div key={option.value} className='flex flex-col'>
|
||||
<span className='text-[18px]'>{item?.dbName}</span>
|
||||
<span>
|
||||
<span>env: </span>
|
||||
<span className='text-gray-500'>{item.env}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>account: </span>
|
||||
<span className='text-gray-500'>{item.account}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>searchName: </span>
|
||||
<Tooltip title={item.searchName}>
|
||||
<span className='text-gray-500'>{item.searchName}</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
notFoundContent={omcListLoading ? <Spin size='small' /> : null}
|
||||
showSearch
|
||||
options={omcDBList}
|
||||
onSearch={fetchOmcList}
|
||||
onSelect={searchName => {
|
||||
const item = omcDBList?.find((item: any) => item.value === searchName) as any;
|
||||
form.setFieldsValue({
|
||||
db_arn: item?.arn,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item name='db_name' label='DB Name' className='mb-3' rules={[{ required: true }]}>
|
||||
<Input readOnly={!!editValue} disabled={!!editValue} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{fileDb === true && (
|
||||
<Form.Item name='db_path' label='Path' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
{fileDb === false && form.getFieldValue('db_type') !== 'omc' && (
|
||||
<>
|
||||
<Form.Item name='db_user' label='Username' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='db_pwd' label='Password' className='mb-6' rules={[{ required: false }]}>
|
||||
<Input type='password' />
|
||||
</Form.Item>
|
||||
<Form.Item name='db_host' label='Host' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='db_port' label='Port' className='mb-6' rules={[{ required: true }]}>
|
||||
<InputNumber min={1} step={1} max={65535} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{form.getFieldValue('db_type') === 'omc' && (
|
||||
<Form.Item name='db_arn' label='Arn' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item name='comment' label='Remark' className='mb-6'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item className='flex flex-row-reverse pt-1 mb-0'>
|
||||
<Button htmlType='submit' type='primary' size='middle' className='mr-1' loading={loading}>
|
||||
Save
|
||||
</Button>
|
||||
<Button size='middle' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<DatabaseForm
|
||||
onCancel={onClose}
|
||||
onSuccess={onSuccess}
|
||||
dbTypeList={dbTypeList}
|
||||
editValue={editValue}
|
||||
choiceDBType={choiceDBType}
|
||||
getFromRenderData={getFromRenderData}
|
||||
dbNames={dbNames}
|
||||
description={description}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { apiInterceptors, createModel, getSupportModels } from '@/client/api';
|
||||
import { renderModelIcon } from '@/components/chat/header/model-selector';
|
||||
import { StartModelParams, SupportModel, SupportModelParams } from '@/types/model';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { StartModelParams, SupportModel } from '@/types/model';
|
||||
import { AutoComplete, Button, Form, Select, Tooltip, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import ModelParams from './model-params';
|
||||
import ConfigurableForm from '../common/configurable-form';
|
||||
|
||||
const { Option } = Select;
|
||||
const FormItem = Form.Item;
|
||||
@@ -18,7 +19,7 @@ function ModelForm({ onCancel, onSuccess }: { onCancel: () => void; onSuccess: (
|
||||
const [_, setModels] = useState<Array<SupportModel> | null>([]);
|
||||
const [selectedWorkerType, setSelectedWorkerType] = useState<string>();
|
||||
const [selectedProvider, setSelectedProvider] = useState<string>();
|
||||
const [params, setParams] = useState<Array<SupportModelParams> | null>(null);
|
||||
const [params, setParams] = useState<Array<ConfigurableParams> | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -215,7 +216,7 @@ function ModelForm({ onCancel, onSuccess }: { onCancel: () => void; onSuccess: (
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<ModelParams params={params.filter(p => p.param_name !== 'name')} form={form} />
|
||||
<ConfigurableForm params={params.filter(p => p.param_name !== 'name')} form={form} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -82,6 +82,15 @@ export const CommonEn = {
|
||||
concurrency_limit: 'concurrency_limit',
|
||||
The_maximum_number_of_tokens: 'The maximum number of tokens or words allowed in a prompt',
|
||||
Theme: 'Theme',
|
||||
database_type: 'Database Type',
|
||||
edit_database: 'Edit Datasource',
|
||||
add_database: 'Add Datasource',
|
||||
create_success: 'Create success',
|
||||
create_failed: 'Create failed',
|
||||
please_select_database_type: 'Please select database type',
|
||||
select_database_type: 'Select database type',
|
||||
description: 'Description',
|
||||
input_description: 'Please input description',
|
||||
Port: 'Port',
|
||||
Username: 'Username',
|
||||
Password: 'Password',
|
||||
|
||||
@@ -88,6 +88,17 @@ export const CommonZh: Resources['translation'] = {
|
||||
concurrency_limit: '并发限制',
|
||||
The_maximum_number_of_tokens: '提示中允许的最大标记或单词数',
|
||||
Theme: '主题',
|
||||
database_type: '数据库类型',
|
||||
edit_database: '编辑数据源',
|
||||
add_database: '添加数据源',
|
||||
update_success: '更新成功',
|
||||
update_failed: '更新失败',
|
||||
create_success: '创建成功',
|
||||
create_failed: '创建失败',
|
||||
please_select_database_type: '请选择数据库类型',
|
||||
select_database_type: '选择数据库类型',
|
||||
description: '描述',
|
||||
input_description: '请输入描述',
|
||||
Port: '端口',
|
||||
Username: '用户名',
|
||||
Password: '密码',
|
||||
@@ -287,8 +298,6 @@ export const CommonZh: Resources['translation'] = {
|
||||
please_input_recommended_questions: '请输入推荐问题',
|
||||
is_effective: '是否生效',
|
||||
add_question: '添加问题',
|
||||
update_success: '更新成功',
|
||||
update_failed: '更新失败',
|
||||
please_select_prompt: '请选择一个提示词',
|
||||
details: '详情',
|
||||
choose: '选择',
|
||||
|
||||
@@ -16,7 +16,7 @@ type DBItem = DbListResponse[0];
|
||||
export function isFileDb(dbTypeList: DBOption[], dbType: DBType) {
|
||||
return dbTypeList.find(item => item.value === dbType)?.isFileDb;
|
||||
}
|
||||
|
||||
let getFromRenderData: any = [];
|
||||
function Database() {
|
||||
// const { setCurrentDialogInfo } = useContext(ChatContext); // unused
|
||||
// const router = useRouter(); // unused
|
||||
@@ -27,8 +27,10 @@ function Database() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modal, setModal] = useState<{
|
||||
open: boolean;
|
||||
info?: DBItem;
|
||||
info?: string;
|
||||
dbType?: DBType;
|
||||
dbTypeData?: any[];
|
||||
description?: string;
|
||||
}>({ open: false });
|
||||
const [draw, setDraw] = useState<{
|
||||
open: boolean;
|
||||
@@ -40,7 +42,7 @@ function Database() {
|
||||
|
||||
const getDbSupportList = async () => {
|
||||
const [, data] = await apiInterceptors(getDbSupportType());
|
||||
setDbSupportList(data ?? []);
|
||||
setDbSupportList(data?.types ?? []);
|
||||
};
|
||||
|
||||
const refreshDbList = async () => {
|
||||
@@ -52,9 +54,8 @@ function Database() {
|
||||
|
||||
const dbTypeList = useMemo(() => {
|
||||
const supportDbList = dbSupportList.map(item => {
|
||||
const { db_type, is_file_db } = item;
|
||||
|
||||
return { ...dbMapper[db_type], value: db_type, isFileDb: is_file_db };
|
||||
const db_type = item?.name;
|
||||
return { ...dbMapper[db_type], value: db_type, isFileDb: true, parameters: item.parameters };
|
||||
}) as DBOption[];
|
||||
const unSupportDbList = Object.keys(dbMapper)
|
||||
.filter(item => !supportDbList.some(db => db.value === item))
|
||||
@@ -67,24 +68,30 @@ function Database() {
|
||||
}, [dbSupportList]);
|
||||
|
||||
const onModify = (item: DBItem) => {
|
||||
setModal({ open: true, info: item });
|
||||
for (let index = 0; index < getFromRenderData.length; index++) {
|
||||
const element = getFromRenderData[index];
|
||||
if (item.params[element.param_name]) {
|
||||
element.default_value = item.params[element.param_name];
|
||||
}
|
||||
}
|
||||
setModal({ open: true, info: item.id, dbType: item.type, description: item.description });
|
||||
};
|
||||
|
||||
const onDelete = (item: DBItem) => {
|
||||
Modal.confirm({
|
||||
title: 'Tips',
|
||||
content: `Do you Want to delete the ${item.db_name}?`,
|
||||
content: `Do you Want to delete the database connection?`,
|
||||
onOk() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
handleDelete(item.db_name, resolve, reject);
|
||||
handleDelete(item.id, resolve, reject);
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (dbName: string, resolve: () => void, reject: () => void) => {
|
||||
const handleDelete = async (id: string, resolve: () => void, reject: () => void) => {
|
||||
try {
|
||||
const [err] = await apiInterceptors(postDbDelete(dbName));
|
||||
const [err] = await apiInterceptors(postDbDelete(id));
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
reject();
|
||||
@@ -101,7 +108,7 @@ function Database() {
|
||||
const dbListByType = useMemo(() => {
|
||||
const mapper = dbTypeList.reduce(
|
||||
(acc, item) => {
|
||||
acc[item.value] = dbList.filter(dbConn => dbConn.db_type === item.value);
|
||||
acc[item.value] = dbList.filter(dbConn => dbConn?.type.toLowerCase() === item.value.toLowerCase());
|
||||
return acc;
|
||||
},
|
||||
{} as Record<DBType, DbListResponse>,
|
||||
@@ -115,7 +122,9 @@ function Database() {
|
||||
}, []);
|
||||
|
||||
const handleDbTypeClick = (info: DBOption) => {
|
||||
const dbItems = dbList.filter(item => item.db_type === info.value);
|
||||
const dbItems = dbList.filter(item => item.type === info.value);
|
||||
getFromRenderData = info?.parameters;
|
||||
|
||||
setDraw({
|
||||
open: true,
|
||||
dbList: dbItems,
|
||||
@@ -126,58 +135,34 @@ function Database() {
|
||||
|
||||
const onRefresh = async (item: DBItem) => {
|
||||
setRefreshLoading(true);
|
||||
const [, res] = await apiInterceptors(postDbRefresh({ db_name: item.db_name, db_type: item.db_type }));
|
||||
const [, res] = await apiInterceptors(postDbRefresh({ id: item.id }));
|
||||
if (res) message.success(t('refreshSuccess'));
|
||||
setRefreshLoading(false);
|
||||
};
|
||||
|
||||
// TODO: unused function call
|
||||
// const handleChat = async (item: IChatDbSchema) => {
|
||||
// const [, data] = await apiInterceptors(
|
||||
// newDialogue({
|
||||
// chat_mode: 'chat_with_db_execute',
|
||||
// }),
|
||||
// );
|
||||
// // 知识库对话都默认私有知识库应用下
|
||||
// if (data?.conv_uid) {
|
||||
// setCurrentDialogInfo?.({
|
||||
// chat_scene: data.chat_mode,
|
||||
// app_code: data.chat_mode,
|
||||
// });
|
||||
// localStorage.setItem(
|
||||
// 'cur_dialog_info',
|
||||
// JSON.stringify({
|
||||
// chat_scene: data.chat_mode,
|
||||
// app_code: data.chat_mode,
|
||||
// }),
|
||||
// );
|
||||
// router.push(`/chat?scene=chat_with_db_execute&id=${data?.conv_uid}&db_name=${item.db_name}`);
|
||||
// }
|
||||
// };
|
||||
const getFileName = (path: string) => {
|
||||
if (!path) return '';
|
||||
// Handle Windows and Unix style paths
|
||||
const parts = path.split(/[/\\]/);
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
return (
|
||||
<ConstructLayout>
|
||||
<div className='relative min-h-full overflow-y-auto px-6 max-h-[90vh]'>
|
||||
<MuiLoading visible={loading} />
|
||||
<div className='flex justify-between items-center mb-6'>
|
||||
<div className='flex items-center gap-4'>
|
||||
{/* <Input
|
||||
variant="filled"
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder={t('please_enter_the_keywords')}
|
||||
// onChange={onSearch}
|
||||
// onPressEnter={onSearch}
|
||||
allowClear
|
||||
className="w-[230px] h-[40px] border-1 border-white backdrop-filter backdrop-blur-lg bg-white bg-opacity-30 dark:border-[#6f7f95] dark:bg-[#6f7f95] dark:bg-opacity-60"
|
||||
/> */}
|
||||
</div>
|
||||
<div className='flex items-center gap-4'></div>
|
||||
|
||||
<div className='flex items-center gap-4'>
|
||||
<Button
|
||||
className='border-none text-white bg-button-gradient'
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setModal({ open: true });
|
||||
console.log(dbList);
|
||||
console.log(dbTypeList);
|
||||
|
||||
setModal({ open: true, dbTypeData: dbTypeList });
|
||||
}}
|
||||
>
|
||||
{t('Add_Datasource')}
|
||||
@@ -201,61 +186,18 @@ function Database() {
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
// <BlurredCard
|
||||
// description={item.db_path ?? ''}
|
||||
// name={item.db_name}
|
||||
// key={item.db_name}
|
||||
// logo={targetDBType?.icon}
|
||||
// RightTop={
|
||||
// <InnerDropdown
|
||||
// menu={{
|
||||
// items: [
|
||||
// {
|
||||
// key: 'del',
|
||||
// label: (
|
||||
// <span
|
||||
// className="text-red-400"
|
||||
// onClick={() => {
|
||||
// onDelete(item);
|
||||
// }}
|
||||
// >
|
||||
// {t('Delete_Btn')}
|
||||
// </span>
|
||||
// ),
|
||||
// },
|
||||
// ],
|
||||
// }}
|
||||
// />
|
||||
// }
|
||||
// rightTopHover={false}
|
||||
// Tags={
|
||||
// <div>
|
||||
// <Tag>{item.db_type}</Tag>
|
||||
// </div>
|
||||
// }
|
||||
// RightBottom={
|
||||
// <ChatButton
|
||||
// text={t('start_chat')}
|
||||
// onClick={() => {
|
||||
// handleChat(item);
|
||||
// }}
|
||||
// />
|
||||
// }
|
||||
// onClick={() => {
|
||||
// // if (targetDBType?.disabled) return;
|
||||
// // handleDbTypeClick(targetDBType);
|
||||
// onModify(item);
|
||||
// }}
|
||||
// />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<FormDialog
|
||||
open={modal.open}
|
||||
dbTypeList={dbTypeList}
|
||||
getFromRenderData={getFromRenderData}
|
||||
description={modal.description}
|
||||
choiceDBType={modal.dbType}
|
||||
editValue={modal.info}
|
||||
dbNames={dbList.map(item => item.db_name)}
|
||||
dbTypeData={modal.dbTypeData}
|
||||
dbNames={dbList.map(item => item.params.database)}
|
||||
onSuccess={() => {
|
||||
setModal({ open: false });
|
||||
refreshDbList();
|
||||
@@ -286,8 +228,8 @@ function Database() {
|
||||
</Button>
|
||||
{dbListByType[draw.type].map(item => (
|
||||
<Card
|
||||
key={item.db_name}
|
||||
title={item.db_name}
|
||||
key={item.params?.database || item.params?.path || ''}
|
||||
title={item.params?.database || getFileName(item.params?.path) || ''}
|
||||
extra={
|
||||
<>
|
||||
<RedoOutlined
|
||||
@@ -314,16 +256,19 @@ function Database() {
|
||||
}
|
||||
className='mb-4'
|
||||
>
|
||||
{item.db_path ? (
|
||||
<p>path: {item.db_path}</p>
|
||||
) : (
|
||||
<>
|
||||
<p>host: {item.db_host}</p>
|
||||
<p>username: {item.db_user}</p>
|
||||
<p>port: {item.db_port}</p>
|
||||
</>
|
||||
)}
|
||||
<p>remark: {item.comment}</p>
|
||||
<>
|
||||
{['host', 'port', 'path', 'user', 'database', 'schema']
|
||||
// Just handle these keys
|
||||
.filter(key => Object.prototype.hasOwnProperty.call(item.params, key))
|
||||
.map(key => (
|
||||
<p key={key}>
|
||||
{key}: {key === 'path' ? getFileName(item.params[key]) : item.params[key]}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
<p>
|
||||
{t('description')}: {item.description}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</Spin>
|
||||
|
||||
23
web/types/common.ts
Normal file
23
web/types/common.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
interface ExtMetadata {
|
||||
tags: string;
|
||||
order: number;
|
||||
[key: string]: string | number | boolean;
|
||||
}
|
||||
|
||||
type NestedField = {
|
||||
[key: string]: ConfigurableParams;
|
||||
};
|
||||
|
||||
export type ConfigurableParams = {
|
||||
param_class: string;
|
||||
param_name: string;
|
||||
param_type: string;
|
||||
default_value: string | boolean | number;
|
||||
description: string;
|
||||
required: boolean;
|
||||
valid_values: null | string[];
|
||||
ext_metadata: ExtMetadata;
|
||||
is_array: boolean;
|
||||
label: string;
|
||||
nested_fields: NestedField | null;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
|
||||
export type DBOption = {
|
||||
label: string;
|
||||
value: DBType;
|
||||
@@ -5,6 +7,7 @@ export type DBOption = {
|
||||
isFileDb?: boolean;
|
||||
icon: string;
|
||||
desc?: string;
|
||||
parameters?: ConfigurableParams[];
|
||||
};
|
||||
|
||||
export type DBType =
|
||||
@@ -27,6 +30,13 @@ export type DBType =
|
||||
| (string & {});
|
||||
|
||||
export type IChatDbSchema = {
|
||||
type: string;
|
||||
id: string;
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
params: any[];
|
||||
parameters: any[];
|
||||
comment: string;
|
||||
db_host: string;
|
||||
db_name: string;
|
||||
@@ -38,10 +48,14 @@ export type IChatDbSchema = {
|
||||
};
|
||||
|
||||
export type DbListResponse = IChatDbSchema[];
|
||||
|
||||
export type IChatDbSupportTypeSchema = {
|
||||
db_type: DBType;
|
||||
is_file_db: boolean;
|
||||
name: string;
|
||||
params: ConfigurableParams;
|
||||
types: any[];
|
||||
label: string;
|
||||
description: string;
|
||||
parameters: any[];
|
||||
};
|
||||
|
||||
export type DbSupportTypeResponse = IChatDbSupportTypeSchema[];
|
||||
@@ -58,19 +72,6 @@ export type ChatFeedBackSchema = {
|
||||
messages: string;
|
||||
};
|
||||
|
||||
export type PromptProps = {
|
||||
id: number;
|
||||
chat_scene: string;
|
||||
sub_chat_scene: string;
|
||||
prompt_type: string;
|
||||
content: string;
|
||||
user_name: string;
|
||||
prompt_name: string;
|
||||
gmt_created: string;
|
||||
gmt_modified: string;
|
||||
};
|
||||
|
||||
export type PostDbRefreshParams = {
|
||||
db_name: string;
|
||||
db_type: DBType;
|
||||
id: number | string;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
|
||||
export type IModelData = {
|
||||
chat_scene: string;
|
||||
model_name: string;
|
||||
@@ -23,15 +25,6 @@ export type BaseModelParams = {
|
||||
params: any;
|
||||
};
|
||||
|
||||
// export type ModelParams = {
|
||||
// model_name: string;
|
||||
// model_path: string;
|
||||
// proxy_api_key: string;
|
||||
// proxy_server_url: string;
|
||||
// model_type: string;
|
||||
// max_context_size: number;
|
||||
// };
|
||||
//
|
||||
export type ModelParams = {
|
||||
[key: string]: string | number | boolean;
|
||||
};
|
||||
@@ -43,31 +36,6 @@ export type StartModelParams = {
|
||||
worker_type: string;
|
||||
params: ModelParams;
|
||||
};
|
||||
|
||||
interface ExtMetadata {
|
||||
tags: string;
|
||||
order: number;
|
||||
[key: string]: string | number | boolean;
|
||||
}
|
||||
|
||||
type NestedField = {
|
||||
[key: string]: SupportModelParams;
|
||||
};
|
||||
|
||||
export type SupportModelParams = {
|
||||
param_class: string;
|
||||
param_name: string;
|
||||
param_type: string;
|
||||
default_value: string | boolean | number;
|
||||
description: string;
|
||||
required: boolean;
|
||||
valid_values: null | string[];
|
||||
ext_metadata: ExtMetadata;
|
||||
is_array: boolean;
|
||||
label: string;
|
||||
nested_fields: NestedField | null;
|
||||
};
|
||||
|
||||
export type SupportModel = {
|
||||
model: string;
|
||||
path: string;
|
||||
@@ -77,7 +45,7 @@ export type SupportModel = {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
params: SupportModelParams;
|
||||
params: ConfigurableParams;
|
||||
provider: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user