From 6477d7156272a6f1fe126c781958369bb10970c6 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期六, 21 三月 2026 11:15:50 +0800
Subject: [PATCH] #ai 思维链

---
 rsf-admin/src/layout/AiChatDrawer.jsx |  166 ++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 146 insertions(+), 20 deletions(-)

diff --git a/rsf-admin/src/layout/AiChatDrawer.jsx b/rsf-admin/src/layout/AiChatDrawer.jsx
index e557728..4eaeea9 100644
--- a/rsf-admin/src/layout/AiChatDrawer.jsx
+++ b/rsf-admin/src/layout/AiChatDrawer.jsx
@@ -42,6 +42,11 @@
 import { clearAiSessionMemory, getAiRuntime, getAiSessions, pinAiSession, removeAiSession, renameAiSession, retainAiSessionLatestRound, streamAiChat } from "@/api/ai/chat";
 
 const DEFAULT_PROMPT_CODE = "home.default";
+const THINKING_PHASE_ORDER = {
+    ANALYZE: 0,
+    TOOL_CALL: 1,
+    ANSWER: 2,
+};
 
 const AiChatDrawer = ({ open, onClose }) => {
     const navigate = useNavigate();
@@ -58,6 +63,8 @@
     const [messages, setMessages] = useState([]);
     const [toolEvents, setToolEvents] = useState([]);
     const [expandedToolIds, setExpandedToolIds] = useState([]);
+    const [thinkingEvents, setThinkingEvents] = useState([]);
+    const [thinkingExpanded, setThinkingExpanded] = useState(true);
     const [input, setInput] = useState("");
     const [loadingRuntime, setLoadingRuntime] = useState(false);
     const [streaming, setStreaming] = useState(false);
@@ -86,6 +93,18 @@
         };
     }, [runtime]);
 
