@tangle-network/sandbox-ui 0.90.2 → 0.91.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/dist/chat.js CHANGED
@@ -19,13 +19,13 @@ import {
19
19
  snapHarnessToModel,
20
20
  snapModelToHarness,
21
21
  validateComposerFiles
22
- } from "./chunk-UU77AFEU.js";
22
+ } from "./chunk-GHG4TCDS.js";
23
23
  import "./chunk-TK46XFLM.js";
24
24
  import {
25
25
  MENTION_PILL_CLASS
26
26
  } from "./chunk-WGOOQAW3.js";
27
- import "./chunk-NF3Q4RJU.js";
28
- import "./chunk-LAZZBSN4.js";
27
+ import "./chunk-WSN2RZC6.js";
28
+ import "./chunk-FWSBQLFG.js";
29
29
  import "./chunk-5XRAKVIV.js";
30
30
  import "./chunk-EI44GEQ5.js";
31
31
  import "./chunk-CZ7CSFO4.js";
@@ -11,10 +11,102 @@ import {
11
11
  } from "./chunk-EI44GEQ5.js";
12
12
 
13
13
  // src/dashboard/model-picker.tsx
14
- import * as React from "react";
15
- import { ChevronDown, Search, Sparkles, Loader2 } from "lucide-react";
14
+ import * as React2 from "react";
15
+ import { ChevronDown, Sparkles } from "lucide-react";
16
16
  import * as Popover from "@radix-ui/react-dropdown-menu";
17
+
18
+ // src/lib/picker-menu.tsx
19
+ import * as React from "react";
20
+ import { Loader2, Search } from "lucide-react";
17
21
  import { jsx, jsxs } from "react/jsx-runtime";
