From 287a666e1b2bb155e86aa88ebace201d1e8a51f6 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 19 三月 2026 13:26:02 +0800
Subject: [PATCH] #AI.国际化

---
 rsf-admin/src/layout/AiChatDrawer.jsx |  174 +++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 106 insertions(+), 68 deletions(-)

diff --git a/rsf-admin/src/layout/AiChatDrawer.jsx b/rsf-admin/src/layout/AiChatDrawer.jsx
index e649aad..e557728 100644
--- a/rsf-admin/src/layout/AiChatDrawer.jsx
+++ b/rsf-admin/src/layout/AiChatDrawer.jsx
@@ -1,6 +1,6 @@
 import React, { useEffect, useMemo, useRef, useState } from "react";
 import { useLocation, useNavigate } from "react-router-dom";
-import { useNotify } from "react-admin";
+import { useNotify, useTranslate } from "react-admin";
 import {
     Alert,
     Box,
@@ -43,17 +43,14 @@
 
 const DEFAULT_PROMPT_CODE = "home.default";
 
-const quickLinks = [
-    { label: "AI 鍙傛暟", path: "/aiParam", icon: <SettingsSuggestOutlinedIcon fontSize="small" /> },
-    { label: "Prompt", path: "/aiPrompt", icon: <PsychologyAltOutlinedIcon fontSize="small" /> },
-    { label: "MCP", path: "/aiMcpMount", icon: <CableOutlinedIcon fontSize="small" /> },
-];
-
 const AiChatDrawer = ({ open, onClose }) => {
     const navigate = useNavigate();
     const location = useLocation();
     const notify = useNotify();
+    const translate = useTranslate();
     const abortRef = useRef(null);
+    const messagesContainerRef = useRef(null);
+    const messagesBottomRef = useRef(null);
     const [runtime, setRuntime] = useState(null);
     const [sessionId, setSessionId] = useState(null);
     const [sessions, setSessions] = useState([]);
@@ -68,6 +65,12 @@
     const [drawerError, setDrawerError] = useState("");
     const [sessionKeyword, setSessionKeyword] = useState("");
     const [renameDialog, setRenameDialog] = useState({ open: false, sessionId: null, title: "" });
+
+    const quickLinks = useMemo(() => ([
+        { label: translate("menu.aiParam"), path: "/aiParam", icon: <SettingsSuggestOutlinedIcon fontSize="small" /> },
+        { label: translate("menu.aiPrompt"), path: "/aiPrompt", icon: <PsychologyAltOutlinedIcon fontSize="small" /> },
+        { label: translate("menu.aiMcpMount"), path: "/aiMcpMount", icon: <CableOutlinedIcon fontSize="small" /> },
+    ]), [translate]);
 
     const promptCode = runtime?.promptCode || DEFAULT_PROMPT_CODE;
 
@@ -95,6 +98,16 @@
         stopStream(false);
     }, []);
 
+    useEffect(() => {
+        if (!open) {
+            return;
+        }
+        const timer = window.requestAnimationFrame(() => {
+            scrollMessagesToBottom();
+        });
+        return () => window.cancelAnimationFrame(timer);
+    }, [open, messages, streaming]);
+
     const initializeDrawer = async (targetSessionId = null) => {
         setToolEvents([]);
         setExpandedToolIds([]);
@@ -115,7 +128,7 @@
             setPersistedMessages(historyMessages);
             setMessages(historyMessages);
         } catch (error) {
-            const message = error.message || "鑾峰彇 AI 杩愯鏃跺け璐�";
+            const message = error.message || translate("ai.drawer.runtimeFailed");
             setDrawerError(message);
         } finally {
             setLoadingRuntime(false);
@@ -127,7 +140,7 @@
             const data = await getAiSessions(DEFAULT_PROMPT_CODE, keyword);
             setSessions(data);
         } catch (error) {
-            const message = error.message || "鑾峰彇 AI 浼氳瘽鍒楄〃澶辫触";
+            const message = error.message || translate("ai.drawer.sessionListFailed");
             setDrawerError(message);
         }
     };
