@theoremjs/studio 0.3.4-next.484.g20d8b9d → 0.3.4-next.522.g9e684cd

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.
Files changed (95) hide show
  1. package/README.md +3 -3
  2. package/compile.d.ts +1 -1
  3. package/compile.js +44 -2
  4. package/draft.d.ts +55 -2
  5. package/draft.js +14 -0
  6. package/example.js +5 -2
  7. package/map-line.d.ts +12 -0
  8. package/map-line.js +98 -0
  9. package/map.d.ts +1 -1
  10. package/map.js +0 -1
  11. package/mod.d.ts +1 -1
  12. package/mod.js +1 -1
  13. package/package.json +4 -4
  14. package/page/assets/{Banner-G4u0gVOw.js → Banner-UZW6WFTG.js} +2 -2
  15. package/page/assets/{ConsoleFrame-BZqxUndX.js → ConsoleFrame-C6ZxuLse.js} +3 -3
  16. package/page/assets/{ContextMenu-DEnGmVYR.js → ContextMenu-ClIUDzvK.js} +2 -2
  17. package/page/assets/{ToolResultCharts-BWBYDar4.js → ToolResultCharts-COgqvWEu.js} +1 -1
  18. package/page/assets/{TraceInspector-B0cUdNg5.js → TraceInspector-D2unePaa.js} +1 -1
  19. package/page/assets/{TraceOverview-Bjqg7laD.js → TraceOverview-CCA1iUNg.js} +2 -2
  20. package/page/assets/{_virtual_studio-type-sources-DBS0HldZ.js → _virtual_studio-type-sources-DfyVGpe8.js} +93 -61
  21. package/page/assets/browser-engine-CRGnAh9x.js +11 -0
  22. package/page/assets/browser-transport-CgmN-3Ia.js +47 -0
  23. package/page/assets/{compile-egress-BSxOdX4K.js → compile-egress-BRGW9qfO.js} +2 -2
  24. package/page/assets/{crypto-iT_LZ8Et.js → crypto-BoqeX_71.js} +1 -1
  25. package/page/assets/{egress-automata-B5ZGE1mT.js → egress-automata-SiHQTP1i.js} +1 -1
  26. package/page/assets/{encode-files-8AFMw7Er.js → encode-files-DFrbgXms.js} +1 -1
  27. package/page/assets/{export-agent-Cxamh6fg.js → export-agent-U8qRBuhg.js} +3 -3
  28. package/page/assets/{guardrail-tester-Lubrp5dY.js → guardrail-tester-DASanQyo.js} +2 -2
  29. package/page/assets/host-patterns-cSf1U25G.js +1 -0
  30. package/page/assets/index-C9rhrCGT.css +1 -0
  31. package/page/assets/index-CGN6iWF2.js +101 -0
  32. package/page/assets/{labels-provider-BSiD6Lrb.js → labels-provider-Gus3heEH.js} +1 -1
  33. package/page/assets/{live-ingress-lDUGPwth.js → live-ingress-qi-y5uvH.js} +1 -1
  34. package/page/assets/{live-messages-BwMauTVi.js → live-messages-cl-CI8gZ.js} +1 -1
  35. package/page/assets/reader-IkImpzZF.js +7 -0
  36. package/page/assets/shaped-data-CITXsEfw.js +21 -0
  37. package/page/assets/{stub-_DAX3TnJ.js → stub-Ccyi1ZiM.js} +2 -2
  38. package/page/assets/studio-code-pane-AoMu0cGy.js +8 -0
  39. package/page/assets/{studio-console-C-rWlbSY.js → studio-console-JNYkyQ0E.js} +1 -1
  40. package/page/assets/{studio-decision-CCpYdeOV.js → studio-decision-BRJuvgS3.js} +1 -1
  41. package/page/assets/{studio-embedding-BM1yTK8B.js → studio-embedding-D-r2kuz3.js} +1 -1
  42. package/page/assets/studio-host-console-HoLSpRVk.js +2 -0
  43. package/page/assets/{studio-labels-CBBymwHa.js → studio-labels-CBcjlfH4.js} +1 -1
  44. package/page/assets/studio-live-DtywUIK2.js +5 -0
  45. package/page/assets/studio-map-BIJ-S7br.js +1 -0
  46. package/page/assets/studio-trace-replay-BuOKQwlj.js +2 -0
  47. package/page/assets/{studio-type-checker-B4qvn-WC.js → studio-type-checker-C8qG2W9D.js} +1 -1
  48. package/page/assets/tool-schema-DDkOMTMz.js +17 -0
  49. package/page/assets/{trace-record-D4xAUHLZ.js → trace-record-Dqvj9bhx.js} +1 -1
  50. package/page/assets/tree-jmLWUWxQ.js +3 -0
  51. package/page/assets/{turn-client-D_bUUpER.js → turn-client-DSatougR.js} +2 -2
  52. package/page/assets/types-4jMDDudJ.js +1 -0
  53. package/page/assets/{use-one-call-DWpPEFdQ.js → use-one-call-BcTU9gJ9.js} +1 -1
  54. package/page/assets/workspace-B2bnm5qv.js +1 -0
  55. package/page/index.html +19 -19
  56. package/read-source.js +10 -1
  57. package/registrations.d.ts +6 -0
  58. package/run-payload.d.ts +1 -1
  59. package/runtime.d.ts +1 -1
  60. package/runtime.js +1 -1
  61. package/server/draft-decide.d.ts +1 -4
  62. package/server/draft-decide.js +1 -1
  63. package/server/draft-replies.js +2 -2
  64. package/source.js +1 -1
  65. package/tools.js +3 -0
  66. package/types.d.ts +6 -0
  67. package/ui/brand-icons.d.ts +2 -2
  68. package/ui/brand-icons.js +6 -4
  69. package/ui/lib/tree-map-layout.d.ts +120 -18
  70. package/ui/lib/tree-map-layout.js +1292 -190
  71. package/ui/map-token.css +25 -0
  72. package/ui/node-icon.js +1 -4
  73. package/ui/profile-editor.js +22 -6
  74. package/ui/read-map.css +1 -24
  75. package/ui/read-map.js +52 -4
  76. package/ui/studio-map.js +1 -3
  77. package/ui/tree-map.css +9 -0
  78. package/ui/tree-map.d.ts +27 -6
  79. package/ui/tree-map.js +215 -75
  80. package/page/assets/browser-engine-CaPsakbj.js +0 -11
  81. package/page/assets/browser-transport-BEha0XY8.js +0 -47
  82. package/page/assets/host-patterns-C9RMP16Q.js +0 -1
  83. package/page/assets/index-C3n9ogf7.js +0 -101
  84. package/page/assets/index-Cbs7B46A.css +0 -1
  85. package/page/assets/reader-oVJF5Iqk.js +0 -7
  86. package/page/assets/shaped-data-Bc6u4pow.js +0 -16
  87. package/page/assets/studio-code-pane-BqHsqd7P.js +0 -8
  88. package/page/assets/studio-host-console-D8_OCbZG.js +0 -2
  89. package/page/assets/studio-live-CgYci_ts.js +0 -5
  90. package/page/assets/studio-map-QDBMJ6x3.js +0 -1
  91. package/page/assets/studio-trace-replay-BIRuiVjZ.js +0 -2
  92. package/page/assets/tool-schema-CvHbJyis.js +0 -18
  93. package/page/assets/tree-DtF83caH.js +0 -3
  94. package/page/assets/types-Bc3MMW0P.js +0 -1
  95. package/page/assets/workspace-ILHOuESH.js +0 -1
