@workerdeck/ui 0.11.0 → 0.13.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/build/{SessionPanel-_U8tjX29.mjs → SessionPanel-CKQa4i0Y.mjs} +337 -269
- package/build/SessionPanel-CKQa4i0Y.mjs.map +1 -0
- package/build/{SessionPanel-CyhygZx_.d.mts → SessionPanel-CZMA44NM.d.mts} +73 -2
- package/build/format.d.mts +65 -1
- package/build/format.mjs +118 -1
- package/build/format.mjs.map +1 -0
- package/build/index.d.mts +129 -10
- package/build/index.mjs +461 -5
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +36 -1
- package/build/workspace.mjs +16 -2
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/Composer.tsx +18 -10
- package/src/components/agent/EngineIcon.tsx +97 -0
- package/src/components/agent/Loader.tsx +11 -46
- package/src/components/agent/Message.tsx +10 -6
- package/src/components/agent/QuestionPrompt.tsx +1 -1
- package/src/components/agent/SessionBrowser.tsx +546 -0
- package/src/components/agent/SessionPanel.tsx +89 -14
- package/src/components/agent/SessionWorkspace.tsx +45 -0
- package/src/components/agent/StatusBar.tsx +9 -2
- package/src/components/agent/ToolCallCard.tsx +11 -2
- package/src/components/agent/Transcript.tsx +28 -11
- package/src/components/agent/line-prompt.tsx +2 -2
- package/src/components/agent/pulse.tsx +60 -0
- package/src/components/agent/transcript-variant.tsx +62 -0
- package/src/components/ui/Menu.tsx +1 -1
- package/src/components/ui/Select.tsx +1 -1
- package/src/components/ui/Tooltip.tsx +1 -1
- package/src/format.ts +1 -0
- package/src/index.ts +12 -0
- package/src/lib/status.ts +124 -0
- package/src/styles/theme.css +45 -15
- package/build/SessionPanel-_U8tjX29.mjs.map +0 -1
package/src/format.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -83,8 +83,11 @@ export {
|
|
|
83
83
|
} from './components/agent/Conversation.tsx'
|
|
84
84
|
export { Message, MessageContent, type MessageProps } from './components/agent/Message.tsx'
|
|
85
85
|
export {
|
|
86
|
+
TranscriptDensityProvider,
|
|
86
87
|
TranscriptVariantProvider,
|
|
88
|
+
useTranscriptDensity,
|
|
87
89
|
useTranscriptVariant,
|
|
90
|
+
type TranscriptDensity,
|
|
88
91
|
type TranscriptVariant,
|
|
89
92
|
} from './components/agent/transcript-variant.tsx'
|
|
90
93
|
export { Response, type ResponseProps } from './components/agent/Response.tsx'
|
|
@@ -133,6 +136,15 @@ export {
|
|
|
133
136
|
type SessionListItemProps,
|
|
134
137
|
type SessionListProps,
|
|
135
138
|
} from './components/agent/SessionList.tsx'
|
|
139
|
+
export {
|
|
140
|
+
SessionBrowser,
|
|
141
|
+
rowShapeClass,
|
|
142
|
+
type SessionBrowserProps,
|
|
143
|
+
} from './components/agent/SessionBrowser.tsx'
|
|
144
|
+
export { SessionStatusIcon } from './components/agent/SessionBrowser.tsx'
|
|
145
|
+
// Lifted out of the VS Code sidebar once the dashboard grew a collapsed rail
|
|
146
|
+
// that needs the same glyph — two copies of a trademark set is one too many.
|
|
147
|
+
export { EngineIcon } from './components/agent/EngineIcon.tsx'
|
|
136
148
|
export {
|
|
137
149
|
SessionEmptyState,
|
|
138
150
|
type SessionEmptyStateProps,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ContextUsage, ModelOption, RateLimitInfo, SessionStatus } from '@workerdeck/protocol'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How a session's live readings become a status line — the pure half, so every
|
|
5
|
+
* host spells "Needs approval", "80% is a warning" and "which window is the
|
|
6
|
+
* binding one" the same way.
|
|
7
|
+
*
|
|
8
|
+
* Structurally typed against `SessionVitals` rather than importing it: this file
|
|
9
|
+
* ships from the React-free `@workerdeck/ui/format` entry, and a host drawing
|
|
10
|
+
* the readings outside React (the VS Code extension host in the window status
|
|
11
|
+
* bar) must not pull a component graph in to do it. A real `SessionVitals`
|
|
12
|
+
* satisfies every shape here.
|
|
13
|
+
*/
|
|
14
|
+
export type StatusSeverity = 'none' | 'warning' | 'error'
|
|
15
|
+
|
|
16
|
+
/** What a status slot shows, before any host's icon vocabulary gets involved.
|
|
17
|
+
* `icon` is a VS Code codicon name — the one host-shaped thing left, because the
|
|
18
|
+
* alternative is a second mapping table in the only consumer. */
|
|
19
|
+
export type StatusPresentation = {
|
|
20
|
+
icon: string
|
|
21
|
+
label: string
|
|
22
|
+
severity: StatusSeverity
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type StatusReadings = {
|
|
26
|
+
status: SessionStatus
|
|
27
|
+
/** `@workerdeck/react`'s `ConnectionState`, structurally — the link state wins
|
|
28
|
+
* the slot, so it has to be part of the reading. */
|
|
29
|
+
connection?: 'live' | 'reconnecting' | 'offline'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const STATUS_META: Record<SessionStatus, StatusPresentation> = {
|
|
33
|
+
starting: { icon: 'loading~spin', label: 'Starting', severity: 'none' },
|
|
34
|
+
running: { icon: 'loading~spin', label: 'Running', severity: 'none' },
|
|
35
|
+
awaiting_approval: { icon: 'warning', label: 'Needs approval', severity: 'warning' },
|
|
36
|
+
idle: { icon: 'check', label: 'Idle', severity: 'none' },
|
|
37
|
+
parked: { icon: 'debug-pause', label: 'Parked', severity: 'none' },
|
|
38
|
+
failed: { icon: 'error', label: 'Failed', severity: 'error' },
|
|
39
|
+
closed: { icon: 'circle-slash', label: 'Closed', severity: 'none' },
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The status slot, connection first. A session status held over a dead socket is
|
|
44
|
+
* the last thing we heard, not the current state — so a lost link takes the slot
|
|
45
|
+
* rather than letting "Running" imply a turn is still streaming.
|
|
46
|
+
*/
|
|
47
|
+
export function statusPresentation(vitals: StatusReadings | undefined): StatusPresentation {
|
|
48
|
+
if (!vitals) return { icon: 'hubot', label: 'Connecting…', severity: 'none' }
|
|
49
|
+
if (vitals.connection === 'offline') {
|
|
50
|
+
return { icon: 'debug-disconnect', label: 'Offline', severity: 'error' }
|
|
51
|
+
}
|
|
52
|
+
if (vitals.connection === 'reconnecting') {
|
|
53
|
+
return { icon: 'sync~spin', label: 'Reconnecting…', severity: 'warning' }
|
|
54
|
+
}
|
|
55
|
+
return STATUS_META[vitals.status] ?? { icon: 'hubot', label: vitals.status, severity: 'none' }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 0–100 → the colour a meter wears. One pair of thresholds for every surface. */
|
|
59
|
+
export function meterSeverity(pct: number | undefined): StatusSeverity {
|
|
60
|
+
if (pct === undefined) return 'none'
|
|
61
|
+
if (pct >= 95) return 'error'
|
|
62
|
+
if (pct >= 80) return 'warning'
|
|
63
|
+
return 'none'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The rate-limit window that gets the one visible slot: whichever is fullest,
|
|
67
|
+
* since the binding constraint is the one worth glancing at. */
|
|
68
|
+
export function tightestWindow(
|
|
69
|
+
rateLimits: Record<string, RateLimitInfo> | undefined,
|
|
70
|
+
): { key: string; info: RateLimitInfo } | undefined {
|
|
71
|
+
const entries = Object.entries(rateLimits ?? {})
|
|
72
|
+
if (entries.length === 0) return undefined
|
|
73
|
+
let best: { key: string; info: RateLimitInfo } | undefined
|
|
74
|
+
for (const [key, info] of entries) {
|
|
75
|
+
// A rejected window outranks any utilization: it is the one actually blocking.
|
|
76
|
+
const rank = info.status === 'rejected' ? Number.POSITIVE_INFINITY : (info.utilization ?? -1)
|
|
77
|
+
const bestRank =
|
|
78
|
+
best === undefined
|
|
79
|
+
? Number.NEGATIVE_INFINITY
|
|
80
|
+
: best.info.status === 'rejected'
|
|
81
|
+
? Number.POSITIVE_INFINITY
|
|
82
|
+
: (best.info.utilization ?? -1)
|
|
83
|
+
if (rank > bestRank) best = { key, info }
|
|
84
|
+
}
|
|
85
|
+
return best
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A rate-limit window's key, named for a human. */
|
|
89
|
+
export function windowLabel(key: string): string {
|
|
90
|
+
if (key === 'five_hour') return 'Session'
|
|
91
|
+
if (key === 'seven_day') return 'Weekly'
|
|
92
|
+
return key.replaceAll('_', ' ')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type ModelReadings = { model?: string; models: readonly ModelOption[] }
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The catalog row a session is actually running, or `undefined` for a model the
|
|
99
|
+
* list doesn't name. Matched leniently: a session reports the *resolved* id
|
|
100
|
+
* (`claude-sonnet-5`) where the row may be keyed on the alias (`sonnet`), and
|
|
101
|
+
* either can carry a `[1m]` context-window suffix.
|
|
102
|
+
*/
|
|
103
|
+
export function currentModel(vitals: ModelReadings | undefined): ModelOption | undefined {
|
|
104
|
+
const id = vitals?.model
|
|
105
|
+
if (!id) return undefined
|
|
106
|
+
const bare = (value: string) => value.replace(/\[.*\]$/, '')
|
|
107
|
+
const wanted = bare(id)
|
|
108
|
+
return vitals.models.find(
|
|
109
|
+
(m) => bare(m.value) === wanted || (m.resolvedModel && bare(m.resolvedModel) === wanted),
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A session's model, named the way the picker names it. Falls back to the raw
|
|
114
|
+
* id, and to "Default" while the session is on the CLI's own pick. */
|
|
115
|
+
export function modelLabel(vitals: ModelReadings | undefined): string {
|
|
116
|
+
if (!vitals?.model) return 'Default'
|
|
117
|
+
return currentModel(vitals)?.displayName ?? vitals.model
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Context percentage as its meter severity — the reading and the colour come
|
|
121
|
+
* from one place so a panel and a status bar never disagree. */
|
|
122
|
+
export function contextSeverity(usage: ContextUsage | undefined): StatusSeverity {
|
|
123
|
+
return meterSeverity(usage?.percentage)
|
|
124
|
+
}
|
package/src/styles/theme.css
CHANGED
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
--bg-code: #f5f5f5;
|
|
25
25
|
--bg-elevated: #ffffffcc;
|
|
26
26
|
|
|
27
|
+
/* A list row's hover and selected fills. **Alpha, not a flat colour**, because
|
|
28
|
+
a row sits on whatever its host paints — `bg-sidebar` in the dashboard,
|
|
29
|
+
`--vscode-sideBar-background` in the extension — and a flat value tuned for
|
|
30
|
+
one of those is invisible on the others. `--bg-surface-hover` was exactly
|
|
31
|
+
that mistake: on the dark sidebar (#131313) it lands on #141414, one step
|
|
32
|
+
of 255 away, so the hover state did not exist. */
|
|
33
|
+
--row-hover: rgb(0 0 0 / 0.05);
|
|
34
|
+
--row-active: rgb(0 0 0 / 0.08);
|
|
35
|
+
|
|
27
36
|
/* ---------- Borders ---------- */
|
|
28
37
|
--border: #ebebeb;
|
|
29
38
|
--border-light: #e0e0e0;
|
|
@@ -36,12 +45,19 @@
|
|
|
36
45
|
--fg-4: #a1a1a1;
|
|
37
46
|
--fg-disabled: #c9c9c9;
|
|
38
47
|
|
|
39
|
-
/* ---------- Accent
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
/* ---------- Accent ----------
|
|
49
|
+
* VS Code blue (`#0078d4`) — the product's primary. It is the *same* hue in
|
|
50
|
+
* both themes rather than the inverse of the canvas: a primary that flips
|
|
51
|
+
* from near-black to near-white is a contrast device, not a brand colour, and
|
|
52
|
+
* WorkerDeck now shares an editor's visual language.
|
|
53
|
+
*
|
|
54
|
+
* Not to be confused with the brand's live green (`docs/assets/BRAND.md`),
|
|
55
|
+
* which stays what it is — a *signal*, not a primary. */
|
|
56
|
+
--accent: #0078d4;
|
|
57
|
+
--accent-hover: #106ebe;
|
|
58
|
+
--accent-dim: #6ca9d8;
|
|
59
|
+
--accent-bg: #eff6fc;
|
|
60
|
+
--accent-ring: #0078d433;
|
|
45
61
|
|
|
46
62
|
/* ---------- Semantic ---------- */
|
|
47
63
|
--success: #0f7b4f;
|
|
@@ -72,7 +88,9 @@
|
|
|
72
88
|
--accent-tint: var(--accent-bg);
|
|
73
89
|
--accent-fg: #ffffff;
|
|
74
90
|
--warn: var(--warning);
|
|
75
|
-
|
|
91
|
+
/* Focus ring is the accent, as in an editor — the comment below already said
|
|
92
|
+
* "accent-derived"; before the blue it was indistinguishable from fg-1. */
|
|
93
|
+
--ring: var(--accent);
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
[data-theme='dark'] {
|
|
@@ -83,6 +101,9 @@
|
|
|
83
101
|
--bg-code: #141414;
|
|
84
102
|
--bg-elevated: #0a0a0acc;
|
|
85
103
|
|
|
104
|
+
--row-hover: rgb(255 255 255 / 0.06);
|
|
105
|
+
--row-active: rgb(255 255 255 / 0.10);
|
|
106
|
+
|
|
86
107
|
/* ---------- Borders ---------- */
|
|
87
108
|
--border: #1f1f1f;
|
|
88
109
|
--border-light: #292929;
|
|
@@ -95,12 +116,16 @@
|
|
|
95
116
|
--fg-4: #6e6e6e;
|
|
96
117
|
--fg-disabled: #454545;
|
|
97
118
|
|
|
98
|
-
/* ---------- Accent
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
--accent
|
|
119
|
+
/* ---------- Accent ----------
|
|
120
|
+
* The same blue as light. `--accent` stays `#0078d4` because it is a *fill*
|
|
121
|
+
* carrying white text (4.6:1) — the brighter `#3794ff` would drop that to
|
|
122
|
+
* 2.9:1. The bright one is the hover and the ring, where nothing sits on top
|
|
123
|
+
* of it and the extra lift is what a dark canvas needs. */
|
|
124
|
+
--accent: #0078d4;
|
|
125
|
+
--accent-hover: #3794ff;
|
|
126
|
+
--accent-dim: #4a7ba0;
|
|
127
|
+
--accent-bg: #12283c;
|
|
128
|
+
--accent-ring: #3794ff4d;
|
|
104
129
|
|
|
105
130
|
/* ---------- Semantic ---------- */
|
|
106
131
|
--success: #3ecf8e;
|
|
@@ -131,9 +156,12 @@
|
|
|
131
156
|
--text: var(--fg-1);
|
|
132
157
|
--text-muted: var(--fg-3);
|
|
133
158
|
--accent-tint: var(--accent-bg);
|
|
134
|
-
|
|
159
|
+
/* White on the blue fill in both themes — the accent no longer inverts. */
|
|
160
|
+
--accent-fg: #ffffff;
|
|
135
161
|
--warn: var(--warning);
|
|
136
|
-
|
|
162
|
+
/* Focus ring is the accent, as in an editor — the comment below already said
|
|
163
|
+
* "accent-derived"; before the blue it was indistinguishable from fg-1. */
|
|
164
|
+
--ring: var(--accent);
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
/* Theme-independent tokens — type tracking, easing, fonts.
|
|
@@ -188,6 +216,8 @@
|
|
|
188
216
|
--color-ring: var(--ring);
|
|
189
217
|
|
|
190
218
|
--color-surface-hover: var(--bg-surface-hover);
|
|
219
|
+
--color-row-hover: var(--row-hover);
|
|
220
|
+
--color-row-active: var(--row-active);
|
|
191
221
|
/* Named code-bg (not code) so it can't shadow the --text-code font-size token:
|
|
192
222
|
with a --color-code defined, `text-code` would resolve to the color namespace
|
|
193
223
|
and paint text in the code *background* color — invisible in both themes. */
|