@tangle-network/sandbox-ui 0.79.0 → 0.81.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.
@@ -11,7 +11,12 @@ import {
11
11
  fmtTokens,
12
12
  providerLabel,
13
13
  shortModel
14
- } from "./chunk-UEMWLVVY.js";
14
+ } from "./chunk-2NS724P4.js";
15
+ import {
16
+ ModelBrandStack,
17
+ modelBrandFor
18
+ } from "./chunk-I3G23XHN.js";
19
+ import "./chunk-EI44GEQ5.js";
15
20
  import "./chunk-U67V476Y.js";
16
21
 
17
22
  // src/workflows/WorkflowGraph.tsx
@@ -341,16 +346,18 @@ var KIND_ICON = {
341
346
  function NodeMark({
342
347
  kind,
343
348
  provider,
349
+ model,
344
350
  accent,
345
351
  tile
346
352
  }) {
347
353
  const Icon = kind && KIND_ICON[kind] || Circle;
354
+ const brand = !provider && model ? modelBrandFor(model) : null;
348
355
  const mark = Math.round(tile * (provider ? 0.58 : 0.5));
349
356
  return /* @__PURE__ */ jsx2(
350
357
  "span",
351
358
  {
352
359
  "aria-hidden": "true",
353
- className: "flex shrink-0 items-center justify-center rounded-lg border border-border",
360
+ className: "flex shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border",
354
361
  style: {
355
362
  width: tile,
356
363
  height: tile,
@@ -364,6 +371,19 @@ function NodeMark({
364
371
  size: mark,
365
372
  className: "rounded-[3px]"
366
373
  }
374
+ ) : brand ? (
375
+ // One lab's own model draws ONE mark (28px at `md`) and fills the tile. A
376
+ // HOSTED model draws two — host behind, lab in front — and that stack is
377
+ // 36px wide, which would push the lab chip past the border of an expanded
378
+ // card's 34px tile. So the stacked pair steps down unless the tile can hold
379
+ // it; the single mark never has to.
380
+ /* @__PURE__ */ jsx2(
381
+ ModelBrandStack,
382
+ {
383
+ identity: brand,
384
+ size: brand.combined || tile >= 38 ? "md" : "sm"
385
+ }
386
+ )
367
387
  ) : /* @__PURE__ */ jsx2(Icon, { size: mark, strokeWidth: 1.75, style: { color: accent } })
368
388
  }
369
389
  );
@@ -373,6 +393,10 @@ function NodeMark({
373
393
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
374
394
  var MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
375
395
  var FIT_VIEW = { padding: 0.16, minZoom: 0.55, maxZoom: 1 };
396
+ function fitViewOnLayoutChange() {
397
+ const moves = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === false;
398
+ return moves ? { ...FIT_VIEW, duration: 220 } : FIT_VIEW;
399
+ }
376
400
  function useColorMode() {
377
401
  const [mode, setMode] = useState(
378
402
  () => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
@@ -475,6 +499,7 @@ function WorkflowNode({ data }) {
475
499
  const accent = TONE_ACCENT[d.tone];
476
500
  const isAgent = d.kind === "agent.run";
477
501
  const subtitle = isAgent && state?.model ? shortModel(state.model) : d.subtitle;
502
+ const markModel = isAgent && state?.model ? state.model : d.model;
478
503
  const runBands = state !== void 0 && d.tone !== "trigger";
479
504
  const duration = fmtDuration(state?.durationMs);
480
505
  const cost = fmtCost(state?.costUsd);
@@ -538,6 +563,7 @@ function WorkflowNode({ data }) {
538
563
  {
539
564
  kind: d.kind,
540
565
  provider: d.provider,
566
+ model: markModel,
541
567
  accent,
542
568
  tile: Math.round(COMPACT_TILE * 0.62)
543
569
  }
@@ -602,6 +628,7 @@ function WorkflowNode({ data }) {
602
628
  {
603
629
  kind: d.kind,
604
630
  provider: d.provider,
631
+ model: markModel,
605
632
  accent,
606
633
  tile: 34
607
634
  }
@@ -726,7 +753,7 @@ function WorkflowGraph({
726
753
  }
727
754
  const inst = rfRef.current;
728
755
  if (!inst || typeof requestAnimationFrame === "undefined") return;
729
- const raf = requestAnimationFrame(() => inst.fitView(FIT_VIEW));
756
+ const raf = requestAnimationFrame(() => inst.fitView(fitViewOnLayoutChange()));
730
757
  return () => cancelAnimationFrame(raf);
731
758
  }, [structural]);
732
759
  const handleNodeClick = useCallback(
@@ -801,5 +828,6 @@ export {
801
828
  DirectionContext,
802
829
  WorkflowGraph,
803
830
  WorkflowNode,
804
- buildStyledEdges
831
+ buildStyledEdges,
832
+ fitViewOnLayoutChange
805
833
  };
package/dist/chat.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ReasoningEffort, HarnessType } from '@tangle-network/agent-interface';
5
5
  export { modelProvider, snapHarnessToModel } from '@tangle-network/agent-interface';
6
6
  import * as Popover from '@radix-ui/react-dropdown-menu';
7
7
  import * as React from 'react';
8
- import { M as ModelInfo } from './model-picker-COjPVMzg.js';
8
+ import { b as ModelInfo } from './model-brand-CsFykzMA.js';
9
9
 
10
10
  /**
11
11
  * A reasoning selection: `auto` is a UI-only sentinel ("let the harness/model default decide",
@@ -269,6 +269,44 @@ interface AgentSessionControlsProps {
269
269
  */
270
270
  declare function AgentSessionControls({ harness, profile, model, reasoning, filterModelsToHarness, trailing, className, context, layout, menuPlacement, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
271
271
 
272
+ /** Limits applied when staging files onto a composer. */
273
+ interface ComposerFileValidationConfig {
274
+ /** Reject any single file larger than this. */
275
+ maxSizeBytes?: number;
276
+ /** Reject files once the staged count would exceed this. */
277
+ maxCount?: number;
278
+ /** Files already staged, counted against `maxCount` before this batch. */
279
+ currentCount?: number;
280
+ /**
281
+ * Comma-separated accept list, same grammar as the native `<input accept>`
282
+ * attribute: file extensions (`.png`), exact MIME types (`image/png`), and
283
+ * MIME wildcards (`image/*`).
284
+ */
285
+ accept?: string;
286
+ }
287
+ /** A file that failed validation, paired with a human-readable reason. */
288
+ interface ComposerFileRejection {
289
+ file: File;
290
+ reason: string;
291
+ }
292
+ /**
293
+ * Checks a single file against a comma-separated `accept` list using the same
294
+ * grammar as the native `<input accept>` attribute. An undefined or empty
295
+ * `accept` accepts everything.
296
+ */
297
+ declare function isAcceptedType(file: File, accept?: string): boolean;
298
+ /**
299
+ * Splits a batch of files into what's safe to stage and what's rejected,
300
+ * checking accept type, then size, then the running count against
301
+ * `currentCount` + `maxCount`. Each rejection carries a reason naming the
302
+ * file and the limit it broke. Pure data in, pure data out — no throwing,
303
+ * no console noise, so callers decide how to surface rejections.
304
+ */
305
+ declare function validateComposerFiles(files: File[] | FileList, config: ComposerFileValidationConfig): {
306
+ accepted: File[];
307
+ rejected: ComposerFileRejection[];
308
+ };
309
+
272
310
  /** A staged attachment shown as a chip above the input. */
273
311
  interface ComposerFile {
274
312
  id: string;
@@ -278,6 +316,14 @@ interface ComposerFile {
278
316
  /** File count for a folder chip. */
279
317
  fileCount?: number;
280
318
  status: "pending" | "uploading" | "ready" | "error";
319
+ /**
320
+ * Object URL for an image thumbnail. The app owns creation and revocation
321
+ * (e.g. `URL.createObjectURL` on stage, `URL.revokeObjectURL` on remove) —
322
+ * the composer only ever reads it.
323
+ */
324
+ previewUrl?: string;
325
+ /** Shown on the chip when `status === "error"`. */
326
+ errorMessage?: string;
281
327
  }
282
328
  interface AgentComposerProps {
283
329
  /** Composer text (controlled). */
@@ -324,9 +370,42 @@ interface AgentComposerProps {
324
370
  onAttachFolder?: (files: FileList) => void;
325
371
  attachments?: ReadonlyArray<ComposerFile>;
326
372
  onRemoveFile?: (id: string) => void;
373
+ /** When set, chips with `status === "error"` render a retry button. */
374
+ onRetryFile?: (id: string) => void;
375
+ /**
376
+ * File types the composer takes, in the native `<input accept>` grammar.
377
+ * Enforced on every ingress path — the picker dialog, drag-and-drop, and
378
+ * clipboard paste — so a type the picker won't offer can't arrive by another
379
+ * route. Non-matching files go to `onRejectFiles` and never reach
380
+ * `onAttach`. Folder attach is exempt (directory selection has no native
381
+ * accept semantics); size/count limits are the app's job in `onAttach`.
382
+ */
327
383
  accept?: string;
384
+ /**
385
+ * Called with the files `accept` filtered out of a drop/paste/pick, each
386
+ * with a human-readable reason. Without it rejection is silent — the same
387
+ * feedback the native picker gives for a type it won't offer.
388
+ */
389
+ onRejectFiles?: (rejections: ComposerFileRejection[]) => void;
328
390
  dropTitle?: string;
329
391
  dropDescription?: string;
392
+ /**
393
+ * `canSend` ignores `busy` so Enter/onSubmit keep firing while a turn is
394
+ * streaming. For composers that queue the next turn instead of blocking on
395
+ * the current one — the Stop-button rendering rule (`busy && onCancel`) is
396
+ * unaffected, so the button still flips to Stop while this is set. Default
397
+ * false.
398
+ */
399
+ canSubmitWhileBusy?: boolean;
400
+ /**
401
+ * Allow submitting with empty text when at least one attachment is staged
402
+ * (an image-only message). Counts attachments of ANY status — a gate on
403
+ * `ready` would make Enter silently dead while an upload is in flight,
404
+ * with no way for the app to explain why. The app's onSubmit owns the send
405
+ * policy (block, queue, or toast on pending/errored files) and what the
406
+ * message body becomes. Default false.
407
+ */
408
+ canSubmitAttachmentsOnly?: boolean;
330
409
  /** Cmd/Ctrl+L focuses the input and shows a hint. Default false. */
331
410
  focusShortcut?: boolean;
332
411
  /** Minimum textarea rows before it grows. Default 2. */
@@ -346,11 +425,13 @@ interface AgentComposerProps {
346
425
  * router-backed composer (no harness) and the sandbox-backed one (with harness).
347
426
  *
348
427
  * Opt-in extras make it the superset of every app's hand-rolled composer:
349
- * file/folder attachments with drag-and-drop + chips, a streaming Stop button
350
- * (`busy` + `onCancel`), a `controls` override slot for bespoke picker rows, and
351
- * a Cmd/Ctrl+L focus shortcut.
428
+ * file/folder attachments with drag-and-drop + chips + clipboard paste
429
+ * (`onAttach` also gates pasting files onto the textarea, auto-naming
430
+ * generic clipboard image blobs `pasted-image-<n>.<ext>`), a streaming Stop
431
+ * button (`busy` + `onCancel`), a `controls` override slot for bespoke
432
+ * picker rows, and a Cmd/Ctrl+L focus shortcut.
352
433
  */
353
- declare function AgentComposer({ value, onChange, onSubmit, placeholder, disabled, busy, onCancel, harness, profile, model, reasoning, context, controls, trailing, onAttach, onAttachFolder, attachments, onRemoveFile, accept, dropTitle, dropDescription, focusShortcut, minRows, maxHeight, className, sendLabel, autoFocus, }: AgentComposerProps): react_jsx_runtime.JSX.Element;
434
+ declare function AgentComposer({ value, onChange, onSubmit, placeholder, disabled, busy, onCancel, harness, profile, model, reasoning, context, controls, trailing, onAttach, onAttachFolder, attachments, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, canSubmitWhileBusy, canSubmitAttachmentsOnly, focusShortcut, minRows, maxHeight, className, sendLabel, autoFocus, }: AgentComposerProps): react_jsx_runtime.JSX.Element;
354
435
 
355
436
  /**
356
437
  * Whether `harness` can run `modelId`. Router-backed harnesses, and provider-less
@@ -464,4 +545,4 @@ declare function createFetchTransport(opts: {
464
545
  fetchImpl?: typeof fetch;
465
546
  }): ArtifactAgentDockTransport;
466
547
 
467
- export { AgentComposer, type AgentComposerProps, type AgentProfileCapability, type AgentProfileDraft, type AgentProfileOption, AgentProfilePicker, type AgentProfilePickerProps, AgentSessionControls, type AgentSessionControlsProps, type AgentSessionHarnessControl, type AgentSessionModelControl, type AgentSessionProfileControl, type AgentSessionReasoningControl, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, type ComposerFile, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport, isModelCompatibleWithHarness, snapModelToHarness };
548
+ export { AgentComposer, type AgentComposerProps, type AgentProfileCapability, type AgentProfileDraft, type AgentProfileOption, AgentProfilePicker, type AgentProfilePickerProps, AgentSessionControls, type AgentSessionControlsProps, type AgentSessionHarnessControl, type AgentSessionModelControl, type AgentSessionProfileControl, type AgentSessionReasoningControl, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, type ComposerFile, type ComposerFileRejection, type ComposerFileValidationConfig, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport, isAcceptedType, isModelCompatibleWithHarness, snapModelToHarness, validateComposerFiles };
package/dist/chat.js CHANGED
@@ -13,13 +13,17 @@ import {
13
13
  ThinkingIndicator,
14
14
  UserMessage,
15
15
  createFetchTransport,
16
+ isAcceptedType,
16
17
  isModelCompatibleWithHarness,
17
18
  modelProvider,
18
19
  snapHarnessToModel,
19
- snapModelToHarness
20
- } from "./chunk-UQXDIQWD.js";
21
- import "./chunk-IWIZ7RZE.js";
22
- import "./chunk-L62ZT6RS.js";
20
+ snapModelToHarness,
21
+ validateComposerFiles
22
+ } from "./chunk-YBFGWPUF.js";
23
+ import "./chunk-TK46XFLM.js";
24
+ import "./chunk-2T6KBMLN.js";
25
+ import "./chunk-R4AR3TKR.js";
26
+ import "./chunk-I3G23XHN.js";
23
27
  import "./chunk-EI44GEQ5.js";
24
28
  import "./chunk-U67V476Y.js";
25
29
  export {
@@ -37,8 +41,10 @@ export {
37
41
  ThinkingIndicator,
38
42
  UserMessage,
39
43
  createFetchTransport,
44
+ isAcceptedType,
40
45
  isModelCompatibleWithHarness,
41
46
  modelProvider,
42
47
  snapHarnessToModel,
43
- snapModelToHarness
48
+ snapModelToHarness,
49
+ validateComposerFiles
44
50
  };
@@ -279,9 +279,11 @@ var CONTROL_FLOW = new Set(CONTROL_FLOW_KEYS);
279
279
  function actionKind(rec) {
280
280
  return Object.keys(rec).find((k) => !CONTROL_FLOW.has(k));
281
281
  }
282
+ var MINTED_PROFILE_ID = /^ap_[A-Za-z0-9_-]{16}$/;
282
283
  function agentTitle(profile) {
283
284
  const named = str(profile);
284
- return named ? humanizeIdentifier(named) : "AI Agent";
285
+ if (!named || MINTED_PROFILE_ID.test(named)) return "AI Agent";
286
+ return humanizeIdentifier(named);
285
287
  }
286
288
  function describeActionBase(action) {
287
289
  const rec = asRecord(action);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  moonshot_default
3
- } from "./chunk-L62ZT6RS.js";
3
+ } from "./chunk-I3G23XHN.js";
4
4
  import {
5
5
  cn
6
6
  } from "./chunk-EI44GEQ5.js";