22
+ var pickerMenuContentClass = cn(
23
+ "z-50 w-[var(--radix-dropdown-menu-trigger-width)] min-w-[320px] max-w-[460px]",
24
+ "max-h-[min(440px,var(--radix-dropdown-menu-content-available-height))] overflow-hidden flex flex-col",
25
+ "rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
26
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
27
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
28
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
29
+ );
30
+ var pickerMenuBodyClass = "flex-1 overflow-y-auto";
31
+ function pickerMenuItemClass({
32
+ active,
33
+ featured
34
+ } = {}) {
35
+ return cn(
36
+ "flex cursor-pointer items-start gap-2 rounded-md px-3 py-2 outline-none",
37
+ "transition-colors duration-[var(--transition-fast)]",
38
+ "hover:bg-accent/50 focus:bg-accent/50",
39
+ featured && "mx-1 border border-primary/10 bg-primary/[0.04] px-2.5",
40
+ active && "bg-primary/10 font-medium text-foreground ring-1 ring-inset ring-primary/25 hover:bg-primary/15 focus:bg-primary/15"
41
+ );
42
+ }
43
+ function PickerMenuSearch({
44
+ value,
45
+ onChange,
46
+ placeholder,
47
+ loading,
48
+ inputRef
49
+ }) {
50
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2", children: [
51
+ /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
52
+ /* @__PURE__ */ jsx(
53
+ "input",
54
+ {
55
+ ref: inputRef,
56
+ type: "text",
57
+ value,
58
+ onChange: (e) => onChange(e.target.value),
59
+ onKeyDown: (e) => {
60
+ if (e.key.length === 1) e.stopPropagation();
61
+ },
62
+ placeholder,
63
+ autoFocus: true,
64
+ className: "flex-1 bg-transparent text-sm font-medium outline-none placeholder:font-normal placeholder:text-muted-foreground"
65
+ }
66
+ ),
67
+ loading && /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin text-muted-foreground" })
68
+ ] });
69
+ }
70
+ function PickerMenuSection({
71
+ label,
72
+ glyph,
73
+ tone,
74
+ children
75
+ }) {
76
+ return /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
77
+ /* @__PURE__ */ jsxs(
78
+ "div",
79
+ {
80
+ className: cn(
81
+ "flex items-center gap-1.5 px-3 pt-1.5 pb-0.5 text-[10px] font-mono uppercase tracking-widest",
82
+ tone === "featured" ? "text-primary" : "text-muted-foreground"
83
+ ),
84
+ children: [
85
+ glyph,
86
+ /* @__PURE__ */ jsx("span", { children: label })
87
+ ]
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsx("div", { children })
91
+ ] });
92
+ }
93
+ function PickerMenuFooter({ children }) {
94
+ return /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--md3-outline-variant)] px-3 py-1.5 text-[10px] text-muted-foreground", children });
95
+ }
96
+ function PickerMenuMeta({ parts }) {
97
+ const shown = parts.filter(Boolean);
98
+ if (shown.length === 0) return null;
99
+ return /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex items-center gap-1.5 text-[11px] text-muted-foreground", children: shown.map((part, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
100
+ i > 0 && /* @__PURE__ */ jsx("span", { className: "shrink-0 opacity-40", children: "\xB7" }),
101
+ /* @__PURE__ */ jsx("span", { className: i === 0 ? "min-w-0 truncate" : "shrink-0", children: part })
102
+ ] }, i)) });
103
+ }
104
+ function PickerMenuTag({ children }) {
105
+ return /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full border border-[var(--md3-outline-variant)] bg-surface-container px-1.5 py-px text-[9px] font-medium text-muted-foreground", children });
106
+ }
107
+
108
+ // src/dashboard/model-picker.tsx
109
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
18
110
  function modelDedupKey(model) {
19
111
  const canonical = canonicalModelId(model).toLowerCase();
20
112
  const segments = canonical.split("/").filter(Boolean);
@@ -122,10 +214,10 @@ function ModelPicker({
122
214
  side,
123
215
  avoidCollisions
124
216
  }) {
125
- const [query, setQuery] = React.useState("");
126
- const [open, setOpen] = React.useState(false);
127
- const searchInputRef = React.useRef(null);
128
- React.useLayoutEffect(() => {
217
+ const [query, setQuery] = React2.useState("");
218
+ const [open, setOpen] = React2.useState(false);
219
+ const searchInputRef = React2.useRef(null);
220
+ React2.useLayoutEffect(() => {
129
221
  if (!open) return;
130
222
  const frame = requestAnimationFrame(() => {
131
223
  searchInputRef.current?.focus();
@@ -133,8 +225,8 @@ function ModelPicker({
133
225
  });
134
226
  return () => cancelAnimationFrame(frame);
135
227
  }, [open]);
136
- const deduped = React.useMemo(() => dedupeModels(models), [models]);
137
- const filtered = React.useMemo(() => {
228
+ const deduped = React2.useMemo(() => dedupeModels(models), [models]);
229
+ const filtered = React2.useMemo(() => {
138
230
  const excluded = new Set((excludeProviders ?? []).map((p) => p.toLowerCase()));
139
231
  const allowedModalities = modalities ? new Set(modalities) : null;
140
232
  const q = query.trim().toLowerCase();
@@ -149,7 +241,7 @@ function ModelPicker({
149
241
  return id.includes(q) || name.includes(q) || provider.includes(q) || identity.host.label.toLowerCase().includes(q) || identity.lab.label.toLowerCase().includes(q);
150
242
  });
151
243
  }, [deduped, query, modalities, excludeProviders]);
152
- const grouped = React.useMemo(() => {
244
+ const grouped = React2.useMemo(() => {
153
245
  const groups = /* @__PURE__ */ new Map();
154
246
  for (const m of filtered) {
155
247
  const key = modelGroupKey(m);
@@ -159,7 +251,7 @@ function ModelPicker({
159
251
  }
160
252
  return Array.from(groups.entries()).map(([key, list]) => [key, [...list].sort(compareModelsByDisplayName)]).sort(([a], [b]) => compareModelGroups(a, b));
161
253
  }, [filtered]);
162
- const current = React.useMemo(
254
+ const current = React2.useMemo(
163
255
  () => models.find((m) => canonicalModelId(m) === value),
164
256
  [models, value]
165
257
  );
@@ -168,17 +260,17 @@ function ModelPicker({
168
260
  current?.name ?? current?.id ?? value,
169
261
  currentIdentity
170
262
  );
171
- const recentIds = React.useMemo(() => {
263
+ const recentIds = React2.useMemo(() => {
172
264
  if (!recents?.length) return [];
173
265
  const lookup = new Map(models.map((m) => [canonicalModelId(m), m]));
174
266
  return recents.map((id) => lookup.get(id)).filter((m) => Boolean(m)).slice(0, 4);
175
267
  }, [recents, models]);
176
- const popularModels = React.useMemo(() => {
268
+ const popularModels = React2.useMemo(() => {
177
269
  if (!popular?.length) return [];
178
270
  const lookup = new Map(models.map((m) => [canonicalModelId(m), m]));
179
271
  return popular.map((id) => lookup.get(id)).filter((m) => Boolean(m));
180
272
  }, [popular, models]);
181
- const featuredModels = React.useMemo(() => {
273
+ const featuredModels = React2.useMemo(() => {
182
274
  const flagged = deduped.filter((m) => m.featured);
183
275
  if (flagged.length > 0) return flagged;
184
276
  const seedKeys = new Set(
@@ -196,7 +288,7 @@ function ModelPicker({
196
288
  setQuery("");
197
289
  };
198
290
  const triggerLabel = label ? `${label}: ${currentLabel || placeholder}` : currentLabel || placeholder;
199
- const trigger = variant === "pill" ? /* @__PURE__ */ jsxs(
291
+ const trigger = variant === "pill" ? /* @__PURE__ */ jsxs2(
200
292
  "button",
201
293
  {
202
294
  type: "button",
@@ -214,12 +306,12 @@ function ModelPicker({
214
306
  triggerClassName
215
307
  ),
216
308
  children: [
217
- currentIdentity ? /* @__PURE__ */ jsx(ModelBrandStack, { identity: currentIdentity, size: "sm" }) : /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3 text-muted-foreground" }),
218
- /* @__PURE__ */ jsx("span", { className: "truncate max-w-[160px]", children: currentLabel || placeholder }),
219
- /* @__PURE__ */ jsx(ChevronDown, { className: "ml-auto h-3 w-3 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" })
309
+ currentIdentity ? /* @__PURE__ */ jsx2(ModelBrandStack, { identity: currentIdentity, size: "sm" }) : /* @__PURE__ */ jsx2(Sparkles, { className: "h-3 w-3 text-muted-foreground" }),
310
+ /* @__PURE__ */ jsx2("span", { className: "truncate max-w-[160px]", children: currentLabel || placeholder }),
311
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "ml-auto h-3 w-3 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" })
220
312
  ]
221
313
  }
222
- ) : /* @__PURE__ */ jsxs(
314
+ ) : /* @__PURE__ */ jsxs2(
223
315
  "button",
224
316
  {
225
317
  type: "button",
@@ -237,19 +329,19 @@ function ModelPicker({
237
329
  triggerClassName
238
330
  ),
239
331
  children: [
240
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
241
- currentIdentity && /* @__PURE__ */ jsx(ModelBrandStack, { identity: currentIdentity, size: "sm" }),
242
- /* @__PURE__ */ jsx("span", { className: cn("truncate", current ? "text-foreground font-medium" : "text-muted-foreground"), children: currentLabel || placeholder })
332
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
333
+ currentIdentity && /* @__PURE__ */ jsx2(ModelBrandStack, { identity: currentIdentity, size: "sm" }),
334
+ /* @__PURE__ */ jsx2("span", { className: cn("truncate", current ? "text-foreground font-medium" : "text-muted-foreground"), children: currentLabel || placeholder })
243
335
  ] }),
244
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" })
336
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "h-4 w-4 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" })
245
337
  ]
246
338
  }
247
339
  );
248
- return /* @__PURE__ */ jsxs("div", { className: cn(variant === "field" ? "space-y-1.5" : "inline-flex", variant === "field" ? className : void 0), children: [
249
- variant === "field" && label && /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-muted-foreground uppercase tracking-[0.06em]", children: label }),
250
- /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, modal: false, children: [
251
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: trigger }),
252
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
340
+ return /* @__PURE__ */ jsxs2("div", { className: cn(variant === "field" ? "space-y-1.5" : "inline-flex", variant === "field" ? className : void 0), children: [
341
+ variant === "field" && label && /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-muted-foreground uppercase tracking-[0.06em]", children: label }),
342
+ /* @__PURE__ */ jsxs2(Popover.Root, { open, onOpenChange: setOpen, modal: false, children: [
343
+ /* @__PURE__ */ jsx2(Popover.Trigger, { asChild: true, children: trigger }),
344
+ /* @__PURE__ */ jsx2(Popover.Portal, { children: /* @__PURE__ */ jsxs2(
253
345
  Popover.Content,
254
346
  {
255
347
  side,
@@ -257,38 +349,20 @@ function ModelPicker({
257
349
  align: variant === "pill" ? "start" : "start",
258
350
  avoidCollisions,
259
351
  collisionPadding: 24,
260
- className: cn(
261
- "z-50 w-[var(--radix-dropdown-menu-trigger-width)] min-w-[320px] max-w-[460px]",
262
- // Cap to the smaller of the fixed height and the viewport space on
263
- // the open side, so a list pinned downward never runs off-screen.
264
- "max-h-[min(440px,var(--radix-dropdown-menu-content-available-height))] overflow-hidden flex flex-col",
265
- "rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
266
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
267
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
268
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
269
- ),
352
+ className: pickerMenuContentClass,
270
353
  children: [
271
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2", children: [
272
- /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
273
- /* @__PURE__ */ jsx(
274
- "input",
275
- {
276
- ref: searchInputRef,
277
- type: "text",
278
- value: query,
279
- onChange: (e) => setQuery(e.target.value),
280
- onKeyDown: (e) => {
281
- if (e.key.length === 1) e.stopPropagation();
282
- },
283
- placeholder: "Search models...",
284
- autoFocus: true,
285
- className: "flex-1 bg-transparent text-sm font-medium outline-none placeholder:font-normal placeholder:text-muted-foreground"
286
- }
287
- ),
288
- loading && /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin text-muted-foreground" })
289
- ] }),
290
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
291
- !query && featuredModels.length > 0 && /* @__PURE__ */ jsx(Section, { label: "Recommended", tone: "featured", children: featuredModels.map((m) => /* @__PURE__ */ jsx(
354
+ /* @__PURE__ */ jsx2(
355
+ PickerMenuSearch,
356
+ {
357
+ value: query,
358
+ onChange: setQuery,
359
+ placeholder: "Search models...",
360
+ loading,
361
+ inputRef: searchInputRef
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsxs2("div", { className: pickerMenuBodyClass, children: [
365
+ !query && featuredModels.length > 0 && /* @__PURE__ */ jsx2(Section, { label: "Recommended", tone: "featured", children: featuredModels.map((m) => /* @__PURE__ */ jsx2(
292
366
  ModelRow,
293
367
  {
294
368
  model: m,
@@ -298,7 +372,7 @@ function ModelPicker({
298
372
  },
299
373
  `featured-${canonicalModelId(m)}`
300
374
  )) }),
301
- !query && popularModels.length > 0 && /* @__PURE__ */ jsx(Section, { label: "Top models", tone: "featured", children: popularModels.map((m) => /* @__PURE__ */ jsx(
375
+ !query && popularModels.length > 0 && /* @__PURE__ */ jsx2(Section, { label: "Top models", tone: "featured", children: popularModels.map((m) => /* @__PURE__ */ jsx2(
302
376
  ModelRow,
303
377
  {
304
378
  model: m,
@@ -308,7 +382,7 @@ function ModelPicker({
308
382
  },
309
383
  `popular-${canonicalModelId(m)}`
310
384
  )) }),
311
- !query && recentIds.length > 0 && /* @__PURE__ */ jsx(Section, { label: "Recent", children: recentIds.map((m) => /* @__PURE__ */ jsx(
385
+ !query && recentIds.length > 0 && /* @__PURE__ */ jsx2(Section, { label: "Recent", children: recentIds.map((m) => /* @__PURE__ */ jsx2(
312
386
  ModelRow,
313
387
  {
314
388
  model: m,
@@ -317,7 +391,7 @@ function ModelPicker({
317
391
  },
318
392
  `recent-${canonicalModelId(m)}`
319
393
  )) }),
320
- grouped.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: loading ? "Loading models..." : query ? "No models match." : "No models available." }) : grouped.map(([groupKey, list]) => /* @__PURE__ */ jsx(Section, { label: modelGroupLabel(groupKey), children: list.map((m) => /* @__PURE__ */ jsx(
394
+ grouped.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: loading ? "Loading models..." : query ? "No models match." : "No models available." }) : grouped.map(([groupKey, list]) => /* @__PURE__ */ jsx2(Section, { label: modelGroupLabel(groupKey), children: list.map((m) => /* @__PURE__ */ jsx2(
321
395
  ModelRow,
322
396
  {
323
397
  model: m,
@@ -327,7 +401,7 @@ function ModelPicker({
327
401
  canonicalModelId(m)
328
402
  )) }, groupKey))
329
403
  ] }),
330
- /* @__PURE__ */ jsxs("div", { className: "border-t border-[var(--md3-outline-variant)] px-3 py-1.5 text-[10px] text-muted-foreground", children: [
404
+ /* @__PURE__ */ jsxs2(PickerMenuFooter, { children: [
331
405
  filtered.length,
332
406
  " of ",
333
407
  deduped.length,
@@ -342,22 +416,15 @@ function ModelPicker({
342
416
  }
343
417
  function Section({ label, children, tone }) {
344
418
  const identity = brandInfo(normalizeBrandKey(label));
345
- return /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
346
- /* @__PURE__ */ jsxs(
347
- "div",
348
- {
349
- className: cn(
350
- "flex items-center gap-1.5 px-3 pt-1.5 pb-0.5 text-[10px] font-mono uppercase tracking-widest",
351
- tone === "featured" ? "text-primary" : "text-muted-foreground"
352
- ),
353
- children: [
354
- identity.key !== "unknown" && /* @__PURE__ */ jsx(BrandLogo, { brand: identity, size: "xs" }),
355
- /* @__PURE__ */ jsx("span", { children: label })
356
- ]
357
- }
358
- ),
359
- /* @__PURE__ */ jsx("div", { children })
360
- ] });
419
+ return /* @__PURE__ */ jsx2(
420
+ PickerMenuSection,
421
+ {
422
+ label,
423
+ tone,
424
+ glyph: identity.key !== "unknown" ? /* @__PURE__ */ jsx2(BrandLogo, { brand: identity, size: "xs" }) : void 0,
425
+ children
426
+ }
427
+ );
361
428
  }
362
429
  function PickerItem({
363
430
  onSelect,
@@ -365,20 +432,14 @@ function PickerItem({
365
432
  featured,
366
433
  children
367
434
  }) {
368
- return /* @__PURE__ */ jsx(
435
+ return /* @__PURE__ */ jsx2(
369
436
  Popover.Item,
370
437
  {
371
438
  onSelect: (e) => {
372
439
  e.preventDefault();
373
440
  onSelect();
374
441
  },
375
- className: cn(
376
- "flex cursor-pointer items-start gap-2 rounded-md px-3 py-2 outline-none",
377
- "transition-colors duration-[var(--transition-fast)]",
378
- "hover:bg-accent/50 focus:bg-accent/50",
379
- featured && "mx-1 border border-primary/10 bg-primary/[0.04] px-2.5",
380
- active && "bg-primary/10 font-medium text-foreground ring-1 ring-inset ring-primary/25 hover:bg-primary/15 focus:bg-primary/15"
381
- ),
442
+ className: pickerMenuItemClass({ active, featured }),
382
443
  children
383
444
  }
384
445
  );
@@ -394,26 +455,35 @@ function ModelRow({
394
455
  const ctx = formatContext(model.context_length);
395
456
  const identity = resolveModelBrandIdentity(model);
396
457
  const via = !identity.combined ? identity.host.label : null;
397
- return /* @__PURE__ */ jsxs(PickerItem, { onSelect: () => onSelect(id), active, featured, children: [
398
- /* @__PURE__ */ jsx(ModelBrandStack, { identity, size: "md" }),
399
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
400
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
401
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-sm font-medium", children: model.name ?? model.id }),
402
- via && /* @__PURE__ */ jsxs("span", { className: "shrink-0 rounded-full border border-[var(--md3-outline-variant)] bg-surface-container px-1.5 py-px text-[9px] font-medium text-muted-foreground", children: [
458
+ return /* @__PURE__ */ jsxs2(PickerItem, { onSelect: () => onSelect(id), active, featured, children: [
459
+ /* @__PURE__ */ jsx2(ModelBrandStack, { identity, size: "md" }),
460
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
461
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
462
+ /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate text-sm font-medium", children: model.name ?? model.id }),
463
+ via && /* @__PURE__ */ jsxs2(PickerMenuTag, { children: [
403
464
  "via ",
404
465
  via
405
466
  ] })
406
467
  ] }),
407
- (ctx || pricing) && /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
408
- ctx && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: ctx }),
409
- ctx && pricing && /* @__PURE__ */ jsx("span", { className: "shrink-0 opacity-40", children: "\xB7" }),
410
- pricing && /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono", children: pricing })
411
- ] })
468
+ /* @__PURE__ */ jsx2(
469
+ PickerMenuMeta,
470
+ {
471
+ parts: [ctx, pricing ? /* @__PURE__ */ jsx2("span", { className: "font-mono", children: pricing }) : null]
472
+ }
473
+ )
412
474
  ] })
413
475
  ] });
414
476
  }
415
477
 
416
478
  export {
479
+ pickerMenuContentClass,
480
+ pickerMenuBodyClass,
481
+ pickerMenuItemClass,
482
+ PickerMenuSearch,
483
+ PickerMenuSection,
484
+ PickerMenuFooter,
485
+ PickerMenuMeta,
486
+ PickerMenuTag,
417
487
  modelDedupKey,
418
488
  dedupeModels,
419
489
  DEFAULT_FEATURED_MODEL_IDS,
@@ -5,11 +5,19 @@ import {
5
5
  HARNESS_OPTIONS,
6
6
  HarnessLogo,
7
7
  chatCapableHarnesses
8
- } from "./chunk-NF3Q4RJU.js";
8
+ } from "./chunk-WSN2RZC6.js";
9
9
  import {
10
10
  ModelPicker,
11
+ PickerMenuFooter,
12
+ PickerMenuMeta,
13
+ PickerMenuSearch,
14
+ PickerMenuSection,
15
+ PickerMenuTag,
16
+ pickerMenuBodyClass,
17
+ pickerMenuContentClass,
18
+ pickerMenuItemClass,
11
19
  stripBrandPrefix
12
- } from "./chunk-LAZZBSN4.js";
20
+ } from "./chunk-FWSBQLFG.js";
13
21
  import {
14
22
  ModelBrandStack,
15
23
  canonicalModelId,
@@ -488,14 +496,29 @@ function ProfileForm({
488
496
 
489
497
  // src/chat/agent-session-controls.tsx
490
498
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
491
- function selectorIgnoreNote(harness) {
492
- const ignoresModel = !harnessHonorsModel(harness);
493
- const ignoresEffort = !harnessHonorsEffort(harness);
494
- if (ignoresModel && ignoresEffort) return "Ignores model & effort";
495
- if (ignoresModel) return "Ignores model selection";
496
- if (ignoresEffort) return "Ignores reasoning effort";
499
+ function harnessAutonomyNote(harness) {
500
+ const ownModel = !harnessHonorsModel(harness);
501
+ const ownEffort = !harnessHonorsEffort(harness);
502
+ if (ownModel && ownEffort) return "own model + thinking";
503
+ if (ownModel) return "own model";
504
+ if (ownEffort) return "own thinking";
497
505
  return null;
498
506
  }
507
+ var HARNESS_GROUP_LABELS = {
508
+ steerable: "Uses your model & thinking",
509
+ fixed: "Brings its own setup",
510
+ "no-agent": "No agent"
511
+ };
512
+ var HARNESS_GROUP_ORDER = [
513
+ "steerable",
514
+ "fixed",
515
+ "no-agent"
516
+ ];
517
+ function harnessGroup(option) {
518
+ if (!option.chatCapable) return "no-agent";
519
+ return harnessAutonomyNote(option.type) === null ? "steerable" : "fixed";
520
+ }
521
+ var HARNESS_SEARCH_THRESHOLD = 7;
499
522
  function InformativeLock({
500
523
  type,
501
524
  label,
@@ -617,6 +640,29 @@ function HarnessDropdown({
617
640
  );
618
641
  const options = HARNESS_OPTIONS.filter((h) => allowed.has(h.type));
619
642
  const selected = options.find((option) => option.type === value);
643
+ const [query, setQuery] = React3.useState("");
644
+ const showSearch = options.length > HARNESS_SEARCH_THRESHOLD;
645
+ const filtered = React3.useMemo(() => {
646
+ const q = query.trim().toLowerCase();
647
+ if (!q) return options;
648
+ return options.filter(
649
+ (o) => o.label.toLowerCase().includes(q) || o.type.toLowerCase().includes(q) || (o.description?.toLowerCase() ?? "").includes(q)
650
+ );
651
+ }, [options, query]);
652
+ const sections = React3.useMemo(() => {
653
+ const byGroup = /* @__PURE__ */ new Map();
654
+ for (const option of filtered) {
655
+ const key = harnessGroup(option);
656
+ const bucket = byGroup.get(key);
657
+ if (bucket) bucket.push(option);
658
+ else byGroup.set(key, [option]);
659
+ }
660
+ return HARNESS_GROUP_ORDER.map((key) => ({
661
+ key,
662
+ label: HARNESS_GROUP_LABELS[key],
663
+ items: byGroup.get(key) ?? []
664
+ })).filter((section) => section.items.length > 0);
665
+ }, [filtered]);
620
666
  if (locked && onNewChat) {
621
667
  return /* @__PURE__ */ jsx3(
622
668
  InformativeLock,
@@ -653,7 +699,7 @@ function HarnessDropdown({
653
699
  ]
654
700
  }
655
701
  ) }),
656
- /* @__PURE__ */ jsx3(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx3(
702
+ /* @__PURE__ */ jsx3(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs3(
657
703
  DropdownMenu2.Content,
658
704
  {
659
705
  side,
@@ -661,39 +707,54 @@ function HarnessDropdown({
661
707
  avoidCollisions,
662
708
  collisionPadding: 24,
663
709
  sideOffset: 6,
664
- className: cn(
665
- // Cap to the viewport space on the open side and scroll, so a tall
666
- // backend list pinned downward (floating composer) never runs off
667
- // the bottom edge.
668
- "z-50 w-72 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-1",
669
- "shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
670
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
671
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
672
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
673
- ),
674
- children: options.map((option) => /* @__PURE__ */ jsxs3(
675
- DropdownMenu2.Item,
676
- {
677
- onSelect: (event) => {
678
- event.preventDefault();
679
- onChange(option.type);
710
+ className: pickerMenuContentClass,
711
+ children: [
712
+ showSearch && /* @__PURE__ */ jsx3(
713
+ PickerMenuSearch,
714
+ {
715
+ value: query,
716
+ onChange: setQuery,
717
+ placeholder: "Search agents..."
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsx3("div", { className: pickerMenuBodyClass, children: sections.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "No agents match." }) : sections.map((section) => /* @__PURE__ */ jsx3(PickerMenuSection, { label: section.label, children: section.items.map((option) => /* @__PURE__ */ jsxs3(
721
+ DropdownMenu2.Item,
722
+ {
723
+ onSelect: (event) => {
724
+ event.preventDefault();
725
+ onChange(option.type);
726
+ },
727
+ className: pickerMenuItemClass({
728
+ active: option.type === value
729
+ }),
730
+ children: [
731
+ /* @__PURE__ */ jsx3(
732
+ HarnessLogo,
733
+ {
734
+ type: option.type,
735
+ size: 20,
736
+ className: "mt-0.5"
737
+ }
738
+ ),
739
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
740
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
741
+ /* @__PURE__ */ jsx3("span", { className: "min-w-0 truncate text-sm font-medium", children: option.label }),
742
+ harnessAutonomyNote(option.type) && /* @__PURE__ */ jsx3(PickerMenuTag, { children: harnessAutonomyNote(option.type) })
743
+ ] }),
744
+ /* @__PURE__ */ jsx3(PickerMenuMeta, { parts: [option.description] })
745
+ ] })
746
+ ]
680
747
  },
681
- className: cn(
682
- "flex cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-2 outline-none",
683
- "transition-colors hover:bg-accent/50 focus:bg-accent/50",
684
- option.type === value && "bg-primary/10 font-medium text-foreground ring-1 ring-inset ring-primary/25 hover:bg-primary/15 focus:bg-primary/15"
685
- ),
686
- children: [
687
- /* @__PURE__ */ jsx3(HarnessLogo, { type: option.type, size: 20, className: "mt-0.5" }),
688
- /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
689
- /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: option.label }),
690
- option.description && /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", children: option.description }),
691
- selectorIgnoreNote(option.type) && /* @__PURE__ */ jsx3("span", { className: "mt-0.5 inline-flex w-fit items-center rounded border border-[var(--md3-outline-variant)] px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: selectorIgnoreNote(option.type) })
692
- ] })
693
- ]
694
- },
695
- option.type
696
- ))
748
+ option.type
749
+ )) }, section.key)) }),
750
+ showSearch && /* @__PURE__ */ jsxs3(PickerMenuFooter, { children: [
751
+ filtered.length,
752
+ " of ",
753
+ options.length,
754
+ " agent",
755
+ options.length === 1 ? "" : "s"
756
+ ] })
757
+ ]
697
758
  }
698
759
  ) })
699
760
  ] });
@@ -1056,7 +1056,7 @@ function SessionSidebar({
1056
1056
  className: cn("relative flex shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-low", className),
1057
1057
  style: { width: resizable ? resize.width : defaultWidth },
1058
1058
  children: [
1059
- /* @__PURE__ */ jsx7("div", { className: "flex shrink-0 items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-1.5", children: /* @__PURE__ */ jsxs7("div", { className: "flex w-full items-center justify-between gap-2", children: [
1059
+ /* @__PURE__ */ jsx7("div", { className: "flex h-14 shrink-0 items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-high px-3", children: /* @__PURE__ */ jsxs7("div", { className: "flex w-full items-center justify-between gap-2", children: [
1060
1060
  /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 items-center gap-2", children: [
1061
1061
  /* @__PURE__ */ jsx7("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary", children: /* @__PURE__ */ jsx7(MessageSquareText, { className: "h-3.5 w-3.5" }) }),
1062
1062
  /* @__PURE__ */ jsxs7("div", { className: "min-w-0", children: [
@@ -193,49 +193,49 @@ var HARNESS_OPTIONS = [
193
193
  {
194
194
  type: "opencode",
195
195
  label: "OpenCode",
196
- description: "Default agent \u2014 broad model support, deterministic streaming",
196
+ description: "Broad model support, deterministic streaming",
197
197
  chatCapable: true
198
198
  },
199
199
  {
200
200
  type: "claude-code",
201
201
  label: "Claude Code",
202
- description: "Native Claude skills and tools (requires ANTHROPIC_API_KEY)",
202
+ description: "Native Claude skills and tools",
203
203
  chatCapable: true
204
204
  },
205
205
  {
206
206
  type: "codex",
207
207
  label: "Codex",
208
- description: "OpenAI Codex CLI (requires OPENAI_API_KEY)",
208
+ description: "OpenAI's Codex CLI",
209
209
  chatCapable: true
210
210
  },
211
211
  {
212
212
  type: "amp",
213
213
  label: "AMP",
214
- description: "Sourcegraph AMP agent",
214
+ description: "Sourcegraph's coding agent",
215
215
  chatCapable: true
216
216
  },
217
217
  {
218
218
  type: "factory-droids",
219
219
  label: "Factory Droids",
220
- description: "Factory Droid agent",
220
+ description: "Factory's Droid agent",
221
221
  chatCapable: true
222
222
  },
223
223
  {
224
224
  type: "kimi-code",
225
225
  label: "Kimi Code",
226
- description: "Moonshot Kimi Code CLI (Kimi subscription OAuth or MOONSHOT_API_KEY)",
226
+ description: "Moonshot's Kimi Code CLI",
227
227
  chatCapable: true
228
228
  },
229
229
  {
230
230
  type: "openclaw",
231
231
  label: "OpenClaw",
232
- description: "Dispatcher routing to Claude / Codex / Gemini CLIs",
232
+ description: "Routes to Claude, Codex, or Gemini",
233
233
  chatCapable: true
234
234
  },
235
235
  {
236
236
  type: "nanoclaw",
237
237
  label: "NanoClaw",
238
- description: "Local socket-bridge agent backend",
238
+ description: "Local socket-bridge backend",
239
239
  chatCapable: true
240
240
  },
241
241
  {
@@ -247,7 +247,7 @@ var HARNESS_OPTIONS = [
247
247
  {
248
248
  type: "cli-base",
249
249
  label: "CLI base (no agent)",
250
- description: "Shell tools only \u2014 for non-AI scheduled tasks",
250
+ description: "Shell tools only, for non-AI scheduled tasks",
251
251
  chatCapable: false
252
252
  }
253
253
  ];
@@ -732,6 +732,13 @@ interface HarnessOption extends Backend {
732
732
  /**
733
733
  * Default option list with human-readable copy. Order is curated so the
734
734
  * recommended choice (`opencode`) appears first; `cli-base` (no agent) last.
735
+ *
736
+ * Descriptions name what the AGENT is, in one short line that fits the menu row
737
+ * without wrapping. They deliberately do NOT mention credentials: which keys a
738
+ * deployment supplies is a product decision, not a property of the harness, and
739
+ * an env-var name (`ANTHROPIC_API_KEY`) is developer text that has no place in a
740
+ * customer-facing menu. A product whose deployment really does require a
741
+ * user-supplied key says so through `optionsOverride`.
735
742
  */
736
743
  declare const HARNESS_OPTIONS: readonly HarnessOption[];
737
744
  /** Harness types that can drive an interactive chat session. */
package/dist/dashboard.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  HarnessLogo,
72
72
  HarnessPicker,
73
73
  chatCapableHarnesses
74
- } from "./chunk-NF3Q4RJU.js";
74
+ } from "./chunk-WSN2RZC6.js";
75
75
  import {
76
76
  DEFAULT_FEATURED_MODEL_IDS,
77
77
  ModelPicker,
@@ -79,7 +79,7 @@ import {
79
79
  formatContext,
80
80
  formatPricing,
81
81
  modelDedupKey
82
- } from "./chunk-LAZZBSN4.js";
82
+ } from "./chunk-FWSBQLFG.js";
83
83
  import {
84
84
  ModelBrandStack,
85
85
  canonicalModelId,
package/dist/globals.css CHANGED
@@ -948,6 +948,9 @@
948
948
  .min-h-\[300px\] {
949
949
  min-height: 300px;
950
950
  }
951
+ .min-h-\[560px\] {
952
+ min-height: 560px;
953
+ }
951
954
  .min-h-\[640px\] {
952
955
  min-height: 640px;
953
956
  }
@@ -1140,6 +1143,9 @@
1140
1143
  .w-\[680px\] {
1141
1144
  width: 680px;
1142
1145
  }
1146
+ .w-\[720px\] {
1147
+ width: 720px;
1148
+ }
1143
1149
  .w-\[760px\] {
1144
1150
  width: 760px;
1145
1151
  }
@@ -1176,9 +1182,6 @@
1176
1182
  .w-\[var\(--tool-icon-size\)\] {
1177
1183
  width: var(--tool-icon-size);
1178
1184
  }
1179
- .w-fit {
1180
- width: fit-content;
1181
- }
1182
1185
  .w-full {
1183
1186
  width: 100%;
1184
1187
  }
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ import {
91
91
  snapHarnessToModel,
92
92
  snapModelToHarness,
93
93
  validateComposerFiles
94
- } from "./chunk-UU77AFEU.js";
94
+ } from "./chunk-GHG4TCDS.js";
95
95
  import {
96
96
  TOOL_CATEGORY_ICONS,
97
97
  cn,
@@ -136,7 +136,7 @@ import {
136
136
  dispatchSessionCreated,
137
137
  shellShouldRevalidate,
138
138
  useOptimisticSessionItems
139
- } from "./chunk-2ISY4R72.js";
139
+ } from "./chunk-RPOPLGW7.js";
140
140
  import {
141
141
  BRAND_PRISM_THEME,
142
142
  CodeSurface,
@@ -323,7 +323,7 @@ import {
323
323
  HarnessLogo,
324
324
  HarnessPicker,
325
325
  chatCapableHarnesses
326
- } from "./chunk-NF3Q4RJU.js";
326
+ } from "./chunk-WSN2RZC6.js";
327
327
  import {
328
328
  DEFAULT_FEATURED_MODEL_IDS,
329
329
  ModelPicker,
@@ -331,7 +331,7 @@ import {
331
331
  formatContext,
332
332
  formatPricing,
333
333
  modelDedupKey
334
- } from "./chunk-LAZZBSN4.js";
334
+ } from "./chunk-FWSBQLFG.js";
335
335
  import {
336
336
  ModelBrandStack,
337
337
  canonicalModelId,
@@ -30,6 +30,11 @@ interface IntegrationProvider {
30
30
  description?: string;
31
31
  iconUrl?: string;
32
32
  connectors?: IntegrationConnector[];
33
+ /**
34
+ * True when the platform can currently receive this provider's events.
35
+ * Absent means the platform predates inbound-readiness discovery.
36
+ */
37
+ eventIngressConfigured?: boolean;
33
38
  }
34
39
  interface IntegrationHealth {
35
40
  connectionId: string;
@@ -268,7 +268,7 @@ function IntegrationsPanel({
268
268
  "data-connected": "true",
269
269
  title: live.accountDisplay ? `${name} \u2014 ${live.accountDisplay}` : name,
270
270
  className: cn(
271
- "group relative flex aspect-square flex-col items-center justify-center gap-2 rounded-xl border p-3 text-center",
271
+ "group relative flex min-w-0 aspect-square flex-col items-center justify-center gap-2 rounded-xl border p-3 text-center",
272
272
  "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]"
273
273
  ),
274
274
  children: [
@@ -284,7 +284,7 @@ function IntegrationsPanel({
284
284
  }
285
285
  ) : null,
286
286
  /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-2 top-2 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--surface-success-text)] text-white", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3", strokeWidth: 3 }) }),
287
- /* @__PURE__ */ jsxs("div", { className: "pointer-events-none flex flex-col items-center gap-2", children: [
287
+ /* @__PURE__ */ jsxs("div", { className: "pointer-events-none flex w-full min-w-0 flex-col items-center gap-2", children: [
288
288
  /* @__PURE__ */ jsx(
289
289
  ProviderIcon,
290
290
  {
@@ -396,7 +396,7 @@ function IntegrationsPanel({
396
396
  onClick: () => onConnect({ providerId: provider.providerId, connectorId }),
397
397
  title: provider.description ? provider.description : `Connect ${name}`,
398
398
  className: cn(
399
- "group flex aspect-square flex-col items-center justify-center gap-2 rounded-xl border border-border bg-card p-3 text-center transition-all",
399
+ "group flex min-w-0 aspect-square flex-col items-center justify-center gap-2 rounded-xl border border-border bg-card p-3 text-center transition-all",
400
400
  "hover:border-primary/40 hover:bg-accent/40 hover:shadow-sm focus:outline-none focus-visible:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary/20"
401
401
  ),
402
402
  children: [
package/dist/pages.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  Switch,
25
25
  Textarea
26
26
  } from "./chunk-4ZZUMXPX.js";
27
- import "./chunk-LAZZBSN4.js";
27
+ import "./chunk-FWSBQLFG.js";
28
28
  import "./chunk-5XRAKVIV.js";
29
29
  import {
30
30
  cn
package/dist/styles.css CHANGED
@@ -948,6 +948,9 @@
948
948
  .min-h-\[300px\] {
949
949
  min-height: 300px;
950
950
  }
951
+ .min-h-\[560px\] {
952
+ min-height: 560px;
953
+ }
951
954
  .min-h-\[640px\] {
952
955
  min-height: 640px;
953
956
  }
@@ -1140,6 +1143,9 @@
1140
1143
  .w-\[680px\] {
1141
1144
  width: 680px;
1142
1145
  }
1146
+ .w-\[720px\] {
1147
+ width: 720px;
1148
+ }
1143
1149
  .w-\[760px\] {
1144
1150
  width: 760px;
1145
1151
  }
@@ -1176,9 +1182,6 @@
1176
1182
  .w-\[var\(--tool-icon-size\)\] {
1177
1183
  width: var(--tool-icon-size);
1178
1184
  }
1179
- .w-fit {
1180
- width: fit-content;
1181
- }
1182
1185
  .w-full {
1183
1186
  width: 100%;
1184
1187
  }
package/dist/workbench.js CHANGED
@@ -19,8 +19,8 @@ import "./chunk-OMUIPJQZ.js";
19
19
  import "./chunk-WYMAC3T3.js";
20
20
  import "./chunk-KEYPUGS3.js";
21
21
  import "./chunk-4ZZUMXPX.js";
22
- import "./chunk-NF3Q4RJU.js";
23
- import "./chunk-LAZZBSN4.js";
22
+ import "./chunk-WSN2RZC6.js";
23
+ import "./chunk-FWSBQLFG.js";
24
24
  import "./chunk-5XRAKVIV.js";
25
25
  import "./chunk-EI44GEQ5.js";
26
26
  import "./chunk-CZ7CSFO4.js";
package/dist/workspace.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  dispatchSessionCreated,
20
20
  shellShouldRevalidate,
21
21
  useOptimisticSessionItems
22
- } from "./chunk-2ISY4R72.js";
22
+ } from "./chunk-RPOPLGW7.js";
23
23
  import "./chunk-EI44GEQ5.js";
24
24
  import "./chunk-CZ7CSFO4.js";
25
25
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.90.2",
3
+ "version": "0.91.0",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",