From 5e40dee0e0a4e4cff4a1aafca2444f61c39cbf32 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 19 三月 2026 11:17:14 +0800
Subject: [PATCH] #AI.会话能力增强

---
 rsf-admin/src/layout/AiChatDrawer.jsx |  132 +++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 125 insertions(+), 7 deletions(-)

diff --git a/rsf-admin/src/layout/AiChatDrawer.jsx b/rsf-admin/src/layout/AiChatDrawer.jsx
index c86e221..e8e2a07 100644
--- a/rsf-admin/src/layout/AiChatDrawer.jsx
+++ b/rsf-admin/src/layout/AiChatDrawer.jsx
@@ -6,6 +6,10 @@
     Box,
     Button,
     Chip,
+    Dialog,
+    DialogActions,
+    DialogContent,
+    DialogTitle,
     Divider,
     Drawer,
     IconButton,
@@ -26,7 +30,11 @@
 import CloseIcon from "@mui/icons-material/Close";
 import AddCommentOutlinedIcon from "@mui/icons-material/AddCommentOutlined";
 import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
-import { getAiRuntime, getAiSessions, removeAiSession, streamAiChat } from "@/api/ai/chat";
+import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
+import PushPinOutlinedIcon from "@mui/icons-material/PushPinOutlined";
+import PushPinIcon from "@mui/icons-material/PushPin";
+import SearchOutlinedIcon from "@mui/icons-material/SearchOutlined";
+import { getAiRuntime, getAiSessions, pinAiSession, removeAiSession, renameAiSession, streamAiChat } from "@/api/ai/chat";
 
 const DEFAULT_PROMPT_CODE = "home.default";
 
@@ -51,6 +59,8 @@
     const [streaming, setStreaming] = useState(false);
     const [usage, setUsage] = useState(null);
     const [drawerError, setDrawerError] = useState("");
+    const [sessionKeyword, setSessionKeyword] = useState("");
+    const [renameDialog, setRenameDialog] = useState({ open: false, sessionId: null, title: "" });
 
     const promptCode = runtime?.promptCode || DEFAULT_PROMPT_CODE;
 
@@ -78,7 +88,7 @@
     const initializeDrawer = async (targetSessionId = null) => {
         await Promise.all([
             loadRuntime(targetSessionId),
-            loadSessions(),
+            loadSessions(sessionKeyword),
         ]);
     };
 
@@ -100,9 +110,9 @@
         }
     };
 
