zhou zhou
2026-03-19 287a666e1b2bb155e86aa88ebace201d1e8a51f6
rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
@@ -9,6 +9,7 @@
    useListContext,
    useNotify,
    useRefresh,
    useTranslate,
} from "react-admin";
import {
    Box,
@@ -39,7 +40,7 @@
    <SearchInput source="condition" alwaysOn />,
    <SelectInput
        source="transportType"
        label="传输类型"
        label="ai.mcp.fields.transportType"
        choices={[
            { id: "SSE_HTTP", name: "SSE_HTTP" },
            { id: "STDIO", name: "STDIO" },
@@ -48,7 +49,7 @@
    />,
    <SelectInput
        source="status"
        label="状态"
        label="common.field.status"
        choices={[
            { id: "1", name: "common.enums.statusTrue" },
            { id: "0", name: "common.enums.statusFalse" },
@@ -83,22 +84,23 @@
};
const transportGroups = [
    { key: "BUILTIN", title: "内置 MCP", description: "系统内置工具挂载,适合直接暴露平台能力。" },
    { key: "SSE_HTTP", title: "远程 SSE MCP", description: "通过远程 MCP Server 挂载外部工具。" },
    { key: "STDIO", title: "本地 STDIO MCP", description: "通过本地命令进程挂载外部 MCP。" },
    { key: "BUILTIN", titleKey: "ai.mcp.groups.builtin.title", descriptionKey: "ai.mcp.groups.builtin.description" },
    { key: "SSE_HTTP", titleKey: "ai.mcp.groups.sse.title", descriptionKey: "ai.mcp.groups.sse.description" },
    { key: "STDIO", titleKey: "ai.mcp.groups.stdio.title", descriptionKey: "ai.mcp.groups.stdio.description" },
];
const resolveHealthMeta = (record) => {
const resolveHealthMeta = (record, translate) => {
    if (record.healthStatus === "HEALTHY") {
        return { color: "success", label: "正常" };
        return { color: "success", label: translate("ai.mcp.health.healthy") };
    }
    if (record.healthStatus === "UNHEALTHY") {
        return { color: "error", label: "失败" };
        return { color: "error", label: translate("ai.mcp.health.unhealthy") };
    }
    return { color: "default", label: "未测试" };
    return { color: "default", label: translate("ai.common.notTested") };
};
const AiMcpMountCards = ({ onView, onEdit, onDelete, onConnectivityTest, deleting, testingConnectivityId }) => {
    const translate = useTranslate();
    const { data, isLoading } = useListContext();
    const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
    const groupedRecords = useMemo(() => {
@@ -120,9 +122,9 @@
        return (
            <Box px={2} py={6}>
                <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
                    <Typography variant="subtitle1">暂无 MCP 挂载</Typography>
                    <Typography variant="subtitle1">{translate("ai.mcp.list.emptyTitle")}</Typography>
                    <Typography variant="body2" color="text.secondary" mt={1}>
                        可以新建内置 MCP、远程 SSE 挂载或本地 STDIO 挂载。
                        {translate("ai.mcp.list.emptyDescription")}
                    </Typography>
                </Card>
            </Box>
@@ -135,14 +137,14 @@
                {groupedRecords.map((group) => (
                    <Box key={group.key}>
                        <Box mb={1.5}>
                            <Typography variant="h6">{group.title}</Typography>
                            <Typography variant="h6">{translate(group.titleKey)}</Typography>
                            <Typography variant="body2" color="text.secondary">
                                {group.description}
                                {translate(group.descriptionKey)}
                            </Typography>
                        </Box>
                        <Grid container spacing={2}>
                            {group.records.map((record) => {
                                const healthMeta = resolveHealthMeta(record);
                                const healthMeta = resolveHealthMeta(record, translate);
                                return (
                                    <Grid item xs={12} md={6} xl={4} key={record.id}>
                                        <Card
@@ -167,39 +169,35 @@
                                                        <Chip
                                                            size="small"
                                                            color={record.statusBool ? "success" : "default"}
                                                            label={record.statusBool ? "启用" : "停用"}
                                                            label={translate(record.statusBool ? "ai.common.enabled" : "ai.common.disabled")}
                                                        />
                                                        <Chip size="small" color={healthMeta.color} label={healthMeta.label} />
                                                    </Stack>
                                                </Stack>
                                                <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
                                                    <Chip size="small" variant="outlined" label={`排序 ${record.sort ?? 0}`} />
                                                    <Chip size="small" variant="outlined" label={translate("ai.mcp.list.sortValue", { value: record.sort ?? 0 })} />
                                                    <Chip size="small" variant="outlined" label={`${record.requestTimeoutMs ?? "--"} ms`} />
                                                    <Chip size="small" variant="outlined" label={`Init ${record.lastInitElapsedMs ?? "--"} ms`} />
                                                </Stack>
                                                <Divider sx={{ my: 1.5 }} />
                                                <Typography variant="caption" color="text.secondary">目标</Typography>
                                                <Typography variant="caption" color="text.secondary">{translate("ai.common.target")}</Typography>
                                                <Typography variant="body2" sx={{ mt: 0.5, wordBreak: "break-all" }}>
                                                    {truncateText(resolveTargetLabel(record), 120)}
                                                </Typography>
                                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
                                                    最近测试
                                                </Typography>
                                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.common.lastTest")}</Typography>
                                                <Typography variant="body2">
                                                    {record.lastTestTime$ ? `${record.lastTestTime$} · ${truncateText(record.lastTestMessage, 72)}` : "尚未执行连通性测试"}
                                                    {record.lastTestTime$ ? `${record.lastTestTime$} · ${truncateText(record.lastTestMessage, 72)}` : translate("ai.mcp.list.noConnectivityTest")}
                                                </Typography>
                                                <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", alignItems: "flex-start" }}>
                                                <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
                                                    <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"
@@ -207,7 +205,7 @@
                                                        onClick={() => onConnectivityTest(record)}
                                                        disabled={testingConnectivityId === record.id}
                                                    >
                                                        {testingConnectivityId === record.id ? "测试中..." : "连通测试"}
                                                        {testingConnectivityId === record.id ? translate("ai.common.testing") : translate("ai.mcp.list.connectivityTest")}
                                                    </Button>
                                                </Stack>
                                                <Button
@@ -217,7 +215,7 @@
                                                    onClick={() => onDelete(record)}
                                                    disabled={deleting}
                                                >
                                                    删除
                                                    {translate("ai.common.delete")}
                                                </Button>
                                            </CardActions>
                                        </Card>
@@ -233,6 +231,7 @@
};
const AiMcpMountList = () => {
    const translate = useTranslate();
    const notify = useNotify();
    const refresh = useRefresh();
    const [deleteOne, { isPending: deleting }] = useDelete();
@@ -243,7 +242,7 @@
    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(
@@ -251,11 +250,11 @@
            { 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" });
                },
            }
        );
@@ -268,19 +267,19 @@
        setTestingConnectivityId(record.id);
        try {
            const result = await testMcpConnectivity(record.id);
            notify(result?.message || "连通性测试完成");
            notify(result?.message || translate("ai.mcp.connectivity.success"));
            refresh();
        } catch (error) {
            notify(error?.message || "连通性测试失败", { type: "error" });
            notify(error?.message || translate("ai.mcp.connectivity.failed"), { type: "error" });
        } finally {
            setTestingConnectivityId(null);
        }
    };
    const dialogTitle = {
        create: "新建 MCP 挂载",
        edit: "编辑 MCP 挂载",
        show: "查看 MCP 挂载详情",
        create: translate("ai.mcp.dialog.create"),
        edit: translate("ai.mcp.dialog.edit"),
        show: translate("ai.mcp.dialog.show"),
    }[dialogState.mode];
    return (
@@ -293,7 +292,7 @@
                    <TopToolbar>
                        <FilterButton />
                        <Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
                            新建
                            {translate("ai.common.new")}
                        </Button>
                        <MyExportButton />
                    </TopToolbar>