@workerdeck/ui 0.11.0 → 0.12.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-CyhygZx_.d.mts → SessionPanel-Dy9lQrOV.d.mts} +44 -2
- package/build/{SessionPanel-_U8tjX29.mjs → SessionPanel-NQ8ksCfj.mjs} +280 -215
- package/build/SessionPanel-NQ8ksCfj.mjs.map +1 -0
- 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 +67 -7
- package/build/index.mjs +330 -5
- package/build/index.mjs.map +1 -1
- package/build/workspace.d.mts +28 -1
- package/build/workspace.mjs +14 -2
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/Loader.tsx +8 -46
- package/src/components/agent/SessionBrowser.tsx +428 -0
- package/src/components/agent/SessionPanel.tsx +28 -1
- package/src/components/agent/SessionWorkspace.tsx +35 -0
- package/src/components/agent/ToolCallCard.tsx +11 -2
- package/src/components/agent/Transcript.tsx +26 -8
- package/src/components/agent/pulse.tsx +60 -0
- package/src/components/agent/transcript-variant.tsx +62 -0
- package/src/format.ts +1 -0
- package/src/index.ts +4 -0
- package/src/lib/status.ts +124 -0
- package/build/SessionPanel-_U8tjX29.mjs.map +0 -1
package/build/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as Tip, A as PermissionModeSelect, B as commandTrigger, C as TranscriptDensityProvider, Ct as Button, D as isMutatingTool, E as useTranscriptVariant, F as McpDialog, G as plainTextToSegments, H as mentionTrigger, I as HostFilesDialog, J as Splitter, K as segmentsToPlainText, L as ContextDialog, M as permissionModeMeta, N as ModelSelect, O as toolIcon, P as SkillsDialog, Q as copyText, R as Composer, S as Response, St as badgeVariants, T as useTranscriptDensity, Tt as cn, U as usePromptAreaState, V as hashtagTrigger, W as PromptArea, X as Spinner, Y as CodeBlock, Z as CopyButton, _ as SessionInfoDialog, _t as SelectItemText, a as SessionEmptyState, at as DialogContent, b as parseUserQuestions, bt as Input, c as Message, ct as DialogTrigger, d as FileCard, dt as MenuItem, et as TooltipContent, f as Conversation, ft as MenuSeparator, g as STATUS_META, gt as SelectItem, h as StatusBar, ht as SelectContent, i as ToolCallCard, it as DialogClose, j as permissionModeChoices, k as PERMISSION_MODES, l as MessageContent, lt as Menu, m as ConversationScrollButton, mt as Select, n as UsageDialog, nt as Dialog, o as Reasoning, ot as DialogHeader, p as ConversationContent, pt as MenuTrigger, q as ProgressRing, r as Transcript, rt as DialogBody, s as PromptTokenText, st as DialogRow, t as SessionPanel, tt as TooltipProvider, u as Loader, ut as MenuContent, v as QUESTION_BEHAVIORS, vt as SelectTrigger, w as TranscriptVariantProvider, wt as buttonVariants, x as PermissionPrompt, xt as Badge, y as QuestionPrompt, yt as SelectValue, z as skillPrompt } from "./SessionPanel-NQ8ksCfj.mjs";
|
|
2
2
|
import { a as formatDuration, c as formatRelativeTime, d as rateLimitWindowSeconds, f as toolInputPreview, i as formatCountdown, l as formatTokens, n as formatBytes, o as formatRateLimitWindow, r as formatCost, s as formatRateLimitWindowLong, t as formatAgoPrecise, u as friendlyModel } from "./format-DqR56Y8l.mjs";
|
|
3
|
-
import { forwardRef } from "react";
|
|
4
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { Trash2 } from "lucide-react";
|
|
3
|
+
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Pencil, Search, Trash2, X } from "lucide-react";
|
|
6
6
|
import { AlertDialog as AlertDialog$1 } from "@base-ui/react/alert-dialog";
|
|
7
7
|
import { Toaster as Toaster$1, toast } from "sonner";
|
|
8
|
+
import { STATE_LABELS, STATE_ORDER, adaptersOf, clearFilters, filterRows, groupRows, hasFacetFilter, sessionLabel, subsetSummary } from "@workerdeck/protocol";
|
|
8
9
|
//#region src/components/ui/Card.tsx
|
|
9
10
|
function Card({ className, ...props }) {
|
|
10
11
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -156,6 +157,330 @@ function SessionList({ sessions, activeId, onSelect, onDelete, emptyText = "No s
|
|
|
156
157
|
});
|
|
157
158
|
}
|
|
158
159
|
//#endregion
|
|
159
|
-
|
|
160
|
+
//#region src/components/agent/SessionBrowser.tsx
|
|
161
|
+
function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelect, onDelete, onRename, emptyState, className }) {
|
|
162
|
+
const visible = useMemo(() => filterRows(rows, config, scope), [
|
|
163
|
+
rows,
|
|
164
|
+
config,
|
|
165
|
+
scope
|
|
166
|
+
]);
|
|
167
|
+
const groups = useMemo(() => groupRows(visible, config), [visible, config]);
|
|
168
|
+
const subset = subsetSummary(config, scope, visible.length, rows.length);
|
|
169
|
+
const adapters = useMemo(() => adaptersOf(rows), [rows]);
|
|
170
|
+
const gateways = useMemo(() => {
|
|
171
|
+
const seen = /* @__PURE__ */ new Map();
|
|
172
|
+
for (const row of rows) seen.set(row.hostId, row.hostName);
|
|
173
|
+
return [...seen].map(([id, name]) => ({
|
|
174
|
+
id,
|
|
175
|
+
name
|
|
176
|
+
}));
|
|
177
|
+
}, [rows]);
|
|
178
|
+
const set = (patch) => onConfigChange({
|
|
179
|
+
...config,
|
|
180
|
+
...patch
|
|
181
|
+
});
|
|
182
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
183
|
+
"data-slot": "session-browser",
|
|
184
|
+
className: cn("flex flex-col gap-3", className),
|
|
185
|
+
children: [
|
|
186
|
+
/* @__PURE__ */ jsxs("div", {
|
|
187
|
+
className: "flex flex-wrap items-center gap-2",
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ jsxs("div", {
|
|
190
|
+
className: "relative min-w-48 flex-1",
|
|
191
|
+
children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-fg-4" }), /* @__PURE__ */ jsx(Input, {
|
|
192
|
+
value: config.search,
|
|
193
|
+
onChange: (e) => set({ search: e.target.value }),
|
|
194
|
+
placeholder: "Search sessions",
|
|
195
|
+
"aria-label": "Search sessions",
|
|
196
|
+
className: "pl-8"
|
|
197
|
+
})]
|
|
198
|
+
}),
|
|
199
|
+
/* @__PURE__ */ jsx(FacetSelect, {
|
|
200
|
+
label: "State",
|
|
201
|
+
value: config.states,
|
|
202
|
+
options: STATE_ORDER.map((s) => ({
|
|
203
|
+
value: s,
|
|
204
|
+
label: STATE_LABELS[s]
|
|
205
|
+
})),
|
|
206
|
+
onChange: (states) => set({ states })
|
|
207
|
+
}),
|
|
208
|
+
adapters.length > 1 ? /* @__PURE__ */ jsx(FacetSelect, {
|
|
209
|
+
label: "Engine",
|
|
210
|
+
value: config.adapters,
|
|
211
|
+
options: adapters.map((a) => ({
|
|
212
|
+
value: a,
|
|
213
|
+
label: a
|
|
214
|
+
})),
|
|
215
|
+
onChange: (adapters) => set({ adapters })
|
|
216
|
+
}) : null,
|
|
217
|
+
gateways.length > 1 ? /* @__PURE__ */ jsx(FacetSelect, {
|
|
218
|
+
label: "Gateway",
|
|
219
|
+
value: config.gateways,
|
|
220
|
+
options: gateways.map((g) => ({
|
|
221
|
+
value: g.id,
|
|
222
|
+
label: g.name
|
|
223
|
+
})),
|
|
224
|
+
onChange: (gateways) => set({ gateways })
|
|
225
|
+
}) : null,
|
|
226
|
+
/* @__PURE__ */ jsx(OneOfSelect, {
|
|
227
|
+
label: "Group",
|
|
228
|
+
value: config.groupBy,
|
|
229
|
+
options: [
|
|
230
|
+
{
|
|
231
|
+
value: "none",
|
|
232
|
+
label: "No grouping"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
value: "state",
|
|
236
|
+
label: "By state"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
value: "adapter",
|
|
240
|
+
label: "By engine"
|
|
241
|
+
},
|
|
242
|
+
...gateways.length > 1 ? [{
|
|
243
|
+
value: "gateway",
|
|
244
|
+
label: "By gateway"
|
|
245
|
+
}] : []
|
|
246
|
+
],
|
|
247
|
+
onChange: (groupBy) => set({ groupBy })
|
|
248
|
+
}),
|
|
249
|
+
/* @__PURE__ */ jsx(OneOfSelect, {
|
|
250
|
+
label: "Sort",
|
|
251
|
+
value: config.sortBy,
|
|
252
|
+
options: [
|
|
253
|
+
{
|
|
254
|
+
value: "recent",
|
|
255
|
+
label: "Recent"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
value: "name",
|
|
259
|
+
label: "Name"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
value: "state",
|
|
263
|
+
label: "State"
|
|
264
|
+
},
|
|
265
|
+
...gateways.length > 1 ? [{
|
|
266
|
+
value: "gateway",
|
|
267
|
+
label: "Gateway"
|
|
268
|
+
}] : []
|
|
269
|
+
],
|
|
270
|
+
onChange: (sortBy) => set({ sortBy })
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
}),
|
|
274
|
+
subset ? /* @__PURE__ */ jsxs("div", {
|
|
275
|
+
className: "flex items-center gap-2 text-label text-fg-4",
|
|
276
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [
|
|
277
|
+
subset.shown,
|
|
278
|
+
" of ",
|
|
279
|
+
subset.total,
|
|
280
|
+
subset.causes.length ? ` · ${subset.causes.join(" · ")}` : null
|
|
281
|
+
] }), /* @__PURE__ */ jsx("button", {
|
|
282
|
+
type: "button",
|
|
283
|
+
className: "text-fg-3 underline underline-offset-2 hover:text-fg-1",
|
|
284
|
+
onClick: () => onConfigChange(clearFilters(config)),
|
|
285
|
+
children: "Show all"
|
|
286
|
+
})]
|
|
287
|
+
}) : null,
|
|
288
|
+
rows.length === 0 ? emptyState ?? /* @__PURE__ */ jsx(Empty, { children: "No sessions yet." }) : visible.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: hasFacetFilter(config) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
289
|
+
"No sessions match.",
|
|
290
|
+
" ",
|
|
291
|
+
/* @__PURE__ */ jsx("button", {
|
|
292
|
+
type: "button",
|
|
293
|
+
className: "underline underline-offset-2 hover:text-fg-1",
|
|
294
|
+
onClick: () => onConfigChange(clearFilters(config)),
|
|
295
|
+
children: "Clear filters"
|
|
296
|
+
})
|
|
297
|
+
] }) : "No sessions here." }) : /* @__PURE__ */ jsx("div", {
|
|
298
|
+
className: "flex flex-col gap-4",
|
|
299
|
+
children: groups.map((group) => /* @__PURE__ */ jsxs("div", {
|
|
300
|
+
className: "flex flex-col gap-1",
|
|
301
|
+
children: [config.groupBy !== "none" && group.label ? /* @__PURE__ */ jsxs("div", {
|
|
302
|
+
className: "flex items-baseline gap-2 px-2.5 text-label font-medium text-fg-4",
|
|
303
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
304
|
+
className: "uppercase tracking-wide",
|
|
305
|
+
children: group.label
|
|
306
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
307
|
+
className: "text-fg-4/70",
|
|
308
|
+
children: group.rows.length
|
|
309
|
+
})]
|
|
310
|
+
}) : null, group.rows.map((row) => /* @__PURE__ */ jsx(SessionRowItem, {
|
|
311
|
+
row,
|
|
312
|
+
active: row.info.id === activeId,
|
|
313
|
+
showGateway: gateways.length > 1,
|
|
314
|
+
onSelect,
|
|
315
|
+
onDelete,
|
|
316
|
+
onRename
|
|
317
|
+
}, `${row.hostId}:${row.info.id}`))]
|
|
318
|
+
}, group.key))
|
|
319
|
+
})
|
|
320
|
+
]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function Empty({ children }) {
|
|
324
|
+
return /* @__PURE__ */ jsx("div", {
|
|
325
|
+
className: "px-2.5 py-6 text-center text-body-sm text-fg-4",
|
|
326
|
+
children
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function SessionRowItem({ row, active, showGateway, onSelect, onDelete, onRename }) {
|
|
330
|
+
const { info } = row;
|
|
331
|
+
const meta = STATUS_META[info.status];
|
|
332
|
+
const [editing, setEditing] = useState(false);
|
|
333
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
334
|
+
"data-slot": "session-row",
|
|
335
|
+
"data-active": active || void 0,
|
|
336
|
+
className: cn("group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors", active ? "border-border bg-surface" : "hover:bg-surface-hover"),
|
|
337
|
+
children: [
|
|
338
|
+
/* @__PURE__ */ jsxs("div", {
|
|
339
|
+
className: "min-w-0 flex-1",
|
|
340
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
341
|
+
className: "flex items-center gap-2",
|
|
342
|
+
children: [
|
|
343
|
+
editing && onRename ? /* @__PURE__ */ jsx(NameEditor, {
|
|
344
|
+
initial: info.title ?? "",
|
|
345
|
+
onCommit: (title) => {
|
|
346
|
+
setEditing(false);
|
|
347
|
+
onRename(row, title);
|
|
348
|
+
},
|
|
349
|
+
onCancel: () => setEditing(false)
|
|
350
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
351
|
+
type: "button",
|
|
352
|
+
onClick: () => onSelect?.(row),
|
|
353
|
+
className: "min-w-0 truncate text-left text-body-sm font-medium text-fg-1 outline-none",
|
|
354
|
+
children: sessionLabel(info)
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
357
|
+
variant: meta.variant,
|
|
358
|
+
dot: true,
|
|
359
|
+
className: "shrink-0",
|
|
360
|
+
children: meta.label
|
|
361
|
+
}),
|
|
362
|
+
row.unseen > 0 ? /* @__PURE__ */ jsx(Badge, {
|
|
363
|
+
variant: "accent",
|
|
364
|
+
className: "shrink-0",
|
|
365
|
+
title: `${row.unseen} new`,
|
|
366
|
+
children: row.unseen
|
|
367
|
+
}) : null
|
|
368
|
+
]
|
|
369
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
370
|
+
type: "button",
|
|
371
|
+
onClick: () => !editing && onSelect?.(row),
|
|
372
|
+
className: "mt-0.5 flex w-full items-center gap-2 text-left font-mono text-label text-fg-4 outline-none",
|
|
373
|
+
children: [
|
|
374
|
+
/* @__PURE__ */ jsx("span", {
|
|
375
|
+
className: "truncate",
|
|
376
|
+
children: info.cwd
|
|
377
|
+
}),
|
|
378
|
+
showGateway ? /* @__PURE__ */ jsx("span", {
|
|
379
|
+
className: "shrink-0",
|
|
380
|
+
children: row.hostName
|
|
381
|
+
}) : null,
|
|
382
|
+
info.profile ? /* @__PURE__ */ jsxs("span", {
|
|
383
|
+
className: "shrink-0",
|
|
384
|
+
children: ["@", info.profile]
|
|
385
|
+
}) : null,
|
|
386
|
+
/* @__PURE__ */ jsx("span", {
|
|
387
|
+
className: "shrink-0",
|
|
388
|
+
children: formatCost(info.totalCostUsd)
|
|
389
|
+
}),
|
|
390
|
+
/* @__PURE__ */ jsx("span", {
|
|
391
|
+
className: "shrink-0",
|
|
392
|
+
children: formatRelativeTime(info.lastActivityAt ?? info.createdAt)
|
|
393
|
+
})
|
|
394
|
+
]
|
|
395
|
+
})]
|
|
396
|
+
}),
|
|
397
|
+
onRename && !editing ? /* @__PURE__ */ jsx(Button, {
|
|
398
|
+
variant: "ghost",
|
|
399
|
+
size: "icon-sm",
|
|
400
|
+
"aria-label": "Rename session",
|
|
401
|
+
className: "opacity-0 transition-opacity group-hover:opacity-100",
|
|
402
|
+
onClick: () => setEditing(true),
|
|
403
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "size-3.5 text-fg-3" })
|
|
404
|
+
}) : null,
|
|
405
|
+
onDelete ? /* @__PURE__ */ jsx(Button, {
|
|
406
|
+
variant: "ghost",
|
|
407
|
+
size: "icon-sm",
|
|
408
|
+
"aria-label": "Close session",
|
|
409
|
+
className: "opacity-0 transition-opacity group-hover:opacity-100",
|
|
410
|
+
onClick: () => onDelete(row),
|
|
411
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5 text-fg-3" })
|
|
412
|
+
}) : null
|
|
413
|
+
]
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Inline rename. Enter commits, Escape cancels, blur commits — but only a blur
|
|
418
|
+
* that is still inside this document: switching windows must not silently commit,
|
|
419
|
+
* nor kill the editor in the frame it opened.
|
|
420
|
+
*/
|
|
421
|
+
function NameEditor({ initial, onCommit, onCancel }) {
|
|
422
|
+
const [value, setValue] = useState(initial);
|
|
423
|
+
const ref = useRef(null);
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
ref.current?.select();
|
|
426
|
+
}, []);
|
|
427
|
+
return /* @__PURE__ */ jsx("input", {
|
|
428
|
+
ref,
|
|
429
|
+
value,
|
|
430
|
+
autoFocus: true,
|
|
431
|
+
"aria-label": "Session name",
|
|
432
|
+
onClick: (e) => e.stopPropagation(),
|
|
433
|
+
onChange: (e) => setValue(e.target.value),
|
|
434
|
+
onBlur: () => document.hasFocus() ? onCommit(value.trim()) : void 0,
|
|
435
|
+
onKeyDown: (e) => {
|
|
436
|
+
if (e.key === "Enter") onCommit(value.trim());
|
|
437
|
+
else if (e.key === "Escape") onCancel();
|
|
438
|
+
e.stopPropagation();
|
|
439
|
+
},
|
|
440
|
+
className: "-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-0.5 text-body-sm font-medium text-fg-1 outline-none"
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/** A multi-select facet. Empty = no filter, which is why the trigger reads the
|
|
444
|
+
* facet's name rather than "All": nothing is being excluded. */
|
|
445
|
+
function FacetSelect({ label, value, options, onChange }) {
|
|
446
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
447
|
+
className: "flex items-center gap-1",
|
|
448
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
449
|
+
multiple: true,
|
|
450
|
+
value,
|
|
451
|
+
onValueChange: (v) => onChange(v),
|
|
452
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
453
|
+
"aria-label": label,
|
|
454
|
+
className: "min-w-28",
|
|
455
|
+
children: /* @__PURE__ */ jsx(SelectValue, { children: value.length === 0 ? label : value.length === 1 ? options.find((o) => o.value === value[0])?.label ?? label : `${label} · ${value.length}` })
|
|
456
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
457
|
+
value: option.value,
|
|
458
|
+
children: /* @__PURE__ */ jsx(SelectItemText, { children: option.label })
|
|
459
|
+
}, option.value)) })]
|
|
460
|
+
}), value.length > 0 ? /* @__PURE__ */ jsx(Button, {
|
|
461
|
+
variant: "ghost",
|
|
462
|
+
size: "icon-sm",
|
|
463
|
+
"aria-label": `Clear ${label}`,
|
|
464
|
+
onClick: () => onChange([]),
|
|
465
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3 text-fg-4" })
|
|
466
|
+
}) : null]
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function OneOfSelect({ label, value, options, onChange }) {
|
|
470
|
+
return /* @__PURE__ */ jsxs(Select, {
|
|
471
|
+
value,
|
|
472
|
+
onValueChange: (v) => onChange(v),
|
|
473
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
474
|
+
"aria-label": label,
|
|
475
|
+
className: "min-w-28",
|
|
476
|
+
children: /* @__PURE__ */ jsx(SelectValue, { children: options.find((o) => o.value === value)?.label ?? label })
|
|
477
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
478
|
+
value: option.value,
|
|
479
|
+
children: /* @__PURE__ */ jsx(SelectItemText, { children: option.label })
|
|
480
|
+
}, option.value)) })]
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
//#endregion
|
|
484
|
+
export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, Badge, Button, Card, CardContent, CardHeader, CardTitle, CodeBlock, Composer, ContextDialog, Conversation, ConversationContent, ConversationScrollButton, CopyButton, Dialog, DialogBody, DialogClose, DialogContent, DialogHeader, DialogRow, DialogTrigger, FileCard, HostFilesDialog, Input, Loader, McpDialog, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Message, MessageContent, ModelSelect, PERMISSION_MODES, PermissionModeSelect, PermissionPrompt, ProgressRing, PromptArea, PromptTokenText, QUESTION_BEHAVIORS, QuestionPrompt, Reasoning, Response, STATUS_META, Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue, SessionBrowser, SessionEmptyState, SessionInfoDialog, SessionList, SessionListItem, SessionPanel, SkillsDialog, Spinner, Splitter, StatusBar, Textarea, Tip, Toaster, ToolCallCard, TooltipContent, TooltipProvider, Transcript, TranscriptDensityProvider, TranscriptVariantProvider, UsageDialog, badgeVariants, buttonVariants, cn, commandTrigger, copyText, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, getChipsByTrigger, hashtagTrigger, isMutatingTool, isSegmentsEmpty, mentionTrigger, parseUserQuestions, permissionModeChoices, permissionModeMeta, plainTextToSegments, rateLimitWindowSeconds, segmentsToPlainText, skillPrompt, toast, toolIcon, toolInputPreview, usePromptAreaState, useTranscriptDensity, useTranscriptVariant };
|
|
160
485
|
|
|
161
486
|
//# sourceMappingURL=index.mjs.map
|
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/agent/SessionList.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/agent/SessionList.tsx","../src/components/agent/SessionBrowser.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport { Pencil, Search, Trash2, X } from 'lucide-react'\nimport {\n STATE_LABELS,\n STATE_ORDER,\n adaptersOf,\n clearFilters,\n filterRows,\n groupRows,\n hasFacetFilter,\n sessionLabel,\n subsetSummary,\n} from '@workerdeck/protocol'\nimport type {\n GroupBy,\n SessionRow,\n SessionState,\n SortBy,\n ViewConfig,\n WorkspaceScope,\n} from '@workerdeck/protocol'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue } from '../ui/Select.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\n/**\n * A sessions list with the affordances a list of thirty needs: search, facets,\n * grouping, sorting, unread counts, and one honest line about what is hidden.\n *\n * The *rules* are `@workerdeck/protocol`'s (`filterRows`/`groupRows`/\n * `subsetSummary`), not this component's — the VS Code sidebar renders the same\n * model with workbench chrome, its activity-bar badge counts the same rows this\n * would show, and iOS mirrors them in Swift. What lives here is the styled\n * rendering of that model, so a host that wants the dashboard's look gets it\n * without reimplementing the model behind it.\n *\n * `SessionList` remains beside this for the plain case (a fixed set of rows, no\n * controls); this is what you reach for when the list is the screen.\n */\n\nexport interface SessionBrowserProps {\n rows: SessionRow[]\n config: ViewConfig\n onConfigChange: (config: ViewConfig) => void\n /** The host's own folders, if it has such a notion. Absent — a dashboard, a\n * phone — makes the scope filter genuinely inert rather than empty. */\n scope?: WorkspaceScope\n activeId?: string\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n /**\n * Rename, from the row's pencil. Empty string restores the derived title. A\n * gateway edit (`PATCH /sessions/:id`), never a local override — every client\n * should see the same name. Omit to make titles read-only.\n *\n * A hover affordance rather than the extension's double-click-the-title,\n * because here a single click on the row navigates: the *first* click of a\n * double-click would have already left the page.\n */\n onRename?: (row: SessionRow, title: string) => void\n /** Rendered when nothing at all exists (as opposed to nothing matching). */\n emptyState?: React.ReactNode\n className?: string\n}\n\nexport function SessionBrowser({\n rows,\n config,\n onConfigChange,\n scope,\n activeId,\n onSelect,\n onDelete,\n onRename,\n emptyState,\n className,\n}: SessionBrowserProps) {\n const visible = useMemo(() => filterRows(rows, config, scope), [rows, config, scope])\n const groups = useMemo(() => groupRows(visible, config), [visible, config])\n const subset = subsetSummary(config, scope, visible.length, rows.length)\n // Derived, not enumerated: a new engine or a new gateway needs no change here,\n // and a facet with one possible value is not a choice worth showing.\n const adapters = useMemo(() => adaptersOf(rows), [rows])\n const gateways = useMemo(() => {\n const seen = new Map<string, string>()\n for (const row of rows) seen.set(row.hostId, row.hostName)\n return [...seen].map(([id, name]) => ({ id, name }))\n }, [rows])\n\n const set = (patch: Partial<ViewConfig>) => onConfigChange({ ...config, ...patch })\n\n return (\n <div data-slot='session-browser' className={cn('flex flex-col gap-3', className)}>\n <div className='flex flex-wrap items-center gap-2'>\n <div className='relative min-w-48 flex-1'>\n <Search className='pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-fg-4' />\n <Input\n value={config.search}\n onChange={(e) => set({ search: e.target.value })}\n placeholder='Search sessions'\n aria-label='Search sessions'\n className='pl-8'\n />\n </div>\n <FacetSelect\n label='State'\n value={config.states}\n options={STATE_ORDER.map((s) => ({ value: s, label: STATE_LABELS[s] }))}\n onChange={(states) => set({ states: states as SessionState[] })}\n />\n {adapters.length > 1 ? (\n <FacetSelect\n label='Engine'\n value={config.adapters}\n options={adapters.map((a) => ({ value: a, label: a }))}\n onChange={(adapters) => set({ adapters })}\n />\n ) : null}\n {gateways.length > 1 ? (\n <FacetSelect\n label='Gateway'\n value={config.gateways}\n options={gateways.map((g) => ({ value: g.id, label: g.name }))}\n onChange={(gateways) => set({ gateways })}\n />\n ) : null}\n <OneOfSelect\n label='Group'\n value={config.groupBy}\n options={[\n { value: 'none', label: 'No grouping' },\n { value: 'state', label: 'By state' },\n { value: 'adapter', label: 'By engine' },\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'By gateway' }] : []),\n ]}\n onChange={(groupBy) => set({ groupBy: groupBy as GroupBy })}\n />\n <OneOfSelect\n label='Sort'\n value={config.sortBy}\n options={[\n { value: 'recent', label: 'Recent' },\n { value: 'name', label: 'Name' },\n { value: 'state', label: 'State' },\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'Gateway' }] : []),\n ]}\n onChange={(sortBy) => set({ sortBy: sortBy as SortBy })}\n />\n </div>\n\n {subset ? (\n <div className='flex items-center gap-2 text-label text-fg-4'>\n <span>\n {subset.shown} of {subset.total}\n {subset.causes.length ? ` · ${subset.causes.join(' · ')}` : null}\n </span>\n <button\n type='button'\n className='text-fg-3 underline underline-offset-2 hover:text-fg-1'\n onClick={() => onConfigChange(clearFilters(config))}>\n Show all\n </button>\n </div>\n ) : null}\n\n {rows.length === 0 ? (\n (emptyState ?? <Empty>No sessions yet.</Empty>)\n ) : visible.length === 0 ? (\n // Two different dead ends, two different ways out. \"Nothing matches\" is\n // a filter someone set; anything else is the state of the world.\n <Empty>\n {hasFacetFilter(config) ? (\n <>\n No sessions match.{' '}\n <button\n type='button'\n className='underline underline-offset-2 hover:text-fg-1'\n onClick={() => onConfigChange(clearFilters(config))}>\n Clear filters\n </button>\n </>\n ) : (\n 'No sessions here.'\n )}\n </Empty>\n ) : (\n <div className='flex flex-col gap-4'>\n {groups.map((group) => (\n <div key={group.key} className='flex flex-col gap-1'>\n {config.groupBy !== 'none' && group.label ? (\n <div className='flex items-baseline gap-2 px-2.5 text-label font-medium text-fg-4'>\n <span className='uppercase tracking-wide'>{group.label}</span>\n <span className='text-fg-4/70'>{group.rows.length}</span>\n </div>\n ) : null}\n {group.rows.map((row) => (\n <SessionRowItem\n key={`${row.hostId}:${row.info.id}`}\n row={row}\n active={row.info.id === activeId}\n showGateway={gateways.length > 1}\n onSelect={onSelect}\n onDelete={onDelete}\n onRename={onRename}\n />\n ))}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nfunction Empty({ children }: { children: React.ReactNode }) {\n return <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{children}</div>\n}\n\ninterface SessionRowItemProps {\n row: SessionRow\n active?: boolean\n showGateway?: boolean\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n onRename?: (row: SessionRow, title: string) => void\n}\n\nfunction SessionRowItem({\n row,\n active,\n showGateway,\n onSelect,\n onDelete,\n onRename,\n}: SessionRowItemProps) {\n const { info } = row\n const meta = STATUS_META[info.status]\n const [editing, setEditing] = useState(false)\n\n return (\n <div\n data-slot='session-row'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <div className='min-w-0 flex-1'>\n <div className='flex items-center gap-2'>\n {/* The editor replaces the link rather than sitting inside it — an\n input nested in a button is invalid, and disabling the button to\n protect the edit disables the field with it. */}\n {editing && onRename ? (\n <NameEditor\n initial={info.title ?? ''}\n onCommit={(title) => {\n setEditing(false)\n onRename(row, title)\n }}\n onCancel={() => setEditing(false)}\n />\n ) : (\n <button\n type='button'\n onClick={() => onSelect?.(row)}\n className='min-w-0 truncate text-left text-body-sm font-medium text-fg-1 outline-none'>\n {sessionLabel(info)}\n </button>\n )}\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n {row.unseen > 0 ? (\n // Transcript rows since this session was last on screen — the same\n // unit the VS Code badge counts, because turns undercount badly.\n <Badge variant='accent' className='shrink-0' title={`${row.unseen} new`}>\n {row.unseen}\n </Badge>\n ) : null}\n </div>\n <button\n type='button'\n onClick={() => !editing && onSelect?.(row)}\n className='mt-0.5 flex w-full items-center gap-2 text-left font-mono text-label text-fg-4 outline-none'>\n <span className='truncate'>{info.cwd}</span>\n {showGateway ? <span className='shrink-0'>{row.hostName}</span> : null}\n {info.profile ? <span className='shrink-0'>@{info.profile}</span> : null}\n <span className='shrink-0'>{formatCost(info.totalCostUsd)}</span>\n <span className='shrink-0'>\n {formatRelativeTime(info.lastActivityAt ?? info.createdAt)}\n </span>\n </button>\n </div>\n {onRename && !editing ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Rename session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => setEditing(true)}>\n <Pencil className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(row)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\n/**\n * Inline rename. Enter commits, Escape cancels, blur commits — but only a blur\n * that is still inside this document: switching windows must not silently commit,\n * nor kill the editor in the frame it opened.\n */\nfunction NameEditor({\n initial,\n onCommit,\n onCancel,\n}: {\n initial: string\n onCommit: (title: string) => void\n onCancel: () => void\n}) {\n const [value, setValue] = useState(initial)\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n ref.current?.select()\n }, [])\n return (\n <input\n ref={ref}\n value={value}\n autoFocus\n aria-label='Session name'\n // The row is a button; a click in here must not select the session.\n onClick={(e) => e.stopPropagation()}\n onChange={(e) => setValue(e.target.value)}\n onBlur={() => (document.hasFocus() ? onCommit(value.trim()) : undefined)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') onCommit(value.trim())\n else if (e.key === 'Escape') onCancel()\n e.stopPropagation()\n }}\n className='-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-0.5 text-body-sm font-medium text-fg-1 outline-none'\n />\n )\n}\n\n/** A multi-select facet. Empty = no filter, which is why the trigger reads the\n * facet's name rather than \"All\": nothing is being excluded. */\nfunction FacetSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string[]\n options: { value: string; label: string }[]\n onChange: (value: string[]) => void\n}) {\n return (\n <div className='flex items-center gap-1'>\n <Select multiple value={value} onValueChange={(v) => onChange(v as string[])}>\n <SelectTrigger aria-label={label} className='min-w-28'>\n <SelectValue>\n {value.length === 0\n ? label\n : value.length === 1\n ? (options.find((o) => o.value === value[0])?.label ?? label)\n : `${label} · ${value.length}`}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {value.length > 0 ? (\n <Button variant='ghost' size='icon-sm' aria-label={`Clear ${label}`} onClick={() => onChange([])}>\n <X className='size-3 text-fg-4' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nfunction OneOfSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string\n options: readonly { value: string; label: string }[]\n onChange: (value: string) => void\n}) {\n return (\n <Select value={value} onValueChange={(v) => onChange(v as string)}>\n <SelectTrigger aria-label={label} className='min-w-28'>\n <SelectValue>{options.find((o) => o.value === value)?.label ?? label}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n"],"mappings":"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"}
|
package/build/workspace.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as SessionPanelProps } from "./SessionPanel-
|
|
1
|
+
import { r as SessionPanelProps } from "./SessionPanel-Dy9lQrOV.mjs";
|
|
2
2
|
import * as _$react from "react";
|
|
3
3
|
import { CSSProperties } from "react";
|
|
4
4
|
import { OpenFile, UseHostFileSearchResult, UseHostFileTreeResult } from "@workerdeck/react";
|
|
@@ -11,10 +11,32 @@ interface SessionWorkspaceProps {
|
|
|
11
11
|
/** Passed straight through to {@link SessionPanel} — including the render-prop
|
|
12
12
|
* form that claims the session-actions menu. */
|
|
13
13
|
header?: SessionPanelProps['header'];
|
|
14
|
+
/**
|
|
15
|
+
* Panel seams the workspace does not interpret, forwarded verbatim.
|
|
16
|
+
*
|
|
17
|
+
* They are listed rather than spread so the workspace stays explicit about
|
|
18
|
+
* what it passes on: the panel owns the session's one attach, and a seam that
|
|
19
|
+
* silently arrived here would be a second place to look for why a session
|
|
20
|
+
* renders the way it does.
|
|
21
|
+
*/
|
|
22
|
+
transcriptVariant?: SessionPanelProps['transcriptVariant'];
|
|
23
|
+
transcriptDensity?: SessionPanelProps['transcriptDensity'];
|
|
24
|
+
unseen?: SessionPanelProps['unseen'];
|
|
25
|
+
onVitals?: SessionPanelProps['onVitals'];
|
|
14
26
|
/** Rail width in pixels on first render. */
|
|
15
27
|
defaultRailWidth?: number;
|
|
16
28
|
/** Start with the file rail collapsed even on a wide viewport. */
|
|
17
29
|
defaultRailCollapsed?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The rail moved. Paired with the two defaults so an embedder can persist the
|
|
32
|
+
* layout — the workspace deliberately does not, because *where* to keep it (a
|
|
33
|
+
* Memento, localStorage, a workspace file) is the embedder's call, and a
|
|
34
|
+
* component that picked one would be wrong in the other hosts.
|
|
35
|
+
*/
|
|
36
|
+
onRailChange?: (rail: {
|
|
37
|
+
width: number;
|
|
38
|
+
collapsed: boolean;
|
|
39
|
+
}) => void;
|
|
18
40
|
className?: string;
|
|
19
41
|
}
|
|
20
42
|
/**
|
|
@@ -42,8 +64,13 @@ declare function SessionWorkspace({
|
|
|
42
64
|
client,
|
|
43
65
|
sessionId,
|
|
44
66
|
header,
|
|
67
|
+
transcriptVariant,
|
|
68
|
+
transcriptDensity,
|
|
69
|
+
unseen,
|
|
70
|
+
onVitals,
|
|
45
71
|
defaultRailWidth,
|
|
46
72
|
defaultRailCollapsed,
|
|
73
|
+
onRailChange,
|
|
47
74
|
className
|
|
48
75
|
}: SessionWorkspaceProps): _$react.JSX.Element;
|
|
49
76
|
//#endregion
|
package/build/workspace.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as Tip, Ct as Button, J as Splitter, Tt as cn, X as Spinner, bt as Input, t as SessionPanel, tt as TooltipProvider } from "./SessionPanel-NQ8ksCfj.mjs";
|
|
2
2
|
import { n as formatBytes } from "./format-DqR56Y8l.mjs";
|
|
3
3
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -694,7 +694,7 @@ const EDITOR_MIN = 120;
|
|
|
694
694
|
* The conditional children before it are `? :` expressions that leave a null in
|
|
695
695
|
* their slot, which is exactly what keeps its index stable.
|
|
696
696
|
*/
|
|
697
|
-
function SessionWorkspace({ client, sessionId, header, defaultRailWidth = 260, defaultRailCollapsed, className }) {
|
|
697
|
+
function SessionWorkspace({ client, sessionId, header, transcriptVariant, transcriptDensity, unseen, onVitals, defaultRailWidth = 260, defaultRailCollapsed, onRailChange, className }) {
|
|
698
698
|
const { info } = useSessionInfo(client, sessionId);
|
|
699
699
|
const cwd = info?.cwd;
|
|
700
700
|
const tree = useHostFileTree(client, cwd);
|
|
@@ -710,6 +710,14 @@ function SessionWorkspace({ client, sessionId, header, defaultRailWidth = 260, d
|
|
|
710
710
|
const wide = useIsWide();
|
|
711
711
|
const [railCollapsed, setRailCollapsed] = useState(defaultRailCollapsed ?? false);
|
|
712
712
|
const [railWidth, setRailWidth] = useState(defaultRailWidth);
|
|
713
|
+
const onRailChangeRef = useRef(onRailChange);
|
|
714
|
+
onRailChangeRef.current = onRailChange;
|
|
715
|
+
useEffect(() => {
|
|
716
|
+
onRailChangeRef.current?.({
|
|
717
|
+
width: railWidth,
|
|
718
|
+
collapsed: railCollapsed
|
|
719
|
+
});
|
|
720
|
+
}, [railWidth, railCollapsed]);
|
|
713
721
|
const [editorHeight, setEditorHeight] = useState(360);
|
|
714
722
|
const hasFiles = files.files.length > 0;
|
|
715
723
|
const overlayRail = !wide;
|
|
@@ -800,6 +808,10 @@ function SessionWorkspace({ client, sessionId, header, defaultRailWidth = 260, d
|
|
|
800
808
|
client,
|
|
801
809
|
sessionId,
|
|
802
810
|
header: hoisted,
|
|
811
|
+
transcriptVariant,
|
|
812
|
+
transcriptDensity,
|
|
813
|
+
unseen,
|
|
814
|
+
onVitals,
|
|
803
815
|
className: "min-h-0 flex-1"
|
|
804
816
|
})
|
|
805
817
|
]
|