fix(frontend): 进入/new预创建会话并强制跳转聊天态

This commit is contained in:
肖应宇 2026-04-09 13:29:46 +08:00 committed by Titan
parent 751cb50a46
commit 87b73e2b08
2 changed files with 56 additions and 5 deletions

View File

@ -2,7 +2,8 @@
import { FilesIcon, ListTodoIcon, XIcon } from "lucide-react"; import { FilesIcon, ListTodoIcon, XIcon } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
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";
@ -28,6 +29,7 @@ import { ThreadTitle } from "@/components/workspace/thread-title";
import { Tooltip } from "@/components/workspace/tooltip"; import { Tooltip } from "@/components/workspace/tooltip";
import { useSpecificChatMode } from "@/components/workspace/use-chat-mode"; import { useSpecificChatMode } from "@/components/workspace/use-chat-mode";
import { Welcome } from "@/components/workspace/welcome"; import { Welcome } from "@/components/workspace/welcome";
import { getAPIClient } from "@/core/api";
import { useI18n } from "@/core/i18n/hooks"; import { useI18n } from "@/core/i18n/hooks";
import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages"; import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages";
import { useNotification } from "@/core/notification/hooks"; import { useNotification } from "@/core/notification/hooks";
@ -66,13 +68,18 @@ export default function ChatPage() {
// 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/is_chatting 参数。 // 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/is_chatting 参数。
const shouldRenderHistory = !showWelcomeStyle; const shouldRenderHistory = !showWelcomeStyle;
const createNewSession = useMemo(() => isNewThread, [isNewThread]);
const safeThreadId = useMemo(() => { const safeThreadId = useMemo(() => {
if (!threadId || threadId === "new") { if (!threadId || threadId === "new") {
return undefined; return undefined;
} }
return threadId; return threadId;
}, [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 streamThreadId = useMemo(() => { const streamThreadId = useMemo(() => {
if (isNewThread && createNewSession) { if (isNewThread && createNewSession) {
@ -80,9 +87,38 @@ export default function ChatPage() {
} }
return safeThreadId; return safeThreadId;
}, [createNewSession, isNewThread, safeThreadId]); }, [createNewSession, isNewThread, safeThreadId]);
const apiClient = useMemo(() => getAPIClient(isMock), [isMock]);
const warnedMissingThreadIdRef = useRef(false);
const initializedThreadRef = useRef<string | null>(null);
const { showNotification } = useNotification(); const { showNotification } = useNotification();
useEffect(() => {
if (!isNewThread) {
warnedMissingThreadIdRef.current = false;
return;
}
if (!safeThreadId) {
if (!warnedMissingThreadIdRef.current) {
warnedMissingThreadIdRef.current = true;
toast.error("缺少 thread_id无法创建会话");
}
return;
}
warnedMissingThreadIdRef.current = false;
if (initializedThreadRef.current === safeThreadId) return;
initializedThreadRef.current = safeThreadId;
void apiClient.threads
.create({
threadId: safeThreadId,
ifExists: "do_nothing",
})
.catch(() => {
initializedThreadRef.current = null;
toast.error("会话创建失败,请稍后重试");
});
}, [apiClient, isNewThread, safeThreadId]);
// 监听宿主页 selectedSkill 消息 // 监听宿主页 selectedSkill 消息
const { const {
skillError: selectedSkillError, skillError: selectedSkillError,
@ -205,10 +241,24 @@ export default function ChatPage() {
if (isSelectedSkillBootstrapping) { if (isSelectedSkillBootstrapping) {
return; return;
} }
if (isNewThread && !safeThreadId) {
toast.error("缺少 thread_id无法发送消息");
return;
}
setHasSubmitted(true); setHasSubmitted(true);
if (safeThreadId && (isNewThread || showWelcomeStyle)) {
router.replace(`/workspace/chats/${safeThreadId}?is_chatting=true`);
}
void sendMessage(safeThreadId, message); void sendMessage(safeThreadId, message);
}, },
[isSelectedSkillBootstrapping, safeThreadId, sendMessage], [
isNewThread,
isSelectedSkillBootstrapping,
router,
safeThreadId,
sendMessage,
showWelcomeStyle,
],
); );
const handleStop = useCallback(async () => { const handleStop = useCallback(async () => {
await thread.stop(); await thread.stop();
@ -456,7 +506,8 @@ export default function ChatPage() {
disabled={ disabled={
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ||
isSelectedSkillBootstrapping || isSelectedSkillBootstrapping ||
isUploading isUploading ||
(isNewThread && !safeThreadId)
} }
onContextChange={(context) => setSettings("context", context)} onContextChange={(context) => setSettings("context", context)}
onSubmit={handleSubmit} onSubmit={handleSubmit}

View File

@ -43,7 +43,7 @@ export function WorkspaceHeader({ className }: { className?: string }) {
) : ( ) : (
<div className="text-primary ml-2 cursor-default font-serif"> <div className="text-primary ml-2 cursor-default font-serif">
{/* TODO: 测试标识 */} {/* TODO: 测试标识 */}
XClaw <span className="text-sm text-[#000000c5]">v3.2.1 dev: 给通信面板加收起按钮</span> XClaw <span className="text-sm text-[#000000c5]">v3.2.1 fix(frontend): /new预创建会话并强制跳转聊天态</span>
</div> </div>
)} )}
<SidebarTrigger /> <SidebarTrigger />