+    const currentThinkingMessageIndex = useMemo(() => {
+        if (!thinkingEvents.length || !messages.length) {
+            return -1;
+        }
+        for (let i = messages.length - 1; i >= 0; i -= 1) {
+            if (messages[i]?.role === "assistant") {
+                return i;
+            }
+        }
+        return -1;
+    }, [messages, thinkingEvents]);
+
     useEffect(() => {
         if (open) {
             initializeDrawer();
@@ -111,6 +130,8 @@
     const initializeDrawer = async (targetSessionId = null) => {
         setToolEvents([]);
         setExpandedToolIds([]);
+        setThinkingEvents([]);
+        setThinkingExpanded(true);
         await Promise.all([
             loadRuntime(targetSessionId),
             loadSessions(sessionKeyword),
@@ -154,6 +175,8 @@
         setMessages([]);
         setToolEvents([]);
         setExpandedToolIds([]);
+        setThinkingEvents([]);
+        setThinkingExpanded(true);
         setUsage(null);
         setDrawerError("");
     };
@@ -175,6 +198,8 @@
         setUsage(null);
         setToolEvents([]);
         setExpandedToolIds([]);
+        setThinkingEvents([]);
+        setThinkingExpanded(true);
         await loadRuntime(targetSessionId);
     };
 
@@ -348,6 +373,44 @@
         ));
     };
 
+    const upsertThinkingEvent = (payload) => {
+        if (!payload?.phase) {
+            return;
+        }
+        setThinkingEvents((prev) => {
+            const index = prev.findIndex((item) => item.phase === payload.phase);
+            if (index < 0) {
+                return [...prev, payload].sort((left, right) => (
+                    (THINKING_PHASE_ORDER[left.phase] ?? Number.MAX_SAFE_INTEGER)
+                    - (THINKING_PHASE_ORDER[right.phase] ?? Number.MAX_SAFE_INTEGER)
+                ));
+            }
+            const next = [...prev];
+            next[index] = { ...next[index], ...payload };
+            return next;
+        });
+    };
+
+    const toggleThinkingExpanded = () => {
+        setThinkingExpanded((prev) => !prev);
+    };
+
+    const getThinkingStatusLabel = (status) => {
+        if (status === "COMPLETED") {
+            return translate("ai.drawer.thinkingStatusCompleted");
+        }
+        if (status === "FAILED") {
+            return translate("ai.drawer.thinkingStatusFailed");
+        }
+        if (status === "ABORTED") {
+            return translate("ai.drawer.thinkingStatusAborted");
+        }
+        if (status === "UPDATED") {
+            return translate("ai.drawer.thinkingStatusUpdated");
+        }
+        return translate("ai.drawer.thinkingStatusStarted");
+    };
+
     const handleSend = async () => {
         const content = input.trim();
         if (!content || streaming) {
@@ -360,6 +423,8 @@
         setDrawerError("");
         setToolEvents([]);
         setExpandedToolIds([]);
+        setThinkingEvents([]);
+        setThinkingExpanded(true);
         setMessages(ensureAssistantPlaceholder(nextMessages));
         setStreaming(true);
 
@@ -394,6 +459,9 @@
                         }
                         if (eventName === "tool_start" || eventName === "tool_result" || eventName === "tool_error") {
                             upsertToolEvent(payload);
+                        }
+                        if (eventName === "thinking") {
+                            upsertThinkingEvent(payload);
                         }
                         if (eventName === "done") {
                             setUsage(payload);
@@ -746,26 +814,84 @@
                                     display="flex"
                                     justifyContent={message.role === "user" ? "flex-end" : "flex-start"}
                                 >
-                                    <Paper
-                                        elevation={0}
-                                        sx={{
-                                            px: 1.5,
-                                            py: 1.25,
-                                            maxWidth: "85%",
-                                            borderRadius: 2,
-                                            bgcolor: message.role === "user" ? "primary.main" : "grey.100",
-                                            color: message.role === "user" ? "primary.contrastText" : "text.primary",
-                                            whiteSpace: "pre-wrap",
-                                            wordBreak: "break-word",
-                                        }}
-                                    >
-                                        <Typography variant="caption" display="block" sx={{ opacity: 0.72, mb: 0.5 }}>
-                                            {message.role === "user" ? translate("ai.drawer.userRole") : translate("ai.drawer.assistantRole")}
-                                        </Typography>
-                                        <Typography variant="body2">
-                                            {message.content || (streaming && index === messages.length - 1 ? translate("ai.drawer.thinking") : "")}
-                                        </Typography>
-                                    </Paper>
+                                    <Stack spacing={1} sx={{ maxWidth: "85%", width: "100%" }} alignItems={message.role === "user" ? "flex-end" : "flex-start"}>
+                                        {message.role === "assistant" && index === currentThinkingMessageIndex && !!thinkingEvents.length && (
+                                            <Paper
+                                                variant="outlined"
+                                                sx={{
+                                                    width: "100%",
+                                                    borderRadius: 2,
+                                                    overflow: "hidden",
+                                                    bgcolor: "grey.50",
+                                                }}
+                                            >
+                                                <Button
+                                                    fullWidth
+                                                    size="small"
+                                                    onClick={toggleThinkingExpanded}
+                                                    endIcon={thinkingExpanded
+                                                        ? <ExpandLessOutlinedIcon fontSize="small" />
+                                                        : <ExpandMoreOutlinedIcon fontSize="small" />}
+                                                    sx={{
+                                                        justifyContent: "space-between",
+                                                        px: 1.25,
+                                                        py: 0.75,
+                                                        color: "text.primary",
+                                                    }}
+                                                >
+                                                    {thinkingExpanded ? translate("ai.drawer.thinkingCollapse") : translate("ai.drawer.thinkingExpand")}
+                                                </Button>
+                                                <Collapse in={thinkingExpanded} timeout="auto" unmountOnExit>
+                                                    <Stack spacing={1} sx={{ px: 1.25, pb: 1.25 }}>
+                                                        {thinkingEvents.map((item) => (
+                                                            <Paper key={item.phase} variant="outlined" sx={{ px: 1, py: 0.9, bgcolor: "common.white" }}>
+                                                                <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
+                                                                    <Typography variant="body2" fontWeight={700}>
+                                                                        {item.title || translate("ai.drawer.thinkingProcess")}
+                                                                    </Typography>
+                                                                    <Chip
+                                                                        size="small"
+                                                                        color={item.status === "FAILED"
+                                                                            ? "error"
+                                                                            : item.status === "COMPLETED"
+                                                                                ? "success"
+                                                                                : item.status === "ABORTED"
+                                                                                    ? "warning"
+                                                                                    : "info"}
+                                                                        label={getThinkingStatusLabel(item.status)}
+                                                                    />
+                                                                </Stack>
+                                                                <Typography variant="caption" display="block" color="text.secondary" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
+                                                                    {item.content || translate("ai.drawer.thinkingEmpty")}
+                                                                </Typography>
+                                                            </Paper>
+                                                        ))}
+                                                    </Stack>
+                                                </Collapse>
+                                            </Paper>
+                                        )}
+                                        <Paper
+                                            elevation={0}
+                                            sx={{
+                                                px: 1.5,
+                                                py: 1.25,
+                                                width: "fit-content",
+                                                maxWidth: "100%",
+                                                borderRadius: 2,
+                                                bgcolor: message.role === "user" ? "primary.main" : "grey.100",
+                                                color: message.role === "user" ? "primary.contrastText" : "text.primary",
+                                                whiteSpace: "pre-wrap",
+                                                wordBreak: "break-word",
+                                            }}
+                                        >
+                                            <Typography variant="caption" display="block" sx={{ opacity: 0.72, mb: 0.5 }}>
+                                                {message.role === "user" ? translate("ai.drawer.userRole") : translate("ai.drawer.assistantRole")}
+                                            </Typography>
+                                            <Typography variant="body2">
+                                                {message.content || (streaming && index === messages.length - 1 ? translate("ai.drawer.thinking") : "")}
+                                            </Typography>
+                                        </Paper>
+                                    </Stack>
                                 </Box>
                             ))}
                             <Box ref={messagesBottomRef} sx={{ height: 1 }} />

--
Gitblit v1.9.1