mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-09 12:59:43 +00:00
refactor: Add frontend code to DB-GPT (#912)
This commit is contained in:
85
web/components/model/model-card.tsx
Normal file
85
web/components/model/model-card.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IModelData } from '@/types/model';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAlt';
|
||||
import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
|
||||
import StopCircleIcon from '@mui/icons-material/StopCircle';
|
||||
import { Tooltip, message } from 'antd';
|
||||
import moment from 'moment';
|
||||
import { apiInterceptors, stopModel } from '@/client/api';
|
||||
import { renderModelIcon } from '../chat/header/model-selector';
|
||||
|
||||
interface Props {
|
||||
info: IModelData;
|
||||
}
|
||||
|
||||
function ModelCard({ info }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
async function stopTheModel(info: IModelData) {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const [, res] = await apiInterceptors(
|
||||
stopModel({
|
||||
host: info.host,
|
||||
port: info.port,
|
||||
model: info.model_name,
|
||||
worker_type: info.model_type,
|
||||
params: {},
|
||||
}),
|
||||
);
|
||||
setLoading(false);
|
||||
if (res === true) {
|
||||
message.success(t('stop_model_success'));
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="relative flex flex-col p-1 md:p-2 sm:w-1/2 lg:w-1/3">
|
||||
<div className="relative flex items-center p-4 min-w-min rounded-lg justify-between bg-white border-gray-200 border hover:shadow-md dark:border-gray-600 dark:bg-black dark:hover:border-white transition-all text-black dark:text-white">
|
||||
<div className="flex flex-col">
|
||||
{info.healthy && (
|
||||
<Tooltip title="Healthy">
|
||||
<SentimentSatisfiedAltIcon className="absolute top-4 right-4 !text-3xl !text-green-600" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{!info.healthy && (
|
||||
<Tooltip title="Unhealthy">
|
||||
<SentimentVeryDissatisfiedIcon className="absolute top-4 right-4 !text-3xl !text-red-600" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="Stop Model">
|
||||
<StopCircleIcon
|
||||
className="absolute right-4 bottom-4 !text-3xl !text-orange-600 cursor-pointer"
|
||||
onClick={() => {
|
||||
stopTheModel(info);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className="flex items-center">
|
||||
{renderModelIcon(info.model_name, { width: 32, height: 32 })}
|
||||
<div className="inline-block ml-2">
|
||||
<h3 className="text-lg font-semibold">{info.model_name}</h3>
|
||||
<h3 className="text-sm opacity-60">{info.model_type}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm mt-2">
|
||||
<p className="font-semibold">Host:</p>
|
||||
<p className="opacity-60">{info.host}</p>
|
||||
<p className="font-semibold mt-2">Manage host:</p>
|
||||
<p className="opacity-60">
|
||||
<span>{info.manager_host}:</span>
|
||||
<span>{info.manager_port}</span>
|
||||
</p>
|
||||
<p className="font-semibold mt-2">Last heart beat:</p>
|
||||
<p className="opacity-60">{moment(info.last_heartbeat).format('YYYY-MM-DD HH:MM:SS')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelCard;
|
100
web/components/model/model-form.tsx
Normal file
100
web/components/model/model-form.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { apiInterceptors, getSupportModels, startModel } from '@/client/api';
|
||||
import { SupportModel, SupportModelParams } from '@/types/model';
|
||||
import { Button, Form, Select, Tooltip, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { renderModelIcon } from '@/components/chat/header/model-selector';
|
||||
import ModelParams from './model-params';
|
||||
const { Option } = Select;
|
||||
|
||||
function ModelForm({ onCancel, onSuccess }: { onCancel: () => void; onSuccess: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const [models, setModels] = useState<Array<SupportModel> | null>([]);
|
||||
const [selectedModel, setSelectedModel] = useState<SupportModel>();
|
||||
const [params, setParams] = useState<Array<SupportModelParams> | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
async function getModels() {
|
||||
const [, res] = await apiInterceptors(getSupportModels());
|
||||
if (res && res.length) {
|
||||
setModels(
|
||||
res.sort((a: SupportModel, b: SupportModel) => {
|
||||
if (a.enabled && !b.enabled) {
|
||||
return -1;
|
||||
} else if (!a.enabled && b.enabled) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.model.localeCompare(b.model);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
setModels(res);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getModels();
|
||||
}, []);
|
||||
|
||||
function handleChange(value: string, option: any) {
|
||||
setSelectedModel(option.model);
|
||||
setParams(option.model.params);
|
||||
}
|
||||
|
||||
async function onFinish(values: any) {
|
||||
if (!selectedModel) {
|
||||
return;
|
||||
}
|
||||
delete values.model;
|
||||
setLoading(true);
|
||||
const [, , data] = await apiInterceptors(
|
||||
startModel({
|
||||
host: selectedModel.host,
|
||||
port: selectedModel.port,
|
||||
model: selectedModel.model,
|
||||
worker_type: selectedModel?.worker_type,
|
||||
params: values,
|
||||
}),
|
||||
);
|
||||
setLoading(false);
|
||||
if (data?.success === true) {
|
||||
onSuccess && onSuccess();
|
||||
return message.success(t('start_model_success'));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} onFinish={onFinish} form={form}>
|
||||
<Form.Item label="Model" name="model" rules={[{ required: true, message: t('model_select_tips') }]}>
|
||||
<Select showSearch onChange={handleChange}>
|
||||
{models?.map((model) => (
|
||||
<Option key={model.model} value={model.model} label={model.model} model={model} disabled={!model.enabled}>
|
||||
{renderModelIcon(model.model)}
|
||||
<Tooltip title={model.enabled ? model.model : t('download_model_tip')}>
|
||||
<span className="ml-2">{model.model}</span>
|
||||
</Tooltip>
|
||||
<Tooltip title={model.enabled ? `${model.host}:${model.port}` : t('download_model_tip')}>
|
||||
<p className="inline-block absolute right-4">
|
||||
<span>{model.host}:</span>
|
||||
<span>{model.port}</span>
|
||||
</p>
|
||||
</Tooltip>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<ModelParams params={params} form={form} />
|
||||
<div className="flex justify-center">
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t('submit')}
|
||||
</Button>
|
||||
<Button className="ml-10" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelForm;
|
54
web/components/model/model-params.tsx
Normal file
54
web/components/model/model-params.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { SupportModelParams } from '@/types/model';
|
||||
import { Checkbox, Form, FormInstance, Input, InputNumber } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface ParamValues {
|
||||
[key: string]: string | number | boolean;
|
||||
}
|
||||
|
||||
function ModelParams({ params, form }: { params: Array<SupportModelParams> | null; form: FormInstance<any> }) {
|
||||
useEffect(() => {
|
||||
if (params) {
|
||||
const initialValues: ParamValues = {};
|
||||
params.forEach((param) => {
|
||||
initialValues[param.param_name] = param.default_value;
|
||||
});
|
||||
form.setFieldsValue(initialValues); // 设置表单字段的初始值
|
||||
}
|
||||
}, [params, form]);
|
||||
if (!params || params?.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderItem(param: SupportModelParams) {
|
||||
switch (param.param_type) {
|
||||
case 'str':
|
||||
return <Input />;
|
||||
case 'int':
|
||||
return <InputNumber />;
|
||||
case 'bool':
|
||||
return <Checkbox />;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{params?.map((param: SupportModelParams) => (
|
||||
<Form.Item
|
||||
key={param.param_name}
|
||||
label={
|
||||
<p className="whitespace-normal overflow-wrap-break-word">{param.description?.length > 20 ? param.param_name : param.description}</p>
|
||||
}
|
||||
name={param.param_name}
|
||||
initialValue={param.default_value}
|
||||
valuePropName={param.param_type === 'bool' ? 'checked' : 'value'}
|
||||
tooltip={param.description}
|
||||
rules={[{ required: param.required, message: `Please input ${param.description}` }]}
|
||||
>
|
||||
{renderItem(param)}
|
||||
</Form.Item>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelParams;
|
Reference in New Issue
Block a user