fix: 修复新会话重复请求并更新部署脚本默认目标

This commit is contained in:
肖应宇 2026-04-08 16:02:14 +08:00
parent fd884bd676
commit 1243bd0aac
5 changed files with 52 additions and 65 deletions

View File

@ -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`);
}}
>

View File

@ -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<string>(() => {
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();

View File

@ -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: [

View File

@ -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);
}

View File

@ -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 <remote> <remote_app_dir>
./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