fix(chat): xclaw_used=false 不加载历史且 /new 先空置

This commit is contained in:
肖应宇 2026-03-31 10:09:46 +08:00
parent ea396bb1af
commit ad5c77e534
4 changed files with 139 additions and 39 deletions

67
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,67 @@
{
"window.title": "${activeEditorShort}${separator}${separator}deer-flow/frontend",
"todo-tree.regex.regex": "((%|#|//|<!--|\\{/\\*|^\\s*\\*)\\s*($TAGS)|^\\s*- \\[ \\])",
"todo-tree.general.tags": [
"TODO:",
"BUG:",
"TAG:",
"DONE:",
"MARK:",
"TEST:",
"XXX:"
],
"todo-tree.regex.regexCaseSensitive": false,
"todo-tree.highlights.defaultHighlight": {
"foreground": "#000000",
"background": "#fff700",
"icon": "check",
"rulerColour": "#fff700",
"type": "tag",
"iconColour": "#fff700"
},
"todo-tree.highlights.customHighlight": {
"TODO:": {
"icon": "todo",
"background": "#fff700",
"rulerColour": "#fff700",
"iconColour": "#fff700"
},
"BUG:": {
"background": "#eb5c5c",
"icon": "bug",
"rulerColour": "#eb5c5c",
"iconColour": "#eb5c5c"
},
"TAG:": {
"background": "#38b2f4",
"icon": "tag",
"rulerColour": "#38b2f4",
"iconColour": "#38b2f4",
"rulerLane": "full"
},
"DONE:": {
"background": "#5eec95",
"icon": "check",
"rulerColour": "#5eec95",
"iconColour": "#5eec95"
},
"MARK:": {
"background": "#f90",
"icon": "note",
"rulerColour": "#f90",
"iconColour": "#f90"
},
"TEST:": {
"background": "#df7be6",
"icon": "flame",
"rulerColour": "#df7be6",
"iconColour": "#df7be6"
},
"XXX:": {
"background": "#d65d8e",
"icon": "versions",
"rulerColour": "#d65d8e",
"iconColour": "#d65d8e"
}
}
}

View File