@@ -171,14 +184,14 @@
         }
         try {
             await removeAiSession(targetSessionId);
-            notify("浼氳瘽宸插垹闄�");
+            notify(translate("ai.drawer.sessionDeleted"));
             if (targetSessionId === sessionId) {
                 startNewSession();
                 await loadRuntime(null);
             }
             await loadSessions(sessionKeyword);
         } catch (error) {
-            const message = error.message || "鍒犻櫎 AI 浼氳瘽澶辫触";
+            const message = error.message || translate("ai.drawer.deleteSessionFailed");
             setDrawerError(message);
             notify(message, { type: "error" });
         }
@@ -190,10 +203,10 @@
         }
         try {
             await pinAiSession(targetSessionId, pinned);
-            notify(pinned ? "浼氳瘽宸茬疆椤�" : "浼氳瘽宸插彇娑堢疆椤�");
+            notify(translate(pinned ? "ai.drawer.pinned" : "ai.drawer.unpinned"));
             await loadSessions(sessionKeyword);
         } catch (error) {
-            const message = error.message || "鏇存柊浼氳瘽缃《鐘舵�佸け璐�";
+            const message = error.message || translate("ai.drawer.pinFailed");
             setDrawerError(message);
             notify(message, { type: "error" });
         }
@@ -217,11 +230,11 @@
         }
         try {
             await renameAiSession(renameDialog.sessionId, renameDialog.title);
-            notify("浼氳瘽宸查噸鍛藉悕");
+            notify(translate("ai.drawer.renamed"));
             closeRenameDialog();
             await loadSessions(sessionKeyword);
         } catch (error) {
-            const message = error.message || "閲嶅懡鍚嶄細璇濆け璐�";
+            const message = error.message || translate("ai.drawer.renameFailed");
             setDrawerError(message);
             notify(message, { type: "error" });
         }
@@ -233,13 +246,13 @@
         }
         try {
             await clearAiSessionMemory(sessionId);
-            notify("浼氳瘽璁板繂宸叉竻绌�");
+            notify(translate("ai.drawer.memoryCleared"));
             await Promise.all([
                 loadRuntime(sessionId),
                 loadSessions(sessionKeyword),
             ]);
         } catch (error) {
-            const message = error.message || "娓呯┖浼氳瘽璁板繂澶辫触";
+            const message = error.message || translate("ai.drawer.clearMemoryFailed");
             setDrawerError(message);
             notify(message, { type: "error" });
         }
@@ -251,13 +264,13 @@
         }
         try {
             await retainAiSessionLatestRound(sessionId);
-            notify("宸蹭粎淇濈暀褰撳墠杞蹇�");
+            notify(translate("ai.drawer.retainLatestRoundSuccess"));
             await Promise.all([
                 loadRuntime(sessionId),
                 loadSessions(sessionKeyword),
             ]);
         } catch (error) {
-            const message = error.message || "淇濈暀褰撳墠杞蹇嗗け璐�";
+            const message = error.message || translate("ai.drawer.retainLatestRoundFailed");
             setDrawerError(message);
             notify(message, { type: "error" });
         }
@@ -269,8 +282,18 @@
             abortRef.current = null;
             setStreaming(false);
             if (showTip) {
-                notify("宸插仠姝㈠綋鍓嶅璇濊緭鍑�");
+                notify(translate("ai.drawer.stopSuccess"));
             }
+        }
+    };
+
+    const scrollMessagesToBottom = () => {
+        if (messagesBottomRef.current) {
+            messagesBottomRef.current.scrollIntoView({ block: "end" });
+            return;
+        }
+        if (messagesContainerRef.current) {
+            messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
         }
     };
 
