fix: 收紧threadId的类型为string,删除无用isNewRoute变量,合并状态判断showInputBox至showWelcomeStyle
This commit is contained in:
parent
ec80730cd8
commit
73b4a6c713
|
|
@ -61,7 +61,6 @@ export default function ChatPage() {
|
|||
setIsNewThread,
|
||||
isMock,
|
||||
showWelcomeStyle,
|
||||
invalidNewRoute,
|
||||
} = useThreadChat();
|
||||
|
||||
// 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/xclaw_used 参数。
|
||||
|
|
@ -119,7 +118,6 @@ export default function ChatPage() {
|
|||
}, [thread.values?.title]);
|
||||
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||
const showInputBox = !invalidNewRoute && !(showWelcomeStyle && thread.isThreadLoading);
|
||||
const [historyCutoff, setHistoryCutoff] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -214,12 +212,10 @@ export default function ChatPage() {
|
|||
setArtifactsOpen,
|
||||
setIsNewThread,
|
||||
]);
|
||||
// shouldRenderHistory || historyCutoff === null
|
||||
// console.log('shouldRenderHistory', shouldRenderHistory, 'historyCutoff', historyCutoff);
|
||||
|
||||
|
||||
return (
|
||||
<ThreadContext.Provider value={{ thread }}>
|
||||
<ThreadContext.Provider value={{ threadId,thread }}>
|
||||
<div
|
||||
className={cn(
|
||||
"m-auto flex h-screen min-h-svh overflow-hidden rounded-t-[20px] transition-[width] duration-300 ease-in-out",
|
||||
|
|
@ -313,43 +309,22 @@ export default function ChatPage() {
|
|||
)}
|
||||
>
|
||||
<div className="flex size-full justify-center">
|
||||
{invalidNewRoute ? (
|
||||
<div className="flex size-full items-center justify-center px-6">
|
||||
<div
|
||||
className="max-w-md rounded-2xl border border-[#E5DDF2] bg-white px-6 py-5 text-center shadow-sm"
|
||||
data-testid="missing-thread-id-state"
|
||||
role="alert"
|
||||
>
|
||||
<h2 className="text-base font-semibold text-[#150033]">
|
||||
缺少 thread_id 参数
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-[#666666]">
|
||||
访问
|
||||
<span className="mx-1 font-mono">/workspace/chats/new</span>
|
||||
时必须显式传入
|
||||
<span className="mx-1 font-mono">?thread_id=...</span>
|
||||
,当前页面不会继续使用本地缓存兜底。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<MessageList
|
||||
className={cn(
|
||||
"size-full",
|
||||
(!showWelcomeStyle || hasSubmitted) && "pt-[58px]",
|
||||
)}
|
||||
threadId={threadId}
|
||||
thread={thread}
|
||||
messagesOverride={
|
||||
shouldRenderHistory || historyCutoff === null
|
||||
? undefined
|
||||
: thread.messages.slice(historyCutoff)
|
||||
}
|
||||
paddingBottom={todoListCollapsed ? 160 : 280}
|
||||
showScrollToBottomButton={!showWelcomeStyle}
|
||||
scrollButtonClassName="bottom-[112px]"
|
||||
/>
|
||||
)}
|
||||
<MessageList
|
||||
className={cn(
|
||||
"size-full",
|
||||
(!showWelcomeStyle || hasSubmitted) && "pt-[58px]",
|
||||
)}
|
||||
threadId={threadId}
|
||||
thread={thread}
|
||||
messagesOverride={
|
||||
shouldRenderHistory || historyCutoff === null
|
||||
? undefined
|
||||
: thread.messages.slice(historyCutoff)
|
||||
}
|
||||
paddingBottom={todoListCollapsed ? 160 : 280}
|
||||
showScrollToBottomButton={!showWelcomeStyle}
|
||||
scrollButtonClassName="bottom-[112px]"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
|
@ -433,7 +408,7 @@ export default function ChatPage() {
|
|||
showWelcomeStyle && !hasSubmitted && "-translate-y-[calc(50vh-96px)]",
|
||||
)}
|
||||
>
|
||||
{showInputBox ? (
|
||||
{!(showWelcomeStyle && thread.isThreadLoading) ? (
|
||||
<InputBox
|
||||
className={cn("w-full rounded-[20px] bg-[#FBFAFC]")}
|
||||
threadId={threadId}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function ArtifactFileDetail({
|
|||
}: {
|
||||
className?: string;
|
||||
filepath: string;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const { artifacts, setOpen, select, fullscreen, setFullscreen } =
|
||||
|
|
@ -511,7 +511,7 @@ export function ArtifactFilePreview({
|
|||
content: string;
|
||||
language: string;
|
||||
zoom?: number;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const zoomScale = zoom / 100;
|
||||
const normalizedContent = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export function ArtifactFileList({
|
|||
}: {
|
||||
className?: string;
|
||||
files: string[];
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const { select: selectArtifact, setOpen } = useArtifacts();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import { resolveThreadQueryIntent } from "@/core/threads/utils";
|
|||
|
||||
export function useThreadChat() {
|
||||
const pathname = usePathname();
|
||||
const params = useParams<{ thread_id?: string }>();
|
||||
const params = useParams<{ thread_id: string }>();
|
||||
const searchParams = useSearchParams();
|
||||
const threadIdFromSearchParams = searchParams.get("thread_id")?.trim();
|
||||
// 兜底:当 params 还未就绪时,从 pathname 解析 thread_id。
|
||||
const threadIdFromPathname = (() => {
|
||||
const parts = pathname.split("?")[0]?.split("/") ?? [];
|
||||
|
|
@ -18,8 +20,11 @@ export function useThreadChat() {
|
|||
return undefined;
|
||||
})();
|
||||
const rawPathThreadId = params?.thread_id ?? threadIdFromPathname;
|
||||
|
||||
const isNewRoute = rawPathThreadId === "new";
|
||||
const threadIdFromPath = isNewRoute ? undefined : rawPathThreadId;
|
||||
const threadIdFromPathOrParams:string = isNewRoute
|
||||
? threadIdFromSearchParams?? params.thread_id
|
||||
: params.thread_id;
|
||||
// console.log("[useThreadChat] pathname", pathname);
|
||||
// console.log("[useThreadChat] params.thread_id", params?.thread_id);
|
||||
// console.log("[useThreadChat] threadIdFromPathname", threadIdFromPathname);
|
||||
|
|
@ -33,10 +38,9 @@ export function useThreadChat() {
|
|||
return isValidThreadId(stored) ? stored : undefined;
|
||||
};
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
// 读取 query 的 thread_id(先用 hook,必要时用 window 兜底)。
|
||||
const readQueryThreadId = () => {
|
||||
const fromHook = searchParams.get("thread_id")?.trim();
|
||||
const fromHook = threadIdFromSearchParams;
|
||||
if (isValidThreadId(fromHook)) {
|
||||
return fromHook;
|
||||
}
|
||||
|
|
@ -51,7 +55,7 @@ export function useThreadChat() {
|
|||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
const queryThreadIdFromParams = readQueryThreadId();
|
||||
// console.log("[useThreadChat] query.thread_id", queryThreadIdFromParams);
|
||||
// 归一化:当值为 "new" 时,替换为 query 中的 thread_id(如果存在)。
|
||||
|
|
@ -65,20 +69,16 @@ export function useThreadChat() {
|
|||
[queryThreadIdFromParams],
|
||||
);
|
||||
const intent = resolveThreadQueryIntent({
|
||||
pathThreadId: threadIdFromPath,
|
||||
pathThreadId: threadIdFromPathOrParams,
|
||||
queryThreadId: queryThreadIdFromParams,
|
||||
isNewRoute,
|
||||
});
|
||||
const { isNewThread: isNewRequested, showWelcomeStyle, invalidNewRoute } = intent;
|
||||
const effectiveThreadIdFromPath =
|
||||
invalidNewRoute
|
||||
? undefined
|
||||
: normalizeThreadId(threadIdFromPath) ??
|
||||
(isNewRoute ? undefined : readStoredThreadId());
|
||||
const { isNewThread: isNewRequested, showWelcomeStyle } = intent;
|
||||
|
||||
// console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath);
|
||||
|
||||
const [threadId, setThreadId] = useState(() => {
|
||||
return effectiveThreadIdFromPath ?? undefined;
|
||||
|
||||
const [threadId, setThreadId] = useState<string>(() => {
|
||||
return threadIdFromPathOrParams;
|
||||
});
|
||||
|
||||
// New session is only controlled by `/workspace/chats/new`.
|
||||
|
|
@ -91,17 +91,14 @@ export function useThreadChat() {
|
|||
}
|
||||
setIsNewThread(isNewRoute);
|
||||
// Prefer path thread id, fall back to query thread_id when path is /new.
|
||||
setThreadId(
|
||||
invalidNewRoute ? undefined : normalizeThreadId(threadIdFromPath),
|
||||
);
|
||||
setThreadId(threadIdFromPathOrParams);
|
||||
}, [
|
||||
invalidNewRoute,
|
||||
isNewRoute,
|
||||
normalizeThreadId,
|
||||
pathname,
|
||||
searchParams,
|
||||
threadId,
|
||||
threadIdFromPath,
|
||||
threadIdFromPathOrParams,
|
||||
]);
|
||||
const isMock = searchParams.get("mock") === "true";
|
||||
return {
|
||||
|
|
@ -110,7 +107,6 @@ export function useThreadChat() {
|
|||
setIsNewThread,
|
||||
isMock,
|
||||
showWelcomeStyle,
|
||||
invalidNewRoute,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { AgentThread } from "@/core/threads/types";
|
|||
import { useThread } from "./messages/context";
|
||||
import { Tooltip } from "./tooltip";
|
||||
|
||||
export function ExportTrigger({ threadId }: { threadId?: string }) {
|
||||
export function ExportTrigger({ threadId }: { threadId: string }) {
|
||||
const { t } = useI18n();
|
||||
const { thread } = useThread();
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export function InputBox({
|
|||
...props
|
||||
}: Omit<ComponentProps<typeof PromptInput>, "onSubmit"> & {
|
||||
assistantId?: string | null;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
status?: ChatStatus;
|
||||
disabled?: boolean;
|
||||
context: Omit<
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { AgentThreadState } from "@/core/threads";
|
|||
|
||||
export interface ThreadContextType {
|
||||
thread: UseStream<AgentThreadState>;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
isMock?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export function MessageListItem({
|
|||
className?: string;
|
||||
message: Message;
|
||||
isLoading?: boolean;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const isHuman = message.type === "human";
|
||||
return (
|
||||
|
|
@ -94,7 +94,7 @@ function MessageImage({
|
|||
maxWidth = "90%",
|
||||
...props
|
||||
}: React.ImgHTMLAttributes<HTMLImageElement> & {
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
maxWidth?: string;
|
||||
}) {
|
||||
if (!src) return null;
|
||||
|
|
@ -124,7 +124,7 @@ function MessageContent_({
|
|||
className?: string;
|
||||
message: Message;
|
||||
isLoading?: boolean;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
||||
const isHuman = message.type === "human";
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export function MessageList({
|
|||
scrollButtonClassName,
|
||||
}: {
|
||||
className?: string;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
thread: UseStream<AgentThreadState>;
|
||||
/** When set (e.g. from onFinish), use instead of thread.messages so SSE end shows complete state. */
|
||||
messagesOverride?: Message[];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function ThreadTitle({
|
|||
threadTitle,
|
||||
}: {
|
||||
className?: string;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
thread?: UseStream<AgentThreadState>;
|
||||
threadTitle?: string;
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function useArtifactContent({
|
|||
enabled,
|
||||
}: {
|
||||
filepath: string;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
enabled?: boolean;
|
||||
}) {
|
||||
const isWriteFile = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -26,5 +26,4 @@ void test("prefers path thread id over query thread id when not on /new", () =>
|
|||
|
||||
assert.equal(intent.isNewThread, false);
|
||||
assert.equal(intent.threadId, "thread-from-path");
|
||||
assert.equal(intent.invalidNewRoute, false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export type ToolEndEvent = {
|
|||
};
|
||||
|
||||
export type ThreadStreamOptions = {
|
||||
threadId?: string | null | undefined;
|
||||
threadId: string | null | undefined;
|
||||
context: LocalSettings["context"];
|
||||
createNewSession?: boolean;
|
||||
isMock?: boolean;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export interface ThreadQueryIntent {
|
|||
threadId: string | undefined;
|
||||
isNewThread: boolean;
|
||||
showWelcomeStyle: boolean;
|
||||
invalidNewRoute: boolean;
|
||||
}
|
||||
|
||||
export function pathOfThread(threadId: string) {
|
||||
|
|
@ -45,8 +44,6 @@ export function resolveThreadQueryIntent({
|
|||
// 新逻辑只由路由 /workspace/chats/new 控制“新会话”
|
||||
isNewThread,
|
||||
showWelcomeStyle: isNewThread,
|
||||
// 新逻辑下不再要求 /new 必带 query thread_id
|
||||
invalidNewRoute: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ interface SkillError {
|
|||
|
||||
interface UseSelectedSkillListenerOptions {
|
||||
/** 当前会话 thread_id,用于调用 bootstrapRemoteSkill */
|
||||
threadId?: string | null;
|
||||
threadId: string | null;
|
||||
}
|
||||
|
||||
interface UseSelectedSkillListenerReturn {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function buildChatUrl({
|
|||
}: {
|
||||
pathThreadId?: string;
|
||||
xclawUsed: boolean;
|
||||
threadId?: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const resolvedThreadId = threadId ?? pathThreadId;
|
||||
if (!pathThreadId && !resolvedThreadId) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue