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