diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 8d40ad62..1fc94353 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -67,10 +67,19 @@ export default function ChatPage() { // 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/is_chatting 参数。 const shouldRenderHistory = !showWelcomeStyle; const createNewSession = useMemo(() => isNewThread, [isNewThread]); + const safeThreadId = useMemo(() => { + if (!threadId || threadId === "new") { + return undefined; + } + return threadId; + }, [threadId]); const streamThreadId = useMemo(() => { - return isNewThread && createNewSession ? undefined : threadId; - }, [createNewSession, isNewThread, threadId]); + if (isNewThread && createNewSession) { + return undefined; + } + return safeThreadId; + }, [createNewSession, isNewThread, safeThreadId]); const { showNotification } = useNotification(); @@ -79,7 +88,7 @@ export default function ChatPage() { skillError: selectedSkillError, clearSkillError: clearSelectedSkillError, isBootstrapping: isSelectedSkillBootstrapping, - } = useSelectedSkillListener({ threadId }); + } = useSelectedSkillListener({ threadId: safeThreadId ?? null }); // 对话行为控制器 const [thread, sendMessage, isUploading] = useThreadStream({ threadId: streamThreadId, @@ -495,7 +504,7 @@ export default function ChatPage() { if (threadId && threadId !== "new") { nextQuery.set("thread_id", threadId); } - router.replace(`/workspace/chats/new?${nextQuery.toString()}`); + router.replace(`/workspace/chats/${threadId}?is_chatting=false`); }} > 确定 diff --git a/frontend/src/components/workspace/chats/use-thread-chat.ts b/frontend/src/components/workspace/chats/use-thread-chat.ts index b056898f..209f7113 100644 --- a/frontend/src/components/workspace/chats/use-thread-chat.ts +++ b/frontend/src/components/workspace/chats/use-thread-chat.ts @@ -1,7 +1,7 @@ "use client"; import { useParams, usePathname, useSearchParams } from "next/navigation"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; export function useThreadChat() { @@ -26,53 +26,13 @@ export function useThreadChat() { const rawPathThreadId = params?.thread_id ?? threadIdFromPathname; const isNewRoute = rawPathThreadId === "new"; - const threadIdFromPathOrParams:string = isNewRoute - ? threadIdFromSearchParams?? params.thread_id - : params.thread_id; + 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); - // 持久化兜底:用于处理首屏水合或 params 时序问题。 - const readStoredThreadId = () => { - if (typeof window === "undefined") { - return undefined; - } - const stored = window.sessionStorage.getItem("workspace.thread_id"); - return isValidThreadId(stored) ? stored : undefined; - }; - - // 读取 query 的 thread_id(先用 hook,必要时用 window 兜底)。 - const readQueryThreadId = () => { - const fromHook = threadIdFromSearchParams; - if (isValidThreadId(fromHook)) { - return fromHook; - } - if (typeof window === "undefined") { - return undefined; - } - const fromLocation = new URLSearchParams(window.location.search).get( - "thread_id", - ); - if (isValidThreadId(fromLocation)) { - return fromLocation.trim(); - } - return undefined; - }; - - const queryThreadIdFromParams = readQueryThreadId(); - // console.log("[useThreadChat] query.thread_id", queryThreadIdFromParams); - // 归一化:当值为 "new" 时,替换为 query 中的 thread_id(如果存在)。 - const normalizeThreadId = useCallback( - (value?: string | null) => { - if (!value) { - return undefined; - } - return value === "new" ? queryThreadIdFromParams : value; - }, - [queryThreadIdFromParams], - ); - // New session is only controlled by `/workspace/chats/new`. const [isNewThread, setIsNewThread] = useState(() => isNewRoute); @@ -82,7 +42,7 @@ export function useThreadChat() { // console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath); const [threadId, setThreadId] = useState(() => { - return threadIdFromPathOrParams; + return threadIdFromPathOrParams ?? ""; }); @@ -93,11 +53,10 @@ export function useThreadChat() { } setIsNewThread(isNewRoute); // Prefer path thread id, fall back to query thread_id when path is /new. - setThreadId(threadIdFromPathOrParams); + setThreadId(threadIdFromPathOrParams ?? ""); setShowWelcomeStyle(isNewRoute || !isChattingFromQuery); }, [ isNewRoute, - normalizeThreadId, pathname, searchParams, isChattingFromQuery, @@ -114,6 +73,11 @@ export function useThreadChat() { }; } +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(); diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts index 3f4de941..734060ce 100644 --- a/frontend/src/core/i18n/locales/zh-CN.ts +++ b/frontend/src/core/i18n/locales/zh-CN.ts @@ -117,31 +117,31 @@ export const zhCN: Translations = { prompt: "为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。", icon: PenLineIcon, - children: [{ id: "1245", name: "自媒体文案" }], + children: [{ id: "1245", name: "微信文章撰写" }], }, { suggestion: "需求文档", prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。", icon: CompassIcon, - children: [{ id: "520", name: "需求文档" }], + children: [{ id: "520", name: "分解功能产品需求文档" }], }, { suggestion: "使用指南", prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。", icon: GraduationCapIcon, - children: [{ id: "409", name: "使用指南" }], + children: [{ id: "409", name: "用户指南编写" }], }, { suggestion: "Excel数据分析", prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。", icon: MicroscopeIcon, - children: [{ id: "5", name: "Excel数据分析" }], + children: [{ id: "5", name: "数据分析" }], }, { suggestion: "市场调研", prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。", icon: ShapesIcon, - children: [{ id: "1216", name: "市场调研" }], + children: [{ id: "1216", name: "市场研究报告" }], }, ], suggestionsCreate: [ diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index 522f8123..bfc8dfec 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -328,15 +328,20 @@ export function useThreadStream({ } setOptimisticMessages(newOptimistic); - if (resolvedThreadId) { + // For "new chat with prefilled thread_id" flows, calling onStart before + // submit can trigger route switch too early, which causes the new page to + // fetch history before the thread/run is actually created. + // Let useStream.onCreated -> handleStreamStart drive onStart instead. + if (resolvedThreadId && !createNewSession) { _handleOnStart(resolvedThreadId); } let uploadedFileInfo: UploadedFileInfo[] = []; try { - // 新会话模式下,删除旧线程并创建同名新线程 - if (createNewSession && resolvedThreadId) { + // 新会话模式下,仅在本地已有历史消息时才重置旧线程。 + // 对于全新 thread_id,避免多发一次 DELETE /threads/{id}(通常会 404)。 + if (createNewSession && resolvedThreadId && thread.messages.length > 0) { await apiClient.threads.delete(resolvedThreadId).catch(() => undefined); } diff --git a/scripts/deploy-frontend-standalone.sh b/scripts/deploy-frontend-standalone.sh index 4fd80e7b..d7c2e238 100644 --- a/scripts/deploy-frontend-standalone.sh +++ b/scripts/deploy-frontend-standalone.sh @@ -5,16 +5,21 @@ set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" +# Default deploy target (can be overridden by positional args) +DEFAULT_REMOTE="root@111.228.39.147" +DEFAULT_REMOTE_APP_DIR="/root/deerflow2" + usage() { cat <<'EOF' Usage: - ./scripts/deploy-frontend-standalone.sh + ./scripts/deploy-frontend-standalone.sh [remote] [remote_app_dir] Arguments: - remote SSH target, e.g. user@example.com - remote_app_dir Remote deerflow2 root dir, e.g. /home/user/deerflow2 + remote SSH target, optional. Default: root@111.228.39.147 + remote_app_dir Remote deerflow2 root dir, optional. Default: /root/deerflow2 Example: + ./scripts/deploy-frontend-standalone.sh ./scripts/deploy-frontend-standalone.sh ubuntu@1.2.3.4 /opt/deerflow2 Notes: @@ -31,13 +36,17 @@ if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then exit 0 fi -if [ "$#" -ne 2 ]; then +if [ "$#" -gt 2 ]; then usage exit 1 fi -REMOTE="$1" -REMOTE_APP_DIR="$2" +REMOTE="${1:-$DEFAULT_REMOTE}" +REMOTE_APP_DIR="${2:-$DEFAULT_REMOTE_APP_DIR}" + +echo "==> Deploy target:" +echo " remote: $REMOTE" +echo " app dir: $REMOTE_APP_DIR" echo "==> Building frontend (standalone)..." pnpm -C frontend build