1- import {
2- CheckIcon ,
3- ChevronDownIcon ,
4- ChevronUpIcon ,
5- ClipboardDocumentIcon ,
6- CodeBracketSquareIcon ,
7- } from "@heroicons/react/20/solid" ;
1+ import { ChevronDownIcon , ChevronUpIcon } from "@heroicons/react/20/solid" ;
2+ import { Clipboard , ClipboardCheck } from "lucide-react" ;
83import { Suspense , useEffect , useState } from "react" ;
4+ import { CodeSquareIcon } from "~/assets/icons/CodeSquareIcon" ;
5+ import { TextSquareIcon } from "~/assets/icons/TextSquareIcon" ;
96import { CodeBlock } from "~/components/code/CodeBlock" ;
107import { StreamdownRenderer } from "~/components/code/StreamdownRenderer" ;
118import { Button , LinkButton } from "~/components/primitives/Buttons" ;
129import { Header3 } from "~/components/primitives/Headers" ;
1310import tablerSpritePath from "~/components/primitives/tabler-sprite.svg" ;
11+ import {
12+ Tooltip ,
13+ TooltipContent ,
14+ TooltipProvider ,
15+ TooltipTrigger ,
16+ } from "~/components/primitives/Tooltip" ;
17+ import { cn } from "~/utils/cn" ;
1418import type { DisplayItem , ToolUse } from "./types" ;
1519
1620export type PromptLink = {
@@ -69,13 +73,7 @@ export function ChatBubble({ children }: { children: React.ReactNode }) {
6973// System
7074// ---------------------------------------------------------------------------
7175
72- function SystemSection ( {
73- text,
74- promptLink,
75- } : {
76- text : string ;
77- promptLink ?: PromptLink ;
78- } ) {
76+ function SystemSection ( { text, promptLink } : { text : string ; promptLink ?: PromptLink } ) {
7977 const [ expanded , setExpanded ] = useState ( false ) ;
8078 const isLong = text . length > 150 ;
8179 const preview = isLong ? text . slice ( 0 , 150 ) + "..." : text ;
@@ -109,7 +107,7 @@ function SystemSection({
109107 ) }
110108 </ div >
111109 < ChatBubble >
112- < div className = "font-sans text-sm font-normal text-text-dimmed streamdown-container " >
110+ < div className = "streamdown-container font-sans text-sm font-normal text-text-dimmed" >
113111 < Suspense fallback = { < span className = "whitespace-pre-wrap" > { displayText } </ span > } >
114112 < StreamdownRenderer > { displayText } </ StreamdownRenderer >
115113 </ Suspense >
@@ -128,7 +126,7 @@ function UserSection({ text }: { text: string }) {
128126 < div className = "flex flex-col gap-1.5 py-2.5" >
129127 < SectionHeader label = "User" />
130128 < ChatBubble >
131- < div className = "font-sans text-sm font-normal text-text-dimmed streamdown-container " >
129+ < div className = "streamdown-container font-sans text-sm font-normal text-text-dimmed" >
132130 < Suspense fallback = { < span className = "whitespace-pre-wrap" > { text } </ span > } >
133131 < StreamdownRenderer > { text } </ StreamdownRenderer >
134132 </ Suspense >
@@ -190,22 +188,44 @@ export function AssistantResponse({
190188 < SectionHeader
191189 label = { headerLabel }
192190 right = {
193- < div className = "flex items-center" >
194- < Button
195- variant = "minimal/small"
196- onClick = { ( ) => setMode ( mode === "rendered" ? "raw" : "rendered" ) }
197- LeadingIcon = { CodeBracketSquareIcon }
198- >
199- { mode === "rendered" ? "Raw" : "Rendered" }
200- </ Button >
201- < Button
202- variant = "minimal/small"
203- onClick = { handleCopy }
204- LeadingIcon = { copied ? CheckIcon : ClipboardDocumentIcon }
205- leadingIconClassName = { copied ? "text-green-500" : undefined }
206- >
207- Copy
208- </ Button >
191+ < div className = "flex items-center gap-2" >
192+ < TooltipProvider >
193+ < Tooltip disableHoverableContent >
194+ < TooltipTrigger
195+ onClick = { ( ) => setMode ( mode === "rendered" ? "raw" : "rendered" ) }
196+ className = "text-text-dimmed transition-colors duration-100 focus-custom hover:cursor-pointer hover:text-text-bright"
197+ >
198+ { mode === "rendered" ? (
199+ < CodeSquareIcon className = "size-4.5" />
200+ ) : (
201+ < TextSquareIcon className = "size-4.5" />
202+ ) }
203+ </ TooltipTrigger >
204+ < TooltipContent side = "top" className = "text-xs" >
205+ { mode === "rendered" ? "Show raw" : "Show rendered" }
206+ </ TooltipContent >
207+ </ Tooltip >
208+ </ TooltipProvider >
209+ < TooltipProvider >
210+ < Tooltip open = { copied || undefined } disableHoverableContent >
211+ < TooltipTrigger
212+ onClick = { handleCopy }
213+ className = { cn (
214+ "transition-colors duration-100 focus-custom hover:cursor-pointer" ,
215+ copied ? "text-success" : "text-text-dimmed hover:text-text-bright"
216+ ) }
217+ >
218+ { copied ? (
219+ < ClipboardCheck className = "size-4" />
220+ ) : (
221+ < Clipboard className = "size-4" />
222+ ) }
223+ </ TooltipTrigger >
224+ < TooltipContent side = "top" className = "text-xs" >
225+ { copied ? "Copied" : "Copy" }
226+ </ TooltipContent >
227+ </ Tooltip >
228+ </ TooltipProvider >
209229 </ div >
210230 }
211231 />
@@ -285,8 +305,18 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
285305 >
286306 < div className = "flex items-center gap-2 px-2.5 py-1.5" >
287307 { hasSubAgent && (
288- < svg className = "size-3.5 text-indigo-400" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = { 1.5 } >
289- < path strokeLinecap = "round" strokeLinejoin = "round" d = "M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z" />
308+ < svg
309+ className = "size-3.5 text-indigo-400"
310+ viewBox = "0 0 24 24"
311+ fill = "none"
312+ stroke = "currentColor"
313+ strokeWidth = { 1.5 }
314+ >
315+ < path
316+ strokeLinecap = "round"
317+ strokeLinejoin = "round"
318+ d = "M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z"
319+ />
290320 </ svg >
291321 ) }
292322 < code
@@ -327,9 +357,7 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
327357 ) ) }
328358 </ div >
329359
330- { activeTab === "agent" && hasSubAgent && (
331- < SubAgentContent parts = { tool . subAgent ! . parts } />
332- ) }
360+ { activeTab === "agent" && hasSubAgent && < SubAgentContent parts = { tool . subAgent ! . parts } /> }
333361
334362 { activeTab === "input" && hasInput && (
335363 < div className = "border-t border-grid-dimmed" >
@@ -338,6 +366,7 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
338366 maxLines = { 12 }
339367 showLineNumbers = { false }
340368 showCopyButton
369+ className = "rounded-none border-0"
341370 />
342371 </ div >
343372 ) }
@@ -350,13 +379,12 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
350379 maxLines = { 16 }
351380 showLineNumbers = { false }
352381 showCopyButton
382+ className = "rounded-none border-0"
353383 />
354384 ) : (
355- < div className = "p-2.5 font-sans text-sm font-normal text-text-dimmed streamdown-container " >
385+ < div className = "streamdown-container p-2.5 font-sans text-sm font-normal text-text-dimmed" >
356386 < Suspense
357- fallback = {
358- < span className = "whitespace-pre-wrap" > { tool . resultOutput } </ span >
359- }
387+ fallback = { < span className = "whitespace-pre-wrap" > { tool . resultOutput } </ span > }
360388 >
361389 < StreamdownRenderer > { tool . resultOutput ! } </ StreamdownRenderer >
362390 </ Suspense >
@@ -380,6 +408,7 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
380408 maxLines = { 16 }
381409 showLineNumbers = { false }
382410 showCopyButton
411+ className = "rounded-none border-0"
383412 />
384413 </ div >
385414 ) }
@@ -393,20 +422,14 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
393422
394423function SubAgentContent ( { parts } : { parts : any [ ] } ) {
395424 // Extract sub-agent run ID from injected metadata part
396- const runPart = parts . find (
397- ( p : any ) => p . type === "data-subagent-run" && p . data ?. runId
398- ) ;
425+ const runPart = parts . find ( ( p : any ) => p . type === "data-subagent-run" && p . data ?. runId ) ;
399426 const subAgentRunId = runPart ?. data ?. runId as string | undefined ;
400427
401428 return (
402429 < div className = "space-y-2 border-t border-indigo-500/20 p-2.5" >
403430 { subAgentRunId && (
404431 < div className = "flex justify-end" >
405- < LinkButton
406- to = { `/runs/${ subAgentRunId } ` }
407- variant = "tertiary/small"
408- target = "_blank"
409- >
432+ < LinkButton to = { `/runs/${ subAgentRunId } ` } variant = "tertiary/small" target = "_blank" >
410433 View sub-agent run
411434 </ LinkButton >
412435 </ div >
0 commit comments