@@ -380,7 +403,7 @@
                             }
                         }
                         if (eventName === "error") {
-                            const message = payload?.message || "AI 瀵硅瘽澶辫触";
+                            const message = payload?.message || translate("ai.drawer.chatFailed");
                             const displayMessage = payload?.requestId ? `${message} [${payload.requestId}]` : message;
                             setDrawerError(displayMessage);
                             notify(displayMessage, { type: "error" });
@@ -390,7 +413,7 @@
             );
         } catch (error) {
             if (error?.name !== "AbortError") {
-                const message = error.message || "AI 瀵硅瘽澶辫触";
+                const message = error.message || translate("ai.drawer.chatFailed");
                 setDrawerError(message);
                 notify(message, { type: "error" });
             }
@@ -432,12 +455,12 @@
                 <Stack direction="row" alignItems="center" spacing={1} px={2} py={1.5}>
                     <SmartToyOutlinedIcon color="primary" />
                     <Typography variant="h6" flex={1}>
-                        AI 瀵硅瘽
+                        {translate("ai.drawer.title")}
                     </Typography>
-                    <IconButton size="small" onClick={startNewSession} title="鏂板缓浼氳瘽" disabled={streaming}>
+                    <IconButton size="small" onClick={startNewSession} title={translate("ai.drawer.newSession")} disabled={streaming}>
                         <AddCommentOutlinedIcon fontSize="small" />
                     </IconButton>
-                    <IconButton size="small" onClick={onClose} title="鍏抽棴">
+                    <IconButton size="small" onClick={onClose} title={translate("ai.common.close")}>
                         <CloseIcon fontSize="small" />
                     </IconButton>
                 </Stack>
@@ -454,9 +477,9 @@
                     >
                         <Box px={2} py={1.5}>
                             <Stack direction="row" alignItems="center" justifyContent="space-between" mb={1}>
-                                <Typography variant="subtitle2">浼氳瘽鍒楄〃</Typography>
+                                <Typography variant="subtitle2">{translate("ai.drawer.sessionList")}</Typography>
                                 <Button size="small" onClick={startNewSession} disabled={streaming}>
-                                    鏂板缓浼氳瘽
+                                    {translate("ai.drawer.newSession")}
                                 </Button>
                             </Stack>
                             <TextField
@@ -464,7 +487,7 @@
                                 onChange={(event) => setSessionKeyword(event.target.value)}
                                 fullWidth
                                 size="small"
-                                placeholder="鎼滅储浼氳瘽鏍囬"
+                                placeholder={translate("ai.drawer.searchPlaceholder")}
                                 InputProps={{
                                     startAdornment: <SearchOutlinedIcon fontSize="small" sx={{ mr: 1, color: "text.secondary" }} />,
                                 }}
@@ -474,7 +497,7 @@
                                 {!sessions.length ? (
                                     <Box px={1.5} py={1.25}>
                                         <Typography variant="body2" color="text.secondary">
-                                            鏆傛棤鍘嗗彶浼氳瘽
+                                            {translate("ai.drawer.noSessions")}
                                         </Typography>
                                     </Box>
                                 ) : (
@@ -488,8 +511,8 @@
                                                 alignItems="flex-start"
                                             >
                                                 <ListItemText
-                                                    primary={item.title || `浼氳瘽 ${item.sessionId}`}
-                                                    secondary={item.lastMessagePreview || item.lastMessageTime || `Session ${item.sessionId}`}
+                                                    primary={item.title || translate("ai.drawer.sessionTitle", { id: item.sessionId })}
+                                                    secondary={item.lastMessagePreview || item.lastMessageTime || translate("ai.drawer.sessionMetric", { id: item.sessionId })}
                                                     primaryTypographyProps={{
                                                         noWrap: true,
                                                         fontSize: 14,
@@ -508,7 +531,7 @@
                                                         event.stopPropagation();
                                                         handlePinSession(item.sessionId, !item.pinned);
                                                     }}
-                                                    title={item.pinned ? "鍙栨秷缃《" : "缃《浼氳瘽"}
+                                                    title={translate(item.pinned ? "ai.drawer.unpinAction" : "ai.drawer.pinAction")}
                                                 >
                                                     {item.pinned ? <PushPinIcon fontSize="small" /> : <PushPinOutlinedIcon fontSize="small" />}
                                                 </IconButton>
@@ -520,7 +543,7 @@
                                                         event.stopPropagation();
                                                         openRenameDialog(item);
                                                     }}
-                                                    title="閲嶅懡鍚嶄細璇�"
+                                                    title={translate("ai.drawer.renameAction")}
                                                 >
                                                     <EditOutlinedIcon fontSize="small" />
                                                 </IconButton>
@@ -532,7 +555,7 @@
                                                         event.stopPropagation();
                                                         handleDeleteSession(item.sessionId);
                                                     }}