package/ui/map-token.css CHANGED
@@ -9,3 +9,28 @@
9
9
  stroke: var(--color-border-emphasized);
10
10
  stroke-width: 1.5;
11
11
  }
12
+
13
+ /* A line drawing in runs out from the node it leaves, as the loader's map draws: its own pace, not
14
+ the scroll's. */
15
+ .map-line-drawing {
16
+ stroke-dasharray: 1;
17
+ stroke-dashoffset: 1;
18
+ animation: map-line-draw var(--duration-medium, 240ms) var(--ease-standard, ease-out) forwards;
19
+ }
20
+
21
+ /* A token drawing in shows over the same span. */
22
+ .map-token-drawing {
23
+ animation: map-draw-in var(--duration-medium, 240ms) var(--ease-standard, ease-out) both;
24
+ }
25
+
26
+ @keyframes map-line-draw {
27
+ to {
28
+ stroke-dashoffset: 0;
29
+ }
30
+ }
31
+
32
+ @keyframes map-draw-in {
33
+ from {
34
+ opacity: 0;
35
+ }
36
+ }
package/ui/node-icon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { IconTool } from '@tabler/icons-react';
2
- import { IconTypeSafe, modelFamilyMark } from "./brand-icons.js";
2
+ import { modelFamilyMark } from "./brand-icons.js";
3
3
  import { FACET_ICON } from "./lib/facet-icons.js";
4
4
  import { PROFILE_TYPE_ICON, toolTypeIcon } from "./lib/type-icons.js";
5
5
  /** A node's icon; Identity shows the profile type's once one is picked. */
