Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2641,6 +2641,7 @@ function ActivityGroupTimelineRow({
details.push(
<ReasoningTraceBlock
key={entry.id}
anchorKey={row.id}
messages={messages}
live={row.active && index === row.entries.length - 1}
showHeader={work.length > 0}
Expand All @@ -2667,7 +2668,7 @@ function ActivityGroupTimelineRow({
shimmer={thinking}
/>
</button>
{row.expanded ? <div className="mt-2 space-y-2">{details}</div> : null}
{row.expanded ? <div className="mt-2">{details}</div> : null}
</div>
);
}
Expand All @@ -2685,22 +2686,24 @@ function ThinkingTimelineRow() {
}

/**
* Thinking inside an expanded activity group: the trace is already one click
* deep, so the text renders under its "Thought" header without another toggle.
* Thinking inside a tool group has its own disclosure, preserved across recycling.
* A group whose row already reads "Thought" (no visible tool) skips the header.
*/
function ReasoningTraceBlock({
anchorKey,
messages,
live,
showHeader,
}: {
anchorKey: string;
messages: ReadonlyArray<ChatMessage>;
live: boolean;
showHeader: boolean;
}) {
const ctx = use(TimelineRowCtx);
const { isWorking, unsettledTurnId } = use(TimelineRowActivityCtx);
const first = messages[0]!;
const expanded = !showHeader || ctx.expandedReasoningMessageIds.has(first.id);
const streaming =
live &&
messages.some((reasoningMessage) => reasoningMessage.streaming) &&
Expand All @@ -2714,39 +2717,56 @@ function ReasoningTraceBlock({
return null;
}
const label = streaming ? "Thinking" : "Thought";
const collapsedPreview = messages.find((message) => message.text.trim().length > 0)?.text.trim();
const headerText = expanded ? label : (collapsedPreview ?? label);
return (
<div className="flex flex-col">
{showHeader ? (
<div className="flex min-h-6 select-none items-center gap-1.5 px-0.5 py-0.5 text-sm leading-relaxed">
<button
Comment thread
maria-rcks marked this conversation as resolved.
type="button"
aria-expanded={expanded}
onClick={() => ctx.onToggleReasoning(first.id, !expanded, anchorKey)}
className="flex min-h-6 cursor-pointer select-none items-center gap-1.5 rounded-md px-0.5 text-start text-sm leading-relaxed transition-colors hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
>
<span className="flex size-6 shrink-0 items-center justify-center text-icon-muted">
<BrainIcon aria-hidden className="block size-4 shrink-0 stroke-[1.8] opacity-70" />
</span>
<span
ref={streaming ? observeVisibleAnimation : undefined}
className="relative min-w-0 flex-1 truncate text-secondary-label"
>
{label}
{streaming ? <ActivityShimmerOverlay>{label}</ActivityShimmerOverlay> : null}
{headerText}
{streaming ? <ActivityShimmerOverlay>{headerText}</ActivityShimmerOverlay> : null}
</span>
<span className="flex size-4 shrink-0 items-center justify-center" aria-hidden>
<ChevronRightIcon
className={cn(
"size-3 shrink-0 text-icon-muted opacity-70 transition-transform duration-200",
expanded && "rotate-90",
)}
/>
</span>
</button>
) : null}
{expanded ? (
<div className="ms-7 flex max-h-96 flex-col gap-3 overflow-auto px-0.5 py-1 select-text">
{messages.map((reasoningMessage) => (
<ChatMarkdown
key={reasoningMessage.id}
className="text-foreground"
text={reasoningMessage.text}
cwd={ctx.markdownCwd}
threadRef={ctx.threadRef ?? undefined}
isStreaming={streaming && reasoningMessage.streaming}
lineBreaks
skills={ctx.skills}
headingLevelOffset={MESSAGE_HEADING_LEVEL}
onUseArtifactTemplate={ctx.onUseArtifactTemplate}
onImageExpand={ctx.onImageExpand}
/>
))}
</div>
) : null}
<div className="ms-7 flex max-h-96 flex-col gap-3 overflow-auto px-0.5 py-1 select-text">
{messages.map((reasoningMessage) => (
<ChatMarkdown
key={reasoningMessage.id}
className="text-foreground"
text={reasoningMessage.text}
cwd={ctx.markdownCwd}
threadRef={ctx.threadRef ?? undefined}
isStreaming={streaming && reasoningMessage.streaming}
lineBreaks
skills={ctx.skills}
headingLevelOffset={MESSAGE_HEADING_LEVEL}
onUseArtifactTemplate={ctx.onUseArtifactTemplate}
onImageExpand={ctx.onImageExpand}
/>
))}
</div>
</div>
);
}
Expand Down
Loading