-                                                    title="鍒犻櫎浼氳瘽"
+                                                    title={translate("ai.drawer.deleteAction")}
                                                 >
                                                     <DeleteOutlineOutlinedIcon fontSize="small" />
                                                 </IconButton>
@@ -554,13 +577,13 @@
                     >
                         <Box px={2} py={1.5} display="flex" flexDirection="column" minHeight={0}>
                             <Typography variant="subtitle2" mb={1}>
-                                宸ュ叿璋冪敤杞ㄨ抗
+                                {translate("ai.drawer.toolTrace")}
                             </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">
-                                            褰撳墠杞湭瑙﹀彂宸ュ叿璋冪敤
+                                            {translate("ai.drawer.noToolTrace")}
                                         </Typography>
                                     </Box>
                                 ) : (
@@ -577,12 +600,12 @@
                                             >
                                                 <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
                                                     <Typography variant="body2" fontWeight={700}>
-                                                        {item.toolName || "鏈煡宸ュ叿"}
+                                                        {item.toolName || translate("ai.drawer.unknownTool")}
                                                     </Typography>
                                                     <Chip
                                                         size="small"
                                                         color={item.status === "FAILED" ? "error" : item.status === "COMPLETED" ? "success" : "info"}
-                                                        label={item.status === "FAILED" ? "澶辫触" : item.status === "COMPLETED" ? "瀹屾垚" : "鎵ц涓�"}
+                                                        label={translate(item.status === "FAILED" ? "ai.drawer.toolStatusFailed" : item.status === "COMPLETED" ? "ai.drawer.toolStatusCompleted" : "ai.drawer.toolStatusRunning")}
                                                     />
                                                     {item.durationMs != null && (
                                                         <Typography variant="caption" color="text.secondary">
@@ -598,24 +621,24 @@
                                                                 : <ExpandMoreOutlinedIcon fontSize="small" />}
                                                             sx={{ ml: "auto", minWidth: "auto", px: 0.5 }}
                                                         >
-                                                            {expandedToolIds.includes(item.toolCallId) ? "鏀惰捣璇︽儏" : "鏌ョ湅璇︽儏"}
+                                                            {expandedToolIds.includes(item.toolCallId) ? translate("ai.drawer.collapseDetail") : translate("ai.drawer.viewDetail")}
                                                         </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}
+                                                            {translate("ai.drawer.toolInput", { value: item.inputSummary })}
                                                         </Typography>
                                                     )}
                                                     {!!item.outputSummary && (
                                                         <Typography variant="caption" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
-                                                            缁撴灉鎽樿: {item.outputSummary}
+                                                            {translate("ai.drawer.toolOutput", { value: item.outputSummary })}
                                                         </Typography>
                                                     )}
                                                     {!!item.errorMessage && (
                                                         <Typography variant="caption" color="error.main" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
-                                                            閿欒: {item.errorMessage}
+                                                            {translate("ai.drawer.toolError", { value: item.errorMessage })}
                                                         </Typography>
                                                     )}
                                                 </Collapse>
@@ -630,15 +653,15 @@
                     <Box flex={1} display="flex" flexDirection="column" minHeight={0}>
                         <Box px={2} py={1.5}>
                             <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
