zhou zhou
4 天以前 40905cbd04c2e332cd4bc2b9e0c5b3e1da9cccfa
rsf-admin/src/page/system/aiParam/AiParamList.jsx
@@ -10,6 +10,7 @@
    useListContext,
    useNotify,
    useRefresh,
    useTranslate,
} from "react-admin";
import {
    Box,
@@ -28,20 +29,23 @@
import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
import CheckCircleOutlineRoundedIcon from "@mui/icons-material/CheckCircleOutlineRounded";
import MyExportButton from "@/page/components/MyExportButton";
import AiParamForm from "./AiParamForm";
import AiConfigDialog from "../aiShared/AiConfigDialog";
import AiRuntimeSummary from "../aiShared/AiRuntimeSummary";
import { setAiParamDefault } from "@/api/ai/configCenter";
const filters = [
    <SearchInput source="condition" alwaysOn />,
    <TextInput source="providerType" label="提供方类型" />,
    <TextInput source="model" label="模型" />,
    <TextInput source="providerType" label="ai.param.fields.providerType" />,
    <TextInput source="model" label="ai.param.fields.model" />,
    <SelectInput
        source="status"
        label="状态"
        label="ai.param.fields.defaultStatus"
        choices={[
            { id: "1", name: "common.enums.statusTrue" },
            { id: "0", name: "common.enums.statusFalse" },
            { id: "1", name: "ai.param.status.default" },
            { id: "0", name: "ai.param.status.nonDefault" },
        ]}
    />,
];
@@ -62,7 +66,8 @@
    return value.length > max ? `${value.slice(0, max)}...` : value;
};
const AiParamCards = ({ onView, onEdit, onDelete, deleting }) => {
const AiParamCards = ({ onView, onEdit, onDelete, onSetDefault, updatingDefaultId, deleting }) => {
    const translate = useTranslate();
    const { data, isLoading } = useListContext();
    const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
@@ -78,9 +83,9 @@
        return (
            <Box px={2} py={6}>
                <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
                    <Typography variant="subtitle1">暂无 AI 参数配置</Typography>
                    <Typography variant="subtitle1">{translate("ai.param.list.emptyTitle")}</Typography>
                    <Typography variant="body2" color="text.secondary" mt={1}>
                        可以先新建一个 OpenAI 兼容模型参数卡片。
                        {translate("ai.param.list.emptyDescription")}
                    </Typography>
                </Card>
            </Box>
@@ -113,7 +118,7 @@
                                    <Chip
                                        size="small"
                                        color={record.statusBool ? "success" : "default"}
                                        label={record.statusBool ? "启用" : "停用"}
                                        label={translate(record.statusBool ? "ai.param.status.default" : "ai.param.status.nonDefault")}
                                    />
                                </Stack>
                                <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
@@ -122,46 +127,53 @@
                                        size="small"
                                        variant="outlined"
                                        color={record.streamingEnabled ? "info" : "default"}
                                        label={record.streamingEnabled ? "流式响应" : "非流式"}
                                        label={translate(record.streamingEnabled ? "ai.param.list.streaming" : "ai.param.list.nonStreaming")}
                                    />
                                </Stack>
                                <Divider sx={{ my: 1.5 }} />
                                <Typography variant="body2" color="text.secondary">
                                    Base URL
                                    {translate("ai.param.fields.baseUrl")}
                                </Typography>
                                <Typography variant="body2" sx={{ mb: 1.5, wordBreak: "break-all" }}>
                                    {truncateText(record.baseUrl, 120)}
                                </Typography>
                                <Grid container spacing={1}>
                                    <Grid item xs={6}>
                                        <Typography variant="caption" color="text.secondary">Temperature</Typography>
                                        <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.temperature")}</Typography>
                                        <Typography variant="body2">{record.temperature ?? "--"}</Typography>
                                    </Grid>
                                    <Grid item xs={6}>
                                        <Typography variant="caption" color="text.secondary">Top P</Typography>
                                        <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.topP")}</Typography>
                                        <Typography variant="body2">{record.topP ?? "--"}</Typography>
                                    </Grid>
                                    <Grid item xs={6}>
                                        <Typography variant="caption" color="text.secondary">Max Tokens</Typography>
                                        <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.maxTokens")}</Typography>
                                        <Typography variant="body2">{record.maxTokens ?? "--"}</Typography>
                                    </Grid>
                                    <Grid item xs={6}>
                                        <Typography variant="caption" color="text.secondary">Timeout</Typography>
                                        <Typography variant="caption" color="text.secondary">{translate("ai.param.fields.timeoutMs")}</Typography>
                                        <Typography variant="body2">{record.timeoutMs ?? "--"} ms</Typography>
                                    </Grid>
                                </Grid>
                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
                                    备注
                                </Typography>
                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("common.field.memo")}</Typography>
                                <Typography variant="body2">{truncateText(record.memo)}</Typography>
                            </CardContent>
                            <CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}>
                                <Stack direction="row" spacing={1}>
                                    <Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
                                        详情
                                        {translate("ai.common.detail")}
                                    </Button>
                                    <Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
                                        编辑
                                        {translate("common.button.edit")}
                                    </Button>
                                    <Button
                                        size="small"
                                        color={record.statusBool ? "success" : "primary"}
                                        startIcon={<CheckCircleOutlineRoundedIcon />}
                                        onClick={() => onSetDefault(record)}
                                        disabled={deleting || updatingDefaultId === record.id || record.statusBool}
                                    >
                                        {translate(record.statusBool ? "ai.param.actions.currentDefault" : "ai.param.actions.setDefault")}
                                    </Button>
                                </Stack>
                                <Button
@@ -171,7 +183,7 @@
                                    onClick={() => onDelete(record)}
                                    disabled={deleting}
                                >
                                    删除
                                    {translate("ai.common.delete")}
                                </Button>
                            </CardActions>
                        </Card>
@@ -183,16 +195,18 @@
};
const AiParamList = () => {
    const translate = useTranslate();
    const notify = useNotify();
    const refresh = useRefresh();
    const [deleteOne, { isPending: deleting }] = useDelete();
    const [dialogState, setDialogState] = useState({ open: false, mode: "create", recordId: null });
    const [updatingDefaultId, setUpdatingDefaultId] = useState(null);
    const openDialog = (mode, recordId = null) => setDialogState({ open: true, mode, recordId });
    const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
    const handleDelete = (record) => {
        if (!record?.id || !window.confirm(`确认删除“${record.name}”吗?`)) {
        if (!record?.id || !window.confirm(translate("ai.common.confirmDelete", { name: record.name }))) {
            return;
        }
        deleteOne(
@@ -200,20 +214,36 @@
            { id: record.id },
            {
                onSuccess: () => {
                    notify("删除成功");
                    notify(translate("ai.common.deleteSuccess"));
                    refresh();
                },
                onError: (error) => {
                    notify(error?.message || "删除失败", { type: "error" });
                    notify(error?.message || translate("ai.common.deleteFailed"), { type: "error" });
                },
            }
        );
    };
    const handleSetDefault = async (record) => {
        if (!record?.id || record.statusBool) {
            return;
        }
        setUpdatingDefaultId(record.id);
        try {
            await setAiParamDefault(record.id);
            notify(translate("ai.param.actions.setDefaultSuccess"));
            refresh();
        } catch (error) {
            notify(error?.message || translate("ai.param.actions.setDefaultFailed"), { type: "error" });
        } finally {
            setUpdatingDefaultId(null);
        }
    };
    const dialogTitle = {
        create: "新建 AI 参数",
        edit: "编辑 AI 参数",
        show: "查看 AI 参数详情",
        create: translate("ai.param.dialog.create"),
        edit: translate("ai.param.dialog.edit"),
        show: translate("ai.param.dialog.show"),
    }[dialogState.mode];
    return (
@@ -226,16 +256,19 @@
                    <TopToolbar>
                        <FilterButton />
                        <Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
                            新建
                            {translate("ai.common.new")}
                        </Button>
                        <MyExportButton />
                    </TopToolbar>
                )}
            >
                <AiRuntimeSummary />
                <AiParamCards
                    onView={(id) => openDialog("show", id)}
                    onEdit={(id) => openDialog("edit", id)}
                    onDelete={handleDelete}
                    onSetDefault={handleSetDefault}
                    updatingDefaultId={updatingDefaultId}
                    deleting={deleting}
                />
            </List>