@tangle-network/sandbox-ui 0.95.1 → 0.97.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.
@@ -515,6 +515,29 @@ function describeAction(action) {
515
515
  }
516
516
  return Object.keys(config).length > 0 ? { ...base, config } : base;
517
517
  }
518
+ function describeScope(scope) {
519
+ const rec = asRecord(scope);
520
+ const parts = [];
521
+ for (const [dimension, raw] of Object.entries(rec).sort(
522
+ ([a], [b]) => a < b ? -1 : a > b ? 1 : 0
523
+ )) {
524
+ const selection = asRecord(raw);
525
+ const except = Array.isArray(selection.except) ? [
526
+ ...new Set(
527
+ selection.except.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry !== "")
528
+ )
529
+ ] : [];
530
+ if (except.length === 0) {
531
+ if (selection.default === "exclude") parts.push(`${dimension}: none`);
532
+ continue;
533
+ }
534
+ const named = except.length <= 2 ? except.join(", ") : `${except.length} selected`;
535
+ parts.push(
536
+ selection.default === "exclude" ? `${dimension}: ${named}` : `${dimension}: all but ${named}`
537
+ );
538
+ }
539
+ return parts.length > 0 ? parts.join(" \xB7 ") : void 0;
540
+ }
518
541
  function describeTrigger(on) {
519
542
  const rec = asRecord(on);
520
543
  if (rec.provider_event) {
@@ -524,7 +547,11 @@ function describeTrigger(on) {
524
547
  const actions = Array.isArray(ev.actions) ? ev.actions.filter((a) => typeof a === "string") : [];
525
548
  const repo = str(ev.repo);
526
549
  const description = describeText(
527
- [actions.length > 0 ? actions.join(", ") : void 0, repo].filter(Boolean).join(" \xB7 ")
550
+ [
551
+ actions.length > 0 ? actions.join(", ") : void 0,
552
+ repo,
553
+ describeScope(ev.scope)
554
+ ].filter(Boolean).join(" \xB7 ")
528
555
  );
529
556
  return withConfig(
530
557
  {
@@ -133,12 +133,30 @@ function dedupeModels(models) {
133
133
  return result;
134
134
  }
135
135
  var DEFAULT_FEATURED_MODEL_IDS = [
136
- "anthropic/claude-opus-4-8",
137
- "anthropic/claude-sonnet-4-6",
138
- "openai/gpt-5.4",
139
- "google/gemini-2.5-pro",
140
- "deepseek/deepseek-v3"
136
+ "anthropic/claude-fable-5",
137
+ "anthropic/claude-opus-5",
138
+ "openai/gpt-5.6-sol",
139
+ "google/gemini-3.1-pro-preview",
140
+ "deepseek/deepseek-v4-flash"
141
141
  ];
142
+ function isTextChatModel(model) {
143
+ const arch = model.architecture;
144
+ if (!arch) return true;
145
+ const inputs = arch.input_modalities;
146
+ const outputs = arch.output_modalities;
147
+ if (inputs?.length || outputs?.length) {
148
+ const inOk = !inputs?.length || inputs.includes("text");
149
+ const outOk = !outputs?.length || outputs.includes("text");
150
+ return inOk && outOk;
151
+ }
152
+ const modality = arch.modality;
153
+ if (!modality) return true;
154
+ const arrow = modality.split("->");
155
+ if (arrow.length === 2) {
156
+ return arrow[0].split("+").includes("text") && arrow[1].split("+").includes("text");
157
+ }
158
+ return modality === "text" || modality === "multimodal";
159
+ }
142
160
  function formatPricing(pricing) {
143
161
  const prompt = Number(pricing?.prompt ?? 0);
144
162
  const completion = Number(pricing?.completion ?? 0);
@@ -226,31 +244,31 @@ function ModelPicker({
226
244
  return () => cancelAnimationFrame(frame);
227
245
  }, [open]);
228
246
  const deduped = React2.useMemo(() => dedupeModels(models), [models]);
229
- const filtered = React2.useMemo(() => {
247
+ const passesCatalogFilters = React2.useMemo(() => {
230
248
  const excluded = new Set((excludeProviders ?? []).map((p) => p.toLowerCase()));
231
249
  const allowedModalities = modalities ? new Set(modalities) : null;
232
- const q = query.trim().toLowerCase();
233
- return deduped.filter((m) => {
250
+ return (m) => {
234
251
  const provider = (m._provider ?? m.provider ?? "").toLowerCase();
235
252
  if (excluded.has(provider)) return false;
236
253
  if (allowedModalities && m.architecture?.modality && !allowedModalities.has(m.architecture.modality)) return false;
237
- if (!q) return true;
254
+ return true;
255
+ };
256
+ }, [modalities, excludeProviders]);
257
+ const eligible = React2.useMemo(
258
+ () => deduped.filter(passesCatalogFilters),
259
+ [deduped, passesCatalogFilters]
260
+ );
261
+ const filtered = React2.useMemo(() => {
262
+ const q = query.trim().toLowerCase();
263
+ if (!q) return eligible;
264
+ return eligible.filter((m) => {
265
+ const provider = (m._provider ?? m.provider ?? "").toLowerCase();
238
266
  const id = canonicalModelId(m).toLowerCase();
239
267
  const name = (m.name ?? "").toLowerCase();
240
268
  const identity = resolveModelBrandIdentity(m);
241
269
  return id.includes(q) || name.includes(q) || provider.includes(q) || identity.host.label.toLowerCase().includes(q) || identity.lab.label.toLowerCase().includes(q);
242
270
  });
243
- }, [deduped, query, modalities, excludeProviders]);
244
- const grouped = React2.useMemo(() => {
245
- const groups = /* @__PURE__ */ new Map();
246
- for (const m of filtered) {
247
- const key = modelGroupKey(m);
248
- const list = groups.get(key);
249
- if (list) list.push(m);
250
- else groups.set(key, [m]);
251
- }
252
- return Array.from(groups.entries()).map(([key, list]) => [key, [...list].sort(compareModelsByDisplayName)]).sort(([a], [b]) => compareModelGroups(a, b));
253
- }, [filtered]);
271
+ }, [eligible, query]);
254
272
  const current = React2.useMemo(
255
273
  () => models.find((m) => canonicalModelId(m) === value),
256
274
  [models, value]
@@ -263,25 +281,41 @@ function ModelPicker({
263
281
  const recentIds = React2.useMemo(() => {
264
282
  if (!recents?.length) return [];
265
283
  const lookup = new Map(models.map((m) => [canonicalModelId(m), m]));
266
- return recents.map((id) => lookup.get(id)).filter((m) => Boolean(m)).slice(0, 4);
267
- }, [recents, models]);
284
+ return recents.map((id) => lookup.get(id)).filter((m) => Boolean(m)).filter(passesCatalogFilters).slice(0, 4);
285
+ }, [recents, models, passesCatalogFilters]);
268
286
  const popularModels = React2.useMemo(() => {
269
287
  if (!popular?.length) return [];
270
288
  const lookup = new Map(models.map((m) => [canonicalModelId(m), m]));
271
- return popular.map((id) => lookup.get(id)).filter((m) => Boolean(m));
272
- }, [popular, models]);
289
+ return popular.map((id) => lookup.get(id)).filter((m) => Boolean(m)).filter(passesCatalogFilters);
290
+ }, [popular, models, passesCatalogFilters]);
273
291
  const featuredModels = React2.useMemo(() => {
274
- const flagged = deduped.filter((m) => m.featured);
292
+ const flagged = eligible.filter((m) => m.featured);
275
293
  if (flagged.length > 0) return flagged;
276
294
  const seedKeys = new Set(
277
295
  DEFAULT_FEATURED_MODEL_IDS.map((id) => modelDedupKey({ id }))
278
296
  );
279
- const seeded = deduped.filter((m) => seedKeys.has(modelDedupKey(m)));
297
+ const seeded = eligible.filter((m) => seedKeys.has(modelDedupKey(m)));
280
298
  const order = DEFAULT_FEATURED_MODEL_IDS.map((id) => modelDedupKey({ id }));
281
299
  return seeded.sort(
282
300
  (a, b) => order.indexOf(modelDedupKey(a)) - order.indexOf(modelDedupKey(b))
283
301
  );
284
- }, [deduped]);
302
+ }, [eligible]);
303
+ const grouped = React2.useMemo(() => {
304
+ const curatedKeys = new Set(
305
+ [...featuredModels, ...popularModels, ...recentIds].map(
306
+ (m) => modelDedupKey(m)
307
+ )
308
+ );
309
+ const groups = /* @__PURE__ */ new Map();
310
+ for (const m of filtered) {
311
+ if (!query.trim() && curatedKeys.has(modelDedupKey(m))) continue;
312
+ const key = modelGroupKey(m);
313
+ const list = groups.get(key);
314
+ if (list) list.push(m);
315
+ else groups.set(key, [m]);
316
+ }
317
+ return Array.from(groups.entries()).map(([key, list]) => [key, [...list].sort(compareModelsByDisplayName)]).sort(([a], [b]) => compareModelGroups(a, b));
318
+ }, [filtered, featuredModels, popularModels, recentIds, query]);
285
319
  const handleSelect = (id) => {
286
320
  onChange(id);
287
321
  setOpen(false);
@@ -295,8 +329,8 @@ function ModelPicker({
295
329
  "aria-label": triggerLabel,
296
330
  disabled,
297
331
  className: cn(
298
- "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container",
299
- "px-2.5 text-xs font-medium text-foreground shadow-sm",
332
+ "inline-flex h-9 items-center gap-1.5 rounded-full border border-[var(--md3-outline-variant)] bg-surface-container",
333
+ "px-3 text-sm font-medium text-foreground shadow-sm",
300
334
  "transition-colors duration-[var(--transition-fast)]",
301
335
  "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high",
302
336
  "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
@@ -306,9 +340,9 @@ function ModelPicker({
306
340
  triggerClassName
307
341
  ),
308
342
  children: [
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" })
343
+ currentIdentity ? /* @__PURE__ */ jsx2(ModelBrandStack, { identity: currentIdentity, size: "sm" }) : /* @__PURE__ */ jsx2(Sparkles, { className: "h-3.5 w-3.5 text-muted-foreground" }),
344
+ /* @__PURE__ */ jsx2("span", { className: "truncate max-w-[180px]", children: currentLabel || placeholder }),
345
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "ml-auto h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" })
312
346
  ]
313
347
  }
314
348
  ) : /* @__PURE__ */ jsxs2(
@@ -404,9 +438,9 @@ function ModelPicker({
404
438
  /* @__PURE__ */ jsxs2(PickerMenuFooter, { children: [
405
439
  filtered.length,
406
440
  " of ",
407
- deduped.length,
441
+ eligible.length,
408
442
  " model",
409
- deduped.length === 1 ? "" : "s"
443
+ eligible.length === 1 ? "" : "s"
410
444
  ] })
411
445
  ]
412
446
  }
@@ -487,6 +521,7 @@ export {
487
521
  modelDedupKey,
488
522
  dedupeModels,
489
523
  DEFAULT_FEATURED_MODEL_IDS,
524
+ isTextChatModel,
490
525
  formatPricing,
491
526
  formatContext,
492
527
  stripBrandPrefix,
package/dist/dashboard.js CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  formatContext,
80
80
  formatPricing,
81
81
  modelDedupKey
82
- } from "./chunk-FWSBQLFG.js";
82
+ } from "./chunk-WMVUJ5SA.js";
83
83
  import {
84
84
  ModelBrandStack,
85
85
  canonicalModelId,
package/dist/globals.css CHANGED
@@ -409,9 +409,6 @@
409
409
  .bottom-\[-0\.75rem\] {
410
410
  bottom: -0.75rem;
411
411
  }
412
- .bottom-full {
413
- bottom: 100%;
414
- }
415
412
  .left-0 {
416
413
  left: 0px;
417
414
  }
@@ -1150,9 +1147,6 @@
1150
1147
  .w-\[300px\] {
1151
1148
  width: 300px;
1152
1149
  }
1153
- .w-\[320px\] {
1154
- width: 320px;
1155
- }
1156
1150
  .w-\[360px\] {
1157
1151
  width: 360px;
1158
1152
  }
@@ -1195,6 +1189,9 @@
1195
1189
  .w-\[min\(88vw\,24rem\)\] {
1196
1190
  width: min(88vw, 24rem);
1197
1191
  }
1192
+ .w-\[min\(320px\,calc\(100vw-2rem\)\)\] {
1193
+ width: min(320px, calc(100vw - 2rem));
1194
+ }
1198
1195
  .w-\[var\(--avatar-size\)\] {
1199
1196
  width: var(--avatar-size);
1200
1197
  }
@@ -1276,8 +1273,8 @@
1276
1273
  .max-w-\[150px\] {
1277
1274
  max-width: 150px;
1278
1275
  }
1279
- .max-w-\[160px\] {
1280
- max-width: 160px;
1276
+ .max-w-\[180px\] {
1277
+ max-width: 180px;
1281
1278
  }
1282
1279
  .max-w-\[250px\] {
1283
1280
  max-width: 250px;
@@ -3441,6 +3438,9 @@
3441
3438
  .capitalize {
3442
3439
  text-transform: capitalize;
3443
3440
  }
3441
+ .lowercase {
3442
+ text-transform: lowercase;
3443
+ }
3444
3444
  .normal-case {
3445
3445
  text-transform: none;
3446
3446
  }
@@ -3954,6 +3954,9 @@
3954
3954
  border-color: color-mix(in oklab, var(--color-primary) 50%, transparent);
3955
3955
  }
3956
3956
  }
3957
+ .hover\:border-transparent:hover {
3958
+ border-color: transparent;
3959
+ }
3957
3960
  .hover\:\!bg-primary:hover {
3958
3961
  background-color: var(--color-primary) !important;
3959
3962
  }
@@ -4538,6 +4541,9 @@
4538
4541
  .data-\[state\=open\]\:border-\[var\(--md3-outline-variant\)\][data-state="open"] {
4539
4542
  border-color: var(--md3-outline-variant);
4540
4543
  }
4544
+ .data-\[state\=open\]\:border-transparent[data-state="open"] {
4545
+ border-color: transparent;
4546
+ }
4541
4547
  .data-\[state\=open\]\:bg-\[var\(--accent-surface-strong\)\][data-state="open"] {
4542
4548
  background-color: var(--accent-surface-strong);
4543
4549
  }
@@ -4559,6 +4565,9 @@
4559
4565
  .data-\[state\=open\]\:bg-surface-container-high[data-state="open"] {
4560
4566
  background-color: var(--color-surface-container-high);
4561
4567
  }
4568
+ .data-\[state\=open\]\:text-foreground[data-state="open"] {
4569
+ color: var(--color-foreground);
4570
+ }
4562
4571
  .data-\[state\=open\]\:text-muted-foreground[data-state="open"] {
4563
4572
  color: var(--color-muted-foreground);
4564
4573
  }
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ import {
91
91
  snapHarnessToModel,
92
92
  snapModelToHarness,
93
93
  validateComposerFiles
94
- } from "./chunk-GUULFESL.js";
94
+ } from "./chunk-A4AABEX4.js";
95
95
  import {
96
96
  TOOL_CATEGORY_ICONS,
97
97
  cn,
@@ -332,7 +332,7 @@ import {
332
332
  formatContext,
333
333
  formatPricing,
334
334
  modelDedupKey
335
- } from "./chunk-FWSBQLFG.js";
335
+ } from "./chunk-WMVUJ5SA.js";
336
336
  import {
337
337
  ModelBrandStack,
338
338
  canonicalModelId,
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-FWSBQLFG.js";
27
+ import "./chunk-WMVUJ5SA.js";
28
28
  import "./chunk-5XRAKVIV.js";
29
29
  import {
30
30
  cn
package/dist/styles.css CHANGED
@@ -409,9 +409,6 @@
409
409
  .bottom-\[-0\.75rem\] {
410
410
  bottom: -0.75rem;
411
411
  }
412
- .bottom-full {
413
- bottom: 100%;
414
- }
415
412
  .left-0 {
416
413
  left: 0px;
417
414
  }
@@ -1150,9 +1147,6 @@
1150
1147
  .w-\[300px\] {
1151
1148
  width: 300px;
1152
1149
  }
1153
- .w-\[320px\] {
1154
- width: 320px;
1155
- }
1156
1150
  .w-\[360px\] {
1157
1151
  width: 360px;
1158
1152
  }
@@ -1195,6 +1189,9 @@
1195
1189
  .w-\[min\(88vw\,24rem\)\] {
1196
1190
  width: min(88vw, 24rem);
1197
1191
  }
1192
+ .w-\[min\(320px\,calc\(100vw-2rem\)\)\] {
1193
+ width: min(320px, calc(100vw - 2rem));
1194
+ }
1198
1195
  .w-\[var\(--avatar-size\)\] {
1199
1196
  width: var(--avatar-size);
1200
1197
  }
@@ -1276,8 +1273,8 @@
1276
1273
  .max-w-\[150px\] {
1277
1274
  max-width: 150px;
1278
1275
  }
1279
- .max-w-\[160px\] {
1280
- max-width: 160px;
1276
+ .max-w-\[180px\] {
1277
+ max-width: 180px;
1281
1278
  }
1282
1279
  .max-w-\[250px\] {
1283
1280
  max-width: 250px;
@@ -3441,6 +3438,9 @@
3441
3438
  .capitalize {
3442
3439
  text-transform: capitalize;
3443
3440
  }
3441
+ .lowercase {
3442
+ text-transform: lowercase;
3443
+ }
3444
3444
  .normal-case {
3445
3445
  text-transform: none;
3446
3446
  }
@@ -3954,6 +3954,9 @@
3954
3954
  border-color: color-mix(in oklab, var(--color-primary) 50%, transparent);
3955
3955
  }
3956
3956
  }
3957
+ .hover\:border-transparent:hover {
3958
+ border-color: transparent;
3959
+ }
3957
3960
  .hover\:\!bg-primary:hover {
3958
3961
  background-color: var(--color-primary) !important;
3959
3962
  }
@@ -4538,6 +4541,9 @@
4538
4541
  .data-\[state\=open\]\:border-\[var\(--md3-outline-variant\)\][data-state="open"] {
4539
4542
  border-color: var(--md3-outline-variant);
4540
4543
  }
4544
+ .data-\[state\=open\]\:border-transparent[data-state="open"] {
4545
+ border-color: transparent;
4546
+ }
4541
4547
  .data-\[state\=open\]\:bg-\[var\(--accent-surface-strong\)\][data-state="open"] {
4542
4548
  background-color: var(--accent-surface-strong);
4543
4549
  }
@@ -4559,6 +4565,9 @@
4559
4565
  .data-\[state\=open\]\:bg-surface-container-high[data-state="open"] {
4560
4566
  background-color: var(--color-surface-container-high);
4561
4567
  }
4568
+ .data-\[state\=open\]\:text-foreground[data-state="open"] {
4569
+ color: var(--color-foreground);
4570
+ }
4562
4571
  .data-\[state\=open\]\:text-muted-foreground[data-state="open"] {
4563
4572
  color: var(--color-muted-foreground);
4564
4573
  }
package/dist/workbench.js CHANGED
@@ -20,7 +20,7 @@ import "./chunk-WYMAC3T3.js";
20
20
  import "./chunk-KEYPUGS3.js";
21
21
  import "./chunk-4ZZUMXPX.js";
22
22
  import "./chunk-WSN2RZC6.js";
23
- import "./chunk-FWSBQLFG.js";
23
+ import "./chunk-WMVUJ5SA.js";
24
24
  import "./chunk-5XRAKVIV.js";
25
25
  import "./chunk-EI44GEQ5.js";
26
26
  import "./chunk-CZ7CSFO4.js";
package/dist/workflows.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  buildWorkflowGraph,
6
6
  triggerNodeId,
7
7
  triggerNodeIndex
8
- } from "./chunk-O7MHUR4U.js";
8
+ } from "./chunk-LHUBJPZR.js";
9
9
  import {
10
10
  ModelBrandStack,
11
11
  modelBrandFor
@@ -36,7 +36,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
36
36
  function lazyWorkflowGraph() {
37
37
  return lazy(
38
38
  () => retryImport(
39
- () => import("./WorkflowGraph-MPNVC5FE.js").then((m) => ({ default: m.WorkflowGraph }))
39
+ () => import("./WorkflowGraph-OPLQFAJK.js").then((m) => ({ default: m.WorkflowGraph }))
40
40
  )
41
41
  );
42
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.95.1",
3
+ "version": "0.97.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",