-                                <Chip size="small" label={`Req: ${runtimeSummary.requestId}`} />
-                                <Chip size="small" label={`Session: ${sessionId || "--"}`} />
-                                <Chip size="small" label={`Prompt: ${runtimeSummary.promptName}`} />
-                                <Chip size="small" label={`Model: ${runtimeSummary.model}`} />
-                                <Chip size="small" label={`MCP: ${runtimeSummary.mountedMcpCount}`} />
-                                <Chip size="small" label={`History: ${persistedMessages.length}`} />
-                                <Chip size="small" label={`Recent: ${runtimeSummary.recentMessageCount}`} />
-                                <Chip size="small" color={runtimeSummary.hasSummary ? "success" : "default"} label={runtimeSummary.hasSummary ? "鏈夋憳瑕�" : "鏃犳憳瑕�"} />
-                                <Chip size="small" color={runtimeSummary.hasFacts ? "info" : "default"} label={runtimeSummary.hasFacts ? "鏈変簨瀹�" : "鏃犱簨瀹�"} />
+                                <Chip size="small" label={translate("ai.drawer.requestMetric", { value: runtimeSummary.requestId })} />
+                                <Chip size="small" label={translate("ai.drawer.sessionMetric", { id: sessionId || "--" })} />
+                                <Chip size="small" label={translate("ai.drawer.promptMetric", { value: runtimeSummary.promptName })} />
+                                <Chip size="small" label={translate("ai.drawer.modelMetric", { value: runtimeSummary.model })} />
+                                <Chip size="small" label={translate("ai.drawer.mcpMetric", { value: runtimeSummary.mountedMcpCount })} />
+                                <Chip size="small" label={translate("ai.drawer.historyMetric", { value: persistedMessages.length })} />
+                                <Chip size="small" label={translate("ai.drawer.recentMetric", { value: runtimeSummary.recentMessageCount })} />
+                                <Chip size="small" color={runtimeSummary.hasSummary ? "success" : "default"} label={translate(runtimeSummary.hasSummary ? "ai.drawer.hasSummary" : "ai.drawer.noSummary")} />
+                                <Chip size="small" color={runtimeSummary.hasFacts ? "info" : "default"} label={translate(runtimeSummary.hasFacts ? "ai.drawer.hasFacts" : "ai.drawer.noFacts")} />
                             </Stack>
                             <Stack direction="row" spacing={1} mt={1.5} flexWrap="wrap" useFlexGap>
                                 {quickLinks.map((item) => (
@@ -659,7 +682,7 @@
                                     onClick={handleRetainLatestRound}
                                     disabled={!sessionId || streaming}
                                 >
-                                    浠呬繚鐣欏綋鍓嶈疆
+                                    {translate("ai.drawer.retainLatestRound")}
                                 </Button>
                                 <Button
                                     size="small"
@@ -669,7 +692,7 @@
                                     onClick={handleClearMemory}
                                     disabled={!sessionId || streaming}
                                 >
-                                    娓呯┖璁板繂
+                                    {translate("ai.drawer.clearMemory")}
                                 </Button>
                             </Stack>
                             {!!runtime?.memorySummary && (
@@ -688,7 +711,7 @@
                             )}
                             {loadingRuntime && (
                                 <Typography variant="body2" color="text.secondary" mt={1}>
-                                    姝e湪鍔犺浇 AI 杩愯鏃朵俊鎭�...
+                                    {translate("ai.drawer.loadingRuntime")}
                                 </Typography>
                             )}
                             {!!drawerError && (
@@ -700,11 +723,20 @@
 
                         <Divider />
 
-                        <Box flex={1} overflow="auto" px={2} py={2} display="flex" flexDirection="column" gap={1.5}>
+                        <Box
+                            ref={messagesContainerRef}
+                            flex={1}
+                            overflow="auto"
+                            px={2}
+                            py={2}
+                            display="flex"
+                            flexDirection="column"
+                            gap={1.5}
+                        >
                             {!messages.length && (
                                 <Paper variant="outlined" sx={{ p: 2, bgcolor: "grey.50" }}>
                                     <Typography variant="body2" color="text.secondary">
-                                        杩欓噷浼氶�氳繃 SSE 娴佸紡杩斿洖 AI 鍥炲銆備綘涔熷彲浠ュ厛鍘讳笂闈㈢殑蹇嵎鍏ュ彛缁存姢鍙傛暟銆丳rompt 鍜� MCP 鎸傝浇銆�
+                                        {translate("ai.drawer.emptyHint")}
                                     </Typography>
                                 </Paper>
                             )}
@@ -728,14 +760,15 @@
                                         }}
                                     >
                                         <Typography variant="caption" display="block" sx={{ opacity: 0.72, mb: 0.5 }}>
-                                            {message.role === "user" ? "浣�" : "AI"}
+                                            {message.role === "user" ? translate("ai.drawer.userRole") : translate("ai.drawer.assistantRole")}
                                         </Typography>
                                         <Typography variant="body2">
-                                            {message.content || (streaming && index === messages.length - 1 ? "鎬濊�冧腑..." : "")}
+                                            {message.content || (streaming && index === messages.length - 1 ? translate("ai.drawer.thinking") : "")}
                                         </Typography>
                                     </Paper>
                                 </Box>
                             ))}
