Skip to content

Commit 463f972

Browse files
committed
Style improvements to the messages
1 parent 218725a commit 463f972

File tree

2 files changed

+79
-43
lines changed

2 files changed

+79
-43
lines changed

apps/webapp/app/components/runs/v3/ai/AIChatMessages.tsx

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { CheckIcon, ClipboardDocumentIcon, CodeBracketSquareIcon } from "@heroicons/react/20/solid";
12
import { lazy, Suspense, useState } from "react";
23
import { CodeBlock } from "~/components/code/CodeBlock";
4+
import { Button } from "~/components/primitives/Buttons";
35
import { Header3 } from "~/components/primitives/Headers";
6+
import { Paragraph } from "~/components/primitives/Paragraph";
47
import type { DisplayItem, ToolUse } from "./types";
58

69
// Lazy load streamdown to avoid SSR issues
@@ -16,7 +19,7 @@ const StreamdownRenderer = lazy(() =>
1619

1720
export function AIChatMessages({ items }: { items: DisplayItem[] }) {
1821
return (
19-
<div className="flex flex-col divide-y divide-grid-bright">
22+
<div className="flex flex-col gap-1">
2023
{items.map((item, i) => {
2124
switch (item.type) {
2225
case "system":
@@ -37,13 +40,7 @@ export function AIChatMessages({ items }: { items: DisplayItem[] }) {
3740
// Section header (shared across all sections)
3841
// ---------------------------------------------------------------------------
3942

40-
function SectionHeader({
41-
label,
42-
right,
43-
}: {
44-
label: string;
45-
right?: React.ReactNode;
46-
}) {
43+
function SectionHeader({ label, right }: { label: string; right?: React.ReactNode }) {
4744
return (
4845
<div className="flex items-center justify-between">
4946
<Header3>{label}</Header3>
@@ -52,6 +49,14 @@ function SectionHeader({
5249
);
5350
}
5451

52+
export function ChatBubble({ children }: { children: React.ReactNode }) {
53+
return (
54+
<div className="rounded-md border border-grid-bright bg-charcoal-750/50 px-3.5 py-2">
55+
{children}
56+
</div>
57+
);
58+
}
59+
5560
// ---------------------------------------------------------------------------
5661
// System
5762
// ---------------------------------------------------------------------------
@@ -62,7 +67,7 @@ function SystemSection({ text }: { text: string }) {
6267
const preview = isLong ? text.slice(0, 150) + "..." : text;
6368

6469
return (
65-
<div className="flex flex-col gap-1 py-2.5">
70+
<div className="flex flex-col gap-1.5 py-2.5">
6671
<SectionHeader
6772
label="System"
6873
right={
@@ -76,9 +81,11 @@ function SystemSection({ text }: { text: string }) {
7681
) : undefined
7782
}
7883
/>
79-
<pre className="whitespace-pre-wrap text-xs leading-relaxed text-text-dimmed">
80-
{expanded || !isLong ? text : preview}
81-
</pre>
84+
<ChatBubble>
85+
<Paragraph variant="small/dimmed" className="whitespace-pre-wrap">
86+
{expanded || !isLong ? text : preview}
87+
</Paragraph>
88+
</ChatBubble>
8289
</div>
8390
);
8491
}
@@ -89,9 +96,11 @@ function SystemSection({ text }: { text: string }) {
8996

9097
function UserSection({ text }: { text: string }) {
9198
return (
92-
<div className="flex flex-col gap-1 py-2.5">
99+
<div className="flex flex-col gap-1.5 py-2.5">
93100
<SectionHeader label="User" />
94-
<p className="text-sm text-text-bright">{text}</p>
101+
<ChatBubble>
102+
<Paragraph variant="small/dimmed">{text}</Paragraph>
103+
</ChatBubble>
95104
</div>
96105
);
97106
}
@@ -108,36 +117,54 @@ export function AssistantResponse({
108117
headerLabel?: string;
109118
}) {
110119
const [mode, setMode] = useState<"rendered" | "raw">("rendered");
120+
const [copied, setCopied] = useState(false);
121+
122+
function handleCopy() {
123+
navigator.clipboard.writeText(text);
124+
setCopied(true);
125+
setTimeout(() => setCopied(false), 2000);
126+
}
111127

112128
return (
113-
<div className="flex flex-col gap-1 py-2.5">
129+
<div className="flex flex-col gap-1.5 py-2.5">
114130
<SectionHeader
115131
label={headerLabel}
116132
right={
117-
<div className="flex items-center gap-2">
118-
<button
133+
<div className="flex items-center">
134+
<Button
135+
variant="minimal/small"
119136
onClick={() => setMode(mode === "rendered" ? "raw" : "rendered")}
120-
className="text-[10px] text-text-link hover:underline"
137+
LeadingIcon={CodeBracketSquareIcon}
121138
>
122139
{mode === "rendered" ? "Raw" : "Rendered"}
123-
</button>
124-
<button
125-
onClick={() => navigator.clipboard.writeText(text)}
126-
className="text-[10px] text-text-link hover:underline"
140+
</Button>
141+
<Button
142+
variant="minimal/small"
143+
onClick={handleCopy}
144+
LeadingIcon={copied ? CheckIcon : ClipboardDocumentIcon}
145+
leadingIconClassName={copied ? "text-green-500" : undefined}
127146
>
128147
Copy
129-
</button>
148+
</Button>
130149
</div>
131150
}
132151
/>
133152
{mode === "rendered" ? (
134-
<div className="streamdown-container text-sm text-text-bright">
135-
<Suspense fallback={<pre className="whitespace-pre-wrap">{text}</pre>}>
136-
<StreamdownRenderer>{text}</StreamdownRenderer>
137-
</Suspense>
138-
</div>
153+
<ChatBubble>
154+
<Paragraph variant="small/dimmed" className="streamdown-container">
155+
<Suspense fallback={<span className="whitespace-pre-wrap">{text}</span>}>
156+
<StreamdownRenderer>{text}</StreamdownRenderer>
157+
</Suspense>
158+
</Paragraph>
159+
</ChatBubble>
139160
) : (
140-
<CodeBlock code={text} maxLines={20} showLineNumbers={false} showCopyButton />
161+
<CodeBlock
162+
code={text}
163+
maxLines={20}
164+
showLineNumbers={false}
165+
showCopyButton={false}
166+
className="pl-2"
167+
/>
141168
)}
142169
</div>
143170
);
@@ -151,9 +178,13 @@ function ToolUseSection({ tools }: { tools: ToolUse[] }) {
151178
return (
152179
<div className="flex flex-col gap-1.5 py-2.5">
153180
<SectionHeader label={tools.length === 1 ? "Tool call" : `Tool calls (${tools.length})`} />
154-
{tools.map((tool) => (
155-
<ToolUseRow key={tool.toolCallId} tool={tool} />
156-
))}
181+
<ChatBubble>
182+
<div className="flex flex-col gap-2">
183+
{tools.map((tool) => (
184+
<ToolUseRow key={tool.toolCallId} tool={tool} />
185+
))}
186+
</div>
187+
</ChatBubble>
157188
</div>
158189
);
159190
}
@@ -228,9 +259,9 @@ function ToolUseRow({ tool }: { tool: ToolUse }) {
228259
)}
229260

230261
{activeTab === "details" && hasDetails && (
231-
<div className="border-t border-grid-dimmed px-2.5 py-2 flex flex-col gap-2">
262+
<div className="flex flex-col gap-2 border-t border-grid-dimmed px-2.5 py-2">
232263
{tool.description && (
233-
<p className="text-xs text-text-dimmed leading-relaxed">{tool.description}</p>
264+
<p className="text-xs leading-relaxed text-text-dimmed">{tool.description}</p>
234265
)}
235266
{tool.parametersJson && (
236267
<div>

apps/webapp/app/components/runs/v3/ai/AISpanDetails.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { CheckIcon, ClipboardDocumentIcon } from "@heroicons/react/20/solid";
22
import { useState } from "react";
33
import { Button } from "~/components/primitives/Buttons";
44
import { Header3 } from "~/components/primitives/Headers";
5+
import { Paragraph } from "~/components/primitives/Paragraph";
56
import { TabButton, TabContainer } from "~/components/primitives/Tabs";
67
import { useHasAdminAccess } from "~/hooks/useUser";
7-
import { AIChatMessages, AssistantResponse } from "./AIChatMessages";
8+
import { AIChatMessages, AssistantResponse, ChatBubble } from "./AIChatMessages";
89
import { AIStatsSummary, AITagsRow } from "./AIModelSummary";
910
import { AIToolsInventory } from "./AIToolsInventory";
1011
import type { AISpanData, DisplayItem } from "./types";
@@ -85,21 +86,25 @@ function OverviewTab({ aiData }: { aiData: AISpanData }) {
8586

8687
{/* Input (last user prompt) */}
8788
{userText && (
88-
<div className="flex flex-col gap-1 py-2.5">
89+
<div className="flex flex-col gap-1.5 py-2.5">
8990
<Header3>Input</Header3>
90-
<p className="text-sm text-text-bright">{userText}</p>
91+
<ChatBubble>
92+
<Paragraph variant="small/dimmed">{userText}</Paragraph>
93+
</ChatBubble>
9194
</div>
9295
)}
9396

9497
{/* Output (assistant response or tool calls) */}
9598
{outputText && <AssistantResponse text={outputText} headerLabel="Output" />}
9699
{outputToolNames.length > 0 && !outputText && (
97-
<div className="flex flex-col gap-1 py-2.5">
100+
<div className="flex flex-col gap-1.5 py-2.5">
98101
<Header3>Output</Header3>
99-
<p className="text-sm text-text-dimmed">
100-
Called {outputToolNames.length === 1 ? "tool" : "tools"}:{" "}
101-
<span className="font-mono text-text-bright">{outputToolNames.join(", ")}</span>
102-
</p>
102+
<ChatBubble>
103+
<Paragraph variant="small/dimmed">
104+
Called {outputToolNames.length === 1 ? "tool" : "tools"}:{" "}
105+
<span className="font-mono text-text-bright">{outputToolNames.join(", ")}</span>
106+
</Paragraph>
107+
</ChatBubble>
103108
</div>
104109
)}
105110
</div>
@@ -109,7 +114,7 @@ function OverviewTab({ aiData }: { aiData: AISpanData }) {
109114
function MessagesTab({ aiData }: { aiData: AISpanData }) {
110115
return (
111116
<div className="px-3">
112-
<div className="flex flex-col divide-y divide-grid-bright">
117+
<div className="flex flex-col">
113118
{aiData.items && aiData.items.length > 0 && <AIChatMessages items={aiData.items} />}
114119
{aiData.responseText && !hasAssistantItem(aiData.items) && (
115120
<AssistantResponse text={aiData.responseText} />

0 commit comments

Comments
 (0)