@workerdeck/ui 0.9.0 → 0.11.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 +81 -3
- package/build/SessionPanel-CyhygZx_.d.mts +277 -0
- package/build/SessionPanel-_U8tjX29.mjs +8409 -0
- package/build/SessionPanel-_U8tjX29.mjs.map +1 -0
- package/build/format-DqR56Y8l.mjs +162 -0
- package/build/format-DqR56Y8l.mjs.map +1 -0
- package/build/format-ljc3lKpA.d.mts +59 -0
- package/build/format.d.mts +2 -0
- package/build/format.mjs +2 -0
- package/build/index.d.mts +610 -87
- package/build/index.mjs +6 -5104
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +199 -0
- package/build/workspace.mjs +849 -0
- package/build/workspace.mjs.map +1 -0
- package/package.json +22 -4
- package/src/components/agent/CodeEditor.tsx +300 -0
- package/src/components/agent/Composer.tsx +522 -87
- package/src/components/agent/ContextDialog.tsx +99 -0
- package/src/components/agent/Conversation.tsx +11 -3
- package/src/components/agent/EditorTabs.tsx +165 -0
- package/src/components/agent/FileCard.tsx +26 -0
- package/src/components/agent/FileTree.tsx +287 -0
- package/src/components/agent/FileViewer.tsx +148 -0
- package/src/components/agent/HostFilesDialog.tsx +218 -0
- package/src/components/agent/Loader.tsx +120 -14
- package/src/components/agent/McpDialog.tsx +363 -0
- package/src/components/agent/Message.tsx +51 -17
- package/src/components/agent/ModelSelect.tsx +34 -6
- package/src/components/agent/PermissionModeSelect.tsx +133 -22
- package/src/components/agent/PermissionPrompt.tsx +164 -6
- package/src/components/agent/PromptTokenText.tsx +39 -0
- package/src/components/agent/QuestionPrompt.tsx +122 -0
- package/src/components/agent/Reasoning.tsx +20 -5
- package/src/components/agent/Response.tsx +128 -0
- package/src/components/agent/SessionEmptyState.tsx +65 -0
- package/src/components/agent/SessionInfoDialog.tsx +163 -0
- package/src/components/agent/SessionPanel.tsx +756 -91
- package/src/components/agent/SessionWorkspace.tsx +282 -0
- package/src/components/agent/SkillsDialog.tsx +195 -0
- package/src/components/agent/StatusBar.tsx +85 -18
- package/src/components/agent/ToolCallCard.tsx +243 -30
- package/src/components/agent/Transcript.tsx +495 -30
- package/src/components/agent/UsageDialog.tsx +168 -0
- package/src/components/agent/line-prompt.tsx +249 -0
- package/src/components/agent/transcript-variant.tsx +61 -0
- package/src/components/prompt-area/prompt-area-engine.ts +53 -0
- package/src/components/prompt-area/types.ts +15 -0
- package/src/components/prompt-area/use-prompt-area.ts +20 -0
- package/src/components/ui/CodeBlock.tsx +40 -2
- package/src/components/ui/CopyButton.tsx +28 -3
- package/src/components/ui/Dialog.tsx +92 -0
- package/src/components/ui/Menu.tsx +55 -0
- package/src/components/ui/Splitter.tsx +133 -0
- package/src/components/ui/Tooltip.tsx +22 -5
- package/src/format.ts +10 -0
- package/src/index.ts +63 -2
- package/src/lib/clipboard.ts +56 -0
- package/src/lib/format.ts +114 -0
- package/src/lib/tool-icon.ts +96 -0
- package/src/workspace.ts +28 -0
package/src/lib/format.ts
CHANGED
|
@@ -51,6 +51,54 @@ export function formatRelativeTime(epochMs: number | undefined, now = Date.now()
|
|
|
51
51
|
return `${d}d ago`
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Human label for a rate-limit window key, compact: 'five_hour' → "5h",
|
|
56
|
+
* 'seven_day_opus' → "7d opus". The per-model suffix is an open set — the CLI
|
|
57
|
+
* adds buckets as plans gain them — so it is rewritten rather than enumerated.
|
|
58
|
+
*/
|
|
59
|
+
export function formatRateLimitWindow(key: string): string {
|
|
60
|
+
if (key === 'five_hour') return '5h'
|
|
61
|
+
if (key === 'seven_day') return '7d'
|
|
62
|
+
const spaced = key.replaceAll('_', ' ')
|
|
63
|
+
return key.startsWith('seven_day_') ? `7d ${spaced.slice('seven day '.length)}` : spaced
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The same key spelled out, where there is room: 'five_hour' → "5-hour
|
|
67
|
+
* session", 'seven_day_fable' → "Weekly · Fable". */
|
|
68
|
+
export function formatRateLimitWindowLong(key: string): string {
|
|
69
|
+
if (key === 'five_hour') return '5-hour session'
|
|
70
|
+
if (key === 'seven_day') return 'Weekly'
|
|
71
|
+
if (key === 'seven_day_oauth_apps') return 'Weekly · apps'
|
|
72
|
+
const capitalize = (s: string) => s.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
73
|
+
if (!key.startsWith('seven_day_')) return capitalize(key.replaceAll('_', ' '))
|
|
74
|
+
return `Weekly · ${capitalize(key.slice('seven_day_'.length).replaceAll('_', ' '))}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* How long a rate-limit window is, in seconds — the denominator behind the pace
|
|
79
|
+
* marker. Derived from the key rather than reported: the CLI sends a reset time
|
|
80
|
+
* and a percentage, never a duration. `undefined` for a window whose key doesn't
|
|
81
|
+
* say, and the marker is then simply not drawn rather than guessed.
|
|
82
|
+
*/
|
|
83
|
+
export function rateLimitWindowSeconds(key: string): number | undefined {
|
|
84
|
+
if (key === 'five_hour') return 5 * 3600
|
|
85
|
+
if (key.startsWith('seven_day')) return 7 * 86_400
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** "8 secs ago" / "3 mins ago" — a freshness line finer-grained than
|
|
90
|
+
* {@link formatRelativeTime}, because a poll that just landed should say so. */
|
|
91
|
+
export function formatAgoPrecise(epochMs: number, now = Date.now()): string {
|
|
92
|
+
const seconds = Math.max(0, Math.floor((now - epochMs) / 1000))
|
|
93
|
+
if (seconds < 60) return `${seconds} sec${seconds === 1 ? '' : 's'} ago`
|
|
94
|
+
if (seconds < 3600) {
|
|
95
|
+
const minutes = Math.floor(seconds / 60)
|
|
96
|
+
return `${minutes} min${minutes === 1 ? '' : 's'} ago`
|
|
97
|
+
}
|
|
98
|
+
const hours = Math.floor(seconds / 3600)
|
|
99
|
+
return `${hours} hour${hours === 1 ? '' : 's'} ago`
|
|
100
|
+
}
|
|
101
|
+
|
|
54
102
|
/** Compact one-line preview of a tool input for card headers. */
|
|
55
103
|
export function toolInputPreview(input: unknown, max = 80): string {
|
|
56
104
|
if (input === null || input === undefined) return ''
|
|
@@ -65,3 +113,69 @@ export function toolInputPreview(input: unknown, max = 80): string {
|
|
|
65
113
|
const text = JSON.stringify(input) ?? ''
|
|
66
114
|
return text.length > max ? text.slice(0, max - 1) + '…' : text
|
|
67
115
|
}
|
|
116
|
+
|
|
117
|
+
/** Families whose name isn't just a capitalised first letter, and how the
|
|
118
|
+
* vendor writes the version after it. GPT is `GPT-5.6`; everyone else spaces
|
|
119
|
+
* it. Anything unlisted is title-cased and spaced. */
|
|
120
|
+
const MODEL_FAMILIES: Record<string, { name: string; joiner?: string }> = {
|
|
121
|
+
gpt: { name: 'GPT', joiner: '-' },
|
|
122
|
+
deepseek: { name: 'DeepSeek' },
|
|
123
|
+
glm: { name: 'GLM' },
|
|
124
|
+
qwen: { name: 'Qwen' },
|
|
125
|
+
kimi: { name: 'Kimi' },
|
|
126
|
+
llama: { name: 'Llama' },
|
|
127
|
+
mistral: { name: 'Mistral' },
|
|
128
|
+
grok: { name: 'Grok' },
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The name a person says, from a wire model id:
|
|
133
|
+
*
|
|
134
|
+
* - `claude-opus-5[1m]` → "Opus 5"
|
|
135
|
+
* - `claude-haiku-4-5-20251001` → "Haiku 4.5"
|
|
136
|
+
* - `gpt-5.6-luna` → "GPT-5.6 Luna"
|
|
137
|
+
* - `gemini-2.5-pro` → "Gemini 2.5 Pro"
|
|
138
|
+
* - `o3-mini` → "o3 Mini"
|
|
139
|
+
*
|
|
140
|
+
* Three kinds of token after the family, because vendors mix them freely: a
|
|
141
|
+
* **version** (`5`, `4-5`, `5.6` — joined with dots, since Anthropic splits what
|
|
142
|
+
* OpenAI writes as one token), a **code name or tier** (`luna`, `codex`, `pro`,
|
|
143
|
+
* `mini` — kept and capitalised, since it is often the only thing telling two
|
|
144
|
+
* models apart), and a **snapshot date** (`20251001` — dropped; it is a build,
|
|
145
|
+
* not a version).
|
|
146
|
+
*
|
|
147
|
+
* Anything genuinely unreadable falls back to the id: a wrong name is worse than
|
|
148
|
+
* a raw one, which is at least true.
|
|
149
|
+
*
|
|
150
|
+
* The server has a narrower version of this (`friendlyModelName` in core's
|
|
151
|
+
* `normalize.ts`) that derives Claude catalog names at authoring time. This one
|
|
152
|
+
* is the *render-time* fallback for an id with no catalog row behind it — the
|
|
153
|
+
* sidebar has only `SessionInfo.model` — so it has to cope with every vendor the
|
|
154
|
+
* provider engine can reach, not just the CLI's own.
|
|
155
|
+
*/
|
|
156
|
+
export function friendlyModel(id: string | undefined): string | undefined {
|
|
157
|
+
if (!id) return undefined
|
|
158
|
+
const withoutVariant = id.split('[')[0] ?? id
|
|
159
|
+
const parts = withoutVariant.toLowerCase().split('-').filter(Boolean)
|
|
160
|
+
if (parts[0] === 'claude') parts.shift()
|
|
161
|
+
const familyToken = parts.shift()
|
|
162
|
+
if (!familyToken) return id
|
|
163
|
+
const family = MODEL_FAMILIES[familyToken]
|
|
164
|
+
const name =
|
|
165
|
+
family?.name ??
|
|
166
|
+
// OpenAI's reasoning series is lower-case by its own convention ('o3-mini'),
|
|
167
|
+
// and "O3" reads as a different product.
|
|
168
|
+
(/^o\d+$/.test(familyToken)
|
|
169
|
+
? familyToken
|
|
170
|
+
: `${familyToken.charAt(0).toUpperCase()}${familyToken.slice(1)}`)
|
|
171
|
+
|
|
172
|
+
const version: string[] = []
|
|
173
|
+
const words: string[] = []
|
|
174
|
+
for (const part of parts) {
|
|
175
|
+
if (/^\d{8}$/.test(part)) continue
|
|
176
|
+
if (/^\d+(\.\d+)?$/.test(part)) version.push(part)
|
|
177
|
+
else words.push(`${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
178
|
+
}
|
|
179
|
+
const versioned = version.length > 0 ? `${name}${family?.joiner ?? ' '}${version.join('.')}` : name
|
|
180
|
+
return [versioned, ...words].join(' ')
|
|
181
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowDownCircle,
|
|
3
|
+
CheckSquare,
|
|
4
|
+
FileDiff,
|
|
5
|
+
FileText,
|
|
6
|
+
FolderSearch,
|
|
7
|
+
Globe,
|
|
8
|
+
Image,
|
|
9
|
+
type LucideIcon,
|
|
10
|
+
MessageCircleQuestion,
|
|
11
|
+
PencilLine,
|
|
12
|
+
Puzzle,
|
|
13
|
+
Search,
|
|
14
|
+
Sparkles,
|
|
15
|
+
SquarePen,
|
|
16
|
+
Terminal,
|
|
17
|
+
UsersRound,
|
|
18
|
+
Wrench,
|
|
19
|
+
} from 'lucide-react'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An icon per tool, so a transcript can be skimmed by shape rather than read.
|
|
23
|
+
*
|
|
24
|
+
* The same mapping the iOS app makes, in lucide's vocabulary rather than SF
|
|
25
|
+
* Symbols — the two clients should be recognisably showing the same thing. An
|
|
26
|
+
* unknown tool falls back to a wrench, and an MCP tool (`mcp__server__name`) to
|
|
27
|
+
* the puzzle piece the MCP screens use, because "which server is this from" is
|
|
28
|
+
* the useful thing to see at a glance.
|
|
29
|
+
*/
|
|
30
|
+
export function toolIcon(toolName: string): LucideIcon {
|
|
31
|
+
switch (toolName) {
|
|
32
|
+
case 'Bash':
|
|
33
|
+
case 'BashOutput':
|
|
34
|
+
case 'KillShell':
|
|
35
|
+
return Terminal
|
|
36
|
+
case 'Read':
|
|
37
|
+
return FileText
|
|
38
|
+
case 'Write':
|
|
39
|
+
return SquarePen
|
|
40
|
+
case 'Edit':
|
|
41
|
+
case 'MultiEdit':
|
|
42
|
+
case 'NotebookEdit':
|
|
43
|
+
return PencilLine
|
|
44
|
+
case 'Glob':
|
|
45
|
+
return FolderSearch
|
|
46
|
+
case 'Grep':
|
|
47
|
+
return Search
|
|
48
|
+
case 'WebFetch':
|
|
49
|
+
return ArrowDownCircle
|
|
50
|
+
case 'WebSearch':
|
|
51
|
+
return Globe
|
|
52
|
+
case 'Task':
|
|
53
|
+
case 'Agent':
|
|
54
|
+
return UsersRound
|
|
55
|
+
case 'TodoWrite':
|
|
56
|
+
return CheckSquare
|
|
57
|
+
case 'Skill':
|
|
58
|
+
return Sparkles
|
|
59
|
+
case 'AskUserQuestion':
|
|
60
|
+
return MessageCircleQuestion
|
|
61
|
+
// The codex engine's own tool names (see its runner's item mapping).
|
|
62
|
+
case 'CodexCommand':
|
|
63
|
+
return Terminal
|
|
64
|
+
case 'CodexFileChange':
|
|
65
|
+
return FileDiff
|
|
66
|
+
case 'CodexWebSearch':
|
|
67
|
+
return Globe
|
|
68
|
+
case 'CodexImageGeneration':
|
|
69
|
+
case 'CodexImageView':
|
|
70
|
+
return Image
|
|
71
|
+
default:
|
|
72
|
+
return toolName.startsWith('mcp__') ? Puzzle : Wrench
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Does this tool *change* the workspace?
|
|
78
|
+
*
|
|
79
|
+
* Worth its own colour in a transcript: skimming a run, "what did it edit" is a
|
|
80
|
+
* different question from "what did it look at", and a write is the one you
|
|
81
|
+
* might need to undo. Names from both first-party engines; an MCP tool is
|
|
82
|
+
* unknowable from its name, so it reads as neutral rather than guessed.
|
|
83
|
+
*/
|
|
84
|
+
export function isMutatingTool(toolName: string): boolean {
|
|
85
|
+
switch (toolName) {
|
|
86
|
+
case 'Write':
|
|
87
|
+
case 'Edit':
|
|
88
|
+
case 'MultiEdit':
|
|
89
|
+
case 'NotebookEdit':
|
|
90
|
+
case 'Update':
|
|
91
|
+
case 'CodexFileChange':
|
|
92
|
+
return true
|
|
93
|
+
default:
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace layer — the VS Code-shaped layout around `SessionPanel`, and
|
|
3
|
+
* the Monaco editor at the middle of it.
|
|
4
|
+
*
|
|
5
|
+
* This is a **separate entry point** (`@workerdeck/ui/workspace`) for one
|
|
6
|
+
* reason: Monaco. It is only reachable from here, so an embedder who imports
|
|
7
|
+
* the root entry never pulls it into their module graph.
|
|
8
|
+
*
|
|
9
|
+
* That matters more than tree-shaking alone would suggest. Rollup does drop
|
|
10
|
+
* `CodeEditor` from a `SessionPanel`-only bundle — but Vite resolves Monaco's
|
|
11
|
+
* `new Worker(new URL(…, import.meta.url))` calls during *transform*, before
|
|
12
|
+
* tree-shaking runs, and emits ~9MB of language-service workers as assets that
|
|
13
|
+
* are never retracted. Keeping the import unreachable from the root entry is
|
|
14
|
+
* what actually prevents that; `sideEffects: false` does not.
|
|
15
|
+
*
|
|
16
|
+
* Consequently `monaco-editor` is an **optional peer dependency**: importing
|
|
17
|
+
* this entry means installing it, and importing only the root entry means not
|
|
18
|
+
* having to. See the README for the Vite configuration Monaco needs.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
SessionWorkspace,
|
|
23
|
+
type SessionWorkspaceProps,
|
|
24
|
+
} from './components/agent/SessionWorkspace.tsx'
|
|
25
|
+
export { FileTree, type FileTreeProps } from './components/agent/FileTree.tsx'
|
|
26
|
+
export { EditorTabs, type EditorTabsProps } from './components/agent/EditorTabs.tsx'
|
|
27
|
+
export { FileViewer, type FileViewerProps } from './components/agent/FileViewer.tsx'
|
|
28
|
+
export { CodeEditor, type CodeEditorProps } from './components/agent/CodeEditor.tsx'
|