import React, { useState } from "react";
|
import {
|
List,
|
SearchInput,
|
TopToolbar,
|
SelectColumnsButton,
|
FilterButton,
|
TextInput,
|
DateInput,
|
SelectInput,
|
useListContext,
|
Pagination,
|
EditButton,
|
DeleteButton,
|
} from 'react-admin';
|
import { Box, Chip, Grid, Stack, Typography } from '@mui/material';
|
import EmptyData from "@/page/components/EmptyData";
|
import MyCreateButton from "@/page/components/MyCreateButton";
|
import MyExportButton from '@/page/components/MyExportButton';
|
import { OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
|
import AiParamCreate from "./AiParamCreate";
|
import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
|
|
const filters = [
|
<SearchInput source="condition" alwaysOn />,
|
<DateInput label='common.time.after' source="timeStart" alwaysOn />,
|
<DateInput label='common.time.before' source="timeEnd" alwaysOn />,
|
<TextInput source="name" label="table.field.aiParam.name" />,
|
<TextInput source="modelCode" label="table.field.aiParam.modelCode" />,
|
<TextInput source="provider" label="table.field.aiParam.provider" />,
|
<TextInput source="modelName" label="table.field.aiParam.modelName" />,
|
<SelectInput
|
source="defaultFlag"
|
label="table.field.aiParam.defaultFlag"
|
choices={[
|
{ id: '1', name: 'common.enums.true' },
|
{ id: '0', name: 'common.enums.false' },
|
]}
|
/>,
|
<TextInput label="common.field.memo" source="memo" />,
|
<SelectInput
|
label="common.field.status"
|
source="status"
|
choices={[
|
{ id: '1', name: 'common.enums.statusTrue' },
|
{ id: '0', name: 'common.enums.statusFalse' },
|
]}
|
/>,
|
];
|
|
const providerLabel = (provider) => {
|
if (provider === 'openai') {
|
return 'OpenAI Compatible';
|
}
|
if (provider === 'mock') {
|
return 'Mock';
|
}
|
return provider || '未配置';
|
};
|
|
const AiParamBoard = () => {
|
const { data, isLoading } = useListContext();
|
const records = data || [];
|
const enabledCount = records.filter((item) => item.status === 1).length;
|
const defaultCount = records.filter((item) => item.defaultFlag === 1).length;
|
const openaiCount = records.filter((item) => item.provider === 'openai').length;
|
const mockCount = records.filter((item) => item.provider === 'mock').length;
|
|
if (!isLoading && !records.length) {
|
return <EmptyData />;
|
}
|
|
return (
|
<AiConsoleLayout
|
title="AI参数"
|
subtitle="保持当前系统后台的白底卡片和轻边框风格,用卡片方式展示模型配置概况,方便和其他 AI 页面统一查看。"
|
stats={[
|
{ label: '参数总数', value: records.length },
|
{ label: '启用', value: enabledCount },
|
{ label: '默认模型', value: defaultCount },
|
{ label: 'OpenAI / Mock', value: `${openaiCount} / ${mockCount}` },
|
]}
|
>
|
<AiConsolePanel
|
title="模型配置"
|
subtitle="展示模型编码、供应商、上下文轮数和默认状态;创建、编辑、删除仍沿用原系统的弹窗与编辑页。"
|
minHeight={460}
|
>
|
<Box
|
sx={{
|
display: 'grid',
|
gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
|
gap: 2,
|
}}
|
>
|
{records.map((record) => (
|
<Box key={record.id}>
|
<Box sx={aiCardSx(record.defaultFlag === 1)}>
|
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
|
<Box>
|
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
|
{record.name || record.modelCode || record.uuid}
|
</Typography>
|
<Typography variant="caption" color="text.secondary">
|
{record.modelCode || '未填写模型编码'}
|
</Typography>
|
</Box>
|
<Stack direction="row" spacing={0.75} flexWrap="wrap" justifyContent="flex-end">
|
<Chip size="small" color={record.status === 1 ? 'success' : 'default'} label={record.status === 1 ? '启用' : '停用'} />
|
{record.defaultFlag === 1 ? <Chip size="small" color="primary" label="默认" /> : null}
|
</Stack>
|
</Stack>
|
<Grid container spacing={1.25} sx={{ mt: 1 }}>
|
<Grid item xs={6}>
|
<Typography variant="caption" color="text.secondary">供应商</Typography>
|
<Typography variant="body2" sx={{ mt: 0.25 }}>{providerLabel(record.provider)}</Typography>
|
</Grid>
|
<Grid item xs={6}>
|
<Typography variant="caption" color="text.secondary">模型名</Typography>
|
<Typography variant="body2" sx={{ mt: 0.25 }}>{record.modelName || '-'}</Typography>
|
</Grid>
|
<Grid item xs={6}>
|
<Typography variant="caption" color="text.secondary">上下文轮数</Typography>
|
<Typography variant="body2" sx={{ mt: 0.25 }}>{record.maxContextMessages || 0}</Typography>
|
</Grid>
|
<Grid item xs={6}>
|
<Typography variant="caption" color="text.secondary">排序</Typography>
|
<Typography variant="body2" sx={{ mt: 0.25 }}>{record.sort || 0}</Typography>
|
</Grid>
|
<Grid item xs={12}>
|
<Typography variant="caption" color="text.secondary">聊天地址</Typography>
|
<Typography variant="body2" sx={{ mt: 0.25, wordBreak: 'break-all' }}>
|
{record.chatUrl || '未配置'}
|
</Typography>
|
</Grid>
|
<Grid item xs={12}>
|
<Typography variant="caption" color="text.secondary">备注</Typography>
|
<Typography variant="body2" sx={{ mt: 0.25, minHeight: 42 }}>
|
{record.memo || '未填写备注'}
|
</Typography>
|
</Grid>
|
</Grid>
|
<Stack direction="row" spacing={1} sx={{ mt: 1.5 }}>
|
<EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
|
<DeleteButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} mutationMode={OPERATE_MODE} />
|
</Stack>
|
</Box>
|
</Box>
|
))}
|
</Box>
|
<Box sx={{ mt: 2 }}>
|
<Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
|
</Box>
|
</AiConsolePanel>
|
</AiConsoleLayout>
|
);
|
};
|
|
const AiParamList = () => {
|
const [createDialog, setCreateDialog] = useState(false);
|
|
return (
|
<Box display="flex" sx={{ width: '100%' }}>
|
<List
|
sx={{ width: '100%', flexGrow: 1 }}
|
title={"menu.aiParam"}
|
empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
|
filters={filters}
|
sort={{ field: "sort", order: "asc" }}
|
actions={(
|
<TopToolbar>
|
<FilterButton />
|
<MyCreateButton onClick={() => { setCreateDialog(true) }} />
|
<SelectColumnsButton preferenceKey='aiParam' />
|
<MyExportButton />
|
</TopToolbar>
|
)}
|
perPage={DEFAULT_PAGE_SIZE}
|
pagination={false}
|
>
|
<AiParamBoard />
|
</List>
|
<AiParamCreate
|
open={createDialog}
|
setOpen={setCreateDialog}
|
/>
|
</Box>
|
)
|
}
|
|
export default AiParamList;
|