@workerdeck/ui 2.15.0 → 3.0.1
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 +6 -5
- package/build/{SessionPanel-BVKqPs_3.d.mts → SessionPanel-C1bU4yTM.d.mts} +11 -11
- package/build/index.d.mts +46 -9
- package/build/index.mjs +70 -43
- package/build/index.mjs.map +1 -1
- package/build/scoped.css +156 -16
- package/build/{use-path-links-BgWXmfAS.mjs → use-path-links-BEWxt0ZM.mjs} +1566 -852
- package/build/use-path-links-BEWxt0ZM.mjs.map +1 -0
- package/build/workspace.d.mts +3 -3
- package/build/workspace.mjs +3 -3
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/PermissionPrompt.tsx +16 -2
- package/src/components/agent/Response.tsx +8 -1
- package/src/components/agent/SessionBrowser.tsx +6 -0
- package/src/components/agent/SessionItem.tsx +3 -0
- package/src/components/agent/SessionPanel.tsx +28 -33
- package/src/components/agent/SessionSteps.tsx +46 -5
- package/src/components/agent/SessionWorkspace.tsx +3 -3
- package/src/components/agent/ShellStrip.tsx +27 -4
- package/src/components/agent/ShellTerminal.tsx +1 -1
- package/src/components/agent/ToolCallCard.tsx +18 -41
- package/src/components/agent/Transcript.tsx +98 -93
- package/src/components/agent/TranscriptItemView.tsx +102 -59
- package/src/components/agent/TranscriptRows.tsx +4 -2
- package/src/components/agent/image-viewer.tsx +326 -0
- package/src/components/agent/markdown-image.tsx +91 -0
- package/src/components/agent/shell-actions.tsx +2 -0
- package/src/components/agent/tool-result-image.tsx +40 -0
- package/src/components/agent/transcript-variant.tsx +3 -21
- package/src/components/prompt-area/use-prompt-area-keydown.ts +4 -2
- package/src/components/terminal/PermissionPrompt.tsx +20 -4
- package/src/components/terminal/TerminalTranscript.tsx +38 -21
- package/src/components/terminal/affordances.tsx +100 -74
- package/src/components/terminal/height.ts +48 -32
- package/src/components/terminal/image-box.ts +31 -0
- package/src/components/terminal/items.tsx +92 -14
- package/src/components/terminal/markdown.tsx +4 -1
- package/src/components/terminal/shell-row.ts +17 -3
- package/src/index.ts +19 -4
- package/src/lib/shell-request.ts +115 -0
- package/src/lib/tool-icon.ts +13 -1
- package/src/styles/terminal.css +129 -22
- package/src/styles/theme.css +4 -0
- package/build/use-path-links-BgWXmfAS.mjs.map +0 -1
|
@@ -3,7 +3,18 @@ import { isAgentRecord, promotedShells, subagentLabel, visibleSubagents } from '
|
|
|
3
3
|
import type { SessionInfo, ShellInfo, SubagentDisplay, SubagentInfo } from '@workerdeck/protocol'
|
|
4
4
|
import { Spinner } from '../ui/Spinner.tsx'
|
|
5
5
|
import { cn } from '../../lib/utils.ts'
|
|
6
|
-
import {
|
|
6
|
+
import { AgentWriteIcon } from '../terminal/affordances.tsx'
|
|
7
|
+
import {
|
|
8
|
+
SHELL_AGENT_WRITE_NOTE,
|
|
9
|
+
SHELL_GLYPH,
|
|
10
|
+
SHELL_KILL_GLYPH,
|
|
11
|
+
shellAgentWriteLabel,
|
|
12
|
+
shellGrantable,
|
|
13
|
+
shellInfoFailed,
|
|
14
|
+
shellInfoLabel,
|
|
15
|
+
shellInfoStatusText,
|
|
16
|
+
shellTitle,
|
|
17
|
+
} from '../terminal/shell-row.ts'
|
|
7
18
|
|
|
8
19
|
export type StepKind = 'agent' | 'shell'
|
|
9
20
|
|
|
@@ -17,12 +28,15 @@ export type Step = {
|
|
|
17
28
|
title: string
|
|
18
29
|
onSelect: () => void
|
|
19
30
|
onKill?: () => void
|
|
31
|
+
agentWrite?: { granted: boolean; label: string; toggle: () => void }
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
export type ShellStepOptions = {
|
|
23
35
|
now: number
|
|
24
36
|
onSelect: (shellId: string) => void
|
|
25
37
|
onKill?: (shellId: string) => void
|
|
38
|
+
// Offered only where the session's agent holds the shell write tools.
|
|
39
|
+
onAgentWrite?: (shellId: string, enabled: boolean) => void
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
export function sessionSteps(
|
|
@@ -46,10 +60,11 @@ export function sessionSteps(
|
|
|
46
60
|
if (shells === undefined) {
|
|
47
61
|
return agents
|
|
48
62
|
}
|
|
49
|
-
|
|
63
|
+
const grants = info.shellAgentWrite !== undefined
|
|
64
|
+
return [...agents, ...promotedShells(info, shells.now).map((shell) => shellStep(shell, shells, grants))]
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
function shellStep(shell: ShellInfo, options: ShellStepOptions): Step {
|
|
67
|
+
function shellStep(shell: ShellInfo, options: ShellStepOptions, grants: boolean): Step {
|
|
53
68
|
const status = shellInfoStatusText(shell)
|
|
54
69
|
const running = shell.status === 'running'
|
|
55
70
|
return {
|
|
@@ -58,10 +73,18 @@ function shellStep(shell: ShellInfo, options: ShellStepOptions): Step {
|
|
|
58
73
|
label: shellInfoLabel(shell),
|
|
59
74
|
noun: 'shell',
|
|
60
75
|
state: shellInfoFailed(shell) ? 'failed' : running ? 'running' : 'done',
|
|
61
|
-
detail: running ? undefined : status,
|
|
76
|
+
detail: running ? (shell.agentWrite === true ? SHELL_AGENT_WRITE_NOTE : undefined) : status,
|
|
62
77
|
title: `${shellTitle(shell)} · ${status}`,
|
|
63
78
|
onSelect: () => options.onSelect(shell.id),
|
|
64
79
|
onKill: running && options.onKill ? () => options.onKill?.(shell.id) : undefined,
|
|
80
|
+
agentWrite:
|
|
81
|
+
grants && options.onAgentWrite && shellGrantable(shell)
|
|
82
|
+
? {
|
|
83
|
+
granted: shell.agentWrite === true,
|
|
84
|
+
label: shellAgentWriteLabel(shell),
|
|
85
|
+
toggle: () => options.onAgentWrite?.(shell.id, shell.agentWrite !== true),
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
|
|
@@ -96,8 +119,26 @@ export function StepRow({ step, active = false, onSelect }: { step: Step; active
|
|
|
96
119
|
<StepIcon step={step} />
|
|
97
120
|
<span className="min-w-0 flex-1 truncate">{step.label}</span>
|
|
98
121
|
{step.detail ? <span className="shrink-0 tabular-nums text-fg-4">{step.detail}</span> : null}
|
|
99
|
-
<ArrowRight className="size-3.5 shrink-0 text-fg-4" />
|
|
122
|
+
{step.kind === 'shell' ? null : <ArrowRight className="size-3.5 shrink-0 text-fg-4" />}
|
|
100
123
|
</button>
|
|
124
|
+
{step.agentWrite ? (
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
aria-label={step.agentWrite.label}
|
|
128
|
+
aria-pressed={step.agentWrite.granted}
|
|
129
|
+
title={step.agentWrite.label}
|
|
130
|
+
onClick={(e) => {
|
|
131
|
+
e.stopPropagation()
|
|
132
|
+
step.agentWrite?.toggle()
|
|
133
|
+
}}
|
|
134
|
+
className={cn(
|
|
135
|
+
'shrink-0 px-1 text-micro leading-none outline-none',
|
|
136
|
+
step.agentWrite.granted ? 'text-warning' : 'text-fg-4 hover:text-fg-1',
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<AgentWriteIcon granted={step.agentWrite.granted} className="size-3.5" />
|
|
140
|
+
</button>
|
|
141
|
+
) : null}
|
|
101
142
|
{step.onKill ? (
|
|
102
143
|
<button
|
|
103
144
|
type="button"
|
|
@@ -17,8 +17,8 @@ export interface SessionWorkspaceProps {
|
|
|
17
17
|
sessionId: string | undefined
|
|
18
18
|
header?: SessionPanelProps['header']
|
|
19
19
|
transcriptVariant?: SessionPanelProps['transcriptVariant']
|
|
20
|
-
transcriptDensity?: SessionPanelProps['transcriptDensity']
|
|
21
20
|
transcriptFont?: SessionPanelProps['transcriptFont']
|
|
21
|
+
affordances?: SessionPanelProps['affordances']
|
|
22
22
|
scrubber?: SessionPanelProps['scrubber']
|
|
23
23
|
bookmarks?: SessionPanelProps['bookmarks']
|
|
24
24
|
onToggleBookmark?: SessionPanelProps['onToggleBookmark']
|
|
@@ -54,8 +54,8 @@ export function SessionWorkspace({
|
|
|
54
54
|
sessionId,
|
|
55
55
|
header,
|
|
56
56
|
transcriptVariant,
|
|
57
|
-
transcriptDensity,
|
|
58
57
|
transcriptFont,
|
|
58
|
+
affordances,
|
|
59
59
|
scrubber,
|
|
60
60
|
bookmarks,
|
|
61
61
|
onToggleBookmark,
|
|
@@ -217,8 +217,8 @@ export function SessionWorkspace({
|
|
|
217
217
|
sessionId={sessionId}
|
|
218
218
|
header={hoisted}
|
|
219
219
|
transcriptVariant={transcriptVariant}
|
|
220
|
-
transcriptDensity={transcriptDensity}
|
|
221
220
|
transcriptFont={transcriptFont}
|
|
221
|
+
affordances={affordances}
|
|
222
222
|
scrubber={scrubber}
|
|
223
223
|
bookmarks={bookmarks}
|
|
224
224
|
onToggleBookmark={onToggleBookmark}
|
|
@@ -4,8 +4,8 @@ import { cn } from '../../lib/utils.ts'
|
|
|
4
4
|
import { Button } from '../ui/Button.tsx'
|
|
5
5
|
import { Ink, Row } from '../terminal/row.tsx'
|
|
6
6
|
import { TerminalSurface } from '../terminal/surface.tsx'
|
|
7
|
-
import { shellInfoFailed, shellInfoStatusText, shellTitle } from '../terminal/shell-row.ts'
|
|
8
|
-
import { KillShellAction, WithActions } from '../terminal/affordances.tsx'
|
|
7
|
+
import { shellAgentWriteLabel, shellGrantable, shellInfoFailed, shellInfoStatusText, shellTitle } from '../terminal/shell-row.ts'
|
|
8
|
+
import { AgentWriteAction, AgentWriteIcon, KillShellAction, WithActions } from '../terminal/affordances.tsx'
|
|
9
9
|
|
|
10
10
|
export interface ShellStripProps {
|
|
11
11
|
shell: ShellInfo | undefined
|
|
@@ -13,23 +13,34 @@ export interface ShellStripProps {
|
|
|
13
13
|
cols?: number
|
|
14
14
|
onBack: () => void
|
|
15
15
|
onKill?: () => void
|
|
16
|
+
onAgentWrite?: (enabled: boolean) => void
|
|
16
17
|
terminal: boolean
|
|
17
18
|
fontSize?: number
|
|
18
19
|
lineHeight?: number
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function ShellStrip({ shell, label, cols, onBack, onKill, terminal, fontSize, lineHeight }: ShellStripProps) {
|
|
22
|
+
export function ShellStrip({ shell, label, cols, onBack, onKill, onAgentWrite, terminal, fontSize, lineHeight }: ShellStripProps) {
|
|
22
23
|
const running = shell?.status === 'running'
|
|
23
24
|
const failed = shell ? shellInfoFailed(shell) : false
|
|
24
25
|
const name = shell ? shellTitle(shell) : label
|
|
25
26
|
const status = shell ? shellInfoStatusText(shell) : undefined
|
|
26
27
|
const width = cols ?? shell?.cols
|
|
27
28
|
const detail = width === undefined ? undefined : `${width} cols`
|
|
29
|
+
const granted = shell?.agentWrite === true
|
|
30
|
+
const grant = shell && onAgentWrite && shellGrantable(shell) ? () => onAgentWrite(!granted) : undefined
|
|
28
31
|
|
|
29
32
|
if (terminal) {
|
|
30
33
|
return (
|
|
31
34
|
<TerminalSurface fontSize={fontSize} lineHeight={lineHeight} className="shrink-0">
|
|
32
|
-
<WithActions
|
|
35
|
+
<WithActions
|
|
36
|
+
placement="inline"
|
|
37
|
+
actions={
|
|
38
|
+
<>
|
|
39
|
+
{grant && shell ? <AgentWriteAction granted={granted} label={shellAgentWriteLabel(shell)} onToggle={grant} /> : null}
|
|
40
|
+
{running && onKill ? <KillShellAction onKill={onKill} /> : null}
|
|
41
|
+
</>
|
|
42
|
+
}
|
|
43
|
+
>
|
|
33
44
|
<Row glyph="←" glyphTone="dim" indent={1} tone={failed ? 'red' : 'magenta'}>
|
|
34
45
|
<button type="button" onClick={onBack} aria-label="Back to the session" className="cursor-pointer text-left">
|
|
35
46
|
{name}
|
|
@@ -55,6 +66,18 @@ export function ShellStrip({ shell, label, cols, onBack, onKill, terminal, fontS
|
|
|
55
66
|
</span>
|
|
56
67
|
) : null}
|
|
57
68
|
{detail ? <span className="shrink-0 text-label text-fg-4">{detail}</span> : null}
|
|
69
|
+
{grant && shell ? (
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
aria-label={shellAgentWriteLabel(shell)}
|
|
73
|
+
aria-pressed={granted}
|
|
74
|
+
title={shellAgentWriteLabel(shell)}
|
|
75
|
+
className={cn('shrink-0 text-label', granted ? 'text-warning' : 'text-fg-3 hover:text-fg-1')}
|
|
76
|
+
onClick={grant}
|
|
77
|
+
>
|
|
78
|
+
<AgentWriteIcon granted={granted} className="size-3.5" />
|
|
79
|
+
</button>
|
|
80
|
+
) : null}
|
|
58
81
|
{running && onKill ? (
|
|
59
82
|
<button
|
|
60
83
|
type="button"
|
|
@@ -126,7 +126,7 @@ export function ShellTerminal({ handle, shellId, onEnded, onShell, fontSize, cla
|
|
|
126
126
|
}, [ready])
|
|
127
127
|
|
|
128
128
|
return (
|
|
129
|
-
<div className={cn('relative min-h-0 flex-1 overflow-hidden px-2 py-1', className)}>
|
|
129
|
+
<div className={cn('wd-shell-term relative min-h-0 flex-1 overflow-hidden px-2 py-1', className)}>
|
|
130
130
|
<div ref={host} className="h-full w-full" onClick={() => term.current?.focus()} />
|
|
131
131
|
{ready && shell.status !== 'idle' ? null : (
|
|
132
132
|
<div className="absolute inset-0 grid place-items-center">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from 'react'
|
|
2
2
|
import type { TranscriptItem } from '@workerdeck/react'
|
|
3
3
|
import { ChevronDown, Clock } from 'lucide-react'
|
|
4
4
|
import { Badge } from '../ui/Badge.tsx'
|
|
@@ -9,27 +9,17 @@ import { toolInputPreview } from '../../lib/format.ts'
|
|
|
9
9
|
import { toolIcon } from '../../lib/tool-icon.ts'
|
|
10
10
|
import { useToolResultFetcher } from './tool-result-fetch.tsx'
|
|
11
11
|
import { useToolTitle } from './tool-titles.tsx'
|
|
12
|
-
import { useToolResultImageSrc } from './tool-result-image.tsx'
|
|
13
|
-
import {
|
|
12
|
+
import { useHostImageSrc, useToolResultImageSrc, type HostImageLoader } from './tool-result-image.tsx'
|
|
13
|
+
import { ViewableImage } from './image-viewer.tsx'
|
|
14
|
+
import { IMAGE_UNAVAILABLE, baseName, hostImagePathOf, imagePlaceholder, resultImageName } from '../terminal/image-box.ts'
|
|
14
15
|
|
|
15
16
|
export type ToolCallItem = Extract<TranscriptItem, { kind: 'tool_call' }>
|
|
16
17
|
|
|
17
18
|
const RESULT_PREVIEW_CHARS = 2000
|
|
18
19
|
|
|
19
|
-
const IMAGE_TOOLS = new Set(['CodexImageGeneration', 'CodexImageView'])
|
|
20
|
-
|
|
21
|
-
function imagePathOf(item: ToolCallItem): string | undefined {
|
|
22
|
-
if (!IMAGE_TOOLS.has(item.name)) {
|
|
23
|
-
return undefined
|
|
24
|
-
}
|
|
25
|
-
const input = item.input as { savedPath?: unknown; path?: unknown } | null
|
|
26
|
-
const path = input?.savedPath ?? input?.path
|
|
27
|
-
return typeof path === 'string' ? path : undefined
|
|
28
|
-
}
|
|
29
|
-
|
|
30
20
|
export interface ToolCallCardProps {
|
|
31
21
|
item: ToolCallItem
|
|
32
|
-
hostImage?:
|
|
22
|
+
hostImage?: HostImageLoader
|
|
33
23
|
className?: string
|
|
34
24
|
}
|
|
35
25
|
|
|
@@ -106,7 +96,7 @@ export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps)
|
|
|
106
96
|
const [fullResult, setFullResult] = useState(false)
|
|
107
97
|
const [fetching, setFetching] = useState(false)
|
|
108
98
|
const fetchResult = useToolResultFetcher()
|
|
109
|
-
const imagePath =
|
|
99
|
+
const imagePath = hostImagePathOf(item)
|
|
110
100
|
const status: Status = item.status ?? (item.result === undefined ? 'running' : 'settled')
|
|
111
101
|
const badge = STATE_BADGE[status]
|
|
112
102
|
const isError = status === 'failed' || item.result?.isError === true
|
|
@@ -155,12 +145,12 @@ export function ToolCallCard({ item, hostImage, className }: ToolCallCardProps)
|
|
|
155
145
|
</div>
|
|
156
146
|
) : null
|
|
157
147
|
|
|
158
|
-
const image = imagePath
|
|
148
|
+
const image = imagePath ? <HostImage path={imagePath} load={hostImage} /> : null
|
|
159
149
|
|
|
160
150
|
const resultImages = item.result?.images?.length ? (
|
|
161
151
|
<div className="flex flex-col gap-2 border-t border-border p-2.5">
|
|
162
152
|
{item.result.images.map((ref) => (
|
|
163
|
-
<ResultImage key={ref.partIndex} toolUseId={item.id} image={ref} />
|
|
153
|
+
<ResultImage key={ref.partIndex} toolUseId={item.id} name={resultImageName(item.name, ref)} image={ref} />
|
|
164
154
|
))}
|
|
165
155
|
</div>
|
|
166
156
|
) : null
|
|
@@ -209,12 +199,14 @@ function PlainPayload({ code, label, className }: { code: string; label: string;
|
|
|
209
199
|
|
|
210
200
|
type ToolResultImage = NonNullable<NonNullable<ToolCallItem['result']>['images']>[number]
|
|
211
201
|
|
|
212
|
-
function ResultImage({ toolUseId, image }: { toolUseId: string; image: ToolResultImage }) {
|
|
202
|
+
function ResultImage({ toolUseId, name, image }: { toolUseId: string; name: string; image: ToolResultImage }) {
|
|
213
203
|
const { src, failed } = useToolResultImageSrc({ toolUseId, ...image })
|
|
214
204
|
return (
|
|
215
205
|
<div className="flex h-60 items-start overflow-hidden rounded-md border border-border bg-surface-hover">
|
|
216
206
|
{src ? (
|
|
217
|
-
<
|
|
207
|
+
<ViewableImage image={{ src, name }} className="h-full max-w-full">
|
|
208
|
+
<img src={src} alt={name} className="h-full max-w-full object-contain" />
|
|
209
|
+
</ViewableImage>
|
|
218
210
|
) : (
|
|
219
211
|
<span className="p-2 text-label text-fg-4">{failed ? IMAGE_UNAVAILABLE : imagePlaceholder(image)}</span>
|
|
220
212
|
)}
|
|
@@ -222,32 +214,17 @@ function ResultImage({ toolUseId, image }: { toolUseId: string; image: ToolResul
|
|
|
222
214
|
)
|
|
223
215
|
}
|
|
224
216
|
|
|
225
|
-
function HostImage({ path, load }: { path: string; load
|
|
226
|
-
const
|
|
227
|
-
useEffect(() => {
|
|
228
|
-
let cancelled = false
|
|
229
|
-
setSrc(undefined)
|
|
230
|
-
load(path)
|
|
231
|
-
.then((url) => {
|
|
232
|
-
if (!cancelled) {
|
|
233
|
-
setSrc(url)
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
.catch(() => {})
|
|
237
|
-
return () => {
|
|
238
|
-
cancelled = true
|
|
239
|
-
}
|
|
240
|
-
}, [path, load])
|
|
217
|
+
function HostImage({ path, load }: { path: string; load?: HostImageLoader }) {
|
|
218
|
+
const { src } = useHostImageSrc(path, load)
|
|
241
219
|
if (!src) {
|
|
242
220
|
return null
|
|
243
221
|
}
|
|
222
|
+
const name = baseName(path)
|
|
244
223
|
return (
|
|
245
224
|
<div className="border-t border-border p-2.5">
|
|
246
|
-
<
|
|
247
|
-
src={src}
|
|
248
|
-
|
|
249
|
-
className="max-h-96 w-auto max-w-full rounded-md border border-border"
|
|
250
|
-
/>
|
|
225
|
+
<ViewableImage image={{ src, name }} className="block max-w-full">
|
|
226
|
+
<img src={src} alt={name} className="max-h-96 w-auto max-w-full rounded-md border border-border" />
|
|
227
|
+
</ViewableImage>
|
|
251
228
|
</div>
|
|
252
229
|
)
|
|
253
230
|
}
|
|
@@ -4,7 +4,7 @@ import { cn } from '../../lib/utils.ts'
|
|
|
4
4
|
import { Conversation, ConversationContent, ConversationScrollButton } from './Conversation.tsx'
|
|
5
5
|
import { Loader } from './Loader.tsx'
|
|
6
6
|
import { SessionEmptyState } from './SessionEmptyState.tsx'
|
|
7
|
-
import { type TerminalAffordances } from '../terminal/affordances.tsx'
|
|
7
|
+
import { AffordanceProvider, resolveAffordances, type TerminalAffordances } from '../terminal/affordances.tsx'
|
|
8
8
|
import { WorkingRow } from '../terminal/items.tsx'
|
|
9
9
|
import { subagentItems, terminalBlocks, type ToolCallItem } from '../terminal/blocks.ts'
|
|
10
10
|
import { taskBrief, taskBusy } from '../terminal/tool-run.ts'
|
|
@@ -12,7 +12,8 @@ import { type TranscriptRow } from './transcript-rows.ts'
|
|
|
12
12
|
import { TranscriptRows } from './TranscriptRows.tsx'
|
|
13
13
|
import { SessionNamesProvider, SkillNamesProvider } from './PromptTokenText.tsx'
|
|
14
14
|
import { TerminalSurface } from '../terminal/surface.tsx'
|
|
15
|
-
import { ROW_GAP, TranscriptVariantProvider, type
|
|
15
|
+
import { ROW_GAP, TranscriptVariantProvider, type TranscriptVariant } from './transcript-variant.tsx'
|
|
16
|
+
import { HostImageProvider } from './tool-result-image.tsx'
|
|
16
17
|
|
|
17
18
|
function useRunStart(status: TranscriptState['status']): number | undefined {
|
|
18
19
|
const running = status === 'running' || status === 'starting'
|
|
@@ -54,7 +55,11 @@ function TerminalShell({
|
|
|
54
55
|
children: ReactNode
|
|
55
56
|
}) {
|
|
56
57
|
if (!active) {
|
|
57
|
-
return
|
|
58
|
+
return (
|
|
59
|
+
<div data-cards="" className="contents">
|
|
60
|
+
<AffordanceProvider value={resolveAffordances(affordances)}>{children}</AffordanceProvider>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
58
63
|
}
|
|
59
64
|
return (
|
|
60
65
|
<TerminalSurface fontSize={fontSize} lineHeight={lineHeight} affordances={affordances} bleed="1ch" className="term-transcript">
|
|
@@ -72,7 +77,6 @@ export interface TranscriptProps {
|
|
|
72
77
|
canBrowseFiles?: boolean
|
|
73
78
|
hostImage?: (path: string) => Promise<string | undefined>
|
|
74
79
|
variant?: TranscriptVariant
|
|
75
|
-
density?: TranscriptDensity
|
|
76
80
|
fontSize?: number
|
|
77
81
|
lineHeight?: number
|
|
78
82
|
affordances?: TerminalAffordances | boolean
|
|
@@ -99,7 +103,6 @@ export function Transcript({
|
|
|
99
103
|
canBrowseFiles,
|
|
100
104
|
hostImage,
|
|
101
105
|
variant = 'cards',
|
|
102
|
-
density = 'comfortable',
|
|
103
106
|
fontSize,
|
|
104
107
|
lineHeight,
|
|
105
108
|
affordances,
|
|
@@ -124,7 +127,7 @@ export function Transcript({
|
|
|
124
127
|
frame ? state.items.find((item): item is ToolCallItem => item.kind === 'tool_call' && item.id === frame.parentToolUseId) : undefined,
|
|
125
128
|
[state.items, frame],
|
|
126
129
|
)
|
|
127
|
-
const gap = ROW_GAP[variant]
|
|
130
|
+
const gap = ROW_GAP[variant]
|
|
128
131
|
const runStartedAt = useRunStart(state.status)
|
|
129
132
|
const boundary = !frame && catchUp && catchUp.from > 0 && catchUp.from < state.items.length ? catchUp.from : undefined
|
|
130
133
|
const recap = useMemo(() => (boundary === undefined ? undefined : recapLine(summarizeSince(state, boundary))), [state, boundary])
|
|
@@ -143,98 +146,100 @@ export function Transcript({
|
|
|
143
146
|
const skillNames = useMemo(() => state.skills?.map((skill) => skill.name), [state.skills])
|
|
144
147
|
return (
|
|
145
148
|
<TranscriptVariantProvider value={variant}>
|
|
146
|
-
<
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
{
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
emptyState
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
) : null}
|
|
169
|
-
{frame && frameTask === undefined && !replaying ? null : (
|
|
170
|
-
<TranscriptRows
|
|
171
|
-
rows={rows}
|
|
172
|
-
boundary={boundary}
|
|
173
|
-
since={catchUp?.since}
|
|
174
|
-
terminal={terminal}
|
|
175
|
-
replaying={replaying}
|
|
176
|
-
stickyPrompt={!frame && stickyPrompt}
|
|
177
|
-
gap={gap}
|
|
178
|
-
fontSize={fontSize}
|
|
179
|
-
lineHeight={lineHeight}
|
|
180
|
-
items={items}
|
|
181
|
-
pendingApprovals={state.pendingApprovals}
|
|
182
|
-
scrubber={scrubber}
|
|
183
|
-
bookmarks={frame ? undefined : bookmarks}
|
|
184
|
-
affordances={affordances}
|
|
185
|
-
fileUrl={fileUrl}
|
|
186
|
-
attachmentUrl={attachmentUrl}
|
|
187
|
-
hostImage={hostImage}
|
|
188
|
-
jumpToRecapRef={jumpToRecapRef}
|
|
189
|
-
repinRef={repinRef}
|
|
190
|
-
reveal={frame ? undefined : reveal}
|
|
191
|
-
frameParentId={frame?.parentToolUseId}
|
|
192
|
-
onOpenSubagent={frame ? undefined : onOpenSubagent}
|
|
193
|
-
/>
|
|
194
|
-
)}
|
|
195
|
-
{(frame ? frameTask !== undefined && taskBusy(frameTask, items) : showLoader(state)) ? (
|
|
196
|
-
terminal ? (
|
|
197
|
-
<>
|
|
198
|
-
{state.items.length > 0 ? <div className="term-blank" aria-hidden /> : null}
|
|
199
|
-
<WorkingRow
|
|
200
|
-
label={state.status === 'starting' ? 'Starting…' : 'Working…'}
|
|
201
|
-
startedAt={runStartedAt}
|
|
202
|
-
tokens={state.contextUsage?.totalTokens}
|
|
149
|
+
<HostImageProvider value={hostImage}>
|
|
150
|
+
<SkillNamesProvider names={skillNames}>
|
|
151
|
+
<SessionNamesProvider names={sessionNames}>
|
|
152
|
+
<Conversation className={cn(replaying && 'invisible', className)}>
|
|
153
|
+
<ConversationContent className={cn(terminal && 'gap-0 p-0')}>
|
|
154
|
+
<TerminalShell active={terminal} fontSize={fontSize} lineHeight={lineHeight} affordances={affordances}>
|
|
155
|
+
{frame ? (
|
|
156
|
+
frameTask === undefined && !replaying ? (
|
|
157
|
+
<div className={cn(terminal ? 'term-row text-fg-4' : 'p-4 text-body-sm text-fg-4')}>
|
|
158
|
+
This sub-agent's work is not in this transcript.
|
|
159
|
+
</div>
|
|
160
|
+
) : null
|
|
161
|
+
) : items.length === 0 && state.status !== 'starting' ? (
|
|
162
|
+
emptyState !== undefined ? (
|
|
163
|
+
emptyState
|
|
164
|
+
) : (
|
|
165
|
+
<SessionEmptyState
|
|
166
|
+
cwd={state.cwd}
|
|
167
|
+
hasCommands={!!state.commands?.length}
|
|
168
|
+
hasSkills={!!state.skills?.some((s) => s.enabled)}
|
|
169
|
+
canBrowseFiles={canBrowseFiles}
|
|
203
170
|
/>
|
|
204
|
-
|
|
205
|
-
) :
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
aria-hidden
|
|
216
|
-
className="wd-hold-appear visible pointer-events-none absolute inset-0 overflow-hidden"
|
|
217
|
-
>
|
|
218
|
-
<div className={cn('mx-auto w-full max-w-[var(--wd-transcript-max-width)]', !terminal && 'px-4 py-4')}>
|
|
219
|
-
{terminal ? (
|
|
220
|
-
<TerminalSurface
|
|
171
|
+
)
|
|
172
|
+
) : null}
|
|
173
|
+
{frame && frameTask === undefined && !replaying ? null : (
|
|
174
|
+
<TranscriptRows
|
|
175
|
+
rows={rows}
|
|
176
|
+
boundary={boundary}
|
|
177
|
+
since={catchUp?.since}
|
|
178
|
+
terminal={terminal}
|
|
179
|
+
replaying={replaying}
|
|
180
|
+
stickyPrompt={!frame && stickyPrompt}
|
|
181
|
+
gap={gap}
|
|
221
182
|
fontSize={fontSize}
|
|
222
183
|
lineHeight={lineHeight}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
184
|
+
items={items}
|
|
185
|
+
pendingApprovals={state.pendingApprovals}
|
|
186
|
+
scrubber={scrubber}
|
|
187
|
+
bookmarks={frame ? undefined : bookmarks}
|
|
188
|
+
affordances={affordances}
|
|
189
|
+
fileUrl={fileUrl}
|
|
190
|
+
attachmentUrl={attachmentUrl}
|
|
191
|
+
hostImage={hostImage}
|
|
192
|
+
jumpToRecapRef={jumpToRecapRef}
|
|
193
|
+
repinRef={repinRef}
|
|
194
|
+
reveal={frame ? undefined : reveal}
|
|
195
|
+
frameParentId={frame?.parentToolUseId}
|
|
196
|
+
onOpenSubagent={frame ? undefined : onOpenSubagent}
|
|
197
|
+
/>
|
|
231
198
|
)}
|
|
199
|
+
{(frame ? frameTask !== undefined && taskBusy(frameTask, items) : showLoader(state)) ? (
|
|
200
|
+
terminal ? (
|
|
201
|
+
<>
|
|
202
|
+
{state.items.length > 0 ? <div className="term-blank" aria-hidden /> : null}
|
|
203
|
+
<WorkingRow
|
|
204
|
+
label={state.status === 'starting' ? 'Starting…' : 'Working…'}
|
|
205
|
+
startedAt={runStartedAt}
|
|
206
|
+
tokens={state.contextUsage?.totalTokens}
|
|
207
|
+
/>
|
|
208
|
+
</>
|
|
209
|
+
) : (
|
|
210
|
+
<Loader label={state.status === 'starting' ? 'Starting session…' : undefined} />
|
|
211
|
+
)
|
|
212
|
+
) : null}
|
|
213
|
+
</TerminalShell>
|
|
214
|
+
</ConversationContent>
|
|
215
|
+
<ConversationScrollButton />
|
|
216
|
+
{replaying ? (
|
|
217
|
+
<div
|
|
218
|
+
data-slot="transcript-hold"
|
|
219
|
+
aria-hidden
|
|
220
|
+
className="wd-hold-appear visible pointer-events-none absolute inset-0 overflow-hidden"
|
|
221
|
+
>
|
|
222
|
+
<div className={cn('mx-auto w-full max-w-[var(--wd-transcript-max-width)]', !terminal && 'px-4 py-4')}>
|
|
223
|
+
{terminal ? (
|
|
224
|
+
<TerminalSurface
|
|
225
|
+
fontSize={fontSize}
|
|
226
|
+
lineHeight={lineHeight}
|
|
227
|
+
affordances={false}
|
|
228
|
+
bleed="1ch"
|
|
229
|
+
className="term-transcript"
|
|
230
|
+
>
|
|
231
|
+
<WorkingRow label="Loading…" />
|
|
232
|
+
</TerminalSurface>
|
|
233
|
+
) : (
|
|
234
|
+
<Loader label="Loading session…" />
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
232
237
|
</div>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
</
|
|
236
|
-
</
|
|
237
|
-
</
|
|
238
|
+
) : null}
|
|
239
|
+
</Conversation>
|
|
240
|
+
</SessionNamesProvider>
|
|
241
|
+
</SkillNamesProvider>
|
|
242
|
+
</HostImageProvider>
|
|
238
243
|
</TranscriptVariantProvider>
|
|
239
244
|
)
|
|
240
245
|
}
|