fix: 修复新会话重复请求并更新部署脚本默认目标
This commit is contained in:
parent
fd884bd676
commit
1243bd0aac
|
|
@ -67,10 +67,19 @@ 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 createNewSession = useMemo(() => isNewThread, [isNewThread]);
|
||||||
|
const safeThreadId = useMemo(() => {
|
||||||
|
if (!threadId || threadId === "new") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return threadId;
|
||||||
|
}, [threadId]);
|
||||||
|
|
||||||
const streamThreadId = useMemo(() => {
|
const streamThreadId = useMemo(() => {
|
||||||
return isNewThread && createNewSession ? undefined : threadId;
|
if (isNewThread && createNewSession) {
|
||||||
}, [createNewSession, isNewThread, threadId]);
|
return undefined;
|
||||||
|
}
|
||||||
|
return safeThreadId;
|
||||||
|
}, [createNewSession, isNewThread, safeThreadId]);
|
||||||
|
|
||||||
const { showNotification } = useNotification();
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
|
|
@ -79,7 +88,7 @@ export default function ChatPage() {
|
||||||
skillError: selectedSkillError,
|
skillError: selectedSkillError,
|
||||||
clearSkillError: clearSelectedSkillError,
|
clearSkillError: clearSelectedSkillError,
|
||||||
isBootstrapping: isSelectedSkillBootstrapping,
|
isBootstrapping: isSelectedSkillBootstrapping,
|
||||||
} = useSelectedSkillListener({ threadId });
|
} = useSelectedSkillListener({ threadId: safeThreadId ?? null });
|
||||||
// 对话行为控制器
|
// 对话行为控制器
|
||||||
const [thread, sendMessage, isUploading] = useThreadStream({
|
const [thread, sendMessage, isUploading] = useThreadStream({
|
||||||
threadId: streamThreadId,
|
threadId: streamThreadId,
|
||||||
|
|
@ -495,7 +504,7 @@ export default function ChatPage() {
|
||||||
if (threadId && threadId !== "new") {
|
if (threadId && threadId !== "new") {
|
||||||
nextQuery.set("thread_id", threadId);
|
nextQuery.set("thread_id", threadId);
|
||||||
}
|
}
|
||||||
router.replace(`/workspace/chats/new?${nextQuery.toString()}`);
|
router.replace(`/workspace/chats/${threadId}?is_chatting=false`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
确定
|
确定
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
export function useThreadChat() {
|
export function useThreadChat() {
|
||||||
|
|
@ -26,53 +26,13 @@ export function useThreadChat() {
|
||||||
const rawPathThreadId = params?.thread_id ?? threadIdFromPathname;
|
const rawPathThreadId = params?.thread_id ?? threadIdFromPathname;
|
||||||
|
|
||||||
const isNewRoute = rawPathThreadId === "new";
|
const isNewRoute = rawPathThreadId === "new";
|
||||||
const threadIdFromPathOrParams:string = isNewRoute
|
const threadIdFromPathOrParams = isNewRoute
|
||||||
? threadIdFromSearchParams?? params.thread_id
|
? normalizeThreadId(threadIdFromSearchParams)
|
||||||
: params.thread_id;
|
: normalizeThreadId(rawPathThreadId);
|
||||||
// console.log("[useThreadChat] pathname", pathname);
|
// console.log("[useThreadChat] pathname", pathname);
|
||||||
// console.log("[useThreadChat] params.thread_id", params?.thread_id);
|
// console.log("[useThreadChat] params.thread_id", params?.thread_id);
|
||||||
// console.log("[useThreadChat] threadIdFromPathname", threadIdFromPathname);
|
// console.log("[useThreadChat] threadIdFromPathname", threadIdFromPathname);
|
||||||
// console.log("[useThreadChat] threadIdFromPath", threadIdFromPath);
|
// 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`.
|
// New session is only controlled by `/workspace/chats/new`.
|
||||||
const [isNewThread, setIsNewThread] = useState(() => isNewRoute);
|
const [isNewThread, setIsNewThread] = useState(() => isNewRoute);
|
||||||
|
|
||||||
|
|
@ -82,7 +42,7 @@ export function useThreadChat() {
|
||||||
// console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath);
|
// console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath);
|
||||||
|
|
||||||
const [threadId, setThreadId] = useState<string>(() => {
|
const [threadId, setThreadId] = useState<string>(() => {
|
||||||
return threadIdFromPathOrParams;
|
return threadIdFromPathOrParams ?? "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -93,11 +53,10 @@ export function useThreadChat() {
|
||||||
}
|
}
|
||||||
setIsNewThread(isNewRoute);
|
setIsNewThread(isNewRoute);
|
||||||
// Prefer path thread id, fall back to query thread_id when path is /new.
|
// Prefer path thread id, fall back to query thread_id when path is /new.
|
||||||
setThreadId(threadIdFromPathOrParams);
|
setThreadId(threadIdFromPathOrParams ?? "");
|
||||||
setShowWelcomeStyle(isNewRoute || !isChattingFromQuery);
|
setShowWelcomeStyle(isNewRoute || !isChattingFromQuery);
|
||||||
}, [
|
}, [
|
||||||
isNewRoute,
|
isNewRoute,
|
||||||
normalizeThreadId,
|
|
||||||
pathname,
|
pathname,
|
||||||
searchParams,
|
searchParams,
|
||||||
isChattingFromQuery,
|
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 {
|
function isValidThreadId(value?: string | null): value is string {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
const normalized = value.trim().toLowerCase();
|
const normalized = value.trim().toLowerCase();
|
||||||
|
|
|
||||||
|
|
@ -117,31 +117,31 @@ export const zhCN: Translations = {
|
||||||
prompt:
|
prompt:
|
||||||
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
|
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
|
||||||
icon: PenLineIcon,
|
icon: PenLineIcon,
|
||||||
children: [{ id: "1245", name: "自媒体文案" }],
|
children: [{ id: "1245", name: "微信文章撰写" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "需求文档",
|
suggestion: "需求文档",
|
||||||
prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。",
|
prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。",
|
||||||
icon: CompassIcon,
|
icon: CompassIcon,
|
||||||
children: [{ id: "520", name: "需求文档" }],
|
children: [{ id: "520", name: "分解功能产品需求文档" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "使用指南",
|
suggestion: "使用指南",
|
||||||
prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。",
|
prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。",
|
||||||
icon: GraduationCapIcon,
|
icon: GraduationCapIcon,
|
||||||
children: [{ id: "409", name: "使用指南" }],
|
children: [{ id: "409", name: "用户指南编写" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "Excel数据分析",
|
suggestion: "Excel数据分析",
|
||||||
prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。",
|
prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。",
|
||||||
icon: MicroscopeIcon,
|
icon: MicroscopeIcon,
|
||||||
children: [{ id: "5", name: "Excel数据分析" }],
|
children: [{ id: "5", name: "数据分析" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "市场调研",
|
suggestion: "市场调研",
|
||||||
prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。",
|
prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。",
|
||||||
icon: ShapesIcon,
|
icon: ShapesIcon,
|
||||||
children: [{ id: "1216", name: "市场调研" }],
|
children: [{ id: "1216", name: "市场研究报告" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
suggestionsCreate: [
|
suggestionsCreate: [
|
||||||
|
|
|
||||||
|
|
@ -328,15 +328,20 @@ export function useThreadStream({
|
||||||
}
|
}
|
||||||
setOptimisticMessages(newOptimistic);
|
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);
|
_handleOnStart(resolvedThreadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadedFileInfo: UploadedFileInfo[] = [];
|
let uploadedFileInfo: UploadedFileInfo[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 新会话模式下,删除旧线程并创建同名新线程
|
// 新会话模式下,仅在本地已有历史消息时才重置旧线程。
|
||||||
if (createNewSession && resolvedThreadId) {
|
// 对于全新 thread_id,避免多发一次 DELETE /threads/{id}(通常会 404)。
|
||||||
|
if (createNewSession && resolvedThreadId && thread.messages.length > 0) {
|
||||||
await apiClient.threads.delete(resolvedThreadId).catch(() => undefined);
|
await apiClient.threads.delete(resolvedThreadId).catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,21 @@ set -euo pipefail
|
||||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
cd "$REPO_ROOT"
|
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() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage:
|
Usage:
|
||||||
./scripts/deploy-frontend-standalone.sh <remote> <remote_app_dir>
|
./scripts/deploy-frontend-standalone.sh [remote] [remote_app_dir]
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
remote SSH target, e.g. user@example.com
|
remote SSH target, optional. Default: root@111.228.39.147
|
||||||
remote_app_dir Remote deerflow2 root dir, e.g. /home/user/deerflow2
|
remote_app_dir Remote deerflow2 root dir, optional. Default: /root/deerflow2
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
./scripts/deploy-frontend-standalone.sh
|
||||||
./scripts/deploy-frontend-standalone.sh ubuntu@1.2.3.4 /opt/deerflow2
|
./scripts/deploy-frontend-standalone.sh ubuntu@1.2.3.4 /opt/deerflow2
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
@ -31,13 +36,17 @@ if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$#" -ne 2 ]; then
|
if [ "$#" -gt 2 ]; then
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REMOTE="$1"
|
REMOTE="${1:-$DEFAULT_REMOTE}"
|
||||||
REMOTE_APP_DIR="$2"
|
REMOTE_APP_DIR="${2:-$DEFAULT_REMOTE_APP_DIR}"
|
||||||
|
|
||||||
|
echo "==> Deploy target:"
|
||||||
|
echo " remote: $REMOTE"
|
||||||
|
echo " app dir: $REMOTE_APP_DIR"
|
||||||
|
|
||||||
echo "==> Building frontend (standalone)..."
|
echo "==> Building frontend (standalone)..."
|
||||||
pnpm -C frontend build
|
pnpm -C frontend build
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue