From f2ed508cb4cc323979fccb156a68b9e8e10cf2cd Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 23 Jun 2026 23:07:21 +0800 Subject: [PATCH] fix: persist canvas chat session selection --- components/canvas/CanvasChatPanel.tsx | 100 +++++++++++++++++++++----- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 79e5bfc1b..759a3b92e 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -4,6 +4,7 @@ import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { useEffect, useRef, useState, useCallback, useMemo } from "react"; import { useSession } from "next-auth/react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { createPortal } from "react-dom"; import type { DetailUiProtocol } from "@/lib/detail-ui-protocol"; import type { DetailUiScreenReviewPatch } from "@/lib/detail-ui-protocol"; @@ -1209,6 +1210,16 @@ interface CanvasChatSession { updatedAt: number; } +const CANVAS_CHAT_QUERY_PARAM = "chat"; + +function normalizeCanvasChatSessionId(value?: string | null) { + const raw = String(value || "").trim(); + if (!raw) return null; + + const rawUuid = raw.startsWith("canvas-") ? raw.slice("canvas-".length) : raw; + return isValidChatUuid(rawUuid) ? `canvas-${rawUuid}` : null; +} + export function CanvasChatPanel({ chatId, initialMessages = [], @@ -1234,6 +1245,13 @@ export function CanvasChatPanel({ ); const effectiveAgentEnabled = agentEnabled && !isVideoWorkspace; const { data: session } = useSession(); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const requestedChatSessionId = useMemo( + () => normalizeCanvasChatSessionId(searchParams.get(CANVAS_CHAT_QUERY_PARAM)), + [searchParams] + ); const [input, setInput] = useState(""); const [attachments, setAttachments] = useState([]); const [selectedModelId, setSelectedModelId] = useState( @@ -1299,8 +1317,10 @@ export function CanvasChatPanel({ }); const [detailModeMessageStartIndex, setDetailModeMessageStartIndex] = useState(0); const canvasId = useMemo(() => toRawCanvasChatId(chatId), [chatId]); - const [activeChatId, setActiveChatId] = useState(chatId); - const activeChatIdRef = useRef(chatId); + const [activeChatId, setActiveChatId] = useState( + () => requestedChatSessionId || chatId + ); + const activeChatIdRef = useRef(requestedChatSessionId || chatId); const inFlightChatIdRef = useRef(null); const inFlightRawChatIdRef = useRef(null); const [chatSessions, setChatSessions] = useState([]); @@ -1334,10 +1354,29 @@ export function CanvasChatPanel({ const pendingLaunchRef = useRef(null); const [launchAutoSendTick, setLaunchAutoSendTick] = useState(0); const hasInitializedChatStateRef = useRef(false); + const [loadedChatSessionStorageKey, setLoadedChatSessionStorageKey] = useState( + null + ); const hasInitializedVideoWorkspaceRef = useRef(false); const sessionMenuRef = useRef(null); const historyScrollTimerRef = useRef | null>(null); const forceHistoryHydrationRef = useRef(null); + + const replaceChatSessionQuery = useCallback( + (sessionId: string) => { + const normalized = normalizeCanvasChatSessionId(sessionId); + if (!normalized || typeof window === "undefined") return; + + const params = new URLSearchParams(window.location.search); + params.set(CANVAS_CHAT_QUERY_PARAM, normalized); + const query = params.toString(); + const hash = window.location.hash || ""; + router.replace(`${pathname}${query ? `?${query}` : ""}${hash}`, { + scroll: false, + }); + }, + [pathname, router] + ); // 🆕 标签悬停预览状态 const [tagPreview, setTagPreview] = useState(null); @@ -1657,10 +1696,20 @@ export function CanvasChatPanel({ createdAt: now, updatedAt: now, }; + const requestedSession: CanvasChatSession | null = + requestedChatSessionId && requestedChatSessionId !== rootChatId + ? { + id: requestedChatSessionId, + title: "新对话", + createdAt: now, + updatedAt: now, + } + : null; + const initialChatId = requestedChatSessionId || rootChatId; + const sessionStorageKey = getSessionStorageKey(canvasId, chatScope); try { - const key = getSessionStorageKey(canvasId, chatScope); - const saved = localStorage.getItem(key); + const saved = localStorage.getItem(sessionStorageKey); const parsed = saved ? (JSON.parse(saved) as CanvasChatSession[]) : []; const sanitized = parsed @@ -1672,21 +1721,33 @@ export function CanvasChatPanel({ updatedAt: Number(session.updatedAt) || now, })); - const hasCurrent = sanitized.some((session) => session.id === rootChatId); - const nextSessions = hasCurrent - ? sanitized - : [fallbackSession, ...sanitized]; + let nextSessions = sanitized; + const requiredSessions = requestedSession + ? [fallbackSession, requestedSession] + : [fallbackSession]; + for (const requiredSession of requiredSessions) { + const hasSession = nextSessions.some( + (session) => session.id === requiredSession.id + ); + if (!hasSession) { + nextSessions = [requiredSession, ...nextSessions]; + } + } const sorted = [...nextSessions].sort((a, b) => b.updatedAt - a.updatedAt); setChatSessions(sorted); - setActiveChatId(rootChatId); - localStorage.setItem(key, JSON.stringify(sorted)); + setActiveChatId(initialChatId); + localStorage.setItem(sessionStorageKey, JSON.stringify(sorted)); + setLoadedChatSessionStorageKey(sessionStorageKey); } catch (error) { console.error("[CanvasChat] Failed to load chat sessions:", error); - setChatSessions([fallbackSession]); - setActiveChatId(rootChatId); + setChatSessions( + requestedSession ? [requestedSession, fallbackSession] : [fallbackSession] + ); + setActiveChatId(initialChatId); + setLoadedChatSessionStorageKey(sessionStorageKey); } - }, [canvasId, chatId, chatScope]); + }, [canvasId, chatId, chatScope, requestedChatSessionId]); useEffect(() => { if (!isSessionMenuOpen) return; @@ -1700,15 +1761,18 @@ export function CanvasChatPanel({ }, [isSessionMenuOpen]); useEffect(() => { + const sessionStorageKey = getSessionStorageKey(canvasId, chatScope); + if (loadedChatSessionStorageKey !== sessionStorageKey) return; + try { localStorage.setItem( - getSessionStorageKey(canvasId, chatScope), + sessionStorageKey, JSON.stringify(chatSessions) ); } catch (error) { console.error("[CanvasChat] Failed to save chat sessions:", error); } - }, [canvasId, chatScope, chatSessions]); + }, [canvasId, chatScope, chatSessions, loadedChatSessionStorageKey]); // Canvas store const addNode = useCanvasStore((state) => state.addNode); @@ -10075,8 +10139,9 @@ export function CanvasChatPanel({ releaseSendGuard(activeChatId); setChatSessions((prevSessions) => [newSession, ...prevSessions]); setActiveChatId(newChatSessionId); + replaceChatSessionQuery(newChatSessionId); toast.success("已新建聊天窗口"); - }, [activeChatId, chatSessions.length, releaseSendGuard]); + }, [activeChatId, chatSessions.length, releaseSendGuard, replaceChatSessionQuery]); const switchToSession = useCallback((sessionId: string) => { if (!sessionId || sessionId === activeChatId) { @@ -10086,9 +10151,10 @@ export function CanvasChatPanel({ forceHistoryHydrationRef.current = toRawCanvasChatId(sessionId); releaseSendGuard(activeChatId); setActiveChatId(sessionId); + replaceChatSessionQuery(sessionId); setIsSessionMenuOpen(false); toast.success("已切换聊天窗口"); - }, [activeChatId, releaseSendGuard]); + }, [activeChatId, releaseSendGuard, replaceChatSessionQuery]); const activeSessionTitle = useMemo(() => { const current = chatSessions.find((session) => session.id === activeChatId);