#AI
zhou zhou
2026-03-17 51877df13075ad10ef51107f15bcd21f1661febe
rsf-admin/src/page/system/aiParam/AiParamList.jsx
@@ -1,42 +1,25 @@
import React, { useState } from "react";
import {
    List,
    DatagridConfigurable,
    SearchInput,
    TopToolbar,
    SelectColumnsButton,
    EditButton,
    FilterButton,
    BulkDeleteButton,
    WrapperField,
    TextField,
    NumberField,
    DateField,
    BooleanField,
    TextInput,
    DateInput,
    SelectInput,
    useListContext,
    Pagination,
    EditButton,
    DeleteButton,
} from 'react-admin';
import { Box } from '@mui/material';
import { styled } from '@mui/material/styles';
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";
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
        height: '.9em'
    },
    '& .RaDatagrid-row': {
        cursor: 'auto'
    },
    '& .opt': {
        width: 200
    },
}));
import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
const filters = [
    <SearchInput source="condition" alwaysOn />,
@@ -63,14 +46,123 @@
            { 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">
        <Box display="flex" sx={{ width: '100%' }}>
            <List
                sx={{ width: '100%', flexGrow: 1 }}
                title={"menu.aiParam"}
                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
                filters={filters}
@@ -84,30 +176,9 @@
                    </TopToolbar>
                )}
                perPage={DEFAULT_PAGE_SIZE}
                pagination={false}
            >
                <StyledDatagrid
                    preferenceKey='aiParam'
                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                    rowClick={false}
                    omit={['id', 'createTime', 'memo', 'statusBool', 'defaultFlagBool']}
                >
                    <NumberField source="id" />
                    <TextField source="uuid" label="table.field.aiParam.uuid" />
                    <TextField source="name" label="table.field.aiParam.name" />
                    <TextField source="modelCode" label="table.field.aiParam.modelCode" />
                    <TextField source="provider" label="table.field.aiParam.provider" />
                    <TextField source="modelName" label="table.field.aiParam.modelName" />
                    <NumberField source="maxContextMessages" label="table.field.aiParam.maxContextMessages" />
                    <NumberField source="sort" label="table.field.aiParam.sort" />
                    <BooleanField source="defaultFlagBool" label="table.field.aiParam.defaultFlag" sortable={false} />
                    <BooleanField source="statusBool" label="common.field.status" sortable={false} />
                    <DateField source="updateTime" label="common.field.updateTime" showTime />
                    <TextField source="memo" label="common.field.memo" sortable={false} />
                    <WrapperField cellClassName="opt" label="common.field.opt">
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                    </WrapperField>
                </StyledDatagrid>
                <AiParamBoard />
            </List>
            <AiParamCreate
                open={createDialog}