zhou zhou
4 天以前 1d0ab9996661fdc66037870d4b98037f2dfa079a
rsf-admin/src/layout/AiChatDrawer.jsx
@@ -6,6 +6,7 @@
    Box,
    Button,
    Chip,
    Collapse,
    Dialog,
    DialogActions,
    DialogContent,
@@ -36,6 +37,8 @@
import PushPinOutlinedIcon from "@mui/icons-material/PushPinOutlined";
import PushPinIcon from "@mui/icons-material/PushPin";
import SearchOutlinedIcon from "@mui/icons-material/SearchOutlined";
import ExpandMoreOutlinedIcon from "@mui/icons-material/ExpandMoreOutlined";
import ExpandLessOutlinedIcon from "@mui/icons-material/ExpandLessOutlined";
import { clearAiSessionMemory, getAiRuntime, getAiSessions, pinAiSession, removeAiSession, renameAiSession, retainAiSessionLatestRound, streamAiChat } from "@/api/ai/chat";
const DEFAULT_PROMPT_CODE = "home.default";
@@ -56,6 +59,8 @@
    const [sessions, setSessions] = useState([]);
    const [persistedMessages, setPersistedMessages] = useState([]);
    const [messages, setMessages] = useState([]);
    const [toolEvents, setToolEvents] = useState([]);
    const [expandedToolIds, setExpandedToolIds] = useState([]);
    const [input, setInput] = useState("");
    const [loadingRuntime, setLoadingRuntime] = useState(false);
    const [streaming, setStreaming] = useState(false);
@@ -91,6 +96,8 @@
    }, []);
    const initializeDrawer = async (targetSessionId = null) => {
        setToolEvents([]);
        setExpandedToolIds([]);
        await Promise.all([
            loadRuntime(targetSessionId),
            loadSessions(sessionKeyword),
@@ -132,6 +139,8 @@
        setSessionId(null);
        setPersistedMessages([]);
        setMessages([]);
        setToolEvents([]);
        setExpandedToolIds([]);
        setUsage(null);
        setDrawerError("");
    };
@@ -151,6 +160,8 @@
            return;
        }
        setUsage(null);
        setToolEvents([]);
        setExpandedToolIds([]);
        await loadRuntime(targetSessionId);
    };
@@ -288,6 +299,32 @@
        return next;
    };
    const upsertToolEvent = (payload) => {
        if (!payload?.toolCallId) {
            return;
        }
        setToolEvents((prev) => {
            const index = prev.findIndex((item) => item.toolCallId === payload.toolCallId);
            if (index < 0) {
                return [...prev, payload];
            }
            const next = [...prev];
            next[index] = { ...next[index], ...payload };
            return next;
        });
    };
    const toggleToolEventExpanded = (toolCallId) => {
        if (!toolCallId) {
            return;
        }
        setExpandedToolIds((prev) => (
            prev.includes(toolCallId)
                ? prev.filter((item) => item !== toolCallId)
                : [...prev, toolCallId]
        ));
    };
    const handleSend = async () => {
        const content = input.trim();
        if (!content || streaming) {
@@ -298,6 +335,8 @@
        setInput("");
        setUsage(null);
        setDrawerError("");
        setToolEvents([]);
        setExpandedToolIds([]);
        setMessages(ensureAssistantPlaceholder(nextMessages));
        setStreaming(true);
@@ -329,6 +368,9 @@
                        }
                        if (eventName === "delta") {
                            appendAssistantDelta(payload?.content || "");
                        }
                        if (eventName === "tool_start" || eventName === "tool_result" || eventName === "tool_error") {
                            upsertToolEvent(payload);
                        }
                        if (eventName === "done") {
                            setUsage(payload);
@@ -382,7 +424,7 @@
                "& .MuiDrawer-paper": {
                    top: 0,
                    height: "100vh",
                    width: { xs: "100vw", md: "50vw" },
                    width: { xs: "100vw", md: "70vw" },
                },
            }}
        >
