@theoremjs/studio 0.3.4-next.464.g5fc5eed → 0.3.4-next.468.ge2da06f
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/README.md +1 -1
- package/package.json +4 -4
- package/page/assets/{Banner-fUKWqHmI.js → Banner-Z4LFsB9Y.js} +2 -2
- package/page/assets/{ConsoleFrame-B3cd6mje.js → ConsoleFrame-BJo1I5fw.js} +1 -1
- package/page/assets/{ContextMenu-C1HQbsdS.js → ContextMenu-BrT5KgtG.js} +2 -2
- package/page/assets/{ToolResultCharts-CngV4ccU.js → ToolResultCharts-CvdG2e3W.js} +1 -1
- package/page/assets/{TraceInspector-BJS_1iXX.js → TraceInspector-CwsPN13M.js} +1 -1
- package/page/assets/{TraceOverview-6hlA2pmX.js → TraceOverview-Bdz85-Rn.js} +1 -1
- package/page/assets/{_virtual_studio-type-sources-Bu1ywN-k.js → _virtual_studio-type-sources-DdSjKnMh.js} +5 -5
- package/page/assets/{browser-engine-BReGdZx4.js → browser-engine-Biheh6VY.js} +8 -8
- package/page/assets/{browser-transport-Bsz-KNbB.js → browser-transport-Cnyo4CDA.js} +4 -4
- package/page/assets/{crypto-Boxudvjl.js → crypto-C5mfRMPv.js} +1 -1
- package/page/assets/{encode-files-DZ4tVtEQ.js → encode-files-CZXrfYAp.js} +1 -1
- package/page/assets/{export-agent-DDZ8cq19.js → export-agent-CVpG2q1m.js} +1 -1
- package/page/assets/{guardrail-tester-C_T6-YOZ.js → guardrail-tester-CJuC6zTI.js} +1 -1
- package/page/assets/{index-Cw3Spve0.js → index-CYmjuHBy.js} +9 -9
- package/page/assets/{live-messages-dPuowG_o.js → live-messages-CcUq850L.js} +1 -1
- package/page/assets/{reader-BB7aL5yX.js → reader-DkhgU7-Z.js} +4 -4
- package/page/assets/shaped-data-Hq2l-bCM.js +16 -0
- package/page/assets/{stub-DyzmGexy.js → stub-9noS56tk.js} +1 -1
- package/page/assets/{studio-code-pane-xsBtdlWI.js → studio-code-pane-CkjZy0ML.js} +3 -3
- package/page/assets/{studio-console-C3uStRGc.js → studio-console-D7Gp4J9U.js} +1 -1
- package/page/assets/{studio-decision-MvC7eEPm.js → studio-decision-BRgQ4PBH.js} +1 -1
- package/page/assets/{studio-embedding-mvARkljv.js → studio-embedding-Dl4-HUCw.js} +1 -1
- package/page/assets/{studio-host-console-CzHR2V7s.js → studio-host-console-C2kDYgZ0.js} +2 -2
- package/page/assets/{studio-labels-DsQLZ9_e.js → studio-labels-BOSQZulx.js} +1 -1
- package/page/assets/{studio-live-DXV_PScy.js → studio-live-BweMJXiN.js} +1 -1
- package/page/assets/{studio-map-DBLOQt2Q.js → studio-map-ClUV7cPI.js} +1 -1
- package/page/assets/{studio-trace-replay-DHJTvGZO.js → studio-trace-replay-BVJ_xUpV.js} +1 -1
- package/page/assets/{studio-type-checker-BaSH5B4-.js → studio-type-checker-Ch0YO2TO.js} +1 -1
- package/page/assets/{tool-schema-E9NPuDcN.js → tool-schema-DUAd6AXZ.js} +6 -6
- package/page/assets/{trace-record-BjsBbPRl.js → trace-record-Dfx7WS9n.js} +1 -1
- package/page/assets/{tree-2VeaKg0m.js → tree-q4zfZBm3.js} +2 -2
- package/page/assets/{turn-client-Dy9-U9_L.js → turn-client-BHueOGtF.js} +2 -2
- package/page/assets/types-CbxtWiCN.js +1 -0
- package/page/assets/{use-one-call-B3x4YoMP.js → use-one-call-Djq1Oucn.js} +1 -1
- package/page/assets/{workspace-Cno6Ge26.js → workspace-D8wB8WWL.js} +1 -1
- package/page/index.html +15 -15
- package/ui/lib/tree-map-layout.d.ts +75 -0
- package/ui/lib/tree-map-layout.js +289 -0
- package/ui/tree-map.css +2 -0
- package/ui/tree-map.d.ts +10 -6
- package/ui/tree-map.js +55 -286
- package/page/assets/shaped-data-0GUF7Al3.js +0 -16
- package/page/assets/types-CMdljBjd.js +0 -1
package/ui/tree-map.js
CHANGED
|
@@ -1,163 +1,49 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { IconAdjustments, IconArrowsMinimize, IconChecks, IconCloud, IconEngine, IconFlag, IconKey, IconRepeat, IconSettingsAutomation, IconShieldCheck, IconTimeline, IconTool, } from '@tabler/icons-react';
|
|
3
|
-
import { useRef } from 'react';
|
|
3
|
+
import { useMemo, useRef } from 'react';
|
|
4
4
|
import { mapCurve } from "../map-line.js";
|
|
5
|
-
import { createBlankDraft, libraryDraft
|
|
5
|
+
import { createBlankDraft, libraryDraft } from "../mod.js";
|
|
6
|
+
import { drawnAt, HALF, INDENT, PAD, placeAt, rowId, treeMapLayout, } from "./lib/tree-map-layout.js";
|
|
6
7
|
import { toolTypeIcon } from "./lib/type-icons.js";
|
|
7
8
|
import { MapToken, useTokenWidths } from "./map-token.js";
|
|
8
9
|
import { nodeIcon } from "./node-icon.js";
|
|
9
10
|
import './tree-map.css';
|
|
10
|
-
/** A row's height, and how far in each level of the tree starts. */
|
|
11
|
-
const ROW = 32;
|
|
12
|
-
const INDENT = 28;
|
|
13
11
|
/** Where under a token its children's line runs: the middle of its icon. */
|
|
14
12
|
const TRUNK = 13;
|
|
15
|
-
/**
|
|
16
|
-
const HALF = 11;
|
|
13
|
+
/** The radius of the line's turn. */
|
|
17
14
|
const TURN = 8;
|
|
18
|
-
|
|
19
|
-
/** Between the tree's widest row and the feeds, and between a token's side and its line. */
|
|
20
|
-
const GAP = 72;
|
|
15
|
+
/** Between a token's side and its line. */
|
|
21
16
|
const LINE_GAP = 4;
|
|
22
17
|
/** How far right of the widest feed a line coming round them runs. */
|
|
23
18
|
const RAIL = 20;
|
|
24
|
-
/** How many of the registry's tools sit under it: one of each type first. */
|
|
25
|
-
const SHOWN_TOOLS = 4;
|
|
26
|
-
const TOOL_TYPES = ['function', 'http', 'mcp', 'agent'];
|
|
27
19
|
/** A token's width until it is measured. */
|
|
28
20
|
const GUESS = 120;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* Each agent's runner (`runTurn`, or `runDecision` for a decision), beside its name; its providers, beside its models; and the
|
|
56
|
-
* workspace's tool registry, beside its Tools row, with the tool runner under it. A feed sits level
|
|
57
|
-
* with the middle of the rows it joins, or under the feed before it when that is lower.
|
|
58
|
-
*/
|
|
59
|
-
const REGISTRY = 'tool-registry';
|
|
60
|
-
/**
|
|
61
|
-
* What runs a profile of this type: `runDecision`, `runSession`, `runEmbedding`, else `runTurn`. A
|
|
62
|
-
* host has none of its own: the tool runner runs its calls.
|
|
63
|
-
*/
|
|
64
|
-
function runnerLabel(type) {
|
|
65
|
-
if (type === 'host')
|
|
66
|
-
return undefined;
|
|
67
|
-
if (type === 'decision')
|
|
68
|
-
return 'Decision runner';
|
|
69
|
-
if (type === 'live')
|
|
70
|
-
return 'Session runner';
|
|
71
|
-
if (type === 'embedding')
|
|
72
|
-
return 'Embedding runner';
|
|
73
|
-
return 'Turn runner';
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* What a turn runner does in one turn, in order.
|
|
77
|
-
* why: The kernel contract's turn lifecycle (docs/contracts/kernel.md, "Turn lifecycle"), step for
|
|
78
|
-
* step; a decision and a session have no such list there, so their runners open into nothing.
|
|
79
|
-
*/
|
|
80
|
-
const TURN_STEPS = [
|
|
81
|
-
{ id: 'resolve', label: 'Resolve', icon: IconAdjustments },
|
|
82
|
-
{ id: 'sanitize', label: 'Sanitize', icon: IconShieldCheck },
|
|
83
|
-
{ id: 'compaction', label: 'Compaction', icon: IconArrowsMinimize },
|
|
84
|
-
{ id: 'canary', label: 'Canary bind', icon: IconKey },
|
|
85
|
-
{ id: 'pre_turn', label: 'pre_turn', icon: IconFlag },
|
|
86
|
-
{ id: 'stream', label: 'Provider stream', icon: IconCloud },
|
|
87
|
-
{ id: 'tools', label: 'Tool loop', icon: IconRepeat },
|
|
88
|
-
{ id: 'before_end', label: 'before_end', icon: IconFlag },
|
|
89
|
-
{ id: 'validation', label: 'Validation', icon: IconChecks },
|
|
90
|
-
{ id: 'egress', label: 'Egress', icon: IconShieldCheck },
|
|
91
|
-
{ id: 'trace', label: 'Trace', icon: IconTimeline },
|
|
92
|
-
{ id: 'post_turn', label: 'post_turn', icon: IconFlag },
|
|
93
|
-
];
|
|
94
|
-
function feedsOf(workspace, rows, detail) {
|
|
95
|
-
const feeds = [];
|
|
96
|
-
const add = (id, label, icon, into, under, depth = 0) => {
|
|
97
|
-
const below = (feeds.at(-1)?.y ?? Number.NEGATIVE_INFINITY) + ROW;
|
|
98
|
-
const middle = into.length ? into.reduce((sum, row) => sum + row.y, 0) / into.length : below;
|
|
99
|
-
feeds.push({
|
|
100
|
-
id,
|
|
101
|
-
label,
|
|
102
|
-
icon,
|
|
103
|
-
y: Math.max(middle, below),
|
|
104
|
-
into: into.map(rowId),
|
|
105
|
-
...(under ? { under } : {}),
|
|
106
|
-
depth,
|
|
107
|
-
});
|
|
108
|
-
};
|
|
109
|
-
for (const agent of workspace.agents) {
|
|
110
|
-
const mine = rows.filter((row) => row.agent === agent.key);
|
|
111
|
-
const name = mine.find((row) => row.node.ref.facet === 'identity');
|
|
112
|
-
if (!name)
|
|
113
|
-
continue;
|
|
114
|
-
const runner = runnerLabel(agent.identity.profileType);
|
|
115
|
-
if (runner) {
|
|
116
|
-
const id = `${agent.key}/runner`;
|
|
117
|
-
add(id, runner, IconEngine, [name]);
|
|
118
|
-
const steps = detail && runner === 'Turn runner' ? TURN_STEPS : [];
|
|
119
|
-
for (const step of steps)
|
|
120
|
-
add(`${id}/${step.id}`, step.label, step.icon, [], id, 1);
|
|
121
|
-
}
|
|
122
|
-
const providers = new Map();
|
|
123
|
-
for (const binding of agent.modelBindings) {
|
|
124
|
-
const row = mine.find((each) => each.node.ref.facet === 'modelBinding' && each.node.ref.key === binding.key);
|
|
125
|
-
if (row)
|
|
126
|
-
providers.set(binding.provider, [...(providers.get(binding.provider) ?? []), row]);
|
|
127
|
-
}
|
|
128
|
-
for (const [provider, into] of providers) {
|
|
129
|
-
add(`${agent.key}/provider:${provider}`, provider, IconCloud, into);
|
|
130
|
-
}
|
|
131
|
-
// The workspace's tools are one registry that each agent's tool policy picks from: drawn once,
|
|
132
|
-
// beside the first Tools row, with a line into every other.
|
|
133
|
-
const tools = mine.find((row) => row.node.ref.facet === 'tools');
|
|
134
|
-
const drawn = feeds.find((feed) => feed.id === REGISTRY);
|
|
135
|
-
if (tools && drawn)
|
|
136
|
-
drawn.into.push(rowId(tools));
|
|
137
|
-
else if (tools && workspace.toolSpecs.length > 0) {
|
|
138
|
-
const registry = REGISTRY;
|
|
139
|
-
add(registry, 'Tool registry', IconTool, [tools]);
|
|
140
|
-
for (const tool of listedTools(workspace.toolSpecs, detail)) {
|
|
141
|
-
const icon = toolTypeIcon(tool.toolType);
|
|
142
|
-
add(`${registry}/${tool.key}`, tool.toolName, icon, [], registry, 1);
|
|
143
|
-
}
|
|
144
|
-
add('tool-runner', 'Tool runner', IconSettingsAutomation, [], registry);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return feeds;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* The registry's tools: the first of each type, then the rest in the registry's order. A few of
|
|
151
|
-
* them, or with `detail` every one.
|
|
152
|
-
*/
|
|
153
|
-
function listedTools(tools, detail) {
|
|
154
|
-
const firsts = TOOL_TYPES.flatMap((type) => {
|
|
155
|
-
const tool = tools.find((each) => each.toolType === type);
|
|
156
|
-
return tool ? [tool] : [];
|
|
157
|
-
});
|
|
158
|
-
const rest = tools.filter((tool) => !firsts.includes(tool));
|
|
159
|
-
const listed = [...firsts, ...rest];
|
|
160
|
-
return detail ? listed : listed.slice(0, SHOWN_TOOLS);
|
|
21
|
+
const STEP_ICONS = {
|
|
22
|
+
resolve: IconAdjustments,
|
|
23
|
+
sanitize: IconShieldCheck,
|
|
24
|
+
compaction: IconArrowsMinimize,
|
|
25
|
+
canary: IconKey,
|
|
26
|
+
pre_turn: IconFlag,
|
|
27
|
+
stream: IconCloud,
|
|
28
|
+
tools: IconRepeat,
|
|
29
|
+
before_end: IconFlag,
|
|
30
|
+
validation: IconChecks,
|
|
31
|
+
egress: IconShieldCheck,
|
|
32
|
+
trace: IconTimeline,
|
|
33
|
+
post_turn: IconFlag,
|
|
34
|
+
};
|
|
35
|
+
function feedIcon(feed) {
|
|
36
|
+
if (feed.kind === 'runner')
|
|
37
|
+
return IconEngine;
|
|
38
|
+
if (feed.kind === 'provider')
|
|
39
|
+
return IconCloud;
|
|
40
|
+
if (feed.kind === 'tool')
|
|
41
|
+
return toolTypeIcon(feed.of ?? '');
|
|
42
|
+
if (feed.kind === 'tool-runner')
|
|
43
|
+
return IconSettingsAutomation;
|
|
44
|
+
if (feed.kind === 'step')
|
|
45
|
+
return STEP_ICONS[feed.of ?? ''] ?? IconFlag;
|
|
46
|
+
return IconTool;
|
|
161
47
|
}
|
|
162
48
|
/** Right from `from` to `rail`, up or down it, and back left to `to`, its corners turned. */
|
|
163
49
|
function sidePath(from, to, rail) {
|
|
@@ -181,156 +67,28 @@ function underPath(from, to) {
|
|
|
181
67
|
return (`M ${from.x + TRUNK} ${from.y + HALF} V ${across} H ${trunk} V ${to.y - TURN}` +
|
|
182
68
|
` Q ${trunk} ${to.y} ${trunk + TURN} ${to.y} H ${to.x - LINE_GAP}`);
|
|
183
69
|
}
|
|
184
|
-
/** Grown, a row's height and the room between two agents' trees. */
|
|
185
|
-
const GROWN_ROW = 28;
|
|
186
|
-
const AGENT_GAP = 16;
|
|
187
|
-
/** Grown, the providers' band and the room under it. */
|
|
188
|
-
const BAND = GROWN_ROW + AGENT_GAP;
|
|
189
|
-
/** Grown, a turn's steps in rows under its runner, so many to a row. */
|
|
190
|
-
const STEPS_ACROSS = 3;
|
|
191
|
-
const STEP_SLOT = 160;
|
|
192
|
-
/** Grown, the registry's field: right of the turn's steps, its tools in columns this tall. */
|
|
193
|
-
const REGISTRY_IN = 186;
|
|
194
|
-
const TOOLS_DOWN = 16;
|
|
195
|
-
const TOOL_SLOT = 210;
|
|
196
|
-
/** Over what stretch of growing the added nodes draw, and how long each takes of it. */
|
|
197
|
-
const DRAW_FROM = 0.2;
|
|
198
|
-
const DRAW_TO = 0.95;
|
|
199
|
-
const DRAW_SPAN = 0.1;
|
|
200
70
|
const clamp = (value) => Math.min(Math.max(value, 0), 1);
|
|
201
|
-
|
|
202
|
-
/** Every other one from each list, while both last: the turn's steps and the tools draw by turns. */
|
|
203
|
-
function byTurns(first, second) {
|
|
204
|
-
return Array.from({ length: Math.max(first.length, second.length) }, (_, index) => [first[index], second[index]].filter((each) => each !== undefined)).flat();
|
|
205
|
-
}
|
|
206
|
-
/** The rows grown: restacked, each agent apart from the next, under the providers' band. */
|
|
207
|
-
function grownRows(rows) {
|
|
208
|
-
const places = new Map();
|
|
209
|
-
let agents = -1;
|
|
210
|
-
rows.forEach((row, index) => {
|
|
211
|
-
if (index === 0 || rows[index - 1]?.agent !== row.agent)
|
|
212
|
-
agents += 1;
|
|
213
|
-
places.set(rowId(row), {
|
|
214
|
-
x: row.x,
|
|
215
|
-
y: PAD + HALF + BAND + index * GROWN_ROW + agents * AGENT_GAP,
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
return places;
|
|
219
|
-
}
|
|
220
|
-
/** The next place under a holder, counting it. */
|
|
221
|
-
function nextUnder(growing, holder) {
|
|
222
|
-
const index = growing.under.get(holder) ?? 0;
|
|
223
|
-
growing.under.set(holder, index + 1);
|
|
224
|
-
return index;
|
|
225
|
-
}
|
|
226
|
-
/** A provider's place in the band, by name: an agent that has one too shares it. */
|
|
227
|
-
function bandPlace(growing, feed) {
|
|
228
|
-
const held = growing.band.get(feed.label);
|
|
229
|
-
if (held)
|
|
230
|
-
return held;
|
|
231
|
-
const place = { x: growing.bandX, y: PAD + HALF };
|
|
232
|
-
growing.band.set(feed.label, place);
|
|
233
|
-
growing.bandX += growing.width(feed.id) + AGENT_GAP;
|
|
234
|
-
return place;
|
|
235
|
-
}
|
|
236
|
-
/** Under the registry: its tools in columns, then the tool runner below them. */
|
|
237
|
-
function registryPlace(growing, feed, above) {
|
|
238
|
-
if (feed.depth === 0) {
|
|
239
|
-
const tools = Math.min(growing.under.get(REGISTRY) ?? 0, TOOLS_DOWN);
|
|
240
|
-
return { x: above.x, y: above.y + (tools + 1) * GROWN_ROW };
|
|
241
|
-
}
|
|
242
|
-
const index = nextUnder(growing, REGISTRY);
|
|
243
|
-
return {
|
|
244
|
-
x: above.x + INDENT + Math.floor(index / TOOLS_DOWN) * TOOL_SLOT,
|
|
245
|
-
y: above.y + ((index % TOOLS_DOWN) + 1) * GROWN_ROW,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
/** A step under its runner, in rows so many across. */
|
|
249
|
-
function stepPlace(growing, holder, above) {
|
|
250
|
-
const index = nextUnder(growing, holder);
|
|
251
|
-
const y = above.y + (Math.floor(index / STEPS_ACROSS) + 1) * GROWN_ROW;
|
|
252
|
-
growing.stepsEnd = Math.max(growing.stepsEnd, y);
|
|
253
|
-
return { x: above.x + INDENT + (index % STEPS_ACROSS) * STEP_SLOT, y };
|
|
254
|
-
}
|
|
255
|
-
function feedPlace(growing, feed) {
|
|
256
|
-
if (feed.id.includes('/provider:'))
|
|
257
|
-
return bandPlace(growing, feed);
|
|
258
|
-
if (feed.id === REGISTRY) {
|
|
259
|
-
return { x: growing.feedX + REGISTRY_IN, y: growing.stepsEnd + GROWN_ROW * 1.5 };
|
|
260
|
-
}
|
|
261
|
-
const above = feed.under === undefined ? undefined : growing.places.get(feed.under);
|
|
262
|
-
if (feed.under === REGISTRY && above)
|
|
263
|
-
return registryPlace(growing, feed, above);
|
|
264
|
-
if (feed.under !== undefined && above)
|
|
265
|
-
return stepPlace(growing, feed.under, above);
|
|
266
|
-
const into = growing.places.get(feed.into[0] ?? '');
|
|
267
|
-
return { x: growing.feedX, y: into?.y ?? feed.y };
|
|
268
|
-
}
|
|
269
|
-
/** Each node's place grown: by its token id, rows and feeds alike. */
|
|
270
|
-
function grownPlaces(rows, feeds, feedX, width) {
|
|
271
|
-
const growing = {
|
|
272
|
-
places: grownRows(rows),
|
|
273
|
-
band: new Map(),
|
|
274
|
-
bandX: feedX,
|
|
275
|
-
under: new Map(),
|
|
276
|
-
stepsEnd: PAD + HALF + BAND,
|
|
277
|
-
feedX,
|
|
278
|
-
width,
|
|
279
|
-
};
|
|
280
|
-
for (const feed of feeds)
|
|
281
|
-
growing.places.set(feed.id, feedPlace(growing, feed));
|
|
282
|
-
return growing.places;
|
|
283
|
-
}
|
|
284
|
-
export function StudioTreeMap({ workspace, agents, grow = 0 }) {
|
|
71
|
+
export function StudioTreeMap({ workspace, agents, grow = 0, fit }) {
|
|
285
72
|
const root = useRef(null);
|
|
286
|
-
|
|
287
|
-
const few = feedsOf(workspace, rows, false);
|
|
288
|
-
const full = feedsOf(workspace, rows, true);
|
|
73
|
+
// why: Every token is drawn from the start, the undrawn ones unseen, so each is measured before it shows.
|
|
289
74
|
const widths = useTokenWidths(root, workspace);
|
|
75
|
+
const layout = useMemo(() => treeMapLayout(workspace, agents, (id) => widths[id] ?? GUESS), [workspace, agents, widths]);
|
|
290
76
|
const width = (id) => widths[id] ?? GUESS;
|
|
291
77
|
const drafts = new Map(workspace.agents.map((agent) => [
|
|
292
78
|
agent.key,
|
|
293
79
|
libraryDraft(workspace, agent.key) ?? createBlankDraft(),
|
|
294
80
|
]));
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
closed.set(feed.id, { x: feedX + feed.depth * INDENT, y: feed.y });
|
|
300
|
-
const grown = grownPlaces(rows, full, feedX, width);
|
|
301
|
-
const feedById = new Map(full.map((feed) => [feed.id, feed]));
|
|
302
|
-
/**
|
|
303
|
-
* Where a node is, this far grown. One growing adds keeps its place in its zone and rides with
|
|
304
|
-
* the feed it comes under, so a zone drifts whole.
|
|
305
|
-
*/
|
|
306
|
-
const at = (id) => {
|
|
307
|
-
const to = grown.get(id) ?? closed.get(id) ?? { x: 0, y: 0 };
|
|
308
|
-
const from = closed.get(id);
|
|
309
|
-
if (from)
|
|
310
|
-
return { x: between(from.x, to.x, grow), y: between(from.y, to.y, grow) };
|
|
311
|
-
const under = feedById.get(id)?.under;
|
|
312
|
-
const holder = under === undefined ? undefined : grown.get(under);
|
|
313
|
-
if (under === undefined || holder === undefined)
|
|
314
|
-
return to;
|
|
315
|
-
const now = at(under);
|
|
316
|
-
return { x: to.x + now.x - holder.x, y: to.y + now.y - holder.y };
|
|
317
|
-
};
|
|
318
|
-
// What growing adds draws in order, the turn's steps and the tools by turns.
|
|
319
|
-
const fewIds = new Set(few.map((feed) => feed.id));
|
|
320
|
-
const added = full.filter((feed) => !fewIds.has(feed.id));
|
|
321
|
-
const order = byTurns(added.filter((feed) => feed.under !== REGISTRY), added.filter((feed) => feed.under === REGISTRY));
|
|
322
|
-
/** How far an added node has drawn, 0 to 1: its line in the first half, its token in the second. */
|
|
323
|
-
const drawn = new Map(order.map((feed, index) => {
|
|
324
|
-
const start = DRAW_FROM + ((DRAW_TO - DRAW_SPAN - DRAW_FROM) * index) / Math.max(order.length - 1, 1);
|
|
325
|
-
return [feed.id, clamp((grow - start) / DRAW_SPAN)];
|
|
326
|
-
}));
|
|
81
|
+
const { rows, feeds } = layout;
|
|
82
|
+
const at = (id) => placeAt(layout, id, grow);
|
|
83
|
+
const drawn = (id) => drawnAt(layout, id, grow);
|
|
84
|
+
const feedById = new Map(feeds.map((feed) => [feed.id, feed]));
|
|
327
85
|
const lineDrawn = (id) => {
|
|
328
|
-
const part = drawn
|
|
86
|
+
const part = drawn(id);
|
|
329
87
|
return part === undefined
|
|
330
88
|
? {}
|
|
331
89
|
: { pathLength: 1, style: { strokeDasharray: 1, strokeDashoffset: 1 - clamp(part * 2) } };
|
|
332
90
|
};
|
|
333
|
-
const shown =
|
|
91
|
+
const shown = feeds.filter((feed) => (drawn(feed.id) ?? 1) > 0);
|
|
334
92
|
const right = (id) => at(id).x + width(id);
|
|
335
93
|
/** Right of every feed from `above` down to `feed`, where each stands now. */
|
|
336
94
|
const railFor = (feed, above) => {
|
|
@@ -348,7 +106,17 @@ export function StudioTreeMap({ workspace, agents, grow = 0 }) {
|
|
|
348
106
|
});
|
|
349
107
|
const mapWidth = Math.max(GUESS, ...places.map(right), ...rails) + PAD;
|
|
350
108
|
const height = Math.max(...places.map((id) => at(id).y)) + HALF + PAD;
|
|
351
|
-
|
|
109
|
+
const grown = useMemo(() => {
|
|
110
|
+
const ids = [...layout.rows.map(rowId), ...layout.feeds.map((feed) => feed.id)];
|
|
111
|
+
const ends = ids.map((id) => ({ id, ...placeAt(layout, id, 1) }));
|
|
112
|
+
return {
|
|
113
|
+
width: Math.max(...ends.map((end) => end.x + (widths[end.id] ?? GUESS))) + RAIL + PAD,
|
|
114
|
+
height: Math.max(...ends.map((end) => end.y)) + HALF + PAD,
|
|
115
|
+
};
|
|
116
|
+
}, [layout, widths]);
|
|
117
|
+
const fits = fit ? Math.min(1, fit.width / grown.width, fit.height / grown.height) : 1;
|
|
118
|
+
const scale = 1 + (fits - 1) * clamp(grow);
|
|
119
|
+
return (_jsxs("div", { ref: root, className: "studio-tree-map", style: { width: mapWidth, height, ...(scale < 1 ? { scale: String(scale) } : {}) }, children: [_jsxs("svg", { className: "studio-tree-map-lines", width: mapWidth, height: height, "aria-hidden": "true", children: [rows.map((row) => row.parent && (_jsx("path", { className: "map-line", d: linePath(at(row.parent), at(rowId(row))) }, rowId(row)))), shown.flatMap((feed) => feed.into.map((id) => {
|
|
352
120
|
const from = at(feed.id);
|
|
353
121
|
const { d } = mapCurve({ x: from.x - LINE_GAP, y: from.y }, { x: right(id) + LINE_GAP, y: at(id).y });
|
|
354
122
|
return _jsx("path", { className: "map-line", d: d }, `${feed.id}>${id}`);
|
|
@@ -366,9 +134,10 @@ export function StudioTreeMap({ workspace, agents, grow = 0 }) {
|
|
|
366
134
|
const draft = drafts.get(row.agent) ?? createBlankDraft();
|
|
367
135
|
const { x, y } = at(rowId(row));
|
|
368
136
|
return (_jsx(MapToken, { id: rowId(row), label: row.node.label, icon: nodeIcon(draft, row.node.ref), x: x, y: y }, rowId(row)));
|
|
369
|
-
}),
|
|
137
|
+
}), feeds.map((feed) => {
|
|
370
138
|
const { x, y } = at(feed.id);
|
|
371
|
-
const part = drawn
|
|
372
|
-
|
|
139
|
+
const part = drawn(feed.id);
|
|
140
|
+
const unseen = part === 0 ? { visibility: 'hidden' } : {};
|
|
141
|
+
return (_jsx(MapToken, { id: feed.id, label: feed.label, icon: feedIcon(feed), x: x, y: y, ...(part === undefined ? {} : { style: { ...unseen, opacity: clamp(part * 2 - 1) } }) }, feed.id));
|
|
373
142
|
})] }));
|
|
374
143
|
}
|