fix(workspace): control history rendering and preview sizing
This commit is contained in:
parent
b30cbd2a3b
commit
ffd10063a9
|
|
@ -50,3 +50,4 @@ frontend/imports
|
||||||
# ignore the legacy `web` folder
|
# ignore the legacy `web` folder
|
||||||
web/
|
web/
|
||||||
memo.md
|
memo.md
|
||||||
|
.codex
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,12 @@ export default function ChatPage() {
|
||||||
} = useArtifacts();
|
} = useArtifacts();
|
||||||
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
|
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
// History render rules:
|
||||||
|
// - /workspace/chats/{thread_id}: always render history
|
||||||
|
// - /workspace/chats/new: render history only when xclaw_used=true
|
||||||
|
const shouldRenderHistory =
|
||||||
|
!isNewThread ||
|
||||||
|
searchParams.get("xclaw_used")?.trim().toLowerCase() === "true";
|
||||||
|
|
||||||
// Submission strategy:
|
// Submission strategy:
|
||||||
// - xclaw_used=true: follow `isnew` (isnew=false => reuse existing thread)
|
// - xclaw_used=true: follow `isnew` (isnew=false => reuse existing thread)
|
||||||
|
|
@ -65,6 +71,13 @@ export default function ChatPage() {
|
||||||
if (!isNewThread) {
|
if (!isNewThread) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const queryThreadId = searchParams.get("thread_id")?.trim();
|
||||||
|
const reuseExistingThread =
|
||||||
|
!!queryThreadId &&
|
||||||
|
searchParams.get("isnew")?.trim().toLowerCase() === "false";
|
||||||
|
if (reuseExistingThread) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (searchParams.get("xclaw_used")?.trim().toLowerCase() !== "true") {
|
if (searchParams.get("xclaw_used")?.trim().toLowerCase() !== "true") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +128,22 @@ export default function ChatPage() {
|
||||||
|
|
||||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||||
const showInputBox = !(isNewThread && thread.isThreadLoading);
|
const showInputBox = !(isNewThread && thread.isThreadLoading);
|
||||||
|
const [historyCutoff, setHistoryCutoff] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldRenderHistory) {
|
||||||
|
setHistoryCutoff(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (historyCutoff === null && !thread.isThreadLoading) {
|
||||||
|
setHistoryCutoff(thread.messages.length);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
historyCutoff,
|
||||||
|
shouldRenderHistory,
|
||||||
|
thread.isThreadLoading,
|
||||||
|
thread.messages.length,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pageTitle = isNewThread
|
const pageTitle = isNewThread
|
||||||
|
|
@ -231,7 +260,7 @@ export default function ChatPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 overflow-hidden">
|
<div className="flex items-center justify-end gap-2 overflow-hidden">
|
||||||
<TokenUsageIndicator messages={thread.messages} />
|
{/* <TokenUsageIndicator messages={thread.messages} /> */}
|
||||||
<DevTodoList
|
<DevTodoList
|
||||||
className="bg-white"
|
className="bg-white"
|
||||||
todos={thread.values.todos ?? []}
|
todos={thread.values.todos ?? []}
|
||||||
|
|
@ -273,15 +302,20 @@ export default function ChatPage() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex size-full justify-center">
|
<div className="flex size-full justify-center">
|
||||||
<MessageList
|
<MessageList
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-full",
|
"size-full",
|
||||||
(!isNewThread || hasSubmitted) && "pt-[58px]",
|
(!isNewThread || hasSubmitted) && "pt-[58px]",
|
||||||
)}
|
)}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
thread={thread}
|
thread={thread}
|
||||||
paddingBottom={todoListCollapsed ? 160 : 280}
|
messagesOverride={
|
||||||
/>
|
shouldRenderHistory || historyCutoff === null
|
||||||
|
? undefined
|
||||||
|
: thread.messages.slice(historyCutoff)
|
||||||
|
}
|
||||||
|
paddingBottom={todoListCollapsed ? 160 : 280}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,6 @@ export const ArtifactContent = ({
|
||||||
}: ArtifactContentProps) => (
|
}: ArtifactContentProps) => (
|
||||||
<div className="min-h-0 flex-1 overflow-auto rounded-[10px]">
|
<div className="min-h-0 flex-1 overflow-auto rounded-[10px]">
|
||||||
{/* <div className={cn("mb-[207px]! p-4", className)} {...props} /> */}
|
{/* <div className={cn("mb-[207px]! p-4", className)} {...props} /> */}
|
||||||
<div className={cn("mb-[150px] h-full p-4", className)} {...props} />
|
<div className={cn("mb-[150px] min-h-full p-4", className)} {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -527,11 +527,11 @@ export function ArtifactFilePreview({
|
||||||
if (language === "markdown") {
|
if (language === "markdown") {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("size-full p-[20px]")}
|
className={cn("w-full p-[20px]")}
|
||||||
style={{ "--zoom-scale": zoomScale } as React.CSSProperties}
|
style={{ "--zoom-scale": zoomScale } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<Streamdown
|
<Streamdown
|
||||||
className="size-full"
|
className="w-full"
|
||||||
{...streamdownPlugins}
|
{...streamdownPlugins}
|
||||||
components={{ a: CitationLink }}
|
components={{ a: CitationLink }}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,22 @@
|
||||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { uuid } from "@/core/utils/uuid";
|
|
||||||
|
|
||||||
export function useThreadChat() {
|
export function useThreadChat() {
|
||||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const xClawUsedFromQuery = searchParams.get("xclaw_used");
|
const xClawUsedFromQuery = searchParams.get("xclaw_used");
|
||||||
|
const isNewFromQuery =
|
||||||
|
searchParams.get("isnew")?.trim().toLowerCase() === "false";
|
||||||
|
const queryThreadIdFromParams = searchParams.get("thread_id")?.trim();
|
||||||
|
const shouldUseQueryThreadId =
|
||||||
|
pathname.startsWith("/workspace/chats/") &&
|
||||||
|
!!queryThreadIdFromParams &&
|
||||||
|
(xClawUsedFromQuery === "true" || isNewFromQuery);
|
||||||
const [threadId, setThreadId] = useState(() => {
|
const [threadId, setThreadId] = useState(() => {
|
||||||
if (threadIdFromPath === "new") {
|
if (threadIdFromPath === "new") {
|
||||||
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
|
return shouldUseQueryThreadId ? queryThreadIdFromParams : undefined;
|
||||||
const queryThreadId =
|
|
||||||
shouldUseQueryThreadId && xClawUsedFromQuery === "true"
|
|
||||||
? searchParams.get("thread_id")?.trim()
|
|
||||||
: undefined;
|
|
||||||
return queryThreadId ?? uuid();
|
|
||||||
}
|
}
|
||||||
return threadIdFromPath;
|
return threadIdFromPath;
|
||||||
});
|
});
|
||||||
|
|
@ -30,12 +30,15 @@ export function useThreadChat() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pathname.endsWith("/new")) {
|
if (pathname.endsWith("/new")) {
|
||||||
setIsNewThread(true);
|
setIsNewThread(true);
|
||||||
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
|
const nextQueryThreadId = searchParams.get("thread_id")?.trim();
|
||||||
const queryThreadId =
|
const nextIsNewFromQuery =
|
||||||
shouldUseQueryThreadId && xClawUsedFromQuery === "true"
|
searchParams.get("isnew")?.trim().toLowerCase() === "false";
|
||||||
? searchParams.get("thread_id")?.trim()
|
const nextXClawUsed = searchParams.get("xclaw_used");
|
||||||
: undefined;
|
const nextShouldUseQueryThreadId =
|
||||||
setThreadId(queryThreadId ?? uuid());
|
pathname.startsWith("/workspace/chats/") &&
|
||||||
|
!!nextQueryThreadId &&
|
||||||
|
(nextXClawUsed === "true" || nextIsNewFromQuery);
|
||||||
|
setThreadId(nextShouldUseQueryThreadId ? nextQueryThreadId : undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsNewThread(false);
|
setIsNewThread(false);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ export function useIframeSkill(): UseIframeSkillReturn {
|
||||||
|
|
||||||
// 0. 监听 query 中 XClawUsed=true 且带 thread_id 时跳转并清理 query
|
// 0. 监听 query 中 XClawUsed=true 且带 thread_id 时跳转并清理 query
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(xClawUsedFromQuery, threadIdFromQuery, lastThreadIdRef.current);
|
|
||||||
|
|
||||||
if (!threadIdFromQuery) return;
|
if (!threadIdFromQuery) return;
|
||||||
if (xClawUsedFromQuery !== "true") return;
|
if (xClawUsedFromQuery !== "true") return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue