import React, { useEffect, useState } from "react";
|
import {
|
Accordion,
|
AccordionDetails,
|
AccordionSummary,
|
Alert,
|
Box,
|
Button,
|
Card,
|
CardContent,
|
CircularProgress,
|
Divider,
|
Grid,
|
Stack,
|
TextField,
|
Typography,
|
} from "@mui/material";
|
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, testMcpTool } from "@/api/ai/mcpMount";
|
|
const AiMcpMountToolsPanel = ({ mountId }) => {
|
const notify = useNotify();
|
const [loading, setLoading] = useState(false);
|
const [tools, setTools] = useState([]);
|
const [error, setError] = useState("");
|
const [inputs, setInputs] = useState({});
|
const [outputs, setOutputs] = useState({});
|
const [testingToolName, setTestingToolName] = useState("");
|
|
useEffect(() => {
|
if (!mountId) {
|
setTools([]);
|
setInputs({});
|
setOutputs({});
|
setError("");
|
return;
|
}
|
loadTools();
|
}, [mountId]);
|
|
const loadTools = async () => {
|
setLoading(true);
|
setError("");
|
try {
|
const data = await previewMcpTools(mountId);
|
setTools(data);
|
setOutputs({});
|
} catch (requestError) {
|
setError(requestError.message || "获取工具列表失败");
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
const handleInputChange = (toolName, value) => {
|
setInputs((prev) => ({
|
...prev,
|
[toolName]: value,
|
}));
|
};
|
|
const handleTest = async (toolName) => {
|
const inputJson = inputs[toolName];
|
if (!inputJson || !inputJson.trim()) {
|
notify("请输入工具测试 JSON", { type: "warning" });
|
return;
|
}
|
setTestingToolName(toolName);
|
try {
|
const result = await testMcpTool(mountId, {
|
toolName,
|
inputJson,
|
});
|
setOutputs((prev) => ({
|
...prev,
|
[toolName]: result?.output || "",
|
}));
|
notify(`工具 ${toolName} 测试完成`);
|
} catch (requestError) {
|
const message = requestError.message || "工具测试失败";
|
setOutputs((prev) => ({
|
...prev,
|
[toolName]: message,
|
}));
|
notify(message, { type: "error" });
|
} finally {
|
setTestingToolName("");
|
}
|
};
|
|
if (!mountId) {
|
return (
|
<Alert severity="info" sx={{ mt: 2 }}>
|
保存挂载后即可预览工具并执行测试。
|
</Alert>
|
);
|
}
|
|
return (
|
<Box mt={3}>
|
<Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
|
<AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
|
<Box flex={1}>
|
<Typography variant="h6">工具预览与测试</Typography>
|
<Typography variant="body2" color="text.secondary">
|
当前挂载解析出的全部工具都显示在这里,可直接输入 JSON 做测试。
|
</Typography>
|
</Box>
|
</AccordionSummary>
|
<AccordionDetails>
|
<Stack direction="row" justifyContent="flex-end" alignItems="center" mb={1.5}>
|
<Button size="small" startIcon={<PreviewOutlinedIcon />} onClick={loadTools} disabled={loading}>
|
刷新工具
|
</Button>
|
</Stack>
|
{loading && (
|
<Box display="flex" justifyContent="center" py={4}>
|
<CircularProgress size={28} />
|
</Box>
|
)}
|
{!!error && !loading && (
|
<Alert severity="warning" sx={{ mb: 2 }}>
|
{error}
|
</Alert>
|
)}
|
{!loading && !error && !tools.length && (
|
<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>
|
</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>
|
</Box>
|
);
|
};
|
|
export default AiMcpMountToolsPanel;
|