+                            <Box ref={messagesBottomRef} sx={{ height: 1 }} />
                         </Box>
 
                         <Divider />
@@ -743,12 +776,17 @@
                         <Box px={2} py={1.5}>
                             {usage?.elapsedMs != null && (
                                 <Typography variant="caption" color="text.secondary" display="block" mb={0.5}>
-                                    Elapsed: {usage.elapsedMs} ms{usage?.firstTokenLatencyMs != null ? ` / First token: ${usage.firstTokenLatencyMs} ms` : ""}
+                                    {translate("ai.drawer.elapsedMetric", { value: usage.elapsedMs })}
+                                    {usage?.firstTokenLatencyMs != null ? ` / ${translate("ai.drawer.firstTokenMetric", { value: usage.firstTokenLatencyMs })}` : ""}
                                 </Typography>
                             )}
                             {usage?.totalTokens != null && (
                                 <Typography variant="caption" color="text.secondary" display="block" mb={1}>
-                                    Tokens: prompt {usage?.promptTokens ?? 0} / completion {usage?.completionTokens ?? 0} / total {usage?.totalTokens ?? 0}
+                                    {translate("ai.drawer.tokenMetric", {
+                                        prompt: usage?.promptTokens ?? 0,
+                                        completion: usage?.completionTokens ?? 0,
+                                        total: usage?.totalTokens ?? 0,
+                                    })}
                                 </Typography>
                             )}
                             <TextField
@@ -759,17 +797,17 @@
                                 multiline
                                 minRows={3}
                                 maxRows={6}
-                                placeholder="杈撳叆浣犵殑闂锛屾寜 Enter 鍙戦�侊紝Shift + Enter 鎹㈣"
+                                placeholder={translate("ai.drawer.inputPlaceholder")}
                             />
                             <Stack direction="row" spacing={1} justifyContent="flex-end" mt={1.25}>
-                                <Button onClick={() => setInput("")}>娓呯┖杈撳叆</Button>
+                                <Button onClick={() => setInput("")}>{translate("ai.drawer.clearInput")}</Button>
                                 {streaming ? (
                                     <Button variant="outlined" color="warning" startIcon={<StopCircleOutlinedIcon />} onClick={() => stopStream(true)}>
-                                        鍋滄
+                                        {translate("ai.drawer.stop")}
                                     </Button>
                                 ) : (
                                     <Button variant="contained" startIcon={<SendRoundedIcon />} onClick={handleSend}>
-                                        鍙戦��
+                                        {translate("ai.drawer.send")}
                                     </Button>
                                 )}
                             </Stack>
@@ -778,21 +816,21 @@
                 </Box>
             </Box>
             <Dialog open={renameDialog.open} onClose={closeRenameDialog} fullWidth maxWidth="xs">
-                <DialogTitle>閲嶅懡鍚嶄細璇�</DialogTitle>
+                <DialogTitle>{translate("ai.drawer.renameDialogTitle")}</DialogTitle>
                 <DialogContent>
                     <TextField
                         value={renameDialog.title}
                         onChange={(event) => setRenameDialog((prev) => ({ ...prev, title: event.target.value }))}
                         autoFocus
                         margin="dense"
-                        label="浼氳瘽鏍囬"
+                        label={translate("ai.drawer.sessionTitleField")}
                         fullWidth
                     />
                 </DialogContent>
                 <DialogActions>
-                    <Button onClick={closeRenameDialog}>鍙栨秷</Button>
+                    <Button onClick={closeRenameDialog}>{translate("ai.common.cancel")}</Button>
                     <Button onClick={handleRenameSubmit} variant="contained" disabled={streaming || !renameDialog.title.trim()}>
-                        淇濆瓨
+                        {translate("ai.common.save")}
                     </Button>
                 </DialogActions>
             </Dialog>

--
Gitblit v1.9.1