mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-13 14:23:58 +00:00
fix(webui): auto-collapse completed activity
This commit is contained in:
parent
547f81e4aa
commit
8fedee276b
@ -225,13 +225,18 @@ export function AgentActivityCluster({
|
||||
|
||||
const [userToggledOuter, setUserToggledOuter] = useState(false);
|
||||
const [outerOpenLocal, setOuterOpenLocal] = useState(false);
|
||||
const [completionHoldOpen, setCompletionHoldOpen] = useState(false);
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
const activityScrollRef = useRef<HTMLDivElement>(null);
|
||||
const activityContentRef = useRef<HTMLDivElement>(null);
|
||||
const autoFollowActivityRef = useRef(true);
|
||||
const scrollFrameRef = useRef<number | null>(null);
|
||||
/** Live work and the trace directly attached to an answer read like a visible trail. */
|
||||
const outerExpanded = userToggledOuter ? outerOpenLocal : isTurnStreaming || hasBodyBelow;
|
||||
const wasTurnStreamingRef = useRef(isTurnStreaming);
|
||||
const wasTurnStreaming = wasTurnStreamingRef.current;
|
||||
/** Live work stays open; completed work briefly shows the done state, then tucks away. */
|
||||
const outerExpanded = userToggledOuter
|
||||
? outerOpenLocal
|
||||
: isTurnStreaming || completionHoldOpen || (wasTurnStreaming && !isTurnStreaming);
|
||||
|
||||
const hasLiveEditingFiles = isTurnStreaming && hasEditingFiles;
|
||||
const singleFilePath = fileCount === 1 ? primaryFilePath : undefined;
|
||||
@ -377,6 +382,19 @@ export function AgentActivityCluster({
|
||||
return () => window.clearInterval(interval);
|
||||
}, [isTurnStreaming]);
|
||||
|
||||
useEffect(() => {
|
||||
const wasStreaming = wasTurnStreamingRef.current;
|
||||
wasTurnStreamingRef.current = isTurnStreaming;
|
||||
if (isTurnStreaming) {
|
||||
setCompletionHoldOpen(false);
|
||||
return undefined;
|
||||
}
|
||||
if (!wasStreaming || userToggledOuter) return undefined;
|
||||
setCompletionHoldOpen(true);
|
||||
const timeout = window.setTimeout(() => setCompletionHoldOpen(false), 900);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [isTurnStreaming, userToggledOuter]);
|
||||
|
||||
const onActivityScroll = useCallback(() => {
|
||||
const el = activityScrollRef.current;
|
||||
if (!el) return;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { AgentActivityCluster } from "@/components/thread/AgentActivityCluster";
|
||||
import type { CliAppInfo, McpPresetInfo, UIMessage } from "@/lib/types";
|
||||
@ -293,6 +293,53 @@ describe("AgentActivityCluster", () => {
|
||||
await waitFor(() => expect(marker).toHaveClass("animate-in"));
|
||||
});
|
||||
|
||||
it("briefly shows completed activity, then auto-collapses before the answer", () => {
|
||||
vi.useFakeTimers();
|
||||
const liveReasoning: UIMessage = {
|
||||
id: "r-collapse",
|
||||
role: "assistant",
|
||||
content: "",
|
||||
reasoning: "checking files",
|
||||
reasoningStreaming: true,
|
||||
isStreaming: true,
|
||||
createdAt: 1,
|
||||
};
|
||||
try {
|
||||
const { rerender } = render(
|
||||
<AgentActivityCluster
|
||||
messages={[liveReasoning]}
|
||||
isTurnStreaming
|
||||
hasBodyBelow
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("agent-activity-scroll")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
...liveReasoning,
|
||||
reasoningStreaming: false,
|
||||
isStreaming: false,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("agent-activity-scroll")).toBeInTheDocument();
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(901);
|
||||
});
|
||||
expect(screen.queryByTestId("agent-activity-scroll")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /1 steps/i })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"false",
|
||||
);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("renders file edit totals and a compact expanded file list", async () => {
|
||||
const restoreMotion = installReducedMotion();
|
||||
try {
|
||||
@ -583,6 +630,8 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /1 tool calls/i }));
|
||||
|
||||
expect(screen.getByText("Shell")).toBeInTheDocument();
|
||||
expect(screen.getByText(/cat << 'EOF' \| bash · script, 6 lines/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/SECRET_TOKEN/)).not.toBeInTheDocument();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user