@datalayer/agent-runtimes 1.5.90 → 1.5.91
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 +29 -297
- package/dist-embed/datalayer-app-main.js +2 -2
- package/lib/components/teams/A2ATeamGraph.d.ts +40 -155
- package/lib/components/teams/A2ATeamGraph.js +122 -400
- package/lib/components/teams/SceneTranscript.d.ts +5 -1
- package/lib/components/teams/SceneTranscript.js +2 -2
- package/lib/components/teams/SceneView.d.ts +17 -5
- package/lib/components/teams/SceneView.js +7 -3
- package/lib/components/teams/backdrops/catalogue.d.ts +35 -0
- package/lib/components/teams/backdrops/catalogue.js +34 -0
- package/lib/components/teams/backdrops/edges.d.ts +20 -0
- package/lib/components/teams/backdrops/edges.js +29 -0
- package/lib/components/teams/backdrops/faces.d.ts +24 -0
- package/lib/components/teams/backdrops/faces.js +39 -0
- package/lib/components/teams/backdrops/halo.d.ts +14 -0
- package/lib/components/teams/backdrops/halo.js +5 -0
- package/lib/components/teams/backdrops/hero.d.ts +14 -0
- package/lib/components/teams/backdrops/hero.js +178 -0
- package/lib/components/teams/backdrops/index.d.ts +22 -0
- package/lib/components/teams/backdrops/index.js +38 -0
- package/lib/components/teams/backdrops/nodes.d.ts +123 -0
- package/lib/components/teams/backdrops/nodes.js +265 -0
- package/lib/components/teams/backdrops/plain.d.ts +14 -0
- package/lib/components/teams/backdrops/plain.js +116 -0
- package/lib/components/teams/backdrops/studio.d.ts +17 -0
- package/lib/components/teams/backdrops/studio.js +122 -0
- package/lib/components/teams/backdrops/theme.d.ts +14 -0
- package/lib/components/teams/backdrops/theme.js +19 -0
- package/lib/components/teams/backdrops/types.d.ts +214 -0
- package/lib/components/teams/backdrops/types.js +5 -0
- package/lib/components/teams/index.d.ts +2 -0
- package/lib/components/teams/index.js +2 -0
- package/lib/components/teams/sceneTranscriptRules.d.ts +32 -1
- package/lib/components/teams/sceneTranscriptRules.js +104 -2
- package/lib/components/teams/transcript/RecordingLabel.d.ts +17 -0
- package/lib/components/teams/transcript/RecordingLabel.js +7 -0
- package/lib/components/teams/transcript/SequenceDiagram.d.ts +39 -0
- package/lib/components/teams/transcript/SequenceDiagram.js +190 -0
- package/lib/components/teams/transcript/TranscriptDetail.d.ts +27 -0
- package/lib/components/teams/transcript/TranscriptDetail.js +48 -0
- package/lib/components/teams/transcript/TranscriptTable.d.ts +23 -0
- package/lib/components/teams/transcript/TranscriptTable.js +112 -0
- package/lib/components/teams/transcript/TranscriptViews.d.ts +66 -0
- package/lib/components/teams/transcript/TranscriptViews.js +135 -0
- package/lib/components/teams/transcript/TranscriptViewsTypes.d.ts +13 -0
- package/lib/components/teams/transcript/TranscriptViewsTypes.js +5 -0
- package/lib/components/teams/transcript/index.d.ts +23 -0
- package/lib/components/teams/transcript/index.js +27 -0
- package/lib/components/teams/transcript/recordingPlayback.d.ts +52 -0
- package/lib/components/teams/transcript/recordingPlayback.js +146 -0
- package/lib/components/teams/transcript/selection.d.ts +59 -0
- package/lib/components/teams/transcript/selection.js +79 -0
- package/lib/components/teams/transcript/sequenceDiagramRules.d.ts +35 -0
- package/lib/components/teams/transcript/sequenceDiagramRules.js +158 -0
- package/lib/components/teams/transcript/transcriptDetailRules.d.ts +80 -0
- package/lib/components/teams/transcript/transcriptDetailRules.js +208 -0
- package/lib/components/teams/transcript/transcriptExport.d.ts +31 -0
- package/lib/components/teams/transcript/transcriptExport.js +82 -0
- package/lib/components/teams/transcript/transcriptRows.d.ts +76 -0
- package/lib/components/teams/transcript/transcriptRows.js +187 -0
- package/lib/components/teams/transcript/useSceneRecording.d.ts +45 -0
- package/lib/components/teams/transcript/useSceneRecording.js +96 -0
- package/lib/examples/ScenesExample.js +16 -3
- package/lib/specs/actions.js +2 -0
- package/lib/specs/apps.d.ts +1 -0
- package/lib/specs/apps.js +219 -0
- package/lib/specs/sceneRecordings.d.ts +14 -0
- package/lib/specs/sceneRecordings.js +12 -0
- package/lib/specs/sceneSchema.js +11 -0
- package/lib/specs/scenes.d.ts +1 -0
- package/lib/specs/scenes.js +159 -0
- package/lib/types/scenes.d.ts +54 -0
- package/package.json +2 -1
- package/scripts/codegen/generate_scenes.py +80 -0
|
@@ -1,68 +1,29 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ReactFlow } from '@xyflow/react';
|
|
4
4
|
import '@xyflow/react/dist/base.css';
|
|
5
|
-
import { Button, Label, Text } from '@primer/react';
|
|
6
5
|
import { Box } from '@datalayer/primer-addons';
|
|
7
|
-
import { AssistantStage, } from '../../chat/assistant/AssistantStage.js';
|
|
8
6
|
import { useSignalValue } from '@datalayer/reactor/react';
|
|
9
7
|
import { teamNotebookKernel } from './teamNotebookKernel.js';
|
|
10
|
-
import { SpecMark } from '../../chat/marks/SpecMark.js';
|
|
11
8
|
import { callFlow, toolWords, } from './a2aTeamFlow.js';
|
|
12
|
-
import { NotebookPreview } from './NotebookPreview.js';
|
|
13
|
-
import { notebookBalloonVisual } from './TeamNotebook.js';
|
|
14
9
|
import { useBalloonRoom } from './useBalloonRoom.js';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
import { sceneBackdrop, sceneStageSx } from './backdrops/index.js';
|
|
11
|
+
import { AT_PLACE, Busy, MEMBER_HANDLES, Members, Moving, clampOffset, connectionNodeId, } from './backdrops/nodes.js';
|
|
12
|
+
import { STUDIO_LAYOUT } from './backdrops/studio.js';
|
|
13
|
+
export { MEMBER_HANDLES, clampOffset, connectionDetails, connectionLabel, memberLabel, } from './backdrops/nodes.js';
|
|
14
|
+
export { arrowPath } from './backdrops/edges.js';
|
|
15
|
+
export { CallEdge, LinkEdge, MemberNode, ConnectionNode, } from './backdrops/studio.js';
|
|
16
|
+
/** Whether a selection is on the edge between two ends: by its id, or by its ends either way. */
|
|
17
|
+
export function edgeSelected(selected, from, to) {
|
|
18
|
+
return Boolean(selected &&
|
|
19
|
+
(selected.id === `${from}->${to}` ||
|
|
20
|
+
(selected.from === from && selected.to === to) ||
|
|
21
|
+
(selected.from === to && selected.to === from)));
|
|
22
|
+
}
|
|
23
|
+
/** The studio's width at full scale: the entry, a peer, and the room between them. */
|
|
24
|
+
const WIDTH = STUDIO_LAYOUT.nodeWidth * 2 + STUDIO_LAYOUT.gap;
|
|
19
25
|
/** The room a balloon that holds a notebook needs above its member. */
|
|
20
26
|
const NOTEBOOK_BALLOON_ROOM = 480;
|
|
21
|
-
/** How tall the notebook in a balloon grows before it scrolls. */
|
|
22
|
-
const NOTEBOOK_PREVIEW_HEIGHT = 180;
|
|
23
|
-
const GAP = 220;
|
|
24
|
-
const WIDTH = NODE_WIDTH * 2 + GAP;
|
|
25
|
-
/** Between two rows of members. */
|
|
26
|
-
const ROW_GAP = 48;
|
|
27
|
-
/** A connection's node: half a member's. */
|
|
28
|
-
const CONNECTION_WIDTH = NODE_WIDTH / 2;
|
|
29
|
-
/**
|
|
30
|
-
* Between a member and its connections, to its right: room for the MCP
|
|
31
|
-
* edge's words, as the A2A edge has room for its own.
|
|
32
|
-
*/
|
|
33
|
-
const CONNECTION_GAP = 140;
|
|
34
|
-
const CONNECTIONS_APART = 16;
|
|
35
|
-
/** Where a member's connections start: right of it. */
|
|
36
|
-
const CONNECTION_X = NODE_WIDTH + CONNECTION_GAP;
|
|
37
|
-
/** Below the character's middle, so the A2A edge leaving it passes above. */
|
|
38
|
-
/** What a member's connections take beside it. */
|
|
39
|
-
const CONNECTIONS_BESIDE = CONNECTION_GAP + CONNECTION_WIDTH;
|
|
40
|
-
/** Where the edge leaves and reaches a member: the middle of its character. */
|
|
41
|
-
const HANDLE = (size) => ({
|
|
42
|
-
opacity: 0,
|
|
43
|
-
pointerEvents: 'none',
|
|
44
|
-
top: size / 2,
|
|
45
|
-
width: 1,
|
|
46
|
-
height: 1,
|
|
47
|
-
minWidth: 0,
|
|
48
|
-
minHeight: 0,
|
|
49
|
-
border: 0,
|
|
50
|
-
});
|
|
51
|
-
/**
|
|
52
|
-
* The handles an edge leaves and reaches a member by, named: the middle of
|
|
53
|
-
* its character on each side, chosen by where the other member stands.
|
|
54
|
-
*/
|
|
55
|
-
export const MEMBER_HANDLES = {
|
|
56
|
-
inLeft: 'in-left',
|
|
57
|
-
outRight: 'out-right',
|
|
58
|
-
inTop: 'in-top',
|
|
59
|
-
outBottom: 'out-bottom',
|
|
60
|
-
inRight: 'in-right',
|
|
61
|
-
outLeft: 'out-left',
|
|
62
|
-
inBottom: 'in-bottom',
|
|
63
|
-
outTop: 'out-top',
|
|
64
|
-
connections: 'connections',
|
|
65
|
-
};
|
|
66
27
|
/**
|
|
67
28
|
* The handles for an edge from one place to another: rightward across,
|
|
68
29
|
* leftward across, or down or up when the other member is mostly below or
|
|
@@ -92,243 +53,12 @@ export function handlesBetween(from, to) {
|
|
|
92
53
|
targetHandle: MEMBER_HANDLES.inBottom,
|
|
93
54
|
};
|
|
94
55
|
}
|
|
95
|
-
/**
|
|
96
|
-
* The members as they are now, by id. Not in the nodes' data: a node whose
|
|
97
|
-
* object changes is taken by React Flow for a new one, measured again, and
|
|
98
|
-
* the edge is dropped until it is — the line vanished as the characters
|
|
99
|
-
* moved. The nodes stay the same objects; what they show is read from here.
|
|
100
|
-
*/
|
|
101
|
-
const Members = createContext({});
|
|
102
|
-
/** The connections being called now, by node id: read by their nodes, as the members are. */
|
|
103
|
-
const Busy = createContext({});
|
|
104
|
-
const AT_PLACE = { dx: 0, dy: 0 };
|
|
105
|
-
/** An offset kept inside its bounds. */
|
|
106
|
-
export function clampOffset(offset, bounds) {
|
|
107
|
-
return {
|
|
108
|
-
dx: Math.min(bounds.maxDx, Math.max(bounds.minDx, offset.dx)),
|
|
109
|
-
dy: Math.min(bounds.maxDy, Math.max(bounds.minDy, offset.dy)),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
56
|
/**
|
|
113
57
|
* Where the members were moved, by page and member: kept for the session,
|
|
114
58
|
* in memory, so a graph drawn again on the page keeps them where they were.
|
|
115
59
|
*/
|
|
116
60
|
const MOVED = new Map();
|
|
117
61
|
const movedKey = (id) => `${typeof location === 'undefined' ? '' : location.pathname}#${id}`;
|
|
118
|
-
/** How far a press moves before it is a drag, not a click, in pixels. */
|
|
119
|
-
const DRAG_THRESHOLD = 4;
|
|
120
|
-
/** How far an arrow key moves a member; with Shift, more. */
|
|
121
|
-
const NUDGE = 10;
|
|
122
|
-
const NUDGE_FAR = 40;
|
|
123
|
-
/** The members' moves, read by their nodes. */
|
|
124
|
-
const Moving = createContext({
|
|
125
|
-
zoom: 1,
|
|
126
|
-
offsetOf: () => AT_PLACE,
|
|
127
|
-
move: () => undefined,
|
|
128
|
-
reset: () => undefined,
|
|
129
|
-
});
|
|
130
|
-
/** A connection's node id: beside its member. */
|
|
131
|
-
const connectionNodeId = (member, connection) => `${member}/${connection}`;
|
|
132
|
-
const MemberNode = memo(function MemberNode({ data, }) {
|
|
133
|
-
const { id, side, size } = data;
|
|
134
|
-
const member = useContext(Members)[id];
|
|
135
|
-
const moving = useContext(Moving);
|
|
136
|
-
const offset = moving.offsetOf(id);
|
|
137
|
-
const stageRef = useRef(null);
|
|
138
|
-
// Its balloon's display, as its menu changes it.
|
|
139
|
-
const [display, setDisplay] = useState();
|
|
140
|
-
// Its balloon as a click on it left it: shown, hidden, or as the team
|
|
141
|
-
// says (`auto`: what it is doing, and on hover).
|
|
142
|
-
const [shown, setShown] = useState('auto');
|
|
143
|
-
// What it says or does now: something new reopens a hidden balloon.
|
|
144
|
-
const persona = member?.persona;
|
|
145
|
-
const news = [
|
|
146
|
-
persona?.saying,
|
|
147
|
-
persona?.tool ? toolLineText(persona.tool) : '',
|
|
148
|
-
persona?.notebook?.name,
|
|
149
|
-
].join('\u0000');
|
|
150
|
-
const seen = useRef(news);
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
if (news !== seen.current) {
|
|
153
|
-
seen.current = news;
|
|
154
|
-
setShown(current => (current === 'hidden' ? 'auto' : current));
|
|
155
|
-
}
|
|
156
|
-
}, [news]);
|
|
157
|
-
if (!member || !persona) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
const shownDisplay = display ?? member.balloonDisplay ?? 'current';
|
|
161
|
-
// History: what it said and did, listed.
|
|
162
|
-
const listed = shownDisplay === 'history' && member.history?.length
|
|
163
|
-
? { history: member.history }
|
|
164
|
-
: {};
|
|
165
|
-
// A notebook it was given: read-only in its balloon, and its large visual
|
|
166
|
-
// the one that runs, editable — under the graph, or in a dialog.
|
|
167
|
-
const notebookTitle = member.notebookTitle ?? (persona.notebook?.name || 'The notebook');
|
|
168
|
-
const given = persona.notebook
|
|
169
|
-
? {
|
|
170
|
-
attachment: (_jsx(NotebookPreview, { notebook: persona.notebook.data, title: notebookTitle, maxHeight: NOTEBOOK_PREVIEW_HEIGHT, onOpen: member.onNotebookOpen, openLabel: member.expandTarget ? 'Open it below to run it' : undefined })),
|
|
171
|
-
visual: notebookBalloonVisual(persona.notebook, notebookTitle),
|
|
172
|
-
}
|
|
173
|
-
: {};
|
|
174
|
-
const balloonNow = persona.tool
|
|
175
|
-
? {
|
|
176
|
-
text: toolLineText(persona.tool),
|
|
177
|
-
tool: persona.tool,
|
|
178
|
-
busy: persona.state !== 'idle',
|
|
179
|
-
...given,
|
|
180
|
-
...listed,
|
|
181
|
-
}
|
|
182
|
-
: persona.saying
|
|
183
|
-
? {
|
|
184
|
-
text: persona.saying,
|
|
185
|
-
...(persona.full ? { fullText: persona.full } : {}),
|
|
186
|
-
more: persona.saying.endsWith('…'),
|
|
187
|
-
speaking: persona.state === 'speaking',
|
|
188
|
-
busy: persona.state === 'thinking' ||
|
|
189
|
-
persona.state === 'working' ||
|
|
190
|
-
persona.state === 'speaking',
|
|
191
|
-
...given,
|
|
192
|
-
...listed,
|
|
193
|
-
}
|
|
194
|
-
: 'history' in listed
|
|
195
|
-
? { text: '', ...listed }
|
|
196
|
-
: undefined;
|
|
197
|
-
return (_jsxs(Box
|
|
198
|
-
// React Flow lets the pointer through a node that is neither dragged
|
|
199
|
-
// nor selected (`pointer-events: none` on its wrapper): the character,
|
|
200
|
-
// its balloon and its menu take it back, and are left to themselves —
|
|
201
|
-
// no drag, no pan, no wheel.
|
|
202
|
-
, {
|
|
203
|
-
// React Flow lets the pointer through a node that is neither dragged
|
|
204
|
-
// nor selected (`pointer-events: none` on its wrapper): the character,
|
|
205
|
-
// its balloon and its menu take it back, and are left to themselves —
|
|
206
|
-
// no drag, no pan, no wheel.
|
|
207
|
-
className: "nodrag nopan nowheel", width: NODE_WIDTH, display: "flex", flexDirection: "column", alignItems: "center", gap: 1, cursor: "default", pointerEvents: "all", "data-team-member": member.id, "data-member-balloon": shown, "data-member-moved": offset.dx || offset.dy
|
|
208
|
-
? `${Math.round(offset.dx)},${Math.round(offset.dy)}`
|
|
209
|
-
: undefined, "data-member-state": persona.state, children: [_jsx(Handle, { id: MEMBER_HANDLES.inLeft, type: "target", position: Position.Left, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.inRight, type: "target", position: Position.Right, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.inTop, type: "target", position: Position.Top, style: { ...HANDLE(size), top: 0 }, isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.inBottom, type: "target", position: Position.Bottom, style: { ...HANDLE(size), top: size }, isConnectable: false }), _jsx(Box, { width: size, height: size, position: "relative", children: persona.away ? (_jsxs(Button, { size: "small", sx: { position: 'absolute', top: size / 2 - 16, left: -24 }, onClick: () => member.onAway?.(false), children: ["Call ", member.name, " back"] })) : (_jsx(AssistantStage, { character: member.character, state: persona.state, size: size,
|
|
210
|
-
// In the node, not over the page; the balloon opens toward the
|
|
211
|
-
// middle of the graph: rightward from the left member.
|
|
212
|
-
place: side === 'left'
|
|
213
|
-
? { position: 'absolute', left: 'auto' }
|
|
214
|
-
: { position: 'absolute', right: 'auto' }, stageRef: stageRef,
|
|
215
|
-
// Dragged within the graph's box: a press that moves more than
|
|
216
|
-
// a few pixels is a drag; one that does not is a click, and the
|
|
217
|
-
// stage ignores the click that ends a drag.
|
|
218
|
-
onDragStart: event => {
|
|
219
|
-
if (event.button !== 0) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const start = { x: event.clientX, y: event.clientY };
|
|
223
|
-
const from = offset;
|
|
224
|
-
let dragging = false;
|
|
225
|
-
const onMove = (next) => {
|
|
226
|
-
const dx = next.clientX - start.x;
|
|
227
|
-
const dy = next.clientY - start.y;
|
|
228
|
-
if (!dragging && Math.hypot(dx, dy) <= DRAG_THRESHOLD) {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
dragging = true;
|
|
232
|
-
moving.move(id, {
|
|
233
|
-
dx: from.dx + dx / moving.zoom,
|
|
234
|
-
dy: from.dy + dy / moving.zoom,
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
const onUp = () => {
|
|
238
|
-
window.removeEventListener('pointermove', onMove);
|
|
239
|
-
window.removeEventListener('pointerup', onUp);
|
|
240
|
-
window.removeEventListener('pointercancel', onUp);
|
|
241
|
-
};
|
|
242
|
-
window.addEventListener('pointermove', onMove);
|
|
243
|
-
window.addEventListener('pointerup', onUp);
|
|
244
|
-
window.addEventListener('pointercancel', onUp);
|
|
245
|
-
}, onCharacterKeyDown: event => {
|
|
246
|
-
const step = event.shiftKey ? NUDGE_FAR : NUDGE;
|
|
247
|
-
const by = {
|
|
248
|
-
ArrowLeft: { dx: -step, dy: 0 },
|
|
249
|
-
ArrowRight: { dx: step, dy: 0 },
|
|
250
|
-
ArrowUp: { dx: 0, dy: -step },
|
|
251
|
-
ArrowDown: { dx: 0, dy: step },
|
|
252
|
-
};
|
|
253
|
-
const nudge = by[event.key];
|
|
254
|
-
if (nudge) {
|
|
255
|
-
event.preventDefault();
|
|
256
|
-
moving.move(id, {
|
|
257
|
-
dx: offset.dx + nudge.dx,
|
|
258
|
-
dy: offset.dy + nudge.dy,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}, onResetPosition: offset.dx || offset.dy ? () => moving.reset(id) : undefined, open: false, onToggle: member.onToggle ?? (() => undefined),
|
|
262
|
-
// A click shows and hides its balloon; the menu's *Ask Sales*
|
|
263
|
-
// still takes the reader to the composer.
|
|
264
|
-
onCharacterClick: () => setShown(current => current === 'shown' ||
|
|
265
|
-
(current === 'auto' &&
|
|
266
|
-
(persona.insist || display === 'history'))
|
|
267
|
-
? 'hidden'
|
|
268
|
-
: 'shown'), balloonDisplay: shownDisplay, onBalloonDisplayChange: next => {
|
|
269
|
-
setDisplay(next);
|
|
270
|
-
member.onBalloonDisplayChange?.(next);
|
|
271
|
-
}, conversationLabel: member.onToggle ? (member.conversationLabel ?? undefined) : false, balloon: shown === 'hidden'
|
|
272
|
-
? undefined
|
|
273
|
-
: (balloonNow ??
|
|
274
|
-
(shown === 'shown'
|
|
275
|
-
? {
|
|
276
|
-
text: `${member.name}, ${member.where ?? ''}`.replace(/, $/, ''),
|
|
277
|
-
}
|
|
278
|
-
: undefined)), expandTarget: member.expandTarget, expandOnArrival: !!member.expandTarget,
|
|
279
|
-
// History, chosen in its menu: shown until Current is.
|
|
280
|
-
insist: shown === 'shown' ||
|
|
281
|
-
(shown === 'auto' && (persona.insist || display === 'history')), onDismiss: away => member.onAway?.(away !== 'none'), suggestions: member.suggestions, onSuggestion: member.onSuggestion, inspector: member.inspector, inspectAgent: member.name, onStop: member.onStop, about: member.about, sandbox: member.sandbox, contextMenu: member.contextMenu,
|
|
282
|
-
// A member of the graph: what is clicked around it is the graph.
|
|
283
|
-
stayPut: true,
|
|
284
|
-
// Named as the member, not its character: `Sales (in your
|
|
285
|
-
// browser)`; the character describes it.
|
|
286
|
-
agentName: member.name, label: memberLabel(member) })) }), _jsxs(Text, { sx: { fontWeight: 600, textAlign: 'center' }, children: [member.emoji ? `${member.emoji} ` : '', member.name] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', textAlign: 'center' }, "data-member-where": "", children: member.where }), _jsx(Handle, { id: MEMBER_HANDLES.outRight, type: "source", position: Position.Right, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outLeft, type: "source", position: Position.Left, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outBottom, type: "source", position: Position.Bottom, style: { ...HANDLE(size), top: size }, isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outTop, type: "source", position: Position.Top, style: { ...HANDLE(size), top: 0 }, isConnectable: false }), member.connections?.length ? (_jsx(Handle, { id: MEMBER_HANDLES.connections, type: "source", position: Position.Right, style: { ...HANDLE(size), left: 'auto', right: 0 }, isConnectable: false })) : null] }));
|
|
287
|
-
});
|
|
288
|
-
/** A member's accessible name: its name, and where it runs. */
|
|
289
|
-
export function memberLabel(member) {
|
|
290
|
-
return member.where ? `${member.name} (${member.where})` : member.name;
|
|
291
|
-
}
|
|
292
|
-
/** A connection's accessible name: `Odoo, Accounting's MCP connection`. */
|
|
293
|
-
export function connectionLabel(connection, member) {
|
|
294
|
-
return member
|
|
295
|
-
? `${connection.label}, ${member}\u2019s MCP connection`
|
|
296
|
-
: `${connection.label}, an MCP connection`;
|
|
297
|
-
}
|
|
298
|
-
/** A connection of a member's: the server's mark, what it reaches, and how. */
|
|
299
|
-
const ConnectionNode = memo(function ConnectionNode({ id, data, }) {
|
|
300
|
-
const { member, connection: connectionId, size } = data;
|
|
301
|
-
const owner = useContext(Members)[member];
|
|
302
|
-
const connection = owner?.connections?.find(known => known.id === connectionId);
|
|
303
|
-
const tool = useContext(Busy)[id];
|
|
304
|
-
// Its details, shown and hidden by a click or Enter on its mark.
|
|
305
|
-
const [told, setTold] = useState(false);
|
|
306
|
-
const detailsId = useId();
|
|
307
|
-
if (!connection) {
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
return (_jsxs(Box, { className: "nodrag nopan nowheel", width: CONNECTION_WIDTH, display: "flex", flexDirection: "column", alignItems: "center", gap: "2px", cursor: "default", pointerEvents: "all", title: connection.name, "data-team-connection": connection.id, "data-connection-busy": tool ? 'true' : 'false', children: [_jsx(Handle, { type: "target", position: Position.Left, style: { ...HANDLE(size), left: (CONNECTION_WIDTH - size) / 2 }, isConnectable: false }), _jsx(Box, { as: "button", type: "button", className: tool ? 'a2a-team-connection-busy' : undefined, "aria-label": connectionLabel(connection, owner?.name), "aria-expanded": told, "aria-controls": detailsId, "data-connection-figure": "", onClick: () => setTold(shown => !shown), onKeyDown: (event) => {
|
|
311
|
-
if (event.key === 'Escape' && told) {
|
|
312
|
-
event.stopPropagation();
|
|
313
|
-
setTold(false);
|
|
314
|
-
}
|
|
315
|
-
}, width: size, height: size, p: 0, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: 2, border: "1px solid", borderColor: tool ? 'accent.emphasis' : 'border.default', bg: "canvas.default", cursor: "pointer", focusVisible: {
|
|
316
|
-
outline: '2px solid',
|
|
317
|
-
outlineColor: 'var(--focus-outlineColor, var(--fgColor-accent))',
|
|
318
|
-
outlineOffset: 2,
|
|
319
|
-
}, children: _jsx(SpecMark, { icon: connection.icon, emoji: connection.emoji, size: Math.round(size * 0.66) }) }), _jsx(Text, { sx: { fontWeight: 600, fontSize: 1, textAlign: 'center' }, children: connection.label }), connection.via && (_jsx(Text, { sx: { fontSize: '10px', color: 'fg.muted', textAlign: 'center' }, "data-connection-via": "", children: connection.via })), _jsx(Box, { id: detailsId, hidden: !told, "data-connection-details": "", fontSize: "10px", color: "fg.muted", textAlign: "center", maxWidth: CONNECTION_WIDTH + 40, children: connectionDetails(connection, owner?.name, tool) })] }));
|
|
320
|
-
});
|
|
321
|
-
/** What a connection's details say: the server, who reaches it, and the call running now. */
|
|
322
|
-
export function connectionDetails(connection, member, calling) {
|
|
323
|
-
const tools = connection.tools.length;
|
|
324
|
-
return [
|
|
325
|
-
`${connection.name}, an MCP server${member ? ` ${member} reaches` : ''}.`,
|
|
326
|
-
tools ? `${tools} tool${tools === 1 ? '' : 's'}.` : '',
|
|
327
|
-
calling ? `Now: ${calling}.` : '',
|
|
328
|
-
]
|
|
329
|
-
.filter(Boolean)
|
|
330
|
-
.join(' ');
|
|
331
|
-
}
|
|
332
62
|
/** The words on the edge for a flow. */
|
|
333
63
|
export function flowWords(flow, entry, peer) {
|
|
334
64
|
return flow === 'asking'
|
|
@@ -337,70 +67,17 @@ export function flowWords(flow, entry, peer) {
|
|
|
337
67
|
? `${peer} answers ${entry}`
|
|
338
68
|
: '';
|
|
339
69
|
}
|
|
340
|
-
const ARROW = 9;
|
|
341
70
|
/**
|
|
342
|
-
*
|
|
343
|
-
* the
|
|
71
|
+
* Where a member's connections sit: right of it, the first one's top where
|
|
72
|
+
* the backdrop puts it (level with the character's middle on `studio`),
|
|
73
|
+
* the next ones under it.
|
|
344
74
|
*/
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const from = forward ? source : target;
|
|
348
|
-
const length = Math.hypot(tip.x - from.x, tip.y - from.y) || 1;
|
|
349
|
-
// Along the edge, toward the tip; and across it.
|
|
350
|
-
const ax = (tip.x - from.x) / length;
|
|
351
|
-
const ay = (tip.y - from.y) / length;
|
|
352
|
-
const bx = tip.x - ax * ARROW;
|
|
353
|
-
const by = tip.y - ay * ARROW;
|
|
354
|
-
const px = -ay * (ARROW / 1.6);
|
|
355
|
-
const py = ax * (ARROW / 1.6);
|
|
356
|
-
return `M ${tip.x},${tip.y} L ${bx + px},${by + py} L ${bx - px},${by - py} Z`;
|
|
357
|
-
}
|
|
358
|
-
const LinkEdge = memo(function LinkEdge({ id, sourceX, sourceY, targetX, targetY, data, }) {
|
|
359
|
-
const flow = data?.flow ?? 'still';
|
|
360
|
-
const path = `M ${sourceX},${sourceY} L ${targetX},${targetY}`;
|
|
361
|
-
// The arrow at the end the message goes to, along the edge.
|
|
362
|
-
const towardPeer = flow === 'asking';
|
|
363
|
-
const arrow = arrowPath({ x: sourceX, y: sourceY }, { x: targetX, y: targetY }, towardPeer);
|
|
364
|
-
return (_jsxs(_Fragment, { children: [_jsx(BaseEdge, { id: id, path: path, style: {
|
|
365
|
-
stroke: data?.connected
|
|
366
|
-
? 'var(--borderColor-accent-emphasis)'
|
|
367
|
-
: 'var(--borderColor-muted)',
|
|
368
|
-
strokeWidth: 2,
|
|
369
|
-
strokeDasharray: data?.connected ? undefined : '6 6',
|
|
370
|
-
opacity: flow === 'still' ? 0.6 : 0.25,
|
|
371
|
-
} }), flow !== 'still' && (_jsxs("g", { "data-a2a-edge-flow": flow, children: [_jsx("path", { d: path, fill: "none", className: `a2a-team-flow a2a-team-flow-${flow}`, stroke: "var(--fgColor-accent)", strokeWidth: 3, strokeLinecap: "round", strokeDasharray: "10 14" }), _jsx("path", { d: arrow, fill: "var(--fgColor-accent)" })] })), _jsx(EdgeLabelRenderer, { children: _jsx(Box, { className: "nodrag nopan", position: "absolute", transform: `translate(-50%, 12px) translate(${(sourceX + targetX) / 2}px, ${(sourceY + targetY) / 2}px)`, pointerEvents: "none", textAlign: "center", children: _jsx(Label, { variant: data?.connected ? 'accent' : 'secondary', children: data?.label }) }) })] }));
|
|
372
|
-
});
|
|
373
|
-
/**
|
|
374
|
-
* A member's link to a connection, drawn as the A2A link is: still, flowing
|
|
375
|
-
* toward the connection while a call runs, and back with its result.
|
|
376
|
-
*/
|
|
377
|
-
export const CallEdge = memo(function CallEdge({ id, sourceX, sourceY, targetX, targetY, data, }) {
|
|
378
|
-
const flow = data?.flow ?? 'still';
|
|
379
|
-
const path = `M ${sourceX},${sourceY} L ${targetX},${targetY}`;
|
|
380
|
-
// The arrow at the end the call or its result goes to, along the edge.
|
|
381
|
-
const arrow = arrowPath({ x: sourceX, y: sourceY }, { x: targetX, y: targetY }, flow === 'asking');
|
|
382
|
-
return (_jsxs(_Fragment, { children: [_jsx(BaseEdge, { id: id, path: path, style: {
|
|
383
|
-
stroke: 'var(--borderColor-accent-emphasis)',
|
|
384
|
-
strokeWidth: 2,
|
|
385
|
-
opacity: flow === 'still' ? 0.6 : 0.25,
|
|
386
|
-
} }), flow !== 'still' && (_jsxs("g", { "data-mcp-edge-call": data?.calling, "data-mcp-edge-flow": flow, children: [_jsx("path", { d: path, fill: "none", className: `a2a-team-flow a2a-team-flow-${flow}`, stroke: "var(--fgColor-accent)", strokeWidth: 3, strokeLinecap: "round", strokeDasharray: "10 14" }), _jsx("path", { d: arrow, fill: "var(--fgColor-accent)" })] })), _jsx(EdgeLabelRenderer, { children: _jsx(Box, { className: "nodrag nopan", position: "absolute", transform: `translate(-50%, 12px) translate(${(sourceX + targetX) / 2}px, ${(sourceY + targetY) / 2}px)`, pointerEvents: "none", textAlign: "center", whiteSpace: "nowrap", "data-mcp-edge-label": "", children: _jsx(Label, { variant: "accent", children: data?.label }) }) })] }));
|
|
387
|
-
});
|
|
388
|
-
const NODE_TYPES = { member: MemberNode, connection: ConnectionNode };
|
|
389
|
-
const EDGE_TYPES = { a2a: LinkEdge, mcp: CallEdge };
|
|
390
|
-
/**
|
|
391
|
-
* Where a member's connections sit: right of it, the first one's mark level
|
|
392
|
-
* with the character's middle (a mark half the character's size, so a
|
|
393
|
-
* quarter down), the next ones under it.
|
|
394
|
-
*/
|
|
395
|
-
function connectionsY(memberY, size, count, connectionHeight) {
|
|
396
|
-
const top = memberY + size / 4;
|
|
397
|
-
return Array.from({ length: count }, (_, at) => top + at * (connectionHeight + CONNECTIONS_APART));
|
|
75
|
+
function connectionsY(memberY, top, count, connectionHeight, apart) {
|
|
76
|
+
return Array.from({ length: count }, (_, at) => memberY + top + at * (connectionHeight + apart));
|
|
398
77
|
}
|
|
399
78
|
/** How far down a member's connections reach, from its top; none, 0. */
|
|
400
|
-
function connectionsReach(
|
|
401
|
-
return count
|
|
402
|
-
? size / 4 + count * connectionHeight + (count - 1) * CONNECTIONS_APART
|
|
403
|
-
: 0;
|
|
79
|
+
function connectionsReach(top, count, connectionHeight, apart) {
|
|
80
|
+
return count ? top + count * connectionHeight + (count - 1) * apart : 0;
|
|
404
81
|
}
|
|
405
82
|
/** What a link to a connection says at rest, as `A2A` says the members' link. */
|
|
406
83
|
export const MCP_LABEL = 'MCP';
|
|
@@ -471,8 +148,58 @@ export function flowOfAll(flows) {
|
|
|
471
148
|
: 'still';
|
|
472
149
|
}
|
|
473
150
|
const NO_PEERS = [];
|
|
151
|
+
/**
|
|
152
|
+
* The rules on the graph's box: what every backdrop needs — React Flow
|
|
153
|
+
* overflowing so a balloon shows whole, the cursors, a busy connection's
|
|
154
|
+
* pulse, and no movement under reduced motion — then the backdrop's own
|
|
155
|
+
* and its variables, light and dark (`sceneStageSx`).
|
|
156
|
+
*/
|
|
157
|
+
function stageRules(theme, clickable) {
|
|
158
|
+
return {
|
|
159
|
+
// An edge is clicked for its latest exchange: it takes the pointer.
|
|
160
|
+
...(clickable
|
|
161
|
+
? { '& .react-flow__edge': { cursor: 'pointer', pointerEvents: 'all' } }
|
|
162
|
+
: {}),
|
|
163
|
+
'@keyframes a2aTeamPulse': {
|
|
164
|
+
'0%, 100%': { boxShadow: '0 0 0 0 transparent' },
|
|
165
|
+
'50%': {
|
|
166
|
+
boxShadow: '0 0 0 4px var(--bgColor-accent-muted)',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
'& .a2a-team-connection-busy': {
|
|
170
|
+
animation: 'a2aTeamPulse 1s ease-in-out infinite',
|
|
171
|
+
},
|
|
172
|
+
// The arrow and the words say which way; the movement is extra.
|
|
173
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
174
|
+
'& .a2a-team-flow, & .a2a-team-connection-busy': {
|
|
175
|
+
animation: 'none',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
'& .react-flow, & .react-flow__renderer, & .react-flow__viewport': {
|
|
179
|
+
overflow: 'visible',
|
|
180
|
+
},
|
|
181
|
+
'& .react-flow__node': { cursor: 'default' },
|
|
182
|
+
// A member is moved by dragging its figure.
|
|
183
|
+
'& [data-team-member] [data-assistant-figure]': { cursor: 'grab' },
|
|
184
|
+
'& [data-team-member] [data-assistant-figure]:active': {
|
|
185
|
+
cursor: 'grabbing',
|
|
186
|
+
},
|
|
187
|
+
...sceneStageSx(theme),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
474
190
|
/** Draw a team over A2A, each link moving with what travels on it. */
|
|
475
|
-
export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows, connected, label = 'A2A', labels, links, positions, size = 96, calls = [], balloonRoom
|
|
191
|
+
export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows, connected, label = 'A2A', labels, links, positions, size = 96, calls = [], balloonRoom, backdrop, selected, onSelectEdge, }) {
|
|
192
|
+
// How the stage is drawn: its nodes and edges, its colours, its layout.
|
|
193
|
+
const look = sceneBackdrop(backdrop);
|
|
194
|
+
const { layout: sized, theme } = look;
|
|
195
|
+
const NODE_WIDTH = sized.nodeWidth;
|
|
196
|
+
const GAP = sized.gap;
|
|
197
|
+
const ROW_GAP = sized.rowGap;
|
|
198
|
+
const CONNECTION_WIDTH = sized.connectionWidth;
|
|
199
|
+
const CONNECTIONS_APART = sized.connectionsApart;
|
|
200
|
+
const CONNECTION_X = NODE_WIDTH + sized.connectionGap;
|
|
201
|
+
const CONNECTIONS_BESIDE = sized.connectionGap + CONNECTION_WIDTH;
|
|
202
|
+
const roomFloor = balloonRoom ?? sized.balloonRoom;
|
|
476
203
|
const peers = useMemo(() => [...(peer ? [peer] : []), ...more], [peer, more]);
|
|
477
204
|
const members = useMemo(() => [entry, ...peers], [entry, peers]);
|
|
478
205
|
const memberOf = (id) => members.find(member => member.id === id);
|
|
@@ -502,16 +229,17 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
502
229
|
const placing = placeMembers(entry.id, peerIds, positions);
|
|
503
230
|
const hasConnections = members.some(member => member.connections?.length);
|
|
504
231
|
// The last column's connections sit beside it, inside the graph.
|
|
505
|
-
const drawn = Math.max(
|
|
232
|
+
const drawn = Math.max(NODE_WIDTH * 2 + GAP, placing.columns * NODE_WIDTH +
|
|
506
233
|
(placing.columns - 1) * GAP +
|
|
507
234
|
(hasConnections ? CONNECTIONS_BESIDE : 0));
|
|
508
235
|
const viewport = useMemo(() => teamViewport(width || drawn, drawn), [width, drawn]);
|
|
509
|
-
// The members' height: the
|
|
510
|
-
const memberHeight = size
|
|
511
|
-
const connectionSize = Math.round(size
|
|
512
|
-
// Its mark, its label and how it is reached
|
|
513
|
-
//
|
|
514
|
-
const connectionHeight = connectionSize
|
|
236
|
+
// The members' height: the figure, its name and where it runs.
|
|
237
|
+
const memberHeight = sized.memberHeight(size);
|
|
238
|
+
const connectionSize = Math.round(size * sized.connectionScale);
|
|
239
|
+
// Its mark, its label and how it is reached, and room under it before
|
|
240
|
+
// the graph's edge.
|
|
241
|
+
const connectionHeight = sized.connectionHeight(connectionSize);
|
|
242
|
+
const connectionsTop = sized.connectionsTop(size);
|
|
515
243
|
// The connections' ids, member by member: the nodes change only when they do.
|
|
516
244
|
const placed = members.map(member => ({
|
|
517
245
|
member: member.id,
|
|
@@ -523,13 +251,13 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
523
251
|
const measured = useBalloonRoom(box, {
|
|
524
252
|
anchor: '[data-team-member]',
|
|
525
253
|
zoom: viewport.zoom,
|
|
526
|
-
floor:
|
|
254
|
+
floor: roomFloor,
|
|
527
255
|
});
|
|
528
256
|
const room = members.some(member => member.persona.notebook)
|
|
529
257
|
? Math.max(NOTEBOOK_BALLOON_ROOM, measured)
|
|
530
258
|
: measured;
|
|
531
259
|
// Beside a member, its connections may reach below it: the row takes them.
|
|
532
|
-
const reach = Math.max(0, ...placed.map(({ connections }) => connectionsReach(
|
|
260
|
+
const reach = Math.max(0, ...placed.map(({ connections }) => connectionsReach(connectionsTop, connections.length, connectionHeight, CONNECTIONS_APART)));
|
|
533
261
|
const rowHeight = Math.max(memberHeight, reach);
|
|
534
262
|
const total = room + placing.rows * rowHeight + (placing.rows - 1) * ROW_GAP;
|
|
535
263
|
const height = Math.ceil(total * viewport.zoom);
|
|
@@ -546,7 +274,7 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
546
274
|
const layout = layoutOf(id);
|
|
547
275
|
const count = placed.find(({ member }) => member === id)?.connections.length ?? 0;
|
|
548
276
|
const beside = count ? CONNECTIONS_BESIDE : 0;
|
|
549
|
-
const tall = Math.max(memberHeight, connectionsReach(
|
|
277
|
+
const tall = Math.max(memberHeight, connectionsReach(connectionsTop, count, connectionHeight, CONNECTIONS_APART));
|
|
550
278
|
return {
|
|
551
279
|
minDx: -layout.x,
|
|
552
280
|
maxDx: drawn - NODE_WIDTH - beside - layout.x,
|
|
@@ -604,7 +332,7 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
604
332
|
},
|
|
605
333
|
// A member's connections move with it.
|
|
606
334
|
...connections.map((connection, at) => {
|
|
607
|
-
const ys = connectionsY(y,
|
|
335
|
+
const ys = connectionsY(y, connectionsTop, connections.length, connectionHeight, CONNECTIONS_APART);
|
|
608
336
|
return {
|
|
609
337
|
id: connectionNodeId(id, connection),
|
|
610
338
|
type: 'connection',
|
|
@@ -620,7 +348,18 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
620
348
|
data: { member: id, connection, size: connectionSize },
|
|
621
349
|
};
|
|
622
350
|
}),
|
|
623
|
-
]), [
|
|
351
|
+
]), [
|
|
352
|
+
layoutKey,
|
|
353
|
+
size,
|
|
354
|
+
memberHeight,
|
|
355
|
+
connectionSize,
|
|
356
|
+
connectionHeight,
|
|
357
|
+
connectionsTop,
|
|
358
|
+
CONNECTIONS_APART,
|
|
359
|
+
CONNECTION_X,
|
|
360
|
+
CONNECTION_WIDTH,
|
|
361
|
+
NODE_WIDTH,
|
|
362
|
+
]);
|
|
624
363
|
// The notebook open under the graph runs on a Pyodide kernel in the page:
|
|
625
364
|
// the entry's sandbox, while it is open.
|
|
626
365
|
const notebookKernel = useSignalValue(teamNotebookKernel);
|
|
@@ -683,10 +422,11 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
683
422
|
label: wordsOf(link)
|
|
684
423
|
? `${labelOf(link.to)} · ${wordsOf(link)}`
|
|
685
424
|
: labelOf(link.to),
|
|
425
|
+
selected: edgeSelected(selected, link.from, link.to),
|
|
686
426
|
...handlesBetween(layoutOf(link.from), layoutOf(link.to)),
|
|
687
427
|
})));
|
|
688
428
|
const edges = useMemo(() => [
|
|
689
|
-
...JSON.parse(linkKey).map(({ from, to, flow: linkFlow, connected: reached, label: said, sourceHandle, targetHandle, }) => ({
|
|
429
|
+
...JSON.parse(linkKey).map(({ from, to, flow: linkFlow, connected: reached, label: said, selected: chosen, sourceHandle, targetHandle, }) => ({
|
|
690
430
|
id: `${from}->${to}`,
|
|
691
431
|
source: from,
|
|
692
432
|
sourceHandle,
|
|
@@ -695,7 +435,12 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
695
435
|
type: 'a2a',
|
|
696
436
|
selectable: false,
|
|
697
437
|
focusable: false,
|
|
698
|
-
data: {
|
|
438
|
+
data: {
|
|
439
|
+
flow: linkFlow,
|
|
440
|
+
connected: reached,
|
|
441
|
+
label: said,
|
|
442
|
+
selected: chosen,
|
|
443
|
+
},
|
|
699
444
|
})),
|
|
700
445
|
...JSON.parse(placedKey).flatMap(({ member, connections }) => connections.map(connection => {
|
|
701
446
|
const node = connectionNodeId(member, connection);
|
|
@@ -713,48 +458,25 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
713
458
|
flow: carried?.flow ?? 'still',
|
|
714
459
|
calling,
|
|
715
460
|
label: carried?.label ?? MCP_LABEL,
|
|
461
|
+
selected: edgeSelected(selected, member, node),
|
|
716
462
|
},
|
|
717
463
|
};
|
|
718
464
|
})),
|
|
719
|
-
], [linkKey, placedKey, busy]);
|
|
465
|
+
], [linkKey, placedKey, busy, selected]);
|
|
720
466
|
const heard = [...words, ...busy.said].filter(Boolean).join('. ');
|
|
721
467
|
const flowNow = flowOfAll(peers.map(one => flowOf(one.id)));
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
'0%, 100%': { boxShadow: '0 0 0 0 transparent' },
|
|
735
|
-
'50%': {
|
|
736
|
-
boxShadow: '0 0 0 4px var(--bgColor-accent-muted)',
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
'& .a2a-team-connection-busy': {
|
|
740
|
-
animation: 'a2aTeamPulse 1s ease-in-out infinite',
|
|
741
|
-
},
|
|
742
|
-
// The arrow and the words say which way; the movement is extra.
|
|
743
|
-
'@media (prefers-reduced-motion: reduce)': {
|
|
744
|
-
'& .a2a-team-flow, & .a2a-team-connection-busy': {
|
|
745
|
-
animation: 'none',
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
'& .react-flow, & .react-flow__renderer, & .react-flow__viewport': {
|
|
749
|
-
overflow: 'visible',
|
|
750
|
-
},
|
|
751
|
-
'& .react-flow__node': { cursor: 'default' },
|
|
752
|
-
// A member is moved by dragging its character.
|
|
753
|
-
'& [data-team-member] [data-assistant-figure]': { cursor: 'grab' },
|
|
754
|
-
'& [data-team-member] [data-assistant-figure]:active': {
|
|
755
|
-
cursor: 'grabbing',
|
|
756
|
-
},
|
|
757
|
-
}, children: [_jsx(Box, { as: "span", "aria-live": "polite", position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap", children: heard }), _jsx(Moving.Provider, { value: movingValue, children: _jsx(Members.Provider, { value: membersById, children: _jsx(Busy.Provider, { value: busy.byNode, children: _jsx(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: NODE_TYPES, edgeTypes: EDGE_TYPES, viewport: viewport, nodesDraggable: false, nodesConnectable: false, nodesFocusable: false, edgesFocusable: false, elementsSelectable: false, deleteKeyCode: null, selectionKeyCode: null, multiSelectionKeyCode: null, panOnDrag: false, panOnScroll: false, zoomOnScroll: false, zoomOnPinch: false, zoomOnDoubleClick: false, preventScrolling: false, style: { overflow: 'visible', background: 'transparent' },
|
|
468
|
+
const clickable = Boolean(onSelectEdge);
|
|
469
|
+
const stageSx = useMemo(() => stageRules(theme, clickable), [theme, clickable]);
|
|
470
|
+
return (_jsxs(Box, { ref: box, "data-a2a-team-graph": "", "data-scene-backdrop": look.id, "data-a2a-flow": flowNow, "data-a2a-calls": calls.length, "data-a2a-members": members.length, "data-a2a-selected": selected ? selected.id : undefined, width: "100%", height: height, position: "relative", sx: stageSx, children: [_jsx(Box, { as: "span", "aria-live": "polite", position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap", children: heard }), _jsx(Moving.Provider, { value: movingValue, children: _jsx(Members.Provider, { value: membersById, children: _jsx(Busy.Provider, { value: busy.byNode, children: _jsx(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: look.nodeTypes, edgeTypes: look.edgeTypes,
|
|
471
|
+
// A click on an edge: its latest exchange, for the page (S3-02).
|
|
472
|
+
onEdgeClick: onSelectEdge
|
|
473
|
+
? (_event, edge) => onSelectEdge({
|
|
474
|
+
id: edge.id,
|
|
475
|
+
from: edge.source,
|
|
476
|
+
to: edge.target,
|
|
477
|
+
over: edge.type === 'mcp' ? 'mcp' : 'a2a',
|
|
478
|
+
})
|
|
479
|
+
: undefined, viewport: viewport, nodesDraggable: false, nodesConnectable: false, nodesFocusable: false, edgesFocusable: false, elementsSelectable: false, deleteKeyCode: null, selectionKeyCode: null, multiSelectionKeyCode: null, panOnDrag: false, panOnScroll: false, zoomOnScroll: false, zoomOnPinch: false, zoomOnDoubleClick: false, preventScrolling: false, style: { overflow: 'visible', background: 'transparent' },
|
|
758
480
|
// The library is MIT: its credit is not required on the page.
|
|
759
481
|
proOptions: { hideAttribution: true }, "aria-label": `${entry.name} and ${peers.map(one => one.name).join(', ')}, over A2A` }) }) }) })] }));
|
|
760
482
|
}
|