diff --git a/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx index 9b9cb59f..13c93271 100644 --- a/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx @@ -39,20 +39,20 @@ export default function AgentChatPage() { const { agent } = useAgent(agent_name); - const { threadId, isNewThread, setIsNewThread } = useThreadChat(); + const { threadId, routeKind } = useThreadChat(); + const isNewRoute = routeKind === "new"; const [settings, setSettings] = useLocalSettings(); const { showNotification } = useNotification(); const [thread, sendMessage] = useThreadStream({ - threadId: isNewThread ? undefined : threadId, + threadId: isNewRoute ? undefined : threadId, context: { ...settings.context, agent_name: agent_name }, - onStart: () => { - setIsNewThread(false); + onStart: (currentThreadId) => { // ! Important: Never use next.js router for navigation in this case, otherwise it will cause the thread to re-mount and lose all states. Use native history API instead. history.replaceState( null, "", - `/workspace/agents/${agent_name}/chats/${threadId}`, + `/workspace/agents/${agent_name}/chats/${currentThreadId}`, ); }, onFinish: (state) => { @@ -89,13 +89,13 @@ export default function AgentChatPage() { MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM; return ( - +
- +
@@ -124,7 +124,7 @@ export default function AgentChatPage() { - +
@@ -132,8 +132,8 @@ export default function AgentChatPage() {
@@ -143,8 +143,8 @@ export default function AgentChatPage() {
) } diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index ad95d9f2..ae45f422 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -1,7 +1,7 @@ "use client"; import { Ticker } from "@tombcato/smart-ticker"; -import { FilesIcon, ListTodoIcon, XIcon } from "lucide-react"; +import { FilesIcon, XIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; @@ -23,11 +23,9 @@ import { } from "@/components/workspace/artifacts"; import { useThreadChat } from "@/components/workspace/chats"; // import { DevTodoList } from "@/components/workspace/dev-todo-list"; -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 { ThreadTitle } from "@/components/workspace/thread-title"; import { Tooltip } from "@/components/workspace/tooltip"; import { useSpecificChatMode } from "@/components/workspace/use-chat-mode"; import { Welcome } from "@/components/workspace/welcome"; @@ -60,42 +58,25 @@ export default function ChatPage() { setArtifacts, select: selectArtifact, selectedArtifact, - deselect: deselectArtifact, - setFullscreen: setArtifactsFullscreen, fullscreen, } = useArtifacts(); - const { threadId, isNewThread, setIsNewThread, isMock, showWelcomeStyle } = + const { threadId, routeKind, viewMode, lifecycleMode, isMock } = useThreadChat(); - - // 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/is_chatting 参数。 - const shouldRenderHistory = !showWelcomeStyle; - const safeThreadId = useMemo(() => { - if (!threadId || threadId === "new") { - return undefined; - } - return threadId; - }, [threadId]); - // `/new` + `thread_id` now reuses the pre-created thread, instead of creating - // a new session on first submit. - const createNewSession = useMemo( - () => isNewThread && !safeThreadId, - [isNewThread, safeThreadId], - ); + const isWelcomeView = viewMode === "welcome"; + const isNewRoute = routeKind === "new"; + const shouldRenderHistory = viewMode === "chat"; const [isThreadInitReady, setIsThreadInitReady] = useState(false); const streamThreadId = useMemo(() => { - if (!safeThreadId) { + if (!threadId) { return undefined; } - // In /new flow, defer history loading until thread init is finished: - // delete -> create -> history. - if (isNewThread && !isThreadInitReady) { + if (lifecycleMode === "reset_on_entry" && !isThreadInitReady) { return undefined; } - return safeThreadId; - }, [isNewThread, isThreadInitReady, safeThreadId]); + return threadId; + }, [isThreadInitReady, lifecycleMode, threadId]); const apiClient = useMemo(() => getAPIClient(isMock), [isMock]); - const warnedMissingThreadIdRef = useRef(false); const initializedThreadRef = useRef(null); const threadInitPromiseRef = useRef | null>(null); @@ -135,30 +116,30 @@ export default function ChatPage() { }, []); useEffect(() => { - if (!isNewThread) { - warnedMissingThreadIdRef.current = false; + if (lifecycleMode === "preserve") { + initializedThreadRef.current = null; + threadInitPromiseRef.current = null; setIsThreadInitReady(true); return; } - if (!safeThreadId) { - if (!warnedMissingThreadIdRef.current) { - warnedMissingThreadIdRef.current = true; - toast.error(t.chatPage.missingThreadIdForCreate); - } + + if (lifecycleMode !== "reset_on_entry" || !threadId) { + initializedThreadRef.current = null; + threadInitPromiseRef.current = null; setIsThreadInitReady(false); return; } - warnedMissingThreadIdRef.current = false; - if (initializedThreadRef.current === safeThreadId) return; - initializedThreadRef.current = safeThreadId; + + if (initializedThreadRef.current === threadId) return; + initializedThreadRef.current = threadId; setIsThreadInitReady(false); const initPromise = apiClient.threads - .delete(safeThreadId) + .delete(threadId) .catch(() => undefined) .then(() => apiClient.threads.create({ - threadId: safeThreadId, + threadId, ifExists: "do_nothing", }), ) @@ -179,10 +160,9 @@ export default function ChatPage() { }); }, [ apiClient, - isNewThread, - safeThreadId, + lifecycleMode, + threadId, t.chatPage.createSessionFailed, - t.chatPage.missingThreadIdForCreate, ]); // 监听宿主页 selectedSkill 消息 @@ -190,21 +170,16 @@ export default function ChatPage() { skillError: selectedSkillError, clearSkillError: clearSelectedSkillError, isBootstrapping: isSelectedSkillBootstrapping, - } = useSelectedSkillListener({ threadId: safeThreadId ?? null }); + } = useSelectedSkillListener({ threadId: threadId ?? null }); // 对话行为控制器 const [thread, sendMessage, isUploading] = useThreadStream({ threadId: streamThreadId, context: settings.context, - createNewSession, isMock, - // 发送消息后跳转的逻辑 onStart: (currentThreadId) => { - setIsNewThread(false); - // if (!shouldStayOnNewRoute) { - // Keep /new in history so router.back() can return to it. - router.replace(`/workspace/chats/${currentThreadId}?is_chatting=true`); - // } - // history.pushState(null, "", pathOfThread(currentThreadId)); + if (isNewRoute || viewMode === "welcome") { + router.replace(`/workspace/chats/${currentThreadId}?is_chatting=true`); + } }, onFinish: (state) => { if (document.hidden || !document.hasFocus()) { @@ -258,7 +233,7 @@ export default function ChatPage() { ]); useEffect(() => { - const pageTitle = isNewThread + const pageTitle = isNewRoute ? t.pages.newChat : thread.values?.title && thread.values.title !== "Untitled" ? thread.values.title @@ -269,7 +244,7 @@ export default function ChatPage() { document.title = `${pageTitle} - ${t.pages.appName}`; } }, [ - isNewThread, + isNewRoute, t.common.loading, t.pages.newChat, t.pages.untitled, @@ -312,30 +287,30 @@ export default function ChatPage() { if (isSelectedSkillBootstrapping) { return; } - if (isNewThread && !safeThreadId) { + if (lifecycleMode === "missing_thread_id") { toast.error(t.chatPage.missingThreadIdForSend); return; } - if (isNewThread && safeThreadId) { + if (lifecycleMode === "reset_on_entry") { await threadInitPromiseRef.current; } - if (isNewThread && safeThreadId && !isThreadInitReady) { + if (lifecycleMode === "reset_on_entry" && !isThreadInitReady) { return; } setHasSubmitted(true); - if (safeThreadId && (isNewThread || showWelcomeStyle)) { - router.replace(`/workspace/chats/${safeThreadId}?is_chatting=true`); + if (threadId && viewMode === "welcome") { + router.replace(`/workspace/chats/${threadId}?is_chatting=true`); } - void sendMessage(safeThreadId, message); + void sendMessage(threadId, message); }, [ - isNewThread, isThreadInitReady, isSelectedSkillBootstrapping, + lifecycleMode, router, - safeThreadId, sendMessage, - showWelcomeStyle, + threadId, + viewMode, t.chatPage.missingThreadIdForSend, ], ); @@ -343,24 +318,8 @@ export default function ChatPage() { await thread.stop(); }, [thread]); - const resetNewSessionState = useCallback(() => { - setIsNewThread(true); - setHasSubmitted(false); - setHistoryCutoff(null); - setArtifacts([]); - deselectArtifact(); - setArtifactsOpen(false); - setArtifactsFullscreen(false); - }, [ - deselectArtifact, - setArtifacts, - setArtifactsFullscreen, - setArtifactsOpen, - setIsNewThread, - ]); - return ( - +
@@ -466,7 +425,7 @@ export default function ChatPage() {
@@ -508,7 +467,7 @@ export default function ChatPage() {
@@ -516,7 +475,7 @@ export default function ChatPage() { ) : (
@@ -548,7 +507,7 @@ export default function ChatPage() {
@@ -570,19 +529,19 @@ export default function ChatPage() {
- {!(showWelcomeStyle && thread.isThreadLoading) ? ( + {!(isWelcomeView && thread.isThreadLoading) ? ( <> - {showWelcomeStyle && !hasSubmitted && ( + {isWelcomeView && !hasSubmitted && ( )}
@@ -602,7 +561,7 @@ export default function ChatPage() { env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || isSelectedSkillBootstrapping || isUploading || - (isNewThread && !safeThreadId) + lifecycleMode === "missing_thread_id" } onContextChange={(context) => setSettings("context", context)} onSubmit={handleSubmit} @@ -657,14 +616,13 @@ export default function ChatPage() { type: POST_MESSAGE_TYPES.IS_CHATTING, isChatting: false, }); - // 始终复用 query 中的 thread_id。 - const nextQuery = new URLSearchParams(); - if (threadId && threadId !== "new") { - nextQuery.set("thread_id", threadId); - } - // /workspace/chats/${threadId}?is_chatting=false + // NOTE: `/workspace/chats/new?thread_id=...` is the + // historical "reset welcome" route. Keep it until the chat + // URL contract is redesigned. router.replace( - `/workspace/chats/new?thread_id=${threadId}`, + threadId + ? `/workspace/chats/new?thread_id=${threadId}` + : "/workspace/chats/new", ); }} > diff --git a/frontend/src/components/workspace/chats/chat-box.tsx b/frontend/src/components/workspace/chats/chat-box.tsx index 70f1b5b8..b87262c6 100644 --- a/frontend/src/components/workspace/chats/chat-box.tsx +++ b/frontend/src/components/workspace/chats/chat-box.tsx @@ -5,12 +5,12 @@ import type { GroupImperativeHandle } from "react-resizable-panels"; import { ConversationEmptyState } from "@/components/ai-elements/conversation"; import { Button } from "@/components/ui/button"; -import { sanitizeArtifactPaths } from "@/core/artifacts/utils"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; +import { sanitizeArtifactPaths } from "@/core/artifacts/utils"; import { useI18n } from "@/core/i18n/hooks"; import { env } from "@/env"; import { cn } from "@/lib/utils"; diff --git a/frontend/src/components/workspace/chats/thread-chat-route.test.ts b/frontend/src/components/workspace/chats/thread-chat-route.test.ts new file mode 100644 index 00000000..90f99634 --- /dev/null +++ b/frontend/src/components/workspace/chats/thread-chat-route.test.ts @@ -0,0 +1,94 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +const { normalizeThreadId, resolveThreadChatRouteState } = await import( + new URL("./thread-chat-route.ts", import.meta.url).href +); + +void test("resolveThreadChatRouteState treats /chats/new as invalid new", () => { + const result = resolveThreadChatRouteState({ + pathname: "/workspace/chats/new", + paramsThreadId: "new", + queryThreadId: null, + isChatting: null, + }); + + assert.deepEqual(result, { + threadId: undefined, + routeKind: "new", + viewMode: "welcome", + lifecycleMode: "missing_thread_id", + }); +}); + +void test("resolveThreadChatRouteState treats /chats/new?thread_id=T1 as reset welcome", () => { + const result = resolveThreadChatRouteState({ + pathname: "/workspace/chats/new", + paramsThreadId: "new", + queryThreadId: "T1", + isChatting: null, + }); + + assert.deepEqual(result, { + threadId: "T1", + routeKind: "new", + viewMode: "welcome", + lifecycleMode: "reset_on_entry", + }); +}); + +void test("resolveThreadChatRouteState treats /chats/:id without is_chatting as preserve welcome", () => { + const result = resolveThreadChatRouteState({ + pathname: "/workspace/chats/T1", + paramsThreadId: "T1", + queryThreadId: null, + isChatting: null, + }); + + assert.deepEqual(result, { + threadId: "T1", + routeKind: "thread", + viewMode: "welcome", + lifecycleMode: "preserve", + }); +}); + +void test("resolveThreadChatRouteState treats /chats/:id?is_chatting=false as preserve welcome", () => { + const result = resolveThreadChatRouteState({ + pathname: "/workspace/chats/T1", + paramsThreadId: "T1", + queryThreadId: null, + isChatting: "false", + }); + + assert.deepEqual(result, { + threadId: "T1", + routeKind: "thread", + viewMode: "welcome", + lifecycleMode: "preserve", + }); +}); + +void test("resolveThreadChatRouteState treats /chats/:id?is_chatting=true as chat view", () => { + const result = resolveThreadChatRouteState({ + pathname: "/workspace/chats/T1", + paramsThreadId: "T1", + queryThreadId: null, + isChatting: "true", + }); + + assert.deepEqual(result, { + threadId: "T1", + routeKind: "thread", + viewMode: "chat", + lifecycleMode: "preserve", + }); +}); + +void test("normalizeThreadId drops reserved and empty values", () => { + assert.equal(normalizeThreadId(""), undefined); + assert.equal(normalizeThreadId("new"), undefined); + assert.equal(normalizeThreadId(" undefined "), undefined); + assert.equal(normalizeThreadId(" null "), undefined); + assert.equal(normalizeThreadId(" T1 "), "T1"); +}); diff --git a/frontend/src/components/workspace/chats/thread-chat-route.ts b/frontend/src/components/workspace/chats/thread-chat-route.ts new file mode 100644 index 00000000..a33a9e6f --- /dev/null +++ b/frontend/src/components/workspace/chats/thread-chat-route.ts @@ -0,0 +1,77 @@ +export type RouteKind = "new" | "thread"; +export type ViewMode = "welcome" | "chat"; +export type LifecycleMode = + | "missing_thread_id" + | "reset_on_entry" + | "preserve"; + +export type ThreadChatRouteState = { + threadId?: string; + routeKind: RouteKind; + viewMode: ViewMode; + lifecycleMode: LifecycleMode; +}; + +export type ResolveThreadChatRouteStateInput = { + pathname: string; + paramsThreadId?: string | null; + queryThreadId?: string | null; + isChatting?: string | null; +}; + +export function resolveThreadChatRouteState({ + pathname, + paramsThreadId, + queryThreadId, + isChatting, +}: ResolveThreadChatRouteStateInput): ThreadChatRouteState { + const pathThreadId = + paramsThreadId?.trim() ?? extractThreadIdFromPathname(pathname); + const routeKind: RouteKind = pathThreadId === "new" ? "new" : "thread"; + const threadId = + routeKind === "new" + ? normalizeThreadId(queryThreadId) + : normalizeThreadId(pathThreadId); + const viewMode: ViewMode = + routeKind === "new" ? "welcome" : isChatting === "true" ? "chat" : "welcome"; + const lifecycleMode: LifecycleMode = + routeKind === "new" + ? threadId + ? "reset_on_entry" + : "missing_thread_id" + : "preserve"; + + return { + threadId, + routeKind, + viewMode, + lifecycleMode, + }; +} + +export function normalizeThreadId(value?: string | null): string | undefined { + if (!value) { + return undefined; + } + const normalized = value.trim().toLowerCase(); + if ( + normalized.length === 0 || + normalized === "new" || + normalized === "undefined" || + normalized === "null" + ) { + return undefined; + } + return value.trim(); +} + +export function extractThreadIdFromPathname( + pathname: string, +): string | undefined { + const parts = pathname.split("?")[0]?.split("/") ?? []; + const idx = parts.lastIndexOf("chats"); + if (idx >= 0 && parts.length > idx + 1) { + return parts[idx + 1]; + } + return undefined; +} diff --git a/frontend/src/components/workspace/chats/use-thread-chat.ts b/frontend/src/components/workspace/chats/use-thread-chat.ts index e58cbc7d..553767ae 100644 --- a/frontend/src/components/workspace/chats/use-thread-chat.ts +++ b/frontend/src/components/workspace/chats/use-thread-chat.ts @@ -1,88 +1,33 @@ "use client"; import { useParams, usePathname, useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; + +import { resolveThreadChatRouteState } from "./thread-chat-route"; + +export type { + LifecycleMode, + RouteKind, + ThreadChatRouteState, + ViewMode, +} from "./thread-chat-route"; export function useThreadChat() { const pathname = usePathname(); const params = useParams<{ thread_id: string }>(); const searchParams = useSearchParams(); - const threadIdFromSearchParams = searchParams.get("thread_id")?.trim(); - // showWelcomeStyle的子判断 - const isChattingFromQuery = (() => { - const isChatting = searchParams.get("is_chatting"); - return isChatting === "true"; - })(); - // 兜底:当 params 还未就绪时,从 pathname 解析 thread_id。 - const threadIdFromPathname = (() => { - const parts = pathname.split("?")[0]?.split("/") ?? []; - const idx = parts.lastIndexOf("chats"); - if (idx >= 0 && parts.length > idx + 1) { - return parts[idx + 1]; - } - return undefined; - })(); - const rawPathThreadId = params?.thread_id ?? threadIdFromPathname; - - const isNewRoute = rawPathThreadId === "new"; - const threadIdFromPathOrParams = isNewRoute - ? normalizeThreadId(threadIdFromSearchParams) - : normalizeThreadId(rawPathThreadId); - // console.log("[useThreadChat] pathname", pathname); - // console.log("[useThreadChat] params.thread_id", params?.thread_id); - // console.log("[useThreadChat] threadIdFromPathname", threadIdFromPathname); - // console.log("[useThreadChat] threadIdFromPath", threadIdFromPath); - // New session is only controlled by `/workspace/chats/new`. - const [isNewThread, setIsNewThread] = useState(() => isNewRoute); - - const [showWelcomeStyle, setShowWelcomeStyle] = useState(() => { - return isNewRoute || !isChattingFromQuery; - }); - // console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath); - - const [threadId, setThreadId] = useState(() => { - return threadIdFromPathOrParams ?? ""; - }); - - useEffect(() => { - // 记住最近一次有效的 thread_id,供下次加载兜底使用。 - if (threadId && threadId !== "new" && typeof window !== "undefined") { - window.sessionStorage.setItem("workspace.thread_id", threadId); - } - setIsNewThread(isNewRoute); - // Prefer path thread id, fall back to query thread_id when path is /new. - setThreadId(threadIdFromPathOrParams ?? ""); - setShowWelcomeStyle(isNewRoute || !isChattingFromQuery); - }, [ - isNewRoute, + // NOTE: The current chat URLs are a historical compatibility layer. + // `/workspace/chats/new?thread_id=...` and `?is_chatting=` should eventually + // be redesigned, but this hook remains the single source of truth for the + // current contract until that URL cleanup happens. + const routeState = resolveThreadChatRouteState({ pathname, - searchParams, - isChattingFromQuery, - threadId, - threadIdFromPathOrParams, - ]); - const isMock = searchParams.get("mock") === "true"; + paramsThreadId: params?.thread_id, + queryThreadId: searchParams.get("thread_id"), + isChatting: searchParams.get("is_chatting"), + }); + return { - threadId, - isNewThread, - setIsNewThread, - isMock, - showWelcomeStyle, + ...routeState, + isMock: searchParams.get("mock") === "true", }; } - -function normalizeThreadId(value?: string | null): string | undefined { - if (!value) return undefined; - return isValidThreadId(value) ? value.trim() : undefined; -} - -function isValidThreadId(value?: string | null): value is string { - if (!value) return false; - const normalized = value.trim().toLowerCase(); - return ( - normalized.length > 0 && - normalized !== "new" && - normalized !== "undefined" && - normalized !== "null" - ); -} diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index 4e350a4f..bb788c29 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -218,7 +218,7 @@ export function InputBox({ status, context, extraHeader, - showWelcomeStyle, + isWelcomeView, hasSubmitted, initialValue, onContextChange, @@ -237,7 +237,7 @@ export function InputBox({ mode: "flash" | "thinking" | "pro" | "ultra" | undefined; }; extraHeader?: React.ReactNode; - showWelcomeStyle: boolean; + isWelcomeView: boolean; hasSubmitted?: boolean; initialValue?: string; onContextChange?: ( @@ -294,14 +294,14 @@ export function InputBox({ const [isInputToolsTourReady, setIsInputToolsTourReady] = useState(false); const { data: referenceFilesData } = useReferenceFiles(threadIdFromProps); - // isNewThread 时禁用收缩,始终保持展开(除非已提交消息) + // Welcome view 时禁用收缩,始终保持展开(除非已提交消息) const effectiveIsFocused = - ((showWelcomeStyle ?? false) && !hasSubmitted) || isFocused; + ((isWelcomeView ?? false) && !hasSubmitted) || isFocused; const shouldShowSuggestionList = - showWelcomeStyle && !hasSubmitted && searchParams.get("mode") !== "skill"; + isWelcomeView && !hasSubmitted && searchParams.get("mode") !== "skill"; useEffect(() => { - if (!showWelcomeStyle || hasSubmitted) { + if (!isWelcomeView || hasSubmitted) { setIsInputToolsTourReady(false); return; } @@ -317,7 +317,7 @@ export function InputBox({ }); return () => window.cancelAnimationFrame(frameId); }, [ - showWelcomeStyle, + isWelcomeView, hasSubmitted, shouldShowSuggestionList, iframeSkill.isBootstrapping, @@ -325,7 +325,7 @@ export function InputBox({ ]); useEffect(() => { - if (!showWelcomeStyle || hasSubmitted || !isInputToolsTourReady) { + if (!isWelcomeView || hasSubmitted || !isInputToolsTourReady) { setIsInputToolsTourOpen(false); return; } @@ -337,7 +337,7 @@ export function InputBox({ if (!hasSeenTourForCurrentThread) { setIsInputToolsTourOpen(true); } - }, [showWelcomeStyle, hasSubmitted, isInputToolsTourReady, threadId]); + }, [isWelcomeView, hasSubmitted, isInputToolsTourReady, threadId]); const finishInputToolsTour = useCallback(() => { const seenState = parseInputToolsTourSeenState( @@ -515,7 +515,7 @@ export function InputBox({ return; } setIsFocused(false); - if (showWelcomeStyle) { + if (isWelcomeView) { sendToParent({ type: POST_MESSAGE_TYPES.IS_CHATTING, isChatting: true, @@ -529,7 +529,7 @@ export function InputBox({ setReferences([]); }, [ - showWelcomeStyle, + isWelcomeView, onSubmit, onStop, references, @@ -760,7 +760,7 @@ export function InputBox({ return () => controller.abort(); */ - }, [disabled, showWelcomeStyle, threadId]); + }, [disabled, isWelcomeView, threadId]); return (
*/} - {showWelcomeStyle && ( + {isWelcomeView && (
)} - {!showWelcomeStyle && ( + {!isWelcomeView && (
0) && (
diff --git a/frontend/src/components/workspace/thread-title.tsx b/frontend/src/components/workspace/thread-title.tsx index 201b18f4..640f8619 100644 --- a/frontend/src/components/workspace/thread-title.tsx +++ b/frontend/src/components/workspace/thread-title.tsx @@ -18,7 +18,8 @@ export function ThreadTitle({ threadTitle?: string; }) { const { t } = useI18n(); - const { isNewThread } = useThreadChat(); + const { routeKind } = useThreadChat(); + const isNewRoute = routeKind === "new"; useEffect(() => { if (!thread) { return; @@ -27,7 +28,7 @@ export function ThreadTitle({ if (thread.values?.title) { _title = thread.values.title; - } else if (isNewThread) { + } else if (isNewRoute) { _title = t.pages.newChat; } if (thread.isThreadLoading) { @@ -36,7 +37,7 @@ export function ThreadTitle({ document.title = `${_title} - ${t.pages.appName}`; } }, [ - isNewThread, + isNewRoute, t.pages.newChat, t.pages.untitled, t.pages.appName, diff --git a/frontend/src/core/threads/utils.ts b/frontend/src/core/threads/utils.ts index 0915dede..22510fa8 100644 --- a/frontend/src/core/threads/utils.ts +++ b/frontend/src/core/threads/utils.ts @@ -2,33 +2,10 @@ import type { Message } from "@langchain/langgraph-sdk"; import type { AgentThread } from "./types"; -export interface ThreadQueryIntentInput { - pathThreadId?: string | null; - queryThreadId?: string | null; - isNewRoute?: boolean; -} - -export interface ThreadQueryIntent { - threadId: string | undefined; - isNewThread: boolean; - showWelcomeStyle: boolean; -} - export function pathOfThread(threadId: string) { return `/workspace/chats/${threadId}`; } -function normalizeThreadId(value?: string | null): string | undefined { - if (!value) { - return undefined; - } - const trimmed = value.trim(); - if (!trimmed || trimmed === "new") { - return undefined; - } - return trimmed; -} - export function textOfMessage(message: Message) { if (typeof message.content === "string") { return message.content;