Skip to content

Commit d0918d2

Browse files
committed
fix: show first reasoning line inline
1 parent 958d1db commit d0918d2

File tree

4 files changed

+36
-13
lines changed

4 files changed

+36
-13
lines changed

src/components/Messages/ReasoningMessage.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,13 @@ export const EmptyContent: Story = {
135135
message: createReasoningMessage(""),
136136
},
137137
};
138+
export const ExpandablePreview: Story = {
139+
args: {
140+
message: createReasoningMessage(
141+
"Assessing quicksort mechanics and choosing example array...\n" +
142+
"Plan: explain pivot selection, partitioning, recursion, base case.\n" +
143+
"Next, I'll outline best practices for implementing the partition step.",
144+
{ isStreaming: false }
145+
),
146+
},
147+
};

src/components/Messages/ReasoningMessage.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ export const ReasoningMessage: React.FC<ReasoningMessageProps> = ({ message, cla
1818
const isStreaming = message.isStreaming;
1919
const trimmedContent = content?.trim() ?? "";
2020
const hasContent = trimmedContent.length > 0;
21+
const summaryLine = hasContent ? (trimmedContent.split(/\r?\n/)[0] ?? "") : "";
22+
const hasAdditionalLines = hasContent && /[\r\n]/.test(trimmedContent);
2123
// OpenAI models often emit terse, single-line traces; surface them inline instead of hiding behind the label.
22-
const isSingleLineTrace = !isStreaming && hasContent && !/[\r\n]/.test(trimmedContent);
23-
const isCollapsible = !isStreaming && hasContent && !isSingleLineTrace;
24+
const isSingleLineTrace = !isStreaming && hasContent && !hasAdditionalLines;
25+
const isCollapsible = !isStreaming && hasContent && hasAdditionalLines;
26+
const showEllipsis = isCollapsible && !isExpanded;
2427

2528
// Auto-collapse when streaming ends
2629
useEffect(() => {
@@ -77,18 +80,26 @@ export const ReasoningMessage: React.FC<ReasoningMessageProps> = ({ message, cla
7780
<span className="text-xs">
7881
<Lightbulb className={cn("size-3.5", isStreaming && "animate-pulse")} />
7982
</span>
80-
<div className="truncate">
83+
<div className="flex min-w-0 items-center gap-1 truncate">
8184
{isStreaming ? (
8285
<Shimmer colorClass="var(--color-thinking-mode)">Thinking...</Shimmer>
83-
) : isSingleLineTrace ? (
86+
) : hasContent ? (
8487
<MarkdownRenderer
85-
content={trimmedContent}
88+
content={summaryLine}
8689
className="truncate [&_*]:inline [&_*]:align-baseline [&_*]:whitespace-nowrap"
8790
style={{ fontSize: 12, lineHeight: "18px" }}
8891
/>
8992
) : (
9093
"Thought"
9194
)}
95+
{showEllipsis && (
96+
<span
97+
className="text-[11px] tracking-widest text-[color:var(--color-text)] opacity-70"
98+
data-testid="reasoning-ellipsis"
99+
>
100+
...
101+
</span>
102+
)}
92103
</div>
93104
</div>
94105
{isCollapsible && (

src/services/mock/scenarios/toolFlows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ const reasoningQuicksortTurn: ScenarioTurn = {
317317
{
318318
kind: "reasoning-delta",
319319
delay: STREAM_BASE_DELAY,
320-
text: "Assessing quicksort mechanics and choosing example array...",
320+
text: "Assessing quicksort mechanics and choosing example array...\n",
321321
},
322322
{
323323
kind: "reasoning-delta",

tests/e2e/scenarios/toolFlows.spec.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,18 @@ test.describe("tool and reasoning flows", () => {
141141
}
142142

143143
const transcript = page.getByRole("log", { name: "Conversation transcript" });
144-
const thinkingHeader = transcript.getByText("Thought");
145-
const hasThoughtLabel = (await thinkingHeader.count()) > 0;
144+
const reasoningPreview = transcript
145+
.getByText("Assessing quicksort mechanics and choosing example array...")
146+
.first();
147+
await expect(reasoningPreview).toBeVisible();
146148

147-
if (hasThoughtLabel) {
148-
await expect(thinkingHeader.first()).toBeVisible();
149-
await thinkingHeader.first().click();
150-
}
149+
const ellipsisIndicator = transcript.getByTestId("reasoning-ellipsis").first();
150+
await expect(ellipsisIndicator).toBeVisible();
151+
152+
await reasoningPreview.click();
151153

152154
await expect(
153-
transcript.getByText("Assessing quicksort mechanics and choosing example array...")
155+
transcript.getByText("Plan: explain pivot selection, partitioning, recursion, base case.")
154156
).toBeVisible();
155157
await ui.chat.expectTranscriptContains("Quicksort works by picking a pivot");
156158
});

0 commit comments

Comments
 (0)