From 92b6bcc5fb63abf2fd8a9c04f8ec7de8b4f84a24 Mon Sep 17 00:00:00 2001 From: MT-Mint <798521692@qq.com> Date: Sat, 9 May 2026 11:14:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(ThreadMemoryPanel):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E8=AE=B0=E5=BF=86=E4=B8=8B=E6=8B=89=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E5=B9=B6=E5=AE=8C=E6=88=90=20i18n=20=E6=8E=A5?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/workspace/chats/[thread_id]/page.tsx | 2 - .../src/components/workspace/input-box.tsx | 23 ++- .../workspace/thread-memory-panel.tsx | 138 +++++++++++++++++ .../workspace/thread-memory-test-panel.tsx | 142 ------------------ frontend/src/core/i18n/locales/en-US.ts | 21 +++ frontend/src/core/i18n/locales/types.ts | 21 +++ frontend/src/core/i18n/locales/zh-CN.ts | 21 +++ 7 files changed, 223 insertions(+), 145 deletions(-) create mode 100644 frontend/src/components/workspace/thread-memory-panel.tsx delete mode 100644 frontend/src/components/workspace/thread-memory-test-panel.tsx diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index dcd226e8..b8e1f3c6 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -27,7 +27,6 @@ import { IframeTestPanel } from "@/components/workspace/iframe-test-panel"; import { InputBox } from "@/components/workspace/input-box"; import { MessageList } from "@/components/workspace/messages"; import { ThreadContext } from "@/components/workspace/messages/context"; -import { ThreadMemoryTestPanel } from "@/components/workspace/thread-memory-test-panel"; import { ThreadTitle } from "@/components/workspace/thread-title"; import { Tooltip } from "@/components/workspace/tooltip"; import { useSpecificChatMode } from "@/components/workspace/use-chat-mode"; @@ -706,7 +705,6 @@ export default function ChatPage() { {/* MARK: 开发测试:iframe 通信功能测试面板 */} {/* {process.env.NODE_ENV !== "production" && } */} - {/* */} ); diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index 2c93afe7..32f2347c 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -4,6 +4,7 @@ import type { ChatStatus } from "ai"; import { Tour } from "antd"; import { CheckIcon, + BrainIcon, GraduationCapIcon, LightbulbIcon, Loader2Icon, @@ -97,6 +98,7 @@ import { Suggestion, Suggestions } from "../ai-elements/suggestion"; import { ScrollArea } from "../ui/scroll-area"; import { ModeHoverGuide } from "./mode-hover-guide"; +import { ThreadMemoryPanel } from "./thread-memory-panel"; import { Tooltip } from "./tooltip"; @@ -280,6 +282,7 @@ export function InputBox({ null, ); const [isFocused, setIsFocused] = useState(false); + const [memoryPanelOpen, setMemoryPanelOpen] = useState(false); const [references, setReferences] = useState([]); const [mentionQuery, setMentionQuery] = useState(""); const [mentionOpen, setMentionOpen] = useState(false); @@ -293,7 +296,8 @@ export function InputBox({ const { data: referenceFilesData } = useReferenceFiles(threadIdFromProps); // Welcome 态下禁用收缩,始终保持展开 - const effectiveIsFocused = (showWelcomeStyle ?? false) || isFocused; + const effectiveIsFocused = + (showWelcomeStyle ?? false) || isFocused || memoryPanelOpen; const shouldShowSuggestionList = showWelcomeStyle && searchParams.get("mode") !== "skill"; @@ -965,6 +969,7 @@ export function InputBox({ /> )} + {/* {!showWelcomeStyle && (
+ {/* 记忆按钮 */} +
+ + + + + + + + + + +
(null); + const [loadingSummary, setLoadingSummary] = useState(false); + const [savingSummary, setSavingSummary] = useState(false); + const [deletingMemory, setDeletingMemory] = useState(false); + const { t } = useI18n(); + if (!threadId || threadId === "new") return null; + + const handleLoadMemorySummary = async () => { + setLoadingSummary(true); + try { + const res = await fetch( + `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/memory-summary`, + ); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = (await res.json()) as { summary: string; memoryVersion: number }; + setMemorySummary(data.summary ?? ""); + setMemoryVersion(data.memoryVersion ?? 0); + toast.success(t.threadMemoryPanel.toastLoadSuccess); + } catch { + toast.error(t.threadMemoryPanel.toastLoadFailed); + } finally { + setLoadingSummary(false); + } + }; + + const handleSaveMemorySummary = async () => { + if (memoryVersion == null) return; + setSavingSummary(true); + try { + const res = await fetch( + `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/memory-summary`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ summary: memorySummary, memoryVersion }), + }, + ); + if (res.status === 409) { + toast.error(t.threadMemoryPanel.toastConflict); + return; + } + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = (await res.json()) as { memoryVersion?: number }; + if (typeof data.memoryVersion === "number") setMemoryVersion(data.memoryVersion); + toast.success(t.threadMemoryPanel.toastSaveSuccess); + } catch { + toast.error(t.threadMemoryPanel.toastSaveFailed); + } finally { + setSavingSummary(false); + } + }; + + const handleDeleteMemory = async () => { + setDeletingMemory(true); + try { + const res = await fetch( + `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/memory`, + { method: "DELETE" }, + ); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + setMemorySummary(""); + setMemoryVersion(0); + toast.success(t.threadMemoryPanel.toastDeleteSuccess); + } catch { + toast.error(t.threadMemoryPanel.toastDeleteFailed); + } finally { + setDeletingMemory(false); + } + }; + + return ( +
+
+ {t.threadMemoryPanel.title} +
+
+
+ + + +
+
+ {t.threadMemoryPanel.threadId}: {threadId.slice(0, 8)}... |{" "} + {t.threadMemoryPanel.version}:{" "} + {memoryVersion == null ? t.threadMemoryPanel.unavailableVersion : memoryVersion} +
+