@@ -11,9 +11,6 @@ export function nodeIcon(draft, ref) {
11
11
  const binding = draft.modelBindings.find((each) => each.key === ref.key);
12
12
  if (!binding)
13
13
  return FACET_ICON.modelBinding;
14
- const definition = draft.providers.find((each) => each.id === binding.provider)?.definition;
15
- if (definition === 'typesafe')
16
- return IconTypeSafe;
17
14
  return modelFamilyMark(binding.apiId) ?? FACET_ICON.modelBinding;
18
15
  }
19
16
  if (ref.facet !== 'toolSpec')
@@ -27,7 +27,7 @@ import { IconAbc, IconAdjustmentsHorizontal, IconAlertTriangle, IconAlignLeft, I
27
27
  import { ATTACHMENT_ACCEPT_MIMES, CONTINUE_STOP_KINDS, fieldMeta, GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_OUTPUT_MIMES, GOOGLE_IMAGE_RESOLUTIONS, GOOGLE_SPEECH_VOICES, HTTP_METHODS, IMAGE_ATTACHMENT_ACCEPT_MIMES, LABEL_KEYS, labelDefault, PROFILE_HANDLE_MAX_CHARS, PROFILE_ID_MAX_CHARS, profileGraphFacet, THINKING_LEVELS, VOICE_ACCEPT_MIMES, } from '@theoremjs/agents';
28
28
  import { BOUNDARIES, BOUNDARY_META, DETECT_ACTION_META, DETECT_ACTIONS, DETECTOR_BOUNDARIES, DETECTOR_GROUP_META, DETECTOR_GROUPS, DETECTOR_META, DETECTORS, TOOL_BOUNDARIES, TOOL_KINDS, } from '@theoremjs/agents/guardrails';
29
29
  import { createContext, lazy, Suspense, useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore, } from 'react';
30
- import { acceptSections, agentToolTarget, bindingSlots, COMPACTION_DRAFT_DEFAULTS, defaultEffortRequired, defaultModelRequired, definitionOf, draftAllows, draftKey, expandAccept, INLINE_LABELS, includableFacets, includeFacet, inputLimitsRequired, isGoogleRoute, isOpenRouterRoute, kernelDefinitions, LOCAL_PROVIDER_ID, lentCompression, modelBindingNodeId, modelRoute, modelSlots, newCriteria, newDecisionImage, newDecisionQuestion, newModelBinding, newOwnDetector, newPattern, newToolSpec, nextAccept, onStudioLocal, optionAt, plantsCanary, protocolOptionFields, removeModelBinding, STUDIO_TRACE_DESTINATION, sampleToolInput, sectionNote, servedProtocols, servesProfileType, setProfileType, studioNetworkNote, studioNodeRef, studioRunsProvider, studioTaintNote, takesContinueInstruction, toolSpecNodeId, updateModelBinding, withOption, writeAsk, } from "../mod.js";
30
+ import { acceptSections, agentToolTarget, bindingSlots, COMPACTION_DRAFT_DEFAULTS, defaultEffortRequired, defaultModelRequired, definitionOf, draftAllows, draftKey, expandAccept, INLINE_LABELS, includableFacets, includeFacet, inputLimitsRequired, isGoogleRoute, isOpenRouterRoute, kernelDefinitions, LOCAL_PROVIDER_ID, lentCompression, MODEL_LIMITS, modelBindingNodeId, modelRoute, modelSlots, newCriteria, newDecisionImage, newDecisionQuestion, newModelBinding, newOwnDetector, newPattern, newToolSpec, nextAccept, onStudioLocal, optionAt, plantsCanary, protocolOptionFields, removeModelBinding, STUDIO_TRACE_DESTINATION, sampleToolInput, sectionNote, servedProtocols, servesProfileType, setProfileType, studioNetworkNote, studioNodeRef, studioRunsProvider, studioTaintNote, TIMER_MAX_MS, takesContinueInstruction, toolSpecNodeId, updateModelBinding, withOption, writeAsk, } from "../mod.js";
31
31
  import { AddProviderButton } from "./add-provider.js";
32
32
  import { IconGoogle, IconOpenAi, IconOpenRouter, IconTypeSafe } from "./brand-icons.js";
33
33
  import { ChoiceRow, InspectorGroupTitle, InspectorRow, InspectorSection, ListRow, NamesRow, NumberRow, OriginLine, SegmentedRow, SetInCode, SliderRow, SwitchRow, TextAreaRow, TextRow, } from "./inspector.js";
@@ -245,6 +245,11 @@ function DecisionModelEditor({ draft, setDraft, bindingKey, }) {
245
245
  set({ timeoutMs });
246
246
  } })] }));