@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { ConversationEmptyState } from "@/components/ai-elements/conversation"; import { ConversationEmptyState } from "@/components/ai-elements/conversation";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { import {
DevDialog, DevDialog,
DevDialogContent, DevDialogContent,
@ -57,13 +58,16 @@ export default function ChatPage() {
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat(); const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
// Submission strategy is controlled by `isnew` query param only. // Submission strategy:
// - isnew=false: reuse existing thread // - xclaw_used=true: follow `isnew` (isnew=false => reuse existing thread)
// - otherwise: create/start a new session // - xclaw_used!=true: always create/start a new session (no history)
const createNewSession = useMemo(() => { const createNewSession = useMemo(() => {
if (!isNewThread) { if (!isNewThread) {
return false; return false;
} }
if (searchParams.get("xclaw_used")?.trim().toLowerCase() !== "true") {
return true;
}
return searchParams.get("isnew")?.trim().toLowerCase() !== "false"; return searchParams.get("isnew")?.trim().toLowerCase() !== "false";
}, [isNewThread, searchParams]); }, [isNewThread, searchParams]);
const streamThreadId = useMemo(() => { const streamThreadId = useMemo(() => {
@ -110,6 +114,7 @@ export default function ChatPage() {
}, [thread.values?.title]); }, [thread.values?.title]);
const [hasSubmitted, setHasSubmitted] = useState(false); const [hasSubmitted, setHasSubmitted] = useState(false);
const showInputBox = !(isNewThread && thread.isThreadLoading);
useEffect(() => { useEffect(() => {
const pageTitle = isNewThread const pageTitle = isNewThread
@ -359,36 +364,41 @@ export default function ChatPage() {
isNewThread && !hasSubmitted && "-translate-y-[calc(50vh-96px)]", isNewThread && !hasSubmitted && "-translate-y-[calc(50vh-96px)]",
)} )}
> >
<InputBox {showInputBox ? (
className={cn("w-full rounded-[20px] bg-[#FBFAFC]")} <InputBox
threadId={threadId} className={cn("w-full rounded-[20px] bg-[#FBFAFC]")}
isNewThread={isNewThread} threadId={threadId}
hasSubmitted={hasSubmitted} isNewThread={isNewThread}
autoFocus={isNewThread} hasSubmitted={hasSubmitted}
status={ autoFocus={isNewThread}
thread.error status={
? "error" thread.error
: isUploading || thread.isLoading ? "error"
? "streaming" : isUploading || thread.isLoading
: "ready" ? "streaming"
} : "ready"
context={settings.context} }
extraHeader={ context={settings.context}
<div className="flex flex-col gap-4"> extraHeader={
{isNewThread && !hasSubmitted && ( <div className="flex flex-col gap-4">
<Welcome mode={settings.context.mode} /> {isNewThread && !hasSubmitted && (
)} <Welcome mode={settings.context.mode} />
</div> )}
} </div>
disabled={ }
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || disabled={
isSelectedSkillBootstrapping || env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ||
isUploading isSelectedSkillBootstrapping ||
} isUploading
onContextChange={(context) => setSettings("context", context)} }
onSubmit={handleSubmit} onContextChange={(context) => setSettings("context", context)}
onStop={handleStop} onSubmit={handleSubmit}
/> onStop={handleStop}
/>
) : (
// <InputBoxSkeleton />
''
)}
{/* {isSelectedSkillBootstrapping && ( {/* {isSelectedSkillBootstrapping && (
<div className="text-muted-foreground w-full translate-y-8 text-center text-xs"> <div className="text-muted-foreground w-full translate-y-8 text-center text-xs">
@ -477,3 +487,21 @@ export default function ChatPage() {
</ThreadContext.Provider> </ThreadContext.Provider>
); );
} }
function InputBoxSkeleton() {
return (
<div className="w-full rounded-[20px] bg-[#FBFAFC] p-4 shadow-[0_0_20px_0_rgba(0,0,0,0.10)]">
<div className="flex flex-col gap-4">
<Skeleton className="h-6 w-[220px]" />
<Skeleton className="h-[120px] w-full rounded-[16px]" />
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Skeleton className="h-9 w-9 rounded-full" />
<Skeleton className="h-9 w-9 rounded-full" />
</div>
<Skeleton className="h-9 w-20 rounded-full" />
</div>
</div>
</div>
);
}

View File

@ -10,12 +10,14 @@ export function useThreadChat() {
const pathname = usePathname(); const pathname = usePathname();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const xClawUsedFromQuery = searchParams.get("xclaw_used");
const [threadId, setThreadId] = useState(() => { const [threadId, setThreadId] = useState(() => {
if (threadIdFromPath === "new") { if (threadIdFromPath === "new") {
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/"); const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
const queryThreadId = shouldUseQueryThreadId const queryThreadId =
? searchParams.get("thread_id")?.trim() shouldUseQueryThreadId && xClawUsedFromQuery === "true"
: undefined; ? searchParams.get("thread_id")?.trim()
: undefined;
return queryThreadId ?? uuid(); return queryThreadId ?? uuid();
} }
return threadIdFromPath; return threadIdFromPath;
@ -29,9 +31,10 @@ export function useThreadChat() {
if (pathname.endsWith("/new")) { if (pathname.endsWith("/new")) {
setIsNewThread(true); setIsNewThread(true);
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/"); const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
const queryThreadId = shouldUseQueryThreadId const queryThreadId =
? searchParams.get("thread_id")?.trim() shouldUseQueryThreadId && xClawUsedFromQuery === "true"
: undefined; ? searchParams.get("thread_id")?.trim()
: undefined;
setThreadId(queryThreadId ?? uuid()); setThreadId(queryThreadId ?? uuid());
return; return;
} }

View File

@ -42,6 +42,8 @@ export function useIframeSkill(): UseIframeSkillReturn {
// 0. 监听 query 中 XClawUsed=true 且带 thread_id 时跳转并清理 query // 0. 监听 query 中 XClawUsed=true 且带 thread_id 时跳转并清理 query
useEffect(() => { useEffect(() => {
console.log(xClawUsedFromQuery, threadIdFromQuery, lastThreadIdRef.current);
if (!threadIdFromQuery) return; if (!threadIdFromQuery) return;
if (xClawUsedFromQuery !== "true") return; if (xClawUsedFromQuery !== "true") return;
if (lastThreadIdRef.current === threadIdFromQuery) return; if (lastThreadIdRef.current === threadIdFromQuery) return;