-    const loadSessions = async () => {
+    const loadSessions = async (keyword = sessionKeyword) => {
         try {
-            const data = await getAiSessions(DEFAULT_PROMPT_CODE);
+            const data = await getAiSessions(DEFAULT_PROMPT_CODE, keyword);
             setSessions(data);
         } catch (error) {
             const message = error.message || "鑾峰彇 AI 浼氳瘽鍒楄〃澶辫触";
@@ -120,6 +130,16 @@
         setUsage(null);
         setDrawerError("");
     };
+
+    useEffect(() => {
+        if (!open) {
+            return;
+        }
+        const timer = window.setTimeout(() => {
+            loadSessions(sessionKeyword);
+        }, 250);
+        return () => window.clearTimeout(timer);
+    }, [sessionKeyword, open]);
 
     const handleSwitchSession = async (targetSessionId) => {
         if (streaming || targetSessionId === sessionId) {
@@ -140,9 +160,52 @@
                 startNewSession();
                 await loadRuntime(null);
             }
-            await loadSessions();
+            await loadSessions(sessionKeyword);
         } catch (error) {
             const message = error.message || "鍒犻櫎 AI 浼氳瘽澶辫触";
+            setDrawerError(message);
+            notify(message, { type: "error" });
+        }
+    };
+
+    const handlePinSession = async (targetSessionId, pinned) => {
+        if (streaming || !targetSessionId) {
+            return;
+        }
+        try {
+            await pinAiSession(targetSessionId, pinned);
+            notify(pinned ? "浼氳瘽宸茬疆椤�" : "浼氳瘽宸插彇娑堢疆椤�");
+            await loadSessions(sessionKeyword);
+        } catch (error) {
+            const message = error.message || "鏇存柊浼氳瘽缃《鐘舵�佸け璐�";
+            setDrawerError(message);
+            notify(message, { type: "error" });
+        }
+    };
+
+    const openRenameDialog = (item) => {
+        setRenameDialog({
+            open: true,
+            sessionId: item?.sessionId || null,
+            title: item?.title || "",
+        });
+    };
+
+    const closeRenameDialog = () => {
+        setRenameDialog({ open: false, sessionId: null, title: "" });
+    };
+
+    const handleRenameSubmit = async () => {
+        if (streaming || !renameDialog.sessionId) {
+            return;
+        }
+        try {
+            await renameAiSession(renameDialog.sessionId, renameDialog.title);
+            notify("浼氳瘽宸查噸鍛藉悕");
+            closeRenameDialog();
+            await loadSessions(sessionKeyword);
+        } catch (error) {
+            const message = error.message || "閲嶅懡鍚嶄細璇濆け璐�";
             setDrawerError(message);
             notify(message, { type: "error" });
         }
@@ -254,7 +317,7 @@
             if (completed) {
                 await Promise.all([
                     loadRuntime(completedSessionId),
-                    loadSessions(),
+                    loadSessions(sessionKeyword),
                 ]);
             }
         }
@@ -313,6 +376,17 @@
                                     鏂板缓浼氳瘽
                                 </Button>
                             </Stack>
+                            <TextField
+                                value={sessionKeyword}
+                                onChange={(event) => setSessionKeyword(event.target.value)}
+                                fullWidth
+                                size="small"
+                                placeholder="鎼滅储浼氳瘽鏍囬"
+                                InputProps={{
+                                    startAdornment: <SearchOutlinedIcon fontSize="small" sx={{ mr: 1, color: "text.secondary" }} />,
+                                }}
+                                sx={{ mb: 1.25 }}
+                            />
                             <Paper variant="outlined" sx={{ overflow: "hidden" }}>
                                 {!sessions.length ? (
                                     <Box px={1.5} py={1.25}>
@@ -332,16 +406,41 @@
                                             >
                                                 <ListItemText
                                                     primary={item.title || `浼氳瘽 ${item.sessionId}`}
-                                                    secondary={item.lastMessageTime || `Session ${item.sessionId}`}
+                                                    secondary={item.lastMessagePreview || item.lastMessageTime || `Session ${item.sessionId}`}
                                                     primaryTypographyProps={{
                                                         noWrap: true,
                                                         fontSize: 14,
+                                                        fontWeight: item.pinned ? 700 : 400,
                                                     }}
                                                     secondaryTypographyProps={{
                                                         noWrap: true,
                                                         fontSize: 12,
                                                     }}
                                                 />
+                                                <IconButton
+                                                    size="small"
+                                                    edge="end"
+                                                    disabled={streaming}
+                                                    onClick={(event) => {
+                                                        event.stopPropagation();
+                                                        handlePinSession(item.sessionId, !item.pinned);
+                                                    }}
+                                                    title={item.pinned ? "鍙栨秷缃《" : "缃《浼氳瘽"}
+                                                >
+                                                    {item.pinned ? <PushPinIcon fontSize="small" /> : <PushPinOutlinedIcon fontSize="small" />}
+                                                </IconButton>
+                                                <IconButton
+                                                    size="small"
+                                                    edge="end"
+                                                    disabled={streaming}
+                                                    onClick={(event) => {
+                                                        event.stopPropagation();
+                                                        openRenameDialog(item);
+                                                    }}
+                                                    title="閲嶅懡鍚嶄細璇�"
+                                                >
+                                                    <EditOutlinedIcon fontSize="small" />
+                                                </IconButton>
                                                 <IconButton
                                                     size="small"
                                                     edge="end"
@@ -476,6 +575,25 @@
                     </Box>
                 </Box>
             </Box>
+            <Dialog open={renameDialog.open} onClose={closeRenameDialog} fullWidth maxWidth="xs">
+                <DialogTitle>閲嶅懡鍚嶄細璇�</DialogTitle>
+                <DialogContent>
+                    <TextField
+                        value={renameDialog.title}
+                        onChange={(event) => setRenameDialog((prev) => ({ ...prev, title: event.target.value }))}
+                        autoFocus
+                        margin="dense"
+                        label="浼氳瘽鏍囬"
+                        fullWidth
+                    />
+                </DialogContent>
+                <DialogActions>
+                    <Button onClick={closeRenameDialog}>鍙栨秷</Button>
+                    <Button onClick={handleRenameSubmit} variant="contained" disabled={streaming || !renameDialog.title.trim()}>
+                        淇濆瓨
+                    </Button>
+                </DialogActions>
+            </Dialog>
         </Drawer>
     );
 };

--
Gitblit v1.9.1