mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 16:12:30 +00:00
fix(webui): normalize thinking trace row box model
Thinking and Used tools are both auxiliary rows, but Thinking still carried an internal mb-2 even when it was standalone. That made collapsed Thinking rows visually taller than tool trace rows despite the shared thread spacing. Only add the extra bottom margin when a Thinking bubble has answer content below it in the same assistant message. Standalone Thinking rows now share the same outer box model as Used tools. Tests lock both standalone and answer-backed cases. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
82ba63e148
commit
321c565ec4
@ -92,7 +92,7 @@ export function MessageBubble({ message }: MessageBubbleProps) {
|
||||
return (
|
||||
<div className={cn("w-full text-[15px]", baseAnim)} style={{ lineHeight: "var(--cjk-line-height)" }}>
|
||||
{hasReasoning ? (
|
||||
<ReasoningBubble text={reasoning} streaming={reasoningStreaming} />
|
||||
<ReasoningBubble text={reasoning} streaming={reasoningStreaming} hasBodyBelow={!empty} />
|
||||
) : null}
|
||||
{empty && message.isStreaming && !hasReasoning ? (
|
||||
<TypingDots />
|
||||
@ -443,6 +443,7 @@ function TraceGroup({ message, animClass }: TraceGroupProps) {
|
||||
interface ReasoningBubbleProps {
|
||||
text: string;
|
||||
streaming: boolean;
|
||||
hasBodyBelow: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,7 +457,7 @@ interface ReasoningBubbleProps {
|
||||
* the user can re-expand to inspect the chain of thought. The local
|
||||
* toggle persists once the user interacts.
|
||||
*/
|
||||
function ReasoningBubble({ text, streaming }: ReasoningBubbleProps) {
|
||||
function ReasoningBubble({ text, streaming, hasBodyBelow }: ReasoningBubbleProps) {
|
||||
const { t } = useTranslation();
|
||||
const [userToggled, setUserToggled] = useState(false);
|
||||
const [openLocal, setOpenLocal] = useState(true);
|
||||
@ -466,7 +467,12 @@ function ReasoningBubble({ text, streaming }: ReasoningBubbleProps) {
|
||||
setOpenLocal((v) => (userToggled ? !v : !open));
|
||||
};
|
||||
return (
|
||||
<div className="mb-2 w-full animate-in fade-in-0 slide-in-from-top-1 duration-200">
|
||||
<div
|
||||
className={cn(
|
||||
"w-full animate-in fade-in-0 slide-in-from-top-1 duration-200",
|
||||
hasBodyBelow && "mb-2",
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggle}
|
||||
|
||||
@ -119,6 +119,7 @@ describe("MessageBubble", () => {
|
||||
expect(screen.getByText("Thinking…")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Step 1: parse intent\./)).toBeInTheDocument();
|
||||
expect(container.querySelector(".reasoning-shimmer")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /thinking/i }).parentElement).not.toHaveClass("mb-2");
|
||||
});
|
||||
|
||||
it("collapses the reasoning section by default once streaming ends", () => {
|
||||
@ -136,6 +137,7 @@ describe("MessageBubble", () => {
|
||||
expect(screen.getByText("Thinking")).toBeInTheDocument();
|
||||
expect(screen.getByText("The answer is 42.")).toBeInTheDocument();
|
||||
expect(screen.queryByText("hidden until expanded")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /thinking/i }).parentElement).toHaveClass("mb-2");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /thinking/i }));
|
||||
expect(screen.getByText("hidden until expanded")).toBeInTheDocument();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user