@workerdeck/ui 0.13.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/build/{SessionPanel-CZMA44NM.d.mts → SessionPanel-B9CHoq8x.d.mts} +213 -27
- package/build/{SessionPanel-CKQa4i0Y.mjs → SessionPanel-DII9MmQ8.mjs} +5192 -2542
- package/build/SessionPanel-DII9MmQ8.mjs.map +1 -0
- package/build/{format-ljc3lKpA.d.mts → format-DfI_je9S.d.mts} +1 -1
- package/build/format.d.mts +39 -4
- package/build/format.mjs +2 -118
- package/build/index.d.mts +494 -45
- package/build/index.mjs +182 -24
- package/build/index.mjs.map +1 -1
- package/build/status-Ydzi7n6j.mjs +143 -0
- package/build/status-Ydzi7n6j.mjs.map +1 -0
- package/build/workspace.d.mts +13 -1
- package/build/workspace.mjs +111 -5
- package/build/workspace.mjs.map +1 -1
- package/package.json +14 -7
- package/src/components/agent/Composer.tsx +251 -84
- package/src/components/agent/Conversation.tsx +12 -12
- package/src/components/agent/FileCard.tsx +0 -26
- package/src/components/agent/FileTree.tsx +9 -8
- package/src/components/agent/Loader.tsx +22 -72
- package/src/components/agent/Message.tsx +11 -43
- package/src/components/agent/PermissionPrompt.tsx +0 -92
- package/src/components/agent/QuestionPrompt.tsx +0 -122
- package/src/components/agent/Reasoning.tsx +5 -19
- package/src/components/agent/Response.tsx +1 -132
- package/src/components/agent/SessionBrowser.tsx +35 -25
- package/src/components/agent/SessionPanel.tsx +312 -63
- package/src/components/agent/SessionWorkspace.tsx +36 -0
- package/src/components/agent/StatusBar.tsx +70 -15
- package/src/components/agent/ToolCallCard.tsx +17 -111
- package/src/components/agent/Transcript.tsx +710 -203
- package/src/components/agent/UsageDialog.tsx +20 -106
- package/src/components/agent/UsageMeters.tsx +133 -0
- package/src/components/agent/pulse.tsx +3 -2
- package/src/components/agent/transcript-rows.ts +82 -0
- package/src/components/agent/transcript-variant.tsx +43 -51
- package/src/components/agent/use-height-epoch.ts +60 -0
- package/src/components/agent/use-path-links.ts +147 -0
- package/src/components/agent/use-transcript-jumps.ts +190 -0
- package/src/components/prompt-area/cursor-helpers.ts +65 -0
- package/src/components/prompt-area/use-prompt-area.ts +16 -10
- package/src/components/terminal/PermissionPrompt.tsx +119 -0
- package/src/components/terminal/QuestionPrompt.tsx +322 -0
- package/src/components/terminal/StatusLine.tsx +159 -0
- package/src/components/terminal/TerminalTranscript.tsx +147 -0
- package/src/components/terminal/affordances.tsx +118 -0
- package/src/components/terminal/diff.tsx +130 -0
- package/src/components/terminal/height.ts +727 -0
- package/src/components/terminal/items.tsx +449 -0
- package/src/components/terminal/markdown.tsx +191 -0
- package/src/components/terminal/press.tsx +120 -0
- package/src/components/terminal/prompt.tsx +343 -0
- package/src/components/terminal/result-preview.ts +72 -0
- package/src/components/terminal/row.tsx +132 -0
- package/src/components/terminal/scrubber.tsx +663 -0
- package/src/components/terminal/surface.tsx +80 -0
- package/src/components/terminal/tool-run.ts +91 -0
- package/src/components/ui/Badge.tsx +6 -1
- package/src/components/ui/Empty.tsx +56 -0
- package/src/components/ui/Splitter.tsx +14 -0
- package/src/index.ts +36 -0
- package/src/lib/status.ts +59 -3
- package/src/lib/tool-icon.ts +14 -0
- package/src/styles/terminal.css +1011 -0
- package/src/styles/theme.css +73 -0
- package/build/SessionPanel-CKQa4i0Y.mjs.map +0 -1
- package/build/format.mjs.map +0 -1
- package/src/components/agent/line-prompt.tsx +0 -249
|
@@ -9,9 +9,19 @@ import { Tip } from '../ui/Tooltip.tsx'
|
|
|
9
9
|
import { cn } from '../../lib/utils.ts'
|
|
10
10
|
import { formatCost, formatCountdown, formatTokens } from '../../lib/format.ts'
|
|
11
11
|
import { STATUS_META } from './status.ts'
|
|
12
|
+
import { useTranscriptVariant } from './transcript-variant.tsx'
|
|
12
13
|
|
|
13
14
|
export interface StatusBarProps {
|
|
14
15
|
state: TranscriptState
|
|
16
|
+
/**
|
|
17
|
+
* Plan windows to draw, when they should not be the session's own. The panel
|
|
18
|
+
* hands over the gateway's per-profile state merged over this transcript's
|
|
19
|
+
* reading (`mergeUsage`): a session's own `rate_limit` readings arrive only at
|
|
20
|
+
* a turn's edges, so one idle since yesterday would otherwise render
|
|
21
|
+
* yesterday's number as current. Absent = `state.rateLimits`, which is what a
|
|
22
|
+
* bar composed by hand outside the panel gets.
|
|
23
|
+
*/
|
|
24
|
+
rateLimits?: Record<string, RateLimitInfo>
|
|
15
25
|
/** @deprecated Pass {@link StatusBarProps.connection}; kept so an embedder
|
|
16
26
|
* still handing over a boolean keeps working. */
|
|
17
27
|
connected?: boolean
|
|
@@ -28,6 +38,13 @@ export interface StatusBarProps {
|
|
|
28
38
|
onOpenStatus?: () => void
|
|
29
39
|
onOpenContext?: () => void
|
|
30
40
|
onOpenUsage?: () => void
|
|
41
|
+
/**
|
|
42
|
+
* The session's own controls (model, permission mode), for
|
|
43
|
+
* `controlsSurface: 'status'`. They sit at the end of the readings cluster —
|
|
44
|
+
* status, context, usage, then what you can *change* — rather than out by the
|
|
45
|
+
* actions menu, because they belong with the session facts they act on.
|
|
46
|
+
*/
|
|
47
|
+
controls?: ReactNode
|
|
31
48
|
/** Trailing slot — the session-actions menu, at the bar's trailing edge. */
|
|
32
49
|
actions?: ReactNode
|
|
33
50
|
/** Which edge the bar sits on, so its separating rule goes on the other side.
|
|
@@ -113,16 +130,22 @@ function RateLimitMeter({ label, info, now }: { label: string; info: RateLimitIn
|
|
|
113
130
|
{info.status === 'rejected' ? <div className='text-danger'>Limit reached</div> : null}
|
|
114
131
|
</div>
|
|
115
132
|
}>
|
|
133
|
+
{/* Inline, not `inline-flex`: a flex container contributes its FIRST
|
|
134
|
+
item's baseline, and the first item here is the ring — so a flex
|
|
135
|
+
version hands the row a 13px circle's baseline instead of the number's,
|
|
136
|
+
and every reading beside it sits on a different line. Inline text has
|
|
137
|
+
only one baseline to give. `align-middle` centres the ring on the
|
|
138
|
+
x-height, which is where it looked right anyway. */}
|
|
116
139
|
<span
|
|
117
140
|
className={cn(
|
|
118
|
-
'
|
|
141
|
+
'cursor-default font-mono text-label whitespace-nowrap',
|
|
119
142
|
info.status === 'rejected' ? 'text-danger' : utilizationColor(pct ?? 0),
|
|
120
143
|
)}>
|
|
121
|
-
<ProgressRing value={pct ?? 0} />
|
|
144
|
+
<ProgressRing value={pct ?? 0} className='mr-1 inline-block align-middle' />
|
|
122
145
|
{label}
|
|
123
146
|
{pct !== undefined ? ` ${pct.toFixed(0)}%` : ''}
|
|
124
147
|
{resetsAtMs !== undefined ? (
|
|
125
|
-
<span className='text-fg-4'
|
|
148
|
+
<span className='text-fg-4'> · {formatCountdown(resetsAtMs, now)}</span>
|
|
126
149
|
) : null}
|
|
127
150
|
</span>
|
|
128
151
|
</Tip>
|
|
@@ -138,7 +161,15 @@ function Slot({ onClick, hint, children }: { onClick?: () => void; hint: string;
|
|
|
138
161
|
type='button'
|
|
139
162
|
onClick={onClick}
|
|
140
163
|
aria-label={hint}
|
|
141
|
-
|
|
164
|
+
// `leading-4` matches the label metrics inside: a button's own line box is
|
|
165
|
+
// the page's 24px one, and the extra strut was padding the bar by 2.5px
|
|
166
|
+
// that nothing was using.
|
|
167
|
+
// No horizontal padding: the bar's own 6px is the inset, and a slot that
|
|
168
|
+
// added its own would start the first reading 10px in. The hover surface
|
|
169
|
+
// hugs the text instead, which is what a status line's items do anyway.
|
|
170
|
+
// `leading-4` matches the label metrics inside — a button's own line box
|
|
171
|
+
// is the page's 24px one, and the strut padded the bar with nothing.
|
|
172
|
+
className='rounded-md py-0.5 leading-4 transition-colors outline-none hover:bg-surface-hover focus-visible:bg-surface-hover'>
|
|
142
173
|
{children}
|
|
143
174
|
</button>
|
|
144
175
|
)
|
|
@@ -146,28 +177,45 @@ function Slot({ onClick, hint, children }: { onClick?: () => void; hint: string;
|
|
|
146
177
|
|
|
147
178
|
export function StatusBar({
|
|
148
179
|
state,
|
|
180
|
+
rateLimits,
|
|
149
181
|
connected,
|
|
150
182
|
connection,
|
|
151
183
|
onOpenStatus,
|
|
152
184
|
onOpenContext,
|
|
153
185
|
onOpenUsage,
|
|
186
|
+
controls,
|
|
154
187
|
actions,
|
|
155
188
|
placement = 'top',
|
|
156
189
|
className,
|
|
157
190
|
}: StatusBarProps) {
|
|
158
191
|
const meta = STATUS_META[state.status]
|
|
159
192
|
const now = useNow()
|
|
160
|
-
const
|
|
161
|
-
const
|
|
193
|
+
const windows = rateLimits ?? state.rateLimits
|
|
194
|
+
const session = windows?.five_hour
|
|
195
|
+
const weekly = windows?.seven_day
|
|
162
196
|
const link: ConnectionState = connection ?? (connected === false ? 'reconnecting' : 'live')
|
|
197
|
+
const terminal = useTranscriptVariant() === 'terminal'
|
|
163
198
|
return (
|
|
164
199
|
<div
|
|
165
200
|
data-slot='status-bar'
|
|
166
201
|
className={cn(
|
|
167
|
-
|
|
202
|
+
// Baselines, not boxes. `items-center` centres each child's *box*, and
|
|
203
|
+
// this bar's children are boxes of different heights for reasons that
|
|
204
|
+
// have nothing to do with their text — a badge's pill padding, a ring
|
|
205
|
+
// beside a number, a select's border and chevron. Centring those lands
|
|
206
|
+
// their text on four slightly different lines. A flex item's baseline is
|
|
207
|
+
// its first line's baseline, so aligning on it puts every reading on one
|
|
208
|
+
// line by construction, whatever is drawn around it.
|
|
209
|
+
// One explicit height, shared with the docked composer above it (see
|
|
210
|
+
// `Composer.tsx`) — the two strips along the foot of the panel read as
|
|
211
|
+
// one piece of chrome, and a pixel of drift between them shows.
|
|
212
|
+
'flex h-[38px] items-baseline gap-2 border-border bg-surface p-1.5',
|
|
168
213
|
// The rule goes between the bar and the content, so which edge it sits
|
|
169
|
-
// on follows the placement
|
|
170
|
-
|
|
214
|
+
// on follows the placement — except under the terminal theme at the
|
|
215
|
+
// foot, where the composer directly above already closes itself with a
|
|
216
|
+
// rule of its own. Two adjacent 1px rules is a 2px rule with a seam in
|
|
217
|
+
// it, and it is off the line grid besides.
|
|
218
|
+
placement === 'bottom' ? (terminal ? undefined : 'border-t') : 'border-b',
|
|
171
219
|
className,
|
|
172
220
|
)}>
|
|
173
221
|
{/* One slot, two meanings: connection trouble wins it, because a session
|
|
@@ -176,16 +224,22 @@ export function StatusBar({
|
|
|
176
224
|
with which credentials — which is the question a status prompts. */}
|
|
177
225
|
<Slot onClick={onOpenStatus} hint='Session info'>
|
|
178
226
|
{link === 'live' ? (
|
|
179
|
-
|
|
180
|
-
|
|
227
|
+
// `items-baseline` so the badge answers the row with its label's
|
|
228
|
+
// baseline rather than its pill's box; the dot and the spinner keep
|
|
229
|
+
// their own centring.
|
|
230
|
+
<Badge variant={meta.variant} dot={!meta.busy} className='items-baseline'>
|
|
231
|
+
{meta.busy ? <Spinner className='size-3 self-center text-current' /> : null}
|
|
181
232
|
{meta.label}
|
|
182
233
|
</Badge>
|
|
183
234
|
) : (
|
|
184
|
-
<Badge
|
|
235
|
+
<Badge
|
|
236
|
+
variant={link === 'offline' ? 'danger' : 'warning'}
|
|
237
|
+
dot={false}
|
|
238
|
+
className='items-baseline'>
|
|
185
239
|
{link === 'offline' ? (
|
|
186
|
-
<WifiOff className='size-3 text-current' />
|
|
240
|
+
<WifiOff className='size-3 self-center text-current' />
|
|
187
241
|
) : (
|
|
188
|
-
<RefreshCw className='size-3 animate-spin text-current' />
|
|
242
|
+
<RefreshCw className='size-3 animate-spin self-center text-current' />
|
|
189
243
|
)}
|
|
190
244
|
{link === 'offline' ? 'Offline' : 'Reconnecting…'}
|
|
191
245
|
</Badge>
|
|
@@ -200,12 +254,13 @@ export function StatusBar({
|
|
|
200
254
|
) : null}
|
|
201
255
|
{session || weekly ? (
|
|
202
256
|
<Slot onClick={onOpenUsage} hint='Plan usage'>
|
|
203
|
-
<span className='inline-flex items-
|
|
257
|
+
<span className='inline-flex items-baseline gap-2'>
|
|
204
258
|
{session ? <RateLimitMeter label='Session' info={session} now={now} /> : null}
|
|
205
259
|
{weekly ? <RateLimitMeter label='Weekly' info={weekly} now={now} /> : null}
|
|
206
260
|
</span>
|
|
207
261
|
</Slot>
|
|
208
262
|
) : null}
|
|
263
|
+
{controls}
|
|
209
264
|
<span className='flex-1' />
|
|
210
265
|
<span className='font-mono text-label text-fg-3'>{formatCost(state.totalCostUsd)}</span>
|
|
211
266
|
{actions}
|
|
@@ -6,10 +6,7 @@ import { CodeBlock } from '../ui/CodeBlock.tsx'
|
|
|
6
6
|
import { Spinner } from '../ui/Spinner.tsx'
|
|
7
7
|
import { cn } from '../../lib/utils.ts'
|
|
8
8
|
import { toolInputPreview } from '../../lib/format.ts'
|
|
9
|
-
import {
|
|
10
|
-
import { LINE_INDENT, LinePayload } from './line-prompt.tsx'
|
|
11
|
-
import { usePulse } from './pulse.tsx'
|
|
12
|
-
import { LineGlyph, useLines } from './transcript-variant.tsx'
|
|
9
|
+
import { toolIcon } from '../../lib/tool-icon.ts'
|
|
13
10
|
|
|
14
11
|
export type ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>
|
|
15
12
|
|
|
@@ -51,15 +48,6 @@ const STATE_BADGE = {
|
|
|
51
48
|
failed: { label: 'Error', variant: 'danger', busy: false },
|
|
52
49
|
} as const
|
|
53
50
|
|
|
54
|
-
/** The gutter dot's colour in `lines`: the state, said without a badge. */
|
|
55
|
-
const STATE_GLYPH = {
|
|
56
|
-
running: 'text-info',
|
|
57
|
-
pending: 'text-info',
|
|
58
|
-
deferred: 'text-accent',
|
|
59
|
-
settled: 'text-fg-4',
|
|
60
|
-
failed: 'text-danger',
|
|
61
|
-
} as const
|
|
62
|
-
|
|
63
51
|
/**
|
|
64
52
|
* The language to highlight a payload as.
|
|
65
53
|
*
|
|
@@ -91,47 +79,35 @@ function resultLanguage(item: ToolCallItem): string | undefined {
|
|
|
91
79
|
type Status = keyof typeof STATE_BADGE
|
|
92
80
|
|
|
93
81
|
export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps) {
|
|
94
|
-
const lines = useLines()
|
|
95
82
|
const [open, setOpen] = useState(false)
|
|
96
83
|
const [fullResult, setFullResult] = useState(false)
|
|
97
84
|
const imagePath = imagePathOf(item)
|
|
98
85
|
const status: Status = item.status ?? (item.result === undefined ? 'running' : 'settled')
|
|
99
86
|
const badge = STATE_BADGE[status]
|
|
100
87
|
const isError = status === 'failed' || item.result?.isError === true
|
|
101
|
-
// Ticks only while this row is actually running, and only in the variant with a
|
|
102
|
-
// gutter to pulse in — an idle transcript of a hundred settled tools starts no
|
|
103
|
-
// timers at all.
|
|
104
|
-
const pulse = usePulse(lines && badge.busy)
|
|
105
88
|
const Icon = toolIcon(item.name)
|
|
106
89
|
|
|
107
90
|
const resultText = item.result?.text ?? ''
|
|
108
91
|
const truncated = !fullResult && resultText.length > RESULT_PREVIEW_CHARS
|
|
109
92
|
const shownResult = truncated ? resultText.slice(0, RESULT_PREVIEW_CHARS) : resultText
|
|
110
93
|
|
|
111
|
-
// In `lines` the payloads are dim label + highlighted band, not framed cards:
|
|
112
|
-
// a tool call is already one row, and boxing what it expands into puts back
|
|
113
|
-
// the chrome the variant exists to remove.
|
|
114
|
-
const blockVariant = lines ? 'plain' : 'panel'
|
|
115
|
-
const Payload = lines ? HighlightedPayload : PlainPayload
|
|
116
94
|
const details = open ? (
|
|
117
|
-
<div className=
|
|
118
|
-
<
|
|
95
|
+
<div className='flex flex-col gap-2 border-t border-border p-2.5'>
|
|
96
|
+
<PlainPayload
|
|
119
97
|
code={JSON.stringify(item.input, null, 2)}
|
|
120
98
|
language='json'
|
|
121
99
|
label='Parameters'
|
|
122
|
-
variant={blockVariant}
|
|
123
100
|
/>
|
|
124
101
|
{item.logs?.length ? (
|
|
125
|
-
<
|
|
102
|
+
<PlainPayload code={item.logs.join('\n')} label='Logs'/>
|
|
126
103
|
) : null}
|
|
127
104
|
{item.result !== undefined ? (
|
|
128
105
|
<div>
|
|
129
|
-
<
|
|
106
|
+
<PlainPayload
|
|
130
107
|
code={shownResult || '(empty result)'}
|
|
131
108
|
language={resultLanguage(item)}
|
|
132
109
|
label={isError ? 'Error' : 'Result'}
|
|
133
|
-
|
|
134
|
-
className={cn(isError && (lines ? '[&_pre]:text-danger' : 'border-danger/40 [&_pre]:text-danger'))}
|
|
110
|
+
className={cn(isError && 'border-danger/40 [&_pre]:text-danger')}
|
|
135
111
|
/>
|
|
136
112
|
{truncated ? (
|
|
137
113
|
<button
|
|
@@ -148,63 +124,7 @@ export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps)
|
|
|
148
124
|
|
|
149
125
|
// The picture is the point of the call — shown without expanding, the way the
|
|
150
126
|
// tool's own output would be if the engine had sent bytes.
|
|
151
|
-
const image = imagePath && hostImage ? <HostImage path={imagePath} load={hostImage}
|
|
152
|
-
|
|
153
|
-
if (lines) {
|
|
154
|
-
return (
|
|
155
|
-
<div data-slot='tool-call' data-state={status} className={cn('w-full', className)}>
|
|
156
|
-
<button
|
|
157
|
-
type='button'
|
|
158
|
-
onClick={() => setOpen((v) => !v)}
|
|
159
|
-
className='flex w-full items-baseline gap-2 text-left outline-none'>
|
|
160
|
-
<LineGlyph
|
|
161
|
-
className={
|
|
162
|
-
// A settled write is green: skimming a run, "what did it change"
|
|
163
|
-
// is the question you come back to, and it is the one you might
|
|
164
|
-
// need to undo. Every other state keeps its own colour — a failed
|
|
165
|
-
// write is a failure first.
|
|
166
|
-
status === 'settled' && !isError && isMutatingTool(item.name)
|
|
167
|
-
? 'text-success'
|
|
168
|
-
: STATE_GLYPH[status]
|
|
169
|
-
}>
|
|
170
|
-
{/* Running: the mark's own pulse, so a working tool row and the
|
|
171
|
-
transcript's working line beat together. Settled: a plain dot,
|
|
172
|
-
which reads as "done" precisely by not moving. */}
|
|
173
|
-
{badge.busy ? pulse : '●'}
|
|
174
|
-
</LineGlyph>
|
|
175
|
-
<span className='min-w-0 flex-1 truncate text-body-sm leading-5 text-fg-3'>
|
|
176
|
-
<span className='font-medium text-fg-1'>{item.name}</span>
|
|
177
|
-
<span className='text-fg-4'>({toolInputPreview(item.input)})</span>
|
|
178
|
-
</span>
|
|
179
|
-
{item.backend && item.backend !== 'server' ? (
|
|
180
|
-
<span className='shrink-0 text-label text-fg-4'>{item.backend}</span>
|
|
181
|
-
) : null}
|
|
182
|
-
{/* No Spinner here: the gutter glyph animates now, and two spinners on
|
|
183
|
-
one row is one too many. `cards` keeps its own — it has no gutter. */}
|
|
184
|
-
{status === 'deferred' ? <Clock className='size-3 shrink-0 self-center text-fg-4' /> : null}
|
|
185
|
-
{isError && !badge.busy ? (
|
|
186
|
-
<span className='shrink-0 text-label text-danger'>error</span>
|
|
187
|
-
) : null}
|
|
188
|
-
</button>
|
|
189
|
-
{/* Collapsed, the first line of the output is the whole story most of
|
|
190
|
-
the time — a summary line costs one row and saves an expand. */}
|
|
191
|
-
{!open && resultText ? (
|
|
192
|
-
<div className='flex items-baseline gap-2'>
|
|
193
|
-
<LineGlyph className='text-fg-4'>⎿</LineGlyph>
|
|
194
|
-
<span
|
|
195
|
-
className={cn(
|
|
196
|
-
'min-w-0 flex-1 truncate text-label leading-5',
|
|
197
|
-
isError ? 'text-danger' : 'text-fg-4',
|
|
198
|
-
)}>
|
|
199
|
-
{resultSummary(resultText)}
|
|
200
|
-
</span>
|
|
201
|
-
</div>
|
|
202
|
-
) : null}
|
|
203
|
-
{image}
|
|
204
|
-
{details ? <div className={LINE_INDENT}>{details}</div> : null}
|
|
205
|
-
</div>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
127
|
+
const image = imagePath && hostImage ? <HostImage path={imagePath} load={hostImage} /> : null
|
|
208
128
|
|
|
209
129
|
return (
|
|
210
130
|
<div
|
|
@@ -243,31 +163,19 @@ export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps)
|
|
|
243
163
|
)
|
|
244
164
|
}
|
|
245
165
|
|
|
246
|
-
/** The
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
type PayloadProps = {
|
|
166
|
+
/** The framed card the `cards` transcript expands into. Unhighlighted by
|
|
167
|
+
* design: it is structured data in a panel, not a file. */
|
|
168
|
+
function PlainPayload({
|
|
169
|
+
code,
|
|
170
|
+
label,
|
|
171
|
+
className,
|
|
172
|
+
}: {
|
|
255
173
|
code: string
|
|
256
174
|
label: string
|
|
257
175
|
language?: string
|
|
258
|
-
variant: 'panel' | 'plain'
|
|
259
176
|
className?: string
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** The framed card, for the `cards` transcript. Unhighlighted by design: it is
|
|
263
|
-
* structured data in a panel, not a file. */
|
|
264
|
-
function PlainPayload({ code, label, variant, className }: PayloadProps) {
|
|
265
|
-
return <CodeBlock code={code} label={label} variant={variant} className={className} />
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** The terminal payload — shared with the line-shaped prompts. */
|
|
269
|
-
function HighlightedPayload({ code, label, language, className }: PayloadProps) {
|
|
270
|
-
return <LinePayload code={code} label={label} language={language} className={className} />
|
|
177
|
+
}) {
|
|
178
|
+
return <CodeBlock code={code} label={label} variant='panel' className={className} />
|
|
271
179
|
}
|
|
272
180
|
|
|
273
181
|
/**
|
|
@@ -282,11 +190,9 @@ function HighlightedPayload({ code, label, language, className }: PayloadProps)
|
|
|
282
190
|
function HostImage({
|
|
283
191
|
path,
|
|
284
192
|
load,
|
|
285
|
-
lines,
|
|
286
193
|
}: {
|
|
287
194
|
path: string
|
|
288
195
|
load: (path: string) => Promise<string | undefined>
|
|
289
|
-
lines?: boolean
|
|
290
196
|
}) {
|
|
291
197
|
const [src, setSrc] = useState<string | undefined>()
|
|
292
198
|
useEffect(() => {
|
|
@@ -305,7 +211,7 @@ function HostImage({
|
|
|
305
211
|
}, [path, load])
|
|
306
212
|
if (!src) return null
|
|
307
213
|
return (
|
|
308
|
-
<div className=
|
|
214
|
+
<div className='border-t border-border p-2.5'>
|
|
309
215
|
<img
|
|
310
216
|
src={src}
|
|
311
217
|
alt={path.split('/').pop() ?? 'Generated image'}
|