zhou zhou
17 小时以前 287a666e1b2bb155e86aa88ebace201d1e8a51f6
rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useState } from "react";
import { useTranslate, useNotify } from "react-admin";
import {
    Accordion,
    AccordionDetails,
@@ -18,10 +19,9 @@
import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined";
import PreviewOutlinedIcon from "@mui/icons-material/PreviewOutlined";
import ExpandMoreOutlinedIcon from "@mui/icons-material/ExpandMoreOutlined";
import { useNotify } from "react-admin";
import { previewMcpTools, testMcpConnectivity, testMcpTool } from "@/api/ai/mcpMount";
const parseInputSchema = (inputSchema) => {
const parseInputSchema = (inputSchema, translate) => {
    if (!inputSchema) {
        return { pretty: "", fields: [], required: [], error: "" };
    }
@@ -46,7 +46,7 @@
            pretty: inputSchema,
            fields: [],
            required: [],
            error: `Input Schema 解析失败: ${error.message}`,
            error: translate("ai.mcp.tools.schemaParseFailed", { message: error.message }),
        };
    }
};
@@ -118,6 +118,7 @@
const AiMcpMountToolsPanel = ({ mountId }) => {
    const notify = useNotify();
    const translate = useTranslate();
    const [loading, setLoading] = useState(false);
    const [tools, setTools] = useState([]);
    const [error, setError] = useState("");
@@ -130,10 +131,10 @@
    const schemaInfoMap = useMemo(() => {
        return tools.reduce((result, tool) => {
            result[tool.name] = parseInputSchema(tool.inputSchema);
            result[tool.name] = parseInputSchema(tool.inputSchema, translate);
            return result;
        }, {});
    }, [tools]);
    }, [tools, translate]);
    useEffect(() => {
        if (!mountId) {
@@ -158,7 +159,7 @@
            setInputs({});
            setStructuredInputs({});
        } catch (requestError) {
            setError(requestError.message || "获取工具列表失败");
            setError(requestError.message || translate("ai.mcp.tools.loadFailed"));
        } finally {
            setLoading(false);
        }
@@ -169,9 +170,9 @@
        try {
            const result = await testMcpConnectivity(mountId);
            setConnectivity(result);
            notify(result?.message || "连通性测试完成");
            notify(result?.message || translate("ai.mcp.connectivity.success"));
        } catch (requestError) {
            const message = requestError.message || "连通性测试失败";
            const message = requestError.message || translate("ai.mcp.connectivity.failed");
            notify(message, { type: "error" });
        } finally {
            setTestingConnectivity(false);
@@ -210,7 +211,7 @@
    const handleTest = async (toolName) => {
        const inputJson = inputs[toolName];
        if (!inputJson || !inputJson.trim()) {
            notify("请输入工具测试 JSON", { type: "warning" });
            notify(translate("ai.mcp.tools.inputRequired"), { type: "warning" });
            return;
        }
        setTestingToolName(toolName);
@@ -223,9 +224,9 @@
                ...prev,
                [toolName]: result?.output || "",
            }));
            notify(`工具 ${toolName} 测试完成`);
            notify(translate("ai.mcp.tools.testSuccess", { name: toolName }));
        } catch (requestError) {
            const message = requestError.message || "工具测试失败";
            const message = requestError.message || translate("ai.mcp.tools.testFailed");
            setOutputs((prev) => ({
                ...prev,
                [toolName]: message,
@@ -239,7 +240,7 @@
    if (!mountId) {
        return (
            <Alert severity="info" sx={{ mt: 2 }}>
                保存挂载后即可预览工具并执行测试。
                {translate("ai.mcp.tools.saveBeforePreview")}
            </Alert>
        );
    }
@@ -249,16 +250,16 @@
            <Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
                <AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
                    <Box flex={1}>
                        <Typography variant="h6">工具预览与测试</Typography>
                        <Typography variant="h6">{translate("ai.mcp.tools.title")}</Typography>
                        <Typography variant="body2" color="text.secondary">
                            支持连通性测试、结构化 Schema 预览和按输入参数自动生成测试表单。
                            {translate("ai.mcp.tools.description")}
                        </Typography>
                    </Box>
                </AccordionSummary>
                <AccordionDetails>
                    <Stack direction="row" justifyContent="space-between" alignItems="center" mb={1.5} flexWrap="wrap" useFlexGap>
                        <Button size="small" startIcon={<PreviewOutlinedIcon />} onClick={loadTools} disabled={loading}>
                            刷新工具
                            {translate("ai.mcp.tools.refresh")}
                        </Button>
                        <Button
                            size="small"
@@ -267,7 +268,7 @@
                            onClick={handleConnectivityTest}
                            disabled={testingConnectivity}
                        >
                            {testingConnectivity ? "测试中..." : "连通性测试"}
                            {testingConnectivity ? translate("ai.common.testing") : translate("ai.mcp.list.connectivityTest")}
                        </Button>
                    </Stack>
                    {!!connectivity && (
@@ -289,7 +290,7 @@
                        </Alert>
                    )}
                    {!loading && !error && !tools.length && (
                        <Alert severity="info">当前挂载未解析出任何工具。</Alert>
                        <Alert severity="info">{translate("ai.mcp.tools.noTools")}</Alert>
                    )}
                    <Grid container spacing={2}>
                        {tools.map((tool) => {
@@ -303,11 +304,11 @@
                                                <Box>
                                                    <Typography variant="subtitle1">{tool.name}</Typography>
                                                    <Typography variant="body2" color="text.secondary">
                                                        {tool.description || "暂无描述"}
                                                        {tool.description || translate("ai.common.none")}
                                                    </Typography>
                                                    {!!tool.toolPurpose && (
                                                        <Typography variant="caption" color="text.secondary" display="block" mt={0.5}>
                                                            用途: {tool.toolPurpose}
                                                            {translate("ai.mcp.tools.purpose", { value: tool.toolPurpose })}
                                                        </Typography>
                                                    )}
                                                </Box>
@@ -318,7 +319,7 @@
                                                        </Typography>
                                                    )}
                                                    <Typography variant="caption" color="text.secondary">
                                                        {schemaInfo.fields.length} 个参数
                                                        {translate("ai.mcp.tools.fieldCount", { count: schemaInfo.fields.length })}
                                                    </Typography>
                                                    <Typography variant="caption" color="text.secondary">
                                                        {tool.returnDirect ? "returnDirect" : "normal"}
@@ -331,13 +332,13 @@
                                                <CardContent>
                                                    {!!tool.queryBoundary && (
                                                        <Alert severity="info" sx={{ mb: 2 }}>
                                                            查询边界: {tool.queryBoundary}
                                                            {translate("ai.mcp.tools.queryBoundary", { value: tool.queryBoundary })}
                                                        </Alert>
                                                    )}
                                                    {!!tool.exampleQuestions?.length && (
                                                        <Alert severity="success" sx={{ mb: 2 }}>
                                                            <Typography variant="body2" fontWeight={700} mb={0.5}>
                                                                示例提问
                                                                {translate("ai.mcp.tools.exampleQuestions")}
                                                            </Typography>
                                                            {tool.exampleQuestions.map((question) => (
                                                                <Typography key={question} variant="body2">
@@ -352,7 +353,7 @@
                                                        </Alert>
                                                    )}
                                                    <TextField
                                                        label="格式化 Input Schema"
                                                        label={translate("ai.mcp.tools.formattedSchema")}
                                                        value={schemaInfo.pretty || tool.inputSchema || ""}
                                                        fullWidth
                                                        multiline
@@ -391,7 +392,7 @@
                                                        </Grid>
                                                    )}
                                                    <TextField
                                                        label="测试输入 JSON"
                                                        label={translate("ai.mcp.tools.testInput")}
                                                        value={inputs[tool.name] || ""}
                                                        onChange={(event) => handleInputChange(tool.name, event.target.value)}
                                                        fullWidth
@@ -399,7 +400,7 @@
                                                        minRows={5}
                                                        maxRows={12}
                                                        sx={{ mt: 2 }}
                                                        placeholder='例如:{"code":"A01"}'
                                                        placeholder={translate("ai.mcp.tools.testInputPlaceholder")}
                                                    />
                                                    <Stack direction="row" justifyContent="flex-end" mt={1.5}>
                                                        <Button
@@ -408,11 +409,11 @@
                                                            onClick={() => handleTest(tool.name)}
                                                            disabled={testingToolName === tool.name}
                                                        >
                                                            {testingToolName === tool.name ? "测试中..." : "执行测试"}
                                                            {testingToolName === tool.name ? translate("ai.common.testing") : translate("ai.mcp.tools.executeTest")}
                                                        </Button>
                                                    </Stack>
                                                    <TextField
                                                        label="测试结果"
                                                        label={translate("ai.mcp.tools.testResult")}
                                                        value={outputs[tool.name] || ""}
                                                        fullWidth
                                                        multiline