247
247
  }
248
+ function LimitsSection({ binding, set }) {
249
+ return (_jsx(InspectorSection, { title: "Limits", path: "models.*.limits", children: MODEL_LIMITS.map(({ name, label, units, isTimer }) => (_jsx(NumberRow, { label: label, path: `models.*.limits.${name}`, units: units, field: `limits.${name}`, value: binding.limits?.[name] ?? null, min: 1, max: isTimer ? TIMER_MAX_MS : undefined, isIntegerOnly: true, onChange: (value) => {
250
+ set({ limits: { ...binding.limits, [name]: value } });
251
+ } }, name))) }));
252
+ }
248
253
  /** A key slot, chosen from the slots the keys panel holds; each says which service its key is for. */
249
254
  export function SlotRow({ label, path, field, value, isRequired, given, onChange, }) {
250
255
  const connection = useContext(LocalConnection);
@@ -550,7 +555,7 @@ function ModelBindingEditor({ draft, setDraft, bindingKey, onSelect, }) {
550
555
  const set = (change) => {
551
556
  setDraft((current) => updateModelBinding(current, bindingKey, change));
552
557
  };
553
- return (_jsxs(_Fragment, { children: [_jsx(ModelSection, { draft: draft, binding: binding, type: type, set: set }), type !== 'embedding' && _jsx(GenerationSection, { binding: binding, set: set }), _jsx(ProtocolOptionsSection, { providers: draft.providers, binding: binding, type: type, set: set }), type === 'text' && _jsx(CompactionSection, { binding: binding, set: set }), type !== 'embedding' && _jsx(EffortsSection, { binding: binding, set: set }), draft.modelBindings.length > 1 && (_jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Button, { label: "Remove model", variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: IconTrash, size: "sm" }), onClick: () => {
558
+ return (_jsxs(_Fragment, { children: [_jsx(ModelSection, { draft: draft, binding: binding, type: type, set: set }), type !== 'embedding' && _jsx(GenerationSection, { binding: binding, set: set }), _jsx(ProtocolOptionsSection, { providers: draft.providers, binding: binding, type: type, set: set }), _jsx(LimitsSection, { binding: binding, set: set }), type === 'text' && _jsx(CompactionSection, { binding: binding, set: set }), type !== 'embedding' && _jsx(EffortsSection, { binding: binding, set: set }), draft.modelBindings.length > 1 && (_jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Button, { label: "Remove model", variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: IconTrash, size: "sm" }), onClick: () => {
554
559
  setDraft((current) => removeModelBinding(current, bindingKey));
555
560
  onSelect('models');
556
561
  } }) }))] }));
@@ -1966,7 +1971,9 @@ function HttpRequestSection({ tool, set }) {
1966
1971
  set({ endpoint });
1967
1972
  } }), _jsx(SegmentedRow, { label: "Method", path: "registerTool.method", field: "method", value: tool.method ?? HTTP_METHODS[0], segments: METHOD_SEGMENTS, onChange: (method) => {
1968
1973
  set({ method });
1969
- } }), _jsx(HeadersRow, { tool: tool, set: set })] }));
1974
+ } }), _jsx(HeadersRow, { tool: tool, set: set }), _jsx(NumberRow, { label: "Max response", path: "registerTool.maxResponseBytes", units: "bytes", field: "maxResponseBytes", value: tool.maxResponseBytes ?? null, min: 1, isIntegerOnly: true, onChange: (maxResponseBytes) => {
1975
+ set({ maxResponseBytes });
1976
+ } })] }));
1970
1977
  }
