127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
"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"
|
||
);
|
||
}
|