"use client"; import { useSearchParams } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import { loginByUsername } from "@/core/auth/api"; import { setStoredAuth } from "@/core/auth"; import { resolveEmbedChatParams } from "@/core/embed/params"; import { registerEmbedSession } from "@/core/embed/status-api"; import { useThreadSettings } from "@/core/settings"; import { useThreadStream } from "@/core/threads/hooks"; import { uuid } from "@/core/utils/uuid"; import { ChatBox } from "@/components/workspace/chats"; import { MessageList, MESSAGE_LIST_DEFAULT_PADDING_BOTTOM, } from "@/components/workspace/messages"; import { ThreadContext } from "@/components/workspace/messages/context"; import { useModels } from "@/core/models/hooks"; import { cn } from "@/lib/utils"; const EMBED_ROUTE_BASE = "/embed/chats"; export default function EmbedChatPage() { const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const searchParams = useSearchParams(); // 主题不在这里处理 —— ?theme= 由 AppProviders 通过 forcedTheme 强制 // (不落 localStorage),避免污染主站用户自己选的主题。 const embedParams = resolveEmbedChatParams(searchParams.toString()); const [authReady, setAuthReady] = useState(false); const [authError, setAuthError] = useState(null); const [threadId, setThreadId] = useState(() => threadIdFromPath === "new" ? uuid() : (threadIdFromPath ?? uuid()), ); const [isNewThread, setIsNewThread] = useState(threadIdFromPath === "new"); const hasAutoSentRef = useRef(false); const lastRegisteredThreadRef = useRef(null); useEffect(() => { if (threadIdFromPath === "new" || !threadIdFromPath) { return; } setIsNewThread(false); setThreadId(threadIdFromPath); }, [threadIdFromPath]); const [settings] = useThreadSettings(threadId); const { tokenUsageEnabled } = useModels(); useEffect(() => { let cancelled = false; async function runLogin() { setAuthError(null); try { const auth = await loginByUsername(embedParams.username); if (cancelled) return; setStoredAuth(auth); setAuthReady(true); } catch (error) { if (cancelled) return; setAuthError( error instanceof Error ? error.message : "Login request failed.", ); } } void runLogin(); return () => { cancelled = true; }; }, [embedParams.username]); const registerSessionForThread = useCallback( (resolvedThreadId: string) => { if (!embedParams.sessionId || lastRegisteredThreadRef.current === resolvedThreadId) { return; } lastRegisteredThreadRef.current = resolvedThreadId; void registerEmbedSession(embedParams.sessionId, resolvedThreadId).catch( (error) => { console.error("[embed] failed to register session mapping", error); lastRegisteredThreadRef.current = null; }, ); }, [embedParams.sessionId], ); const [thread, sendMessage] = useThreadStream({ threadId: authReady && !isNewThread ? threadId : undefined, context: settings.context, isMock: false, onStart: (createdThreadId) => { setThreadId(createdThreadId); setIsNewThread(false); registerSessionForThread(createdThreadId); history.replaceState( null, "", `${window.location.pathname}${window.location.search}#${EMBED_ROUTE_BASE}/${createdThreadId}`, ); }, }); useEffect(() => { if (!authReady || !threadId) { return; } registerSessionForThread(threadId); }, [authReady, threadId, registerSessionForThread]); const handleClarificationSubmit = useCallback( (answer: string) => { void sendMessage(threadId, { text: answer, files: [] }); }, [sendMessage, threadId], ); useEffect(() => { if (!authReady || !embedParams.message || hasAutoSentRef.current) { return; } hasAutoSentRef.current = true; void sendMessage(threadId, { text: embedParams.message, files: [] }); }, [authReady, embedParams.message, sendMessage, threadId]); if (!embedParams.sessionId) { return (
缺少参数 sessionId:父页面需在 iframe URL 中传入由己方生成的会话 id,用于查询问答是否结束。
); } if (authError) { return (
登录失败:{authError}
); } if (!authReady) { return (
正在进入问答…
); } return (
); }