1971
1978
  function HttpMappingSection({ tool, set }) {
1972
1979
  return (_jsxs(InspectorSection, { title: "Mapping", path: "registerTool.mapping", children: [_jsx(NamesRow, { label: "Path", path: "registerTool.mapping.pathParams", field: "pathParams", value: tool.pathParams ?? [], placeholder: "Names in {braces}", onChange: (pathParams) => {
@@ -1992,7 +1999,16 @@ function McpServerSection({ tool, set }) {
1992
1999
  set({ serverUrl });
1993
2000
  } }), _jsx(TextRow, { label: "Remote tool", path: "registerTool.mcpToolName", field: "mcpToolName", isRequired: true, value: tool.mcpToolName ?? '', placeholder: "search_flights", onChange: (mcpToolName) => {
1994
2001
  set({ mcpToolName });
1995
- } }), _jsx(HeadersRow, { tool: tool, set: set })] }));
2002
+ } }), _jsx(HeadersRow, { tool: tool, set: set }), _jsx(NumberRow, { label: "Max response", path: "registerTool.maxResponseBytes", units: "bytes", field: "maxResponseBytes", value: tool.maxResponseBytes ?? null, min: 1, isIntegerOnly: true, onChange: (maxResponseBytes) => {
2003
+ set({ maxResponseBytes });
2004
+ } })] }));
2005
+ }
2006
+ function McpSessionSection({ tool, set }) {
2007
+ return (_jsxs(InspectorSection, { title: "Session", path: "registerTool.session", children: [_jsx(NumberRow, { label: "Idle", path: "registerTool.session.idleMs", units: "ms", field: "sessionIdleMs", value: tool.sessionIdleMs ?? null, min: 1, isIntegerOnly: true, onChange: (sessionIdleMs) => {
2008
+ set({ sessionIdleMs });
2009
+ } }), _jsx(NumberRow, { label: "Max", path: "registerTool.session.max", units: "sessions", field: "sessionMax", value: tool.sessionMax ?? null, min: 1, isIntegerOnly: true, onChange: (sessionMax) => {
2010
+ set({ sessionMax });
2011
+ } })] }));
1996
2012
  }
1997
2013
  /** Where an authenticated tool's credential comes from, how it's sent, and what signed out does. */
1998
2014
  function AuthCredentialRows({ tool, authType, set, }) {
@@ -2067,7 +2083,7 @@ function ToolSpecEditor({ draft, setDraft, toolKey, onSelect, }) {
2067
2083
  }));
2068
2084
  };
2069
2085
  const remote = tool.toolType === 'http' || tool.toolType === 'mcp';
2070
- return (_jsxs(_Fragment, { children: [_jsx(ToolSection, { draft: draft, setDraft: setDraft, tool: tool, set: set }), _jsx(ContractSection, { tool: tool, set: set }), _jsx(ActivitySection, { tool: tool, set: set }), tool.toolType === 'function' && _jsx(StubSection, { tool: tool, set: set }), tool.toolType === 'http' && _jsx(HttpSections, { tool: tool, set: set }), tool.toolType === 'agent' && workspace && (_jsx(AgentSection, { tool: tool, set: set, workspace: workspace })), tool.toolType === 'mcp' && _jsx(McpServerSection, { tool: tool, set: set }), remote && _jsx(AuthSection, { tool: tool, set: set }), remote && _jsx(ToolTest, { tool: tool, guardrails: draft.guardrails }, tool.key), _jsx(PolicySection, { draft: draft, tool: tool, set: set }), _jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Button, { label: "Remove tool", variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: IconTrash, size: "sm" }), onClick: () => {
2086
+ return (_jsxs(_Fragment, { children: [_jsx(ToolSection, { draft: draft, setDraft: setDraft, tool: tool, set: set }), _jsx(ContractSection, { tool: tool, set: set }), _jsx(ActivitySection, { tool: tool, set: set }), tool.toolType === 'function' && _jsx(StubSection, { tool: tool, set: set }), tool.toolType === 'http' && _jsx(HttpSections, { tool: tool, set: set }), tool.toolType === 'agent' && workspace && (_jsx(AgentSection, { tool: tool, set: set, workspace: workspace })), tool.toolType === 'mcp' && _jsx(McpServerSection, { tool: tool, set: set }), tool.toolType === 'mcp' && _jsx(McpSessionSection, { tool: tool, set: set }), remote && _jsx(AuthSection, { tool: tool, set: set }), remote && _jsx(ToolTest, { tool: tool, guardrails: draft.guardrails }, tool.key), _jsx(PolicySection, { draft: draft, tool: tool, set: set }), _jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Button, { label: "Remove tool", variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: IconTrash, size: "sm" }), onClick: () => {
2071
2087
  setDraft((current) => removeToolSpec(current, toolKey, tool.toolName));
2072
2088
  onSelect('tools');
2073
2089
  } }) })] }));
@@ -2531,7 +2547,7 @@ function DecisionImageRow({ image, index, onRename, onRemove, }) {
2531
2547
  const reference = `<${image.name}>`;
2532
2548
  return (_jsxs(InspectorRow, { label: label, path: "decision.images.name", hasIssue: status !== undefined, children: [_jsx(Thumbnail, { src: `data:${image.mimeType};base64,${image.data}`, label: `Questions write it as ${reference}` }), _jsx(StackItem, { size: "fill", children: _jsx(TextInput, { label: `${label} name`, isLabelHidden: true, size: "sm", status: status, value: image.name, onChange: onRename }) }), _jsx(Tooltip, { content: `Remove ${reference}. A question that names it then has no image to read.`, children: _jsx(IconButton, { label: `Remove ${reference}`, variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: IconX, size: "sm" }), onClick: onRemove }) })] }));
