Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 38x 38x 2x 2x 2x 2x 38x 38x 4x | import { useState } from 'react'
import { ChevronDown, ChevronRight, Copy } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { TokenDisplay } from '@/shared/components/TokenDisplay'
import { toast } from 'sonner'
export interface RunError {
message: string
code?: string
step?: string
details?: string
}
interface ErrorPanelProps {
error: RunError
}
export function ErrorPanel({ error }: ErrorPanelProps) {
const [showTechDetails, setShowTechDetails] = useState(false)
return (
<Card className="border-l-4 border-l-red-500" data-testid="run-error-panel">
<CardContent className="p-4">
<h3 className="mb-2 font-semibold text-destructive">Error</h3>
<p className="text-sm">{error.message}</p>
{error.code && (
<p className="mt-1 text-xs text-muted-foreground">Code: {error.code}</p>
)}
{error.step && (
<p className="mt-1 text-xs text-muted-foreground">Step: {error.step}</p>
)}
{error.details && (
<div className="mt-2">
<button
type="button"
onClick={() => setShowTechDetails((v) => !v)}
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
data-testid="toggle-error-details"
>
{showTechDetails ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
{showTechDetails ? 'Hide Technical Details' : 'Show Technical Details'}
</button>
{showTechDetails && (
<pre className="mt-2 overflow-auto rounded bg-muted p-2 text-xs" data-testid="error-details">
{error.details}
</pre>
)}
</div>
)}
</CardContent>
</Card>
)
}
interface OutputPanelProps {
output: string
}
export function OutputPanel({ output }: OutputPanelProps) {
return (
<Card className="border-l-4 border-l-green-500" data-testid="run-output-panel">
<CardContent className="p-4">
<h3 className="mb-2 font-semibold text-green-600">Output</h3>
<pre className="overflow-auto whitespace-pre-wrap rounded bg-muted p-3 text-xs leading-relaxed" data-testid="run-output">
{output}
</pre>
</CardContent>
</Card>
)
}
interface TokenSummaryCardsProps {
tokenInput: number | null | undefined
tokenOutput: number | null | undefined
}
export function TokenSummaryCards({ tokenInput, tokenOutput }: TokenSummaryCardsProps) {
const input = tokenInput ?? 0
const output = tokenOutput ?? 0
const total = input + output
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-3" data-testid="run-token-summary">
<Card>
<CardContent className="flex flex-col gap-1 p-4">
<span className="text-xs text-muted-foreground">Token Input</span>
<TokenDisplay value={input} />
</CardContent>
</Card>
<Card>
<CardContent className="flex flex-col gap-1 p-4">
<span className="text-xs text-muted-foreground">Token Output</span>
<TokenDisplay value={output} />
</CardContent>
</Card>
<Card>
<CardContent className="flex flex-col gap-1 p-4">
<span className="text-xs text-muted-foreground">Total</span>
<TokenDisplay value={total} />
</CardContent>
</Card>
</div>
)
}
export function CopyRunIdButton({ runId }: { runId: string }) {
return (
<Button
variant="ghost"
size="icon"
type="button"
aria-label="Copy Run ID"
data-testid="copy-run-id"
onClick={() => {
void navigator.clipboard.writeText(runId).then(() => {
toast.success('Run ID copied')
})
}}
>
<Copy className="size-4" />
</Button>
)
}
|