@@ -502,6 +544,89 @@
                        </Box>
                    </Box>
                    <Box
                        width={{ xs: "100%", md: 280 }}
                        borderRight={{ xs: "none", md: "1px solid rgba(224, 224, 224, 1)" }}
                        borderBottom={{ xs: "1px solid rgba(224, 224, 224, 1)", md: "none" }}
                        display="flex"
                        flexDirection="column"
                        minHeight={0}
                    >
                        <Box px={2} py={1.5} display="flex" flexDirection="column" minHeight={0}>
                            <Typography variant="subtitle2" mb={1}>
                                工具调用轨迹
                            </Typography>
                            <Paper variant="outlined" sx={{ flex: 1, minHeight: { xs: 140, md: 0 }, overflow: "hidden", bgcolor: "grey.50" }}>
                                {!toolEvents.length ? (
                                    <Box px={1.5} py={1.25}>
                                        <Typography variant="body2" color="text.secondary">
                                            当前轮未触发工具调用
                                        </Typography>
                                    </Box>
                                ) : (
                                    <Stack spacing={1} sx={{ p: 1.25, maxHeight: { xs: 220, md: "calc(100vh - 180px)" }, overflow: "auto" }}>
                                        {toolEvents.map((item) => (
                                            <Paper
                                                key={item.toolCallId}
                                                variant="outlined"
                                                sx={{
                                                    p: 1.25,
                                                    bgcolor: item.status === "FAILED" ? "error.lighter" : "common.white",
                                                    borderColor: item.status === "FAILED" ? "error.light" : "divider",
                                                }}
                                            >
                                                <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
                                                    <Typography variant="body2" fontWeight={700}>
                                                        {item.toolName || "未知工具"}
                                                    </Typography>
                                                    <Chip
                                                        size="small"
                                                        color={item.status === "FAILED" ? "error" : item.status === "COMPLETED" ? "success" : "info"}
                                                        label={item.status === "FAILED" ? "失败" : item.status === "COMPLETED" ? "完成" : "执行中"}
                                                    />
                                                    {item.durationMs != null && (
                                                        <Typography variant="caption" color="text.secondary">
                                                            {item.durationMs} ms
                                                        </Typography>
                                                    )}
                                                    {(item.inputSummary || item.outputSummary || item.errorMessage) && (
                                                        <Button
                                                            size="small"
                                                            onClick={() => toggleToolEventExpanded(item.toolCallId)}
                                                            endIcon={expandedToolIds.includes(item.toolCallId)
                                                                ? <ExpandLessOutlinedIcon fontSize="small" />
                                                                : <ExpandMoreOutlinedIcon fontSize="small" />}
                                                            sx={{ ml: "auto", minWidth: "auto", px: 0.5 }}
                                                        >
                                                            {expandedToolIds.includes(item.toolCallId) ? "收起详情" : "查看详情"}
                                                        </Button>
                                                    )}
                                                </Stack>
                                                <Collapse in={expandedToolIds.includes(item.toolCallId)} timeout="auto" unmountOnExit>
                                                    {!!item.inputSummary && (
                                                        <Typography variant="caption" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
                                                            入参: {item.inputSummary}
                                                        </Typography>
                                                    )}
                                                    {!!item.outputSummary && (
                                                        <Typography variant="caption" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
                                                            结果摘要: {item.outputSummary}
                                                        </Typography>
                                                    )}
                                                    {!!item.errorMessage && (
                                                        <Typography variant="caption" color="error.main" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
                                                            错误: {item.errorMessage}
                                                        </Typography>
                                                    )}
                                                </Collapse>
                                            </Paper>
                                        ))}
                                    </Stack>
                                )}
                            </Paper>
                        </Box>
                    </Box>
                    <Box flex={1} display="flex" flexDirection="column" minHeight={0}>
                        <Box px={2} py={1.5}>
                            <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>