2533
2549
  }
2534
- /** The pictures every decision sends, each under the name its questions write, and a drop to add more. */
2550
+ /** The pictures a decision can send, each under the name its questions write, and a drop to add more. */
2535
2551
  function DecisionImagesSection({ draft, setDraft }) {
2536
2552
  const keys = useLentKeys();
2537
2553
  const { reading, changeAction } = useFilesRead(async (picked) => {
package/ui/read-map.css CHANGED
@@ -11,20 +11,9 @@
11
11
  pointer-events: none;
12
12
  }
13
13
 
14
- /* The line draws in from the node it leaves. */
15
- .read-map-line {
16
- stroke-dasharray: 1;
17
- stroke-dashoffset: 1;
18
- animation: read-map-line-in var(--duration-medium, 240ms) var(--ease-standard, ease-out) forwards;
19
- }
20
-
21
14
  .read-map-dot {
22
15
  fill: var(--color-text-secondary);
23
- animation: read-map-in var(--duration-fast, 120ms) var(--ease-standard, ease-out) both;
24
- }
25
-
26
- .read-map-node {
27
- animation: read-map-in var(--duration-medium, 240ms) var(--ease-standard, ease-out) both;
16
+ animation: map-draw-in var(--duration-fast, 120ms) var(--ease-standard, ease-out) both;
28
17
  }
29
18
 
30
19
  .read-map-foot {
@@ -34,15 +23,3 @@
34
23
  gap: 16px;
35
24
  justify-content: space-between;
36
25
  }
37
-
38
- @keyframes read-map-line-in {
39
- to {
40
- stroke-dashoffset: 0;
41
- }
42
- }
43
-
44
- @keyframes read-map-in {
45
- from {
46
- opacity: 0;
47
- }
48
- }
package/ui/read-map.js CHANGED
@@ -4,6 +4,7 @@ import { IconFileCode, IconFolder } from '@tabler/icons-react';
4
4
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
5
  import { mapCurve } from "../map-line.js";
6
6
  import { readMap } from "./lib/read-map.js";
7
+ import { ease } from "./lib/tree-map-layout.js";
7
8
  import { AGENT_ICON, profileTypeIcon, toolTypeIcon } from "./lib/type-icons.js";
8
9
  import { MapToken, useTokenWidths } from "./map-token.js";
9
10
  import './read-map.css';
@@ -15,6 +16,8 @@ const LAG_MS = 500;
15
16
  const CATCH_UP_MS = 900;
16
17
  /** The map shows for at least this long, so a quick read can still be seen. */
17
18
  const SHOWN_MS = 2000;
19
+ /** How long a node already drawn takes to glide to where a new folder moves it. */
20
+ const GLIDE_MS = 240;
18
21
  /** The panel's own padding, step 4. */
19
22
  const PAD = 16;
20
23
  const ROW = 30;
@@ -127,6 +130,50 @@ function lineTo(from, fromWidth, to, dots) {
127
130
  dots: Array.from({ length: dots }, (_, index) => curve.at((index + 1) / (dots + 1))),
128
131
  };
129
132
  }
133
+ /** Where a glide stands at a time: eased from where it was to where it goes. */
134
+ function glideAt({ from, to, at }, now) {
135
+ const part = ease(Math.min(1, (now - at) / GLIDE_MS));
136
+ return { x: from.x + (to.x - from.x) * part, y: from.y + (to.y - from.y) * part };
137
+ }
138
+ /**
139
+ * The nodes where they stand now: a node drawn shows where it is laid out, and one a later folder
140
+ * moves glides there from where it stood, its lines with it, rather than jumping.
141
+ */
142
+ function useGlided(placed) {
143
+ const glides = useRef(new Map());
144
+ const [now, setNow] = useState(() => performance.now());
145
+ useLayoutEffect(() => {
146
+ const at = performance.now();
147
+ let moving = false;
148
+ for (const { node, x, y } of placed) {
149
+ const last = glides.current.get(node.id);
150
+ if (last && last.to.x === x && last.to.y === y) {
151
+ moving ||= at - last.at < GLIDE_MS;
152
+ continue;
153
+ }
154
+ glides.current.set(node.id, {
155
+ from: last ? glideAt(last, at) : { x, y },
156
+ to: { x, y },
157
+ at: last ? at : 0,
158
+ });
159
+ moving ||= last !== undefined;
160
+ }
161
+ if (!moving)
162
+ return;
163
+ let frame = 0;
164
+ const tick = (time) => {
165
+ setNow(time);
166
+ if (time - at < GLIDE_MS)
167
+ frame = requestAnimationFrame(tick);
168
+ };
169
+ frame = requestAnimationFrame(tick);
170
+ return () => cancelAnimationFrame(frame);
171
+ }, [placed]);
172
+ return placed.map((each) => {
173
+ const glide = glides.current.get(each.node.id);
174
+ return glide ? { ...each, ...glideAt(glide, now) } : each;
175
+ });
176
+ }
130
177
  export function ReadMap({ reading, onDrawn }) {
131
178
  const drawn = useDrawn(reading, onDrawn);
132
179
  const { ref, size } = useSize();
@@ -149,8 +196,9 @@ export function ReadMap({ reading, onDrawn }) {
149
196
  }));
150
197
  }, [map, size, drawn.steps]);
151
198
  const widths = useTokenWidths(ref, placed);
152
- const byId = new Map(placed.map((each) => [each.node.id, each]));
153
- return (_jsxs("div", { ref: ref, className: "read-map", "aria-hidden": "true", children: [_jsx("svg", { className: "read-map-lines", "aria-hidden": "true", width: size?.width ?? 0, height: size?.height ?? 0, children: placed.map((to) => {
199
+ const shown = useGlided(placed);
200
+ const byId = new Map(shown.map((each) => [each.node.id, each]));
201
+ return (_jsxs("div", { ref: ref, className: "read-map", "aria-hidden": "true", children: [_jsx("svg", { className: "read-map-lines", "aria-hidden": "true", width: size?.width ?? 0, height: size?.height ?? 0, children: shown.map((to) => {
154
202
  const from = to.node.parent === undefined ? undefined : byId.get(to.node.parent);
155
203
  const width = from && widths[from.node.id];
156
204
  if (!from || width === undefined)
@@ -158,6 +206,6 @@ export function ReadMap({ reading, onDrawn }) {
158
206
  const read = to.node.own.filter((step) => step < drawn.steps).length;
159
207
  const dots = Math.ceil((Math.min(DOTS, to.node.own.length) * read) / Math.max(1, to.node.own.length));
160
208
  const line = lineTo(from, width, to, Math.min(DOTS, to.node.own.length));
161
- return (_jsxs("g", { children: [_jsx("path", { className: "map-line read-map-line", d: line.d, pathLength: 1 }), line.dots.slice(0, dots).map(({ x, y }, index) => (_jsx("circle", { className: "read-map-dot", cx: x, cy: y, r: 2.5 }, index)))] }, to.node.id));
162
- }) }), placed.map(({ node, x, y }) => (_jsx(MapToken, { className: "read-map-node", id: node.id, label: short(node.label), icon: nodeIcon(node), x: x, y: y }, node.id))), _jsxs("div", { className: "read-map-foot", children: [_jsx(Text, { type: "supporting", color: "secondary", maxLines: 1, children: saying(steps, drawn.steps) }), _jsx(Text, { type: "supporting", color: "secondary", maxLines: 1, children: tally(steps, drawn.steps) })] })] }));
209
+ return (_jsxs("g", { children: [_jsx("path", { className: "map-line map-line-drawing", d: line.d, pathLength: 1 }), line.dots.slice(0, dots).map(({ x, y }, index) => (_jsx("circle", { className: "read-map-dot", cx: x, cy: y, r: 2.5 }, index)))] }, to.node.id));
210
+ }) }), shown.map(({ node, x, y }) => (_jsx(MapToken, { className: "map-token-drawing", id: node.id, label: short(node.label), icon: nodeIcon(node), x: x, y: y }, node.id))), _jsxs("div", { className: "read-map-foot", children: [_jsx(Text, { type: "supporting", color: "secondary", maxLines: 1, children: saying(steps, drawn.steps) }), _jsx(Text, { type: "supporting", color: "secondary", maxLines: 1, children: tally(steps, drawn.steps) })] })] }));
163
211
  }
package/ui/studio-map.js CHANGED
@@ -19,7 +19,7 @@ import { TextInput } from '@astryxdesign/core/TextInput';
19
19
  import { IconArrowsMaximize, IconArrowsMinimize, IconCloud, IconCode, IconExternalLink, IconEye, IconFocus2, IconLayoutGrid, IconSearch, IconX, } from '@tabler/icons-react';
20
20
  import { useEffect, useMemo, useRef, useState, } from 'react';
21
21
  import { mapFamily, mapFound, mapLayout, mapLinkWords, mapNoteLines, workspaceMap, } from "../mod.js";
22
- import { IconTypeSafe, modelFamilyMark } from "./brand-icons.js";
22
+ import { modelFamilyMark } from "./brand-icons.js";
23
23
  import { FACET_ICON } from "./lib/facet-icons.js";
24
24
  import { readKeptMap, writeKeptMap } from "./lib/studio-map-kept.js";
25
25
  import { useProject } from "./lib/studio-project.js";
@@ -53,8 +53,6 @@ function nodeIcon({ kind, type, note }) {
53
53
  if (kind === 'tool')
54
54
  return toolTypeIcon(type ?? '');
55
55
  // A model's note is its API model id, which names the family.
56
- if (kind === 'model' && type === 'typesafe')
57
- return IconTypeSafe;
58
56
  if (kind === 'model')
59
57
  return modelFamilyMark(note) ?? FACET_ICON.modelBinding;
60
58
  if (kind === 'provider')
package/ui/tree-map.css CHANGED
@@ -16,6 +16,15 @@
16
16
  stroke: color-mix(in srgb, var(--color-border-emphasized) 60%, var(--color-background-surface));
17
17
  }
18
18
 
19
+ /* Grown, a title over each column, and what the column is under it. */
20
+ .studio-tree-map-heading {
21
+ position: absolute;
22
+ display: flex;
23
+ flex-direction: column;
24
+ white-space: nowrap;
25
+ pointer-events: none;
26
+ }
27
+
19
28
  .studio-tree-map[data-draggable] {
20
29
  cursor: grab;
21
30
  touch-action: none;
package/ui/tree-map.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type MapPlace } from '../map-line.ts';
1
2
  import { type StudioWorkspace } from '../mod.ts';
2
3
  import { type Box } from './lib/tree-map-layout.ts';
3
4
  export interface StudioTreeMapProps {
@@ -5,18 +6,38 @@ export interface StudioTreeMapProps {
5
6
  /** The keys of the agents to draw, in the workspace's order; every agent when unset. */
6
7
  agents?: readonly string[] | undefined;
7
8
  /**
8
- * How far the tree has grown, from 0 to 1. As it grows each agent drifts into a zone of its own
9
- * and what feeds the tree spreads into its columns. What growing adds draws in as its room opens,
10
- * its line and then its token: the protocols under each provider, what branches off a tool, the
11
- * runner's steps, and the tool runner with its checks. Shrinking takes it all back the same way.
9
+ * How far the tree has grown, from 0 to 1. As it grows the map grows to its size, then draws out
10
+ * from the tree's agents as one pen would, at an even pace: each line runs out from the node it
11
+ * leaves, and the node at its end shows as the line reaches it, drifting the last of the way along
12
+ * it. Nothing moves before the drawing comes to it: what the map shows closed glides into place
13
+ * once its line reaches it, its line with it, or, where something would show where it stands, sets
14
+ * off just before that shows and goes slower, so its line catches it as it lands.
15
+ * Shrinking takes it all back the same way.
12
16
  */
13
17
  grow?: number | undefined;
14
18
  /**
15
19
  * The box the map fills grown: its rows and gaps stretch to fill it, and it stands in the middle
16
- * of it. When the map cannot fit, it eases smaller as it grows until all of it does.
20
+ * of it.
17
21
  */
18
22
  fit?: Box | undefined;
23
+ /**
24
+ * The box the map stands in now, when it changes as the map grows: closed and grown, the map
25
+ * stands in the middle of it, eased smaller where it would not fit. `fit` by default.
26
+ */
27
+ room?: Box | undefined;
28
+ /**
29
+ * The keys of the agents the map shows closed, when not `agents`. Growing takes away what the
30
+ * tree does not draw, and an agent the tree draws as an agent tool's agent glides into it.
31
+ */
32
+ still?: readonly string[] | undefined;
33
+ /**
34
+ * Where the tokens the map shows closed stand, by id, in the map's own pixels, for a map that
35
+ * grows out of something else: closed, the map stands at its own size from the top left of what
36
+ * holds it, these tokens where they are given and nothing else; growing, the tree draws out round
37
+ * them, and each glides to where it stands grown as the drawing comes to it. In place of `still`.
38
+ */
39
+ closed?: ReadonlyMap<string, MapPlace> | undefined;
19
40
  /** A token dragged moves to where it is dropped, its lines with it; the field dragged moves the whole map. */
20
41
  draggable?: boolean | undefined;
21
42
  }
22
- export declare function StudioTreeMap({ workspace, agents, grow, fit, draggable, }: StudioTreeMapProps): import("react").JSX.Element;
43
+ export declare function StudioTreeMap({ workspace, agents, grow, fit, room, still, closed: given, draggable, }: StudioTreeMapProps): import("react").JSX.Element;