From 3d81df739dc45599c257d8cdefe0996f66ccdeae Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 19 三月 2026 12:18:14 +0800
Subject: [PATCH] #AI.MCP 管理增强
---
rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx | 344 +++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 275 insertions(+), 69 deletions(-)
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
index 1901478..2a333e9 100644
--- a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
import {
Accordion,
AccordionDetails,
@@ -9,8 +9,8 @@
Card,
CardContent,
CircularProgress,
- Divider,
Grid,
+ MenuItem,
Stack,
TextField,
Typography,
@@ -19,7 +19,102 @@
import PreviewOutlinedIcon from "@mui/icons-material/PreviewOutlined";
import ExpandMoreOutlinedIcon from "@mui/icons-material/ExpandMoreOutlined";
import { useNotify } from "react-admin";
-import { previewMcpTools, testMcpTool } from "@/api/ai/mcpMount";
+import { previewMcpTools, testMcpConnectivity, testMcpTool } from "@/api/ai/mcpMount";
+
+const parseInputSchema = (inputSchema) => {
+ if (!inputSchema) {
+ return { pretty: "", fields: [], required: [], error: "" };
+ }
+ try {
+ const schema = JSON.parse(inputSchema);
+ const properties = schema?.properties || {};
+ const required = Array.isArray(schema?.required) ? schema.required : [];
+ return {
+ pretty: JSON.stringify(schema, null, 2),
+ required,
+ error: "",
+ fields: Object.entries(properties).map(([name, definition]) => ({
+ name,
+ title: definition?.title || name,
+ description: definition?.description || "",
+ type: definition?.type || "string",
+ enumValues: Array.isArray(definition?.enum) ? definition.enum : [],
+ })),
+ };
+ } catch (error) {
+ return {
+ pretty: inputSchema,
+ fields: [],
+ required: [],
+ error: `Input Schema 瑙f瀽澶辫触: ${error.message}`,
+ };
+ }
+};
+
+const normalizeFieldValue = (field, rawValue) => {
+ if (rawValue === "" || rawValue == null) {
+ return undefined;
+ }
+ if (field.type === "integer") {
+ const parsed = Number.parseInt(rawValue, 10);
+ return Number.isNaN(parsed) ? rawValue : parsed;
+ }
+ if (field.type === "number") {
+ const parsed = Number(rawValue);
+ return Number.isNaN(parsed) ? rawValue : parsed;
+ }
+ if (field.type === "boolean") {
+ return rawValue === true || rawValue === "true";
+ }
+ return rawValue;
+};
+
+const buildInputJson = (schemaInfo, fieldValues) => {
+ if (!schemaInfo?.fields?.length) {
+ return "";
+ }
+ const payload = {};
+ schemaInfo.fields.forEach((field) => {
+ const normalized = normalizeFieldValue(field, fieldValues?.[field.name]);
+ if (normalized !== undefined) {
+ payload[field.name] = normalized;
+ }
+ });
+ return JSON.stringify(payload, null, 2);
+};
+
+const readStructuredValues = (schemaInfo, inputJson) => {
+ if (!schemaInfo?.fields?.length || !inputJson || !inputJson.trim()) {
+ return {};
+ }
+ try {
+ const parsed = JSON.parse(inputJson);
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
+ return {};
+ }
+ const values = {};
+ schemaInfo.fields.forEach((field) => {
+ const value = parsed[field.name];
+ if (value === undefined || value === null) {
+ return;
+ }
+ values[field.name] = typeof value === "boolean" ? String(value) : String(value);
+ });
+ return values;
+ } catch (error) {
+ return {};
+ }
+};
+
+const resolveConnectivitySeverity = (healthStatus) => {
+ if (healthStatus === "HEALTHY") {
+ return "success";
+ }
+ if (healthStatus === "UNHEALTHY") {
+ return "error";
+ }
+ return "info";
+};
const AiMcpMountToolsPanel = ({ mountId }) => {
const notify = useNotify();
@@ -27,14 +122,26 @@
const [tools, setTools] = useState([]);
const [error, setError] = useState("");
const [inputs, setInputs] = useState({});
+ const [structuredInputs, setStructuredInputs] = useState({});
const [outputs, setOutputs] = useState({});
const [testingToolName, setTestingToolName] = useState("");
+ const [testingConnectivity, setTestingConnectivity] = useState(false);
+ const [connectivity, setConnectivity] = useState(null);
+
+ const schemaInfoMap = useMemo(() => {
+ return tools.reduce((result, tool) => {
+ result[tool.name] = parseInputSchema(tool.inputSchema);
+ return result;
+ }, {});
+ }, [tools]);
useEffect(() => {
if (!mountId) {
setTools([]);
setInputs({});
+ setStructuredInputs({});
setOutputs({});
+ setConnectivity(null);
setError("");
return;
}
@@ -48,10 +155,26 @@
const data = await previewMcpTools(mountId);
setTools(data);
setOutputs({});
+ setInputs({});
+ setStructuredInputs({});
} catch (requestError) {
setError(requestError.message || "鑾峰彇宸ュ叿鍒楄〃澶辫触");
} finally {
setLoading(false);
+ }
+ };
+
+ const handleConnectivityTest = async () => {
+ setTestingConnectivity(true);
+ try {
+ const result = await testMcpConnectivity(mountId);
+ setConnectivity(result);
+ notify(result?.message || "杩為�氭�ф祴璇曞畬鎴�");
+ } catch (requestError) {
+ const message = requestError.message || "杩為�氭�ф祴璇曞け璐�";
+ notify(message, { type: "error" });
+ } finally {
+ setTestingConnectivity(false);
}
};
@@ -60,6 +183,28 @@
...prev,
[toolName]: value,
}));
+ setStructuredInputs((prev) => ({
+ ...prev,
+ [toolName]: readStructuredValues(schemaInfoMap[toolName], value),
+ }));
+ };
+
+ const handleStructuredFieldChange = (toolName, fieldName, value) => {
+ const schemaInfo = schemaInfoMap[toolName];
+ setStructuredInputs((prev) => {
+ const nextToolValues = {
+ ...(prev[toolName] || {}),
+ [fieldName]: value,
+ };
+ setInputs((prevInputs) => ({
+ ...prevInputs,
+ [toolName]: buildInputJson(schemaInfo, nextToolValues),
+ }));
+ return {
+ ...prev,
+ [toolName]: nextToolValues,
+ };
+ });
};
const handleTest = async (toolName) => {
@@ -106,16 +251,33 @@
<Box flex={1}>
<Typography variant="h6">宸ュ叿棰勮涓庢祴璇�</Typography>
<Typography variant="body2" color="text.secondary">
- 褰撳墠鎸傝浇瑙f瀽鍑虹殑鍏ㄩ儴宸ュ叿閮芥樉绀哄湪杩欓噷锛屽彲鐩存帴杈撳叆 JSON 鍋氭祴璇曘��
+ 鏀寔杩為�氭�ф祴璇曘�佺粨鏋勫寲 Schema 棰勮鍜屾寜杈撳叆鍙傛暟鑷姩鐢熸垚娴嬭瘯琛ㄥ崟銆�
</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
- <Stack direction="row" justifyContent="flex-end" alignItems="center" mb={1.5}>
+ <Stack direction="row" justifyContent="space-between" alignItems="center" mb={1.5} flexWrap="wrap" useFlexGap>
<Button size="small" startIcon={<PreviewOutlinedIcon />} onClick={loadTools} disabled={loading}>
鍒锋柊宸ュ叿
</Button>
+ <Button
+ size="small"
+ variant="outlined"
+ startIcon={<PlayCircleOutlineOutlinedIcon />}
+ onClick={handleConnectivityTest}
+ disabled={testingConnectivity}
+ >
+ {testingConnectivity ? "娴嬭瘯涓�..." : "杩為�氭�ф祴璇�"}
+ </Button>
</Stack>
+ {!!connectivity && (
+ <Alert severity={resolveConnectivitySeverity(connectivity.healthStatus)} sx={{ mb: 2 }}>
+ {connectivity.message}
+ {connectivity.initElapsedMs != null && ` 路 Init ${connectivity.initElapsedMs} ms`}
+ {connectivity.toolCount != null && ` 路 Tools ${connectivity.toolCount}`}
+ {connectivity.testedAt && ` 路 ${connectivity.testedAt}`}
+ </Alert>
+ )}
{loading && (
<Box display="flex" justifyContent="center" py={4}>
<CircularProgress size={28} />
@@ -130,71 +292,115 @@
<Alert severity="info">褰撳墠鎸傝浇鏈В鏋愬嚭浠讳綍宸ュ叿銆�</Alert>
)}
<Grid container spacing={2}>
- {tools.map((tool) => (
- <Grid item xs={12} key={tool.name}>
- <Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
- <AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
- <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2} width="100%" pr={1}>
- <Box>
- <Typography variant="subtitle1">{tool.name}</Typography>
- <Typography variant="body2" color="text.secondary">
- {tool.description || "鏆傛棤鎻忚堪"}
- </Typography>
- </Box>
- <Typography variant="caption" color="text.secondary">
- {tool.returnDirect ? "returnDirect" : "normal"}
- </Typography>
- </Stack>
- </AccordionSummary>
- <AccordionDetails>
- <Card variant="outlined" sx={{ borderRadius: 3 }}>
- <CardContent>
- <TextField
- label="Input Schema"
- value={tool.inputSchema || ""}
- fullWidth
- multiline
- minRows={5}
- maxRows={12}
- InputProps={{ readOnly: true }}
- />
- <TextField
- label="娴嬭瘯杈撳叆 JSON"
- value={inputs[tool.name] || ""}
- onChange={(event) => handleInputChange(tool.name, event.target.value)}
- fullWidth
- multiline
- minRows={5}
- maxRows={12}
- sx={{ mt: 2 }}
- placeholder='渚嬪锛歿"code":"A01"}'
- />
- <Stack direction="row" justifyContent="flex-end" mt={1.5}>
- <Button
- variant="contained"
- startIcon={<PlayCircleOutlineOutlinedIcon />}
- onClick={() => handleTest(tool.name)}
- disabled={testingToolName === tool.name}
- >
- {testingToolName === tool.name ? "娴嬭瘯涓�..." : "鎵ц娴嬭瘯"}
- </Button>
+ {tools.map((tool) => {
+ const schemaInfo = schemaInfoMap[tool.name] || { pretty: "", fields: [], required: [], error: "" };
+ const structuredValues = structuredInputs[tool.name] || {};
+ return (
+ <Grid item xs={12} key={tool.name}>
+ <Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
+ <AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
+ <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2} width="100%" pr={1}>
+ <Box>
+ <Typography variant="subtitle1">{tool.name}</Typography>
+ <Typography variant="body2" color="text.secondary">
+ {tool.description || "鏆傛棤鎻忚堪"}
+ </Typography>
+ </Box>
+ <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
+ <Typography variant="caption" color="text.secondary">
+ {schemaInfo.fields.length} 涓弬鏁�
+ </Typography>
+ <Typography variant="caption" color="text.secondary">
+ {tool.returnDirect ? "returnDirect" : "normal"}
+ </Typography>
</Stack>
- <TextField
- label="娴嬭瘯缁撴灉"
- value={outputs[tool.name] || ""}
- fullWidth
- multiline
- minRows={5}
- maxRows={16}
- sx={{ mt: 2 }}
- InputProps={{ readOnly: true }}
- />
- </CardContent>
- </Card>
- </AccordionDetails>
- </Accordion>
- </Grid>
- ))}
+ </Stack>
+ </AccordionSummary>
+ <AccordionDetails>
+ <Card variant="outlined" sx={{ borderRadius: 3 }}>
+ <CardContent>
+ {!!schemaInfo.error && (
+ <Alert severity="warning" sx={{ mb: 2 }}>
+ {schemaInfo.error}
+ </Alert>
+ )}
+ <TextField
+ label="鏍煎紡鍖� Input Schema"
+ value={schemaInfo.pretty || tool.inputSchema || ""}
+ fullWidth
+ multiline
+ minRows={6}
+ maxRows={16}
+ InputProps={{ readOnly: true }}
+ />
+ {!!schemaInfo.fields.length && (
+ <Grid container spacing={2} sx={{ mt: 0.5 }}>
+ {schemaInfo.fields.map((field) => (
+ <Grid item xs={12} md={field.type === "boolean" ? 6 : 12} key={`${tool.name}-${field.name}`}>
+ <TextField
+ select={field.type === "boolean" || field.enumValues.length > 0}
+ type={field.type === "integer" || field.type === "number" ? "number" : "text"}
+ label={`${field.title}${schemaInfo.required.includes(field.name) ? " *" : ""}`}
+ value={structuredValues[field.name] ?? ""}
+ onChange={(event) => handleStructuredFieldChange(tool.name, field.name, event.target.value)}
+ fullWidth
+ helperText={field.description || field.type}
+ sx={{ mt: 2 }}
+ >
+ {field.type === "boolean" && (
+ [
+ <MenuItem key="true" value="true">true</MenuItem>,
+ <MenuItem key="false" value="false">false</MenuItem>,
+ ]
+ )}
+ {field.type !== "boolean" && field.enumValues.map((value) => (
+ <MenuItem key={value} value={String(value)}>
+ {String(value)}
+ </MenuItem>
+ ))}
+ </TextField>
+ </Grid>
+ ))}
+ </Grid>
+ )}
+ <TextField
+ label="娴嬭瘯杈撳叆 JSON"
+ value={inputs[tool.name] || ""}
+ onChange={(event) => handleInputChange(tool.name, event.target.value)}
+ fullWidth
+ multiline
+ minRows={5}
+ maxRows={12}
+ sx={{ mt: 2 }}
+ placeholder='渚嬪锛歿"code":"A01"}'
+ />
+ <Stack direction="row" justifyContent="flex-end" mt={1.5}>
+ <Button
+ variant="contained"
+ startIcon={<PlayCircleOutlineOutlinedIcon />}
+ onClick={() => handleTest(tool.name)}
+ disabled={testingToolName === tool.name}
+ >
+ {testingToolName === tool.name ? "娴嬭瘯涓�..." : "鎵ц娴嬭瘯"}
+ </Button>
+ </Stack>
+ <TextField
+ label="娴嬭瘯缁撴灉"
+ value={outputs[tool.name] || ""}
+ fullWidth
+ multiline
+ minRows={5}
+ maxRows={16}
+ sx={{ mt: 2 }}
+ InputProps={{ readOnly: true }}
+ />
+ </CardContent>
+ </Card>
+ </AccordionDetails>
+ </Accordion>
+ </Grid>
+ );
+ })}
</Grid>
</AccordionDetails>
</Accordion>
--
Gitblit v1.9.1