deerflow2/frontend/src/components/workspace/chats/use-thread-chat.ts

127 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useParams, usePathname, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { resolveThreadQueryIntent } from "@/core/threads/utils";
export function useThreadChat() {
const pathname = usePathname();
const params = useParams<{ thread_id?: string }>();
// 兜底:当 params 还未就绪时,从 pathname 解析 thread_id。
const threadIdFromPathname = (() => {
const parts = pathname.split("?")[0]?.split("/") ?? [];
const idx = parts.lastIndexOf("chats");
if (idx >= 0 && parts.length > idx + 1) {
return parts[idx + 1];
}
return undefined;
})();
const rawPathThreadId = params?.thread_id ?? threadIdFromPathname;
const isNewRoute = rawPathThreadId === "new";
const threadIdFromPath = isNewRoute ? undefined : 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;
};
const searchParams = useSearchParams();
// 读取 query 的 thread_id先用 hook必要时用 window 兜底)。
const readQueryThreadId = () => {
const fromHook = searchParams.get("thread_id")?.trim();
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],
);
const intent = resolveThreadQueryIntent({
pathThreadId: threadIdFromPath,
queryThreadId: queryThreadIdFromParams,
isNewRoute,
});
const { isNewThread: isNewRequested, showWelcomeStyle, invalidNewRoute } = intent;
const effectiveThreadIdFromPath =
invalidNewRoute
? undefined
: normalizeThreadId(threadIdFromPath) ??
(isNewRoute ? undefined : readStoredThreadId());
// console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath);
const [threadId, setThreadId] = useState(() => {
return effectiveThreadIdFromPath ?? undefined;
});
// New session is only controlled by `/workspace/chats/new`.
const [isNewThread, setIsNewThread] = useState(() => isNewRequested);
useEffect(() => {
// 记住最近一次有效的 thread_id供下次加载兜底使用。
if (threadId && threadId !== "new" && typeof window !== "undefined") {
window.sessionStorage.setItem("workspace.thread_id", threadId);
}
setIsNewThread(isNewRoute);
// Prefer path thread id, fall back to query thread_id when path is /new.
setThreadId(
invalidNewRoute ? undefined : normalizeThreadId(threadIdFromPath),
);
}, [
invalidNewRoute,
isNewRoute,
normalizeThreadId,
pathname,
searchParams,
threadId,
threadIdFromPath,
]);
const isMock = searchParams.get("mock") === "true";
return {
threadId,
isNewThread,
setIsNewThread,
isMock,
showWelcomeStyle,
invalidNewRoute,
};
}
function isValidThreadId(value?: string | null): value is string {
if (!value) return false;
const normalized = value.trim().toLowerCase();
return (
normalized.length > 0 &&
normalized !== "new" &&
normalized !== "undefined" &&
normalized !== "null"
);
}