@datalayer/agent-runtimes 1.3.74 → 1.3.78
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/lib/agents/AgentDetails.d.ts +16 -2
- package/lib/agents/AgentDetails.js +381 -272
- package/lib/chat/ChatFloating.d.ts +27 -1
- package/lib/chat/ChatFloating.js +97 -13
- package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
- package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
- package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
- package/lib/chat/assistant/AssistantContextMenu.js +68 -0
- package/lib/chat/assistant/AssistantStage.d.ts +144 -4
- package/lib/chat/assistant/AssistantStage.js +280 -10
- package/lib/chat/assistant/BalloonParts.d.ts +33 -5
- package/lib/chat/assistant/BalloonParts.js +66 -44
- package/lib/chat/assistant/BalloonVisual.d.ts +10 -2
- package/lib/chat/assistant/BalloonVisual.js +4 -3
- package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
- package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +54 -6
- package/lib/chat/assistant/SpeechBalloon.js +73 -22
- package/lib/chat/assistant/assistantDetails.d.ts +84 -0
- package/lib/chat/assistant/assistantDetails.js +140 -0
- package/lib/chat/assistant/formats/files.d.ts +23 -0
- package/lib/chat/assistant/formats/files.js +48 -0
- package/lib/chat/assistant/formats/index.d.ts +2 -0
- package/lib/chat/assistant/formats/index.js +1 -0
- package/lib/chat/assistant/state.d.ts +7 -14
- package/lib/chat/assistant/state.js +21 -20
- package/lib/chat/assistant/toolLine.d.ts +16 -0
- package/lib/chat/assistant/toolLine.js +46 -0
- package/lib/chat/base/ChatBase.js +71 -1
- package/lib/chat/base/loopMessage.d.ts +37 -0
- package/lib/chat/base/loopMessage.js +37 -0
- package/lib/chat/indicators/TypingDots.d.ts +13 -0
- package/lib/chat/indicators/TypingDots.js +21 -0
- package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
- package/lib/chat/messages/ChatMarkdown.js +18 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +25 -33
- package/lib/chat/styles/streamdownStyles.js +3 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/ToolCallDisplay.js +8 -7
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
- package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
- package/lib/components/inspector/AgentInspector.d.ts +59 -0
- package/lib/components/inspector/AgentInspector.js +220 -0
- package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
- package/lib/components/inspector/AgentInspectorDialog.js +9 -0
- package/lib/components/inspector/JsonTree.d.ts +24 -0
- package/lib/components/inspector/JsonTree.js +89 -0
- package/lib/components/inspector/a2aInspect.d.ts +56 -0
- package/lib/components/inspector/a2aInspect.js +478 -0
- package/lib/components/inspector/agentInspector.d.ts +179 -0
- package/lib/components/inspector/agentInspector.js +267 -0
- package/lib/components/inspector/chatInspect.d.ts +38 -0
- package/lib/components/inspector/chatInspect.js +65 -0
- package/lib/components/inspector/classify.d.ts +19 -0
- package/lib/components/inspector/classify.js +42 -0
- package/lib/components/inspector/index.d.ts +14 -0
- package/lib/components/inspector/index.js +18 -0
- package/lib/components/teams/A2ATeamGraph.d.ts +69 -1
- package/lib/components/teams/A2ATeamGraph.js +277 -37
- package/lib/components/teams/NotebookPreviewView.js +9 -1
- package/lib/components/teams/TeamNotebook.d.ts +3 -1
- package/lib/components/teams/TeamNotebook.js +3 -3
- package/lib/components/teams/TeamNotebookView.js +15 -0
- package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
- package/lib/components/teams/jupyterThemeVariables.js +441 -0
- package/lib/components/teams/teamConnections.d.ts +9 -1
- package/lib/components/teams/teamConnections.js +20 -0
- package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
- package/lib/components/teams/teamNotebookKernel.js +17 -0
- package/lib/components/teams/useA2ATeam.d.ts +20 -3
- package/lib/components/teams/useA2ATeam.js +108 -23
- package/lib/examples/AgentA2ATeamExample.d.ts +13 -0
- package/lib/examples/AgentA2ATeamExample.js +67 -13
- package/lib/examples/AssistantExample.js +65 -26
- package/lib/examples/AssistantGalleryExample.js +2 -1
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +1 -1
- package/lib/examples/utils/assistantGallery.d.ts +7 -6
- package/lib/examples/utils/assistantGallery.js +23 -8
- package/lib/jupyter/exec/Python.d.ts +0 -1
- package/lib/jupyter/exec/Python.js +0 -32
- package/lib/jupyter/exec/Snippets.d.ts +4 -12
- package/lib/jupyter/exec/Snippets.js +0 -8
- package/lib/loop/apps/AppFeedback.js +3 -1
- package/lib/loop/apps/AppKept.d.ts +22 -0
- package/lib/loop/apps/AppKept.js +36 -0
- package/lib/loop/apps/AppRenderer.d.ts +11 -1
- package/lib/loop/apps/AppRenderer.js +22 -4
- package/lib/loop/apps/checks.d.ts +7 -0
- package/lib/loop/apps/checks.js +39 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/kept.d.ts +30 -0
- package/lib/loop/apps/kept.js +52 -0
- package/lib/loop/apps/records.d.ts +12 -4
- package/lib/loop/apps/records.js +13 -5
- package/lib/loop/apps/saved.d.ts +30 -0
- package/lib/loop/apps/saved.js +40 -0
- package/lib/loop/core/index.d.ts +17 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/embed/AppEmbed.d.ts +8 -1
- package/lib/loop/embed/AppEmbed.js +4 -4
- package/lib/loop/embed/LoopEmbed.d.ts +6 -1
- package/lib/loop/embed/LoopEmbed.js +4 -2
- package/lib/loop/embed/element.js +1 -1
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
- package/lib/loop/plugins/agents/browserService.d.ts +7 -1
- package/lib/loop/plugins/agents/browserService.js +4 -0
- package/lib/loop/plugins/agents/plugin.d.ts +16 -0
- package/lib/loop/plugins/agents/switchable.d.ts +3 -0
- package/lib/loop/plugins/agents/switchable.js +1 -0
- package/lib/loop/plugins/app-activity/index.js +3 -0
- package/lib/loop/plugins/app-computer/index.js +3 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
- package/lib/loop/plugins/app-rules/index.js +3 -0
- package/lib/loop/plugins/assistant/index.d.ts +7 -2
- package/lib/loop/plugins/assistant/index.js +58 -4
- package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
- package/lib/loop/plugins/assistant-characters/index.js +17 -0
- package/lib/loop/plugins/chat/ChatView.js +4 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
- package/lib/loop/presets.d.ts +4 -0
- package/lib/loop/presets.js +3 -1
- package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
- package/lib/loop/shell/LoopWorkspace.js +5 -4
- package/lib/loop/shell/SidebarRail.d.ts +53 -0
- package/lib/loop/shell/SidebarRail.js +74 -0
- package/lib/loop/shell/index.d.ts +2 -0
- package/lib/loop/shell/index.js +2 -0
- package/lib/loop/shell/paneMotion.d.ts +21 -0
- package/lib/loop/shell/paneMotion.js +37 -0
- package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
- package/lib/specs/apps.js +8 -5
- package/lib/specs/uiPlugins.js +529 -0
- package/lib/types/agentspecs.d.ts +5 -3
- package/lib/types/chat.d.ts +12 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
- package/scripts/codegen/generate_ui_plugins.py +112 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, memo, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
|
+
import { createContext, memo, useContext, useEffect, useId, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { BaseEdge, EdgeLabelRenderer, Handle, Position, ReactFlow, } from '@xyflow/react';
|
|
4
4
|
import '@xyflow/react/dist/base.css';
|
|
5
5
|
import { Button, Label, Text } from '@primer/react';
|
|
6
6
|
import { Box } from '@datalayer/primer-addons';
|
|
7
|
-
import { AssistantStage } from '../../chat/assistant/AssistantStage';
|
|
7
|
+
import { AssistantStage, } from '../../chat/assistant/AssistantStage';
|
|
8
|
+
import { useSignalValue } from '@datalayer/reactor/react';
|
|
9
|
+
import { teamNotebookKernel } from './teamNotebookKernel';
|
|
8
10
|
import { SpecMark } from '../../chat/marks/SpecMark';
|
|
9
11
|
import { toolWords, } from './a2aTeamFlow';
|
|
10
12
|
import { NotebookPreview } from './NotebookPreview';
|
|
@@ -14,7 +16,7 @@ import { toolLineText } from '../../chat/assistant/toolLine';
|
|
|
14
16
|
const NODE_WIDTH = 200;
|
|
15
17
|
const BALLOON_ROOM = 144;
|
|
16
18
|
/** The room a balloon that holds a notebook needs above its member. */
|
|
17
|
-
const NOTEBOOK_BALLOON_ROOM =
|
|
19
|
+
const NOTEBOOK_BALLOON_ROOM = 480;
|
|
18
20
|
/** How tall the notebook in a balloon grows before it scrolls. */
|
|
19
21
|
const NOTEBOOK_PREVIEW_HEIGHT = 180;
|
|
20
22
|
const GAP = 220;
|
|
@@ -44,16 +46,67 @@ const HANDLE = (size) => ({
|
|
|
44
46
|
const Members = createContext({});
|
|
45
47
|
/** The connections being called now, by node id: read by their nodes, as the members are. */
|
|
46
48
|
const Busy = createContext({});
|
|
49
|
+
const AT_PLACE = { dx: 0, dy: 0 };
|
|
50
|
+
/** An offset kept inside its bounds. */
|
|
51
|
+
export function clampOffset(offset, bounds) {
|
|
52
|
+
return {
|
|
53
|
+
dx: Math.min(bounds.maxDx, Math.max(bounds.minDx, offset.dx)),
|
|
54
|
+
dy: Math.min(bounds.maxDy, Math.max(bounds.minDy, offset.dy)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Where the members were moved, by page and member: kept for the session,
|
|
59
|
+
* in memory, so a graph drawn again on the page keeps them where they were.
|
|
60
|
+
*/
|
|
61
|
+
const MOVED = new Map();
|
|
62
|
+
const movedKey = (id) => `${typeof location === 'undefined' ? '' : location.pathname}#${id}`;
|
|
63
|
+
/** How far a press moves before it is a drag, not a click, in pixels. */
|
|
64
|
+
const DRAG_THRESHOLD = 4;
|
|
65
|
+
/** How far an arrow key moves a member; with Shift, more. */
|
|
66
|
+
const NUDGE = 10;
|
|
67
|
+
const NUDGE_FAR = 40;
|
|
68
|
+
/** The members' moves, read by their nodes. */
|
|
69
|
+
const Moving = createContext({
|
|
70
|
+
zoom: 1,
|
|
71
|
+
offsetOf: () => AT_PLACE,
|
|
72
|
+
move: () => undefined,
|
|
73
|
+
reset: () => undefined,
|
|
74
|
+
});
|
|
47
75
|
/** A connection's node id: under its member. */
|
|
48
76
|
const connectionNodeId = (member, connection) => `${member}/${connection}`;
|
|
49
77
|
const MemberNode = memo(function MemberNode({ data, }) {
|
|
50
78
|
const { id, side, size } = data;
|
|
51
79
|
const member = useContext(Members)[id];
|
|
80
|
+
const moving = useContext(Moving);
|
|
81
|
+
const offset = moving.offsetOf(id);
|
|
52
82
|
const stageRef = useRef(null);
|
|
53
|
-
|
|
83
|
+
// Its balloon's display, as its menu changes it.
|
|
84
|
+
const [display, setDisplay] = useState();
|
|
85
|
+
// Its balloon as a click on it left it: shown, hidden, or as the team
|
|
86
|
+
// says (`auto`: what it is doing, and on hover).
|
|
87
|
+
const [shown, setShown] = useState('auto');
|
|
88
|
+
// What it says or does now: something new reopens a hidden balloon.
|
|
89
|
+
const persona = member?.persona;
|
|
90
|
+
const news = [
|
|
91
|
+
persona?.saying,
|
|
92
|
+
persona?.tool ? toolLineText(persona.tool) : '',
|
|
93
|
+
persona?.notebook?.name,
|
|
94
|
+
].join('\u0000');
|
|
95
|
+
const seen = useRef(news);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (news !== seen.current) {
|
|
98
|
+
seen.current = news;
|
|
99
|
+
setShown(current => (current === 'hidden' ? 'auto' : current));
|
|
100
|
+
}
|
|
101
|
+
}, [news]);
|
|
102
|
+
if (!member || !persona) {
|
|
54
103
|
return null;
|
|
55
104
|
}
|
|
56
|
-
const
|
|
105
|
+
const shownDisplay = display ?? member.balloonDisplay ?? 'current';
|
|
106
|
+
// History: what it said and did, listed.
|
|
107
|
+
const listed = shownDisplay === 'history' && member.history?.length
|
|
108
|
+
? { history: member.history }
|
|
109
|
+
: {};
|
|
57
110
|
// A notebook it was given: read-only in its balloon, and its large visual
|
|
58
111
|
// the one that runs, editable — under the graph, or in a dialog.
|
|
59
112
|
const notebookTitle = member.notebookTitle ?? (persona.notebook?.name || 'The notebook');
|
|
@@ -63,6 +116,29 @@ const MemberNode = memo(function MemberNode({ data, }) {
|
|
|
63
116
|
visual: notebookBalloonVisual(persona.notebook, notebookTitle),
|
|
64
117
|
}
|
|
65
118
|
: {};
|
|
119
|
+
const balloonNow = persona.tool
|
|
120
|
+
? {
|
|
121
|
+
text: toolLineText(persona.tool),
|
|
122
|
+
tool: persona.tool,
|
|
123
|
+
busy: persona.state !== 'idle',
|
|
124
|
+
...given,
|
|
125
|
+
...listed,
|
|
126
|
+
}
|
|
127
|
+
: persona.saying
|
|
128
|
+
? {
|
|
129
|
+
text: persona.saying,
|
|
130
|
+
...(persona.full ? { fullText: persona.full } : {}),
|
|
131
|
+
more: persona.saying.endsWith('…'),
|
|
132
|
+
speaking: persona.state === 'speaking',
|
|
133
|
+
busy: persona.state === 'thinking' ||
|
|
134
|
+
persona.state === 'working' ||
|
|
135
|
+
persona.state === 'speaking',
|
|
136
|
+
...given,
|
|
137
|
+
...listed,
|
|
138
|
+
}
|
|
139
|
+
: 'history' in listed
|
|
140
|
+
? { text: '', ...listed }
|
|
141
|
+
: undefined;
|
|
66
142
|
return (_jsxs(Box
|
|
67
143
|
// React Flow lets the pointer through a node that is neither dragged
|
|
68
144
|
// nor selected (`pointer-events: none` on its wrapper): the character,
|
|
@@ -81,37 +157,106 @@ const MemberNode = memo(function MemberNode({ data, }) {
|
|
|
81
157
|
gap: 1,
|
|
82
158
|
cursor: 'default',
|
|
83
159
|
pointerEvents: 'all',
|
|
84
|
-
}, "data-team-member": member.id, "data-member-
|
|
160
|
+
}, "data-team-member": member.id, "data-member-balloon": shown, "data-member-moved": offset.dx || offset.dy
|
|
161
|
+
? `${Math.round(offset.dx)},${Math.round(offset.dy)}`
|
|
162
|
+
: undefined, "data-member-state": persona.state, children: [_jsx(Handle, { type: "target", position: Position.Left, style: HANDLE(size), isConnectable: false }), _jsx(Box, { sx: { 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,
|
|
85
163
|
// In the node, not over the page; the balloon opens toward the
|
|
86
164
|
// middle of the graph: rightward from the left member.
|
|
87
165
|
place: side === 'left'
|
|
88
166
|
? { position: 'absolute', left: 'auto' }
|
|
89
|
-
: { position: 'absolute', right: 'auto' }, stageRef: stageRef,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
167
|
+
: { position: 'absolute', right: 'auto' }, stageRef: stageRef,
|
|
168
|
+
// Dragged within the graph's box: a press that moves more than
|
|
169
|
+
// a few pixels is a drag; one that does not is a click, and the
|
|
170
|
+
// stage ignores the click that ends a drag.
|
|
171
|
+
onDragStart: event => {
|
|
172
|
+
if (event.button !== 0) {
|
|
173
|
+
return;
|
|
95
174
|
}
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
...given,
|
|
175
|
+
const start = { x: event.clientX, y: event.clientY };
|
|
176
|
+
const from = offset;
|
|
177
|
+
let dragging = false;
|
|
178
|
+
const onMove = (next) => {
|
|
179
|
+
const dx = next.clientX - start.x;
|
|
180
|
+
const dy = next.clientY - start.y;
|
|
181
|
+
if (!dragging && Math.hypot(dx, dy) <= DRAG_THRESHOLD) {
|
|
182
|
+
return;
|
|
105
183
|
}
|
|
106
|
-
|
|
184
|
+
dragging = true;
|
|
185
|
+
moving.move(id, {
|
|
186
|
+
dx: from.dx + dx / moving.zoom,
|
|
187
|
+
dy: from.dy + dy / moving.zoom,
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const onUp = () => {
|
|
191
|
+
window.removeEventListener('pointermove', onMove);
|
|
192
|
+
window.removeEventListener('pointerup', onUp);
|
|
193
|
+
window.removeEventListener('pointercancel', onUp);
|
|
194
|
+
};
|
|
195
|
+
window.addEventListener('pointermove', onMove);
|
|
196
|
+
window.addEventListener('pointerup', onUp);
|
|
197
|
+
window.addEventListener('pointercancel', onUp);
|
|
198
|
+
}, onCharacterKeyDown: event => {
|
|
199
|
+
const step = event.shiftKey ? NUDGE_FAR : NUDGE;
|
|
200
|
+
const by = {
|
|
201
|
+
ArrowLeft: { dx: -step, dy: 0 },
|
|
202
|
+
ArrowRight: { dx: step, dy: 0 },
|
|
203
|
+
ArrowUp: { dx: 0, dy: -step },
|
|
204
|
+
ArrowDown: { dx: 0, dy: step },
|
|
205
|
+
};
|
|
206
|
+
const nudge = by[event.key];
|
|
207
|
+
if (nudge) {
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
moving.move(id, {
|
|
210
|
+
dx: offset.dx + nudge.dx,
|
|
211
|
+
dy: offset.dy + nudge.dy,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}, onResetPosition: offset.dx || offset.dy ? () => moving.reset(id) : undefined, open: false, onToggle: member.onToggle ?? (() => undefined),
|
|
215
|
+
// A click shows and hides its balloon; the menu's *Ask Sales*
|
|
216
|
+
// still takes the reader to the composer.
|
|
217
|
+
onCharacterClick: () => setShown(current => current === 'shown' ||
|
|
218
|
+
(current === 'auto' &&
|
|
219
|
+
(persona.insist || display === 'history'))
|
|
220
|
+
? 'hidden'
|
|
221
|
+
: 'shown'), balloonDisplay: shownDisplay, onBalloonDisplayChange: next => {
|
|
222
|
+
setDisplay(next);
|
|
223
|
+
member.onBalloonDisplayChange?.(next);
|
|
224
|
+
}, conversationLabel: member.onToggle ? (member.conversationLabel ?? undefined) : false, balloon: shown === 'hidden'
|
|
225
|
+
? undefined
|
|
226
|
+
: (balloonNow ??
|
|
227
|
+
(shown === 'shown'
|
|
228
|
+
? {
|
|
229
|
+
text: `${member.name}, ${member.where ?? ''}`.replace(/, $/, ''),
|
|
230
|
+
}
|
|
231
|
+
: undefined)), expandTarget: member.expandTarget, expandOnArrival: !!member.expandTarget,
|
|
232
|
+
// History, chosen in its menu: shown until Current is.
|
|
233
|
+
insist: shown === 'shown' ||
|
|
234
|
+
(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,
|
|
107
235
|
// A member of the graph: what is clicked around it is the graph.
|
|
108
|
-
stayPut: true
|
|
236
|
+
stayPut: true,
|
|
237
|
+
// Named as the member, not its character: `Sales (in your
|
|
238
|
+
// browser)`; the character describes it.
|
|
239
|
+
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, { type: "source", position: Position.Right, style: HANDLE(size), isConnectable: false }), member.connections?.length ? (_jsx(Handle, { id: "connections", type: "source", position: Position.Bottom, style: { ...HANDLE(size), top: 'auto', bottom: 0 }, isConnectable: false })) : null] }));
|
|
109
240
|
});
|
|
241
|
+
/** A member's accessible name: its name, and where it runs. */
|
|
242
|
+
export function memberLabel(member) {
|
|
243
|
+
return member.where ? `${member.name} (${member.where})` : member.name;
|
|
244
|
+
}
|
|
245
|
+
/** A connection's accessible name: `Odoo, Accounting's MCP connection`. */
|
|
246
|
+
export function connectionLabel(connection, member) {
|
|
247
|
+
return member
|
|
248
|
+
? `${connection.label}, ${member}\u2019s MCP connection`
|
|
249
|
+
: `${connection.label}, an MCP connection`;
|
|
250
|
+
}
|
|
110
251
|
/** A connection of a member's: the server's mark, what it reaches, and how. */
|
|
111
252
|
const ConnectionNode = memo(function ConnectionNode({ id, data, }) {
|
|
112
253
|
const { member, connection: connectionId, size } = data;
|
|
113
|
-
const
|
|
254
|
+
const owner = useContext(Members)[member];
|
|
255
|
+
const connection = owner?.connections?.find(known => known.id === connectionId);
|
|
114
256
|
const tool = useContext(Busy)[id];
|
|
257
|
+
// Its details, shown and hidden by a click or Enter on its mark.
|
|
258
|
+
const [told, setTold] = useState(false);
|
|
259
|
+
const detailsId = useId();
|
|
115
260
|
if (!connection) {
|
|
116
261
|
return null;
|
|
117
262
|
}
|
|
@@ -123,9 +268,15 @@ const ConnectionNode = memo(function ConnectionNode({ id, data, }) {
|
|
|
123
268
|
gap: '2px',
|
|
124
269
|
cursor: 'default',
|
|
125
270
|
pointerEvents: 'all',
|
|
126
|
-
}, title: connection.name, "data-team-connection": connection.id, "data-connection-busy": tool ? 'true' : 'false', children: [_jsx(Handle, { type: "target", position: Position.Top, style: { ...HANDLE(0), top: 0 }, isConnectable: false }), _jsx(Box, { className: tool ? 'a2a-team-connection-busy' : undefined,
|
|
271
|
+
}, title: connection.name, "data-team-connection": connection.id, "data-connection-busy": tool ? 'true' : 'false', children: [_jsx(Handle, { type: "target", position: Position.Top, style: { ...HANDLE(0), top: 0 }, 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) => {
|
|
272
|
+
if (event.key === 'Escape' && told) {
|
|
273
|
+
event.stopPropagation();
|
|
274
|
+
setTold(false);
|
|
275
|
+
}
|
|
276
|
+
}, sx: {
|
|
127
277
|
width: size,
|
|
128
278
|
height: size,
|
|
279
|
+
p: 0,
|
|
129
280
|
display: 'flex',
|
|
130
281
|
alignItems: 'center',
|
|
131
282
|
justifyContent: 'center',
|
|
@@ -133,8 +284,30 @@ const ConnectionNode = memo(function ConnectionNode({ id, data, }) {
|
|
|
133
284
|
border: '1px solid',
|
|
134
285
|
borderColor: tool ? 'accent.emphasis' : 'border.default',
|
|
135
286
|
bg: 'canvas.default',
|
|
136
|
-
|
|
287
|
+
cursor: 'pointer',
|
|
288
|
+
'&:focus-visible': {
|
|
289
|
+
outline: '2px solid',
|
|
290
|
+
outlineColor: 'var(--focus-outlineColor, var(--fgColor-accent))',
|
|
291
|
+
outlineOffset: 2,
|
|
292
|
+
},
|
|
293
|
+
}, 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": "", sx: {
|
|
294
|
+
fontSize: '10px',
|
|
295
|
+
color: 'fg.muted',
|
|
296
|
+
textAlign: 'center',
|
|
297
|
+
maxWidth: CONNECTION_WIDTH + 40,
|
|
298
|
+
}, children: connectionDetails(connection, owner?.name, tool) })] }));
|
|
137
299
|
});
|
|
300
|
+
/** What a connection's details say: the server, who reaches it, and the call running now. */
|
|
301
|
+
export function connectionDetails(connection, member, calling) {
|
|
302
|
+
const tools = connection.tools.length;
|
|
303
|
+
return [
|
|
304
|
+
`${connection.name}, an MCP server${member ? ` ${member} reaches` : ''}.`,
|
|
305
|
+
tools ? `${tools} tool${tools === 1 ? '' : 's'}.` : '',
|
|
306
|
+
calling ? `Now: ${calling}.` : '',
|
|
307
|
+
]
|
|
308
|
+
.filter(Boolean)
|
|
309
|
+
.join(' ');
|
|
310
|
+
}
|
|
138
311
|
/** The words on the edge for a flow. */
|
|
139
312
|
export function flowWords(flow, entry, peer) {
|
|
140
313
|
return flow === 'asking'
|
|
@@ -201,7 +374,7 @@ export function teamViewport(width) {
|
|
|
201
374
|
return { zoom, x: (width - WIDTH * zoom) / 2, y: 0 };
|
|
202
375
|
}
|
|
203
376
|
/** Draw a team of two over A2A, the link moving with what travels on it. */
|
|
204
|
-
export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size = 96, calls = [], }) {
|
|
377
|
+
export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size = 96, calls = [], balloonRoom = BALLOON_ROOM, }) {
|
|
205
378
|
const box = useRef(null);
|
|
206
379
|
const [width, setWidth] = useState(0);
|
|
207
380
|
useEffect(() => {
|
|
@@ -234,17 +407,53 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
234
407
|
const hasConnections = placed.some(({ connections }) => connections.length);
|
|
235
408
|
// A notebook in a balloon: more room above the members.
|
|
236
409
|
const room = [entry, peer].some(member => member.persona.notebook)
|
|
237
|
-
? NOTEBOOK_BALLOON_ROOM
|
|
238
|
-
:
|
|
410
|
+
? Math.max(NOTEBOOK_BALLOON_ROOM, balloonRoom)
|
|
411
|
+
: balloonRoom;
|
|
239
412
|
const height = Math.ceil((room +
|
|
240
413
|
memberHeight +
|
|
241
414
|
(hasConnections ? CONNECTION_GAP + connectionHeight : 0)) *
|
|
242
415
|
viewport.zoom);
|
|
416
|
+
// Where the members were moved, kept inside the graph's box.
|
|
417
|
+
const total = room +
|
|
418
|
+
memberHeight +
|
|
419
|
+
(hasConnections ? CONNECTION_GAP + connectionHeight : 0);
|
|
420
|
+
const boundsOf = (id) => {
|
|
421
|
+
const layoutX = id === entry.id ? 0 : NODE_WIDTH + GAP;
|
|
422
|
+
const below = placed.find(({ member }) => member === id)?.connections.length
|
|
423
|
+
? CONNECTION_GAP + connectionHeight
|
|
424
|
+
: 0;
|
|
425
|
+
return {
|
|
426
|
+
minDx: -layoutX,
|
|
427
|
+
maxDx: WIDTH - NODE_WIDTH - layoutX,
|
|
428
|
+
minDy: -room,
|
|
429
|
+
maxDy: total - room - memberHeight - below,
|
|
430
|
+
};
|
|
431
|
+
};
|
|
432
|
+
const [moved, setMoved] = useState(() => ({
|
|
433
|
+
[entry.id]: MOVED.get(movedKey(entry.id)) ?? AT_PLACE,
|
|
434
|
+
[peer.id]: MOVED.get(movedKey(peer.id)) ?? AT_PLACE,
|
|
435
|
+
}));
|
|
436
|
+
const offsetOf = (id) => clampOffset(moved[id] ?? AT_PLACE, boundsOf(id));
|
|
437
|
+
const entryOffset = offsetOf(entry.id);
|
|
438
|
+
const peerOffset = offsetOf(peer.id);
|
|
439
|
+
const movingValue = {
|
|
440
|
+
zoom: viewport.zoom,
|
|
441
|
+
offsetOf,
|
|
442
|
+
move: (id, offset) => {
|
|
443
|
+
const next = clampOffset(offset, boundsOf(id));
|
|
444
|
+
MOVED.set(movedKey(id), next);
|
|
445
|
+
setMoved(current => ({ ...current, [id]: next }));
|
|
446
|
+
},
|
|
447
|
+
reset: (id) => {
|
|
448
|
+
MOVED.delete(movedKey(id));
|
|
449
|
+
setMoved(current => ({ ...current, [id]: AT_PLACE }));
|
|
450
|
+
},
|
|
451
|
+
};
|
|
243
452
|
const nodes = useMemo(() => [
|
|
244
453
|
{
|
|
245
454
|
id: entry.id,
|
|
246
455
|
type: 'member',
|
|
247
|
-
position: { x:
|
|
456
|
+
position: { x: entryOffset.dx, y: room + entryOffset.dy },
|
|
248
457
|
width: NODE_WIDTH,
|
|
249
458
|
height: memberHeight,
|
|
250
459
|
draggable: false,
|
|
@@ -256,7 +465,10 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
256
465
|
{
|
|
257
466
|
id: peer.id,
|
|
258
467
|
type: 'member',
|
|
259
|
-
position: {
|
|
468
|
+
position: {
|
|
469
|
+
x: NODE_WIDTH + GAP + peerOffset.dx,
|
|
470
|
+
y: room + peerOffset.dy,
|
|
471
|
+
},
|
|
260
472
|
width: NODE_WIDTH,
|
|
261
473
|
height: memberHeight,
|
|
262
474
|
draggable: false,
|
|
@@ -265,13 +477,15 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
265
477
|
data: { id: peer.id, side: 'right', size },
|
|
266
478
|
},
|
|
267
479
|
...JSON.parse(placedKey).flatMap(({ member, connections }, side) => {
|
|
268
|
-
|
|
480
|
+
// A member's connections move with it.
|
|
481
|
+
const moveBy = side === 0 ? entryOffset : peerOffset;
|
|
482
|
+
const xs = connectionsX((side === 0 ? 0 : NODE_WIDTH + GAP) + moveBy.dx, connections.length);
|
|
269
483
|
return connections.map((connection, at) => ({
|
|
270
484
|
id: connectionNodeId(member, connection),
|
|
271
485
|
type: 'connection',
|
|
272
486
|
position: {
|
|
273
487
|
x: xs[at],
|
|
274
|
-
y: room + memberHeight + CONNECTION_GAP,
|
|
488
|
+
y: room + memberHeight + CONNECTION_GAP + moveBy.dy,
|
|
275
489
|
},
|
|
276
490
|
width: CONNECTION_WIDTH,
|
|
277
491
|
height: connectionHeight,
|
|
@@ -290,8 +504,29 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
290
504
|
placedKey,
|
|
291
505
|
connectionSize,
|
|
292
506
|
connectionHeight,
|
|
507
|
+
entryOffset.dx,
|
|
508
|
+
entryOffset.dy,
|
|
509
|
+
peerOffset.dx,
|
|
510
|
+
peerOffset.dy,
|
|
293
511
|
]);
|
|
294
|
-
|
|
512
|
+
// The notebook open under the graph runs on a Pyodide kernel in the page:
|
|
513
|
+
// the entry's sandbox, while it is open.
|
|
514
|
+
const notebookKernel = useSignalValue(teamNotebookKernel);
|
|
515
|
+
const entryMember = useMemo(() => entry.sandbox
|
|
516
|
+
? entry
|
|
517
|
+
: {
|
|
518
|
+
...entry,
|
|
519
|
+
// The entry runs in the page: its sandbox is the Pyodide kernel
|
|
520
|
+
// of the notebook open under the graph, and none until one is.
|
|
521
|
+
sandbox: notebookKernel
|
|
522
|
+
? {
|
|
523
|
+
kind: 'browser',
|
|
524
|
+
status: 'running',
|
|
525
|
+
connection: notebookKernel,
|
|
526
|
+
}
|
|
527
|
+
: { kind: 'browser', status: 'not running' },
|
|
528
|
+
}, [entry, notebookKernel]);
|
|
529
|
+
const members = useMemo(() => ({ [entry.id]: entryMember, [peer.id]: peer }), [entry.id, entryMember, peer]);
|
|
295
530
|
const words = flowWords(flow, entry.name, peer.name);
|
|
296
531
|
// The tool each connection is answering now, in words, by its node's id.
|
|
297
532
|
const busy = useMemo(() => {
|
|
@@ -379,6 +614,11 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
379
614
|
overflow: 'visible',
|
|
380
615
|
},
|
|
381
616
|
'& .react-flow__node': { cursor: 'default' },
|
|
617
|
+
// A member is moved by dragging its character.
|
|
618
|
+
'& [data-team-member] [data-assistant-figure]': { cursor: 'grab' },
|
|
619
|
+
'& [data-team-member] [data-assistant-figure]:active': {
|
|
620
|
+
cursor: 'grabbing',
|
|
621
|
+
},
|
|
382
622
|
}, children: [_jsx(Box, { as: "span", "aria-live": "polite", sx: {
|
|
383
623
|
position: 'absolute',
|
|
384
624
|
width: 1,
|
|
@@ -386,8 +626,8 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
386
626
|
overflow: 'hidden',
|
|
387
627
|
clip: 'rect(0 0 0 0)',
|
|
388
628
|
whiteSpace: 'nowrap',
|
|
389
|
-
}, children: heard }), _jsx(Members.Provider, { value: members, 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' },
|
|
390
|
-
|
|
391
|
-
|
|
629
|
+
}, children: heard }), _jsx(Moving.Provider, { value: movingValue, children: _jsx(Members.Provider, { value: members, 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' },
|
|
630
|
+
// The library is MIT: its credit is not required on the page.
|
|
631
|
+
proOptions: { hideAttribution: true }, "aria-label": `${entry.name} and ${peer.name}, over A2A` }) }) }) })] }));
|
|
392
632
|
}
|
|
393
633
|
export default A2ATeamGraph;
|
|
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
|
|
|
3
3
|
import { useTheme } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
5
|
import { JupyterReactTheme, Notebook, ServiceManagerLess, } from '@datalayer/jupyter-react';
|
|
6
|
+
import { JUPYTER_DARK_VARIABLES, JUPYTER_LIGHT_VARIABLES, } from './jupyterThemeVariables';
|
|
6
7
|
/** Each preview has its own id in jupyter-react's store. */
|
|
7
8
|
let drawn = 0;
|
|
8
9
|
export function NotebookPreviewView({ nbformat, height, }) {
|
|
@@ -16,7 +17,14 @@ export function NotebookPreviewView({ nbformat, height, }) {
|
|
|
16
17
|
}, [nbformat]);
|
|
17
18
|
const content = useMemo(() => JSON.parse(JSON.stringify(nbformat)), [nbformat]);
|
|
18
19
|
const { colorScheme } = useTheme();
|
|
19
|
-
|
|
20
|
+
const mode = colorScheme?.startsWith('dark') ? 'dark' : 'light';
|
|
21
|
+
return (_jsxs(Box, { "data-notebook-preview-theme": mode, sx: { height },
|
|
22
|
+
// JupyterLab writes its theme for the whole page, so two previews in
|
|
23
|
+
// two modes would share the last one written: each carries its own
|
|
24
|
+
// mode's variables on its own subtree.
|
|
25
|
+
style: (mode === 'dark'
|
|
26
|
+
? JUPYTER_DARK_VARIABLES
|
|
27
|
+
: JUPYTER_LIGHT_VARIABLES), children: [_jsx("span", { "data-jupyter-react-theme-root": "notebook-preview", hidden: true }), _jsx(JupyterReactTheme, { colormode: mode, children: _jsx(Box, { "data-notebook-preview-view": id, sx: {
|
|
20
28
|
height,
|
|
21
29
|
// No toolbar: no room kept for one.
|
|
22
30
|
'& .datalayer-NotebookPanel-header': {
|
|
@@ -25,11 +25,13 @@ export type TeamNotebookProps = {
|
|
|
25
25
|
title: string;
|
|
26
26
|
/** How tall the notebook is, in pixels. */
|
|
27
27
|
height?: number;
|
|
28
|
+
/** Leave the heading out: the dialog it is drawn in shows the title. */
|
|
29
|
+
hideTitle?: boolean;
|
|
28
30
|
};
|
|
29
31
|
/** The file a notebook is saved as: the peer's name for it, else its name. */
|
|
30
32
|
export declare function notebookFileName(notebook: A2APeerArtifact): string;
|
|
31
33
|
/** A notebook a member of the team gave, under its title, to run in the page. */
|
|
32
|
-
export declare function TeamNotebook({ notebook, title, height, }: TeamNotebookProps): JSX.Element;
|
|
34
|
+
export declare function TeamNotebook({ notebook, title, height, hideTitle, }: TeamNotebookProps): JSX.Element;
|
|
33
35
|
/**
|
|
34
36
|
* A notebook as a balloon's large visual: read-only in the balloon, and,
|
|
35
37
|
* expanded, this notebook — editable, run on the browser sandbox, loaded
|
|
@@ -16,7 +16,7 @@ export function notebookFileName(notebook) {
|
|
|
16
16
|
return `${stem}.ipynb`;
|
|
17
17
|
}
|
|
18
18
|
/** A notebook a member of the team gave, under its title, to run in the page. */
|
|
19
|
-
export function TeamNotebook({ notebook, title, height = 560, }) {
|
|
19
|
+
export function TeamNotebook({ notebook, title, height = 560, hideTitle = false, }) {
|
|
20
20
|
return (_jsxs(Box, { as: "section", "aria-label": title, "data-team-notebook": "",
|
|
21
21
|
// Focused when the notebook in a balloon is clicked (`focusExpanded`).
|
|
22
22
|
tabIndex: -1, sx: {
|
|
@@ -32,7 +32,7 @@ export function TeamNotebook({ notebook, title, height = 560, }) {
|
|
|
32
32
|
outlineOffset: 4,
|
|
33
33
|
borderRadius: 2,
|
|
34
34
|
},
|
|
35
|
-
}, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, m: 0 }, children: title }), _jsx(Suspense, { fallback: _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Opening the notebook\u2026" }), children: _jsx(TeamNotebookView, { notebook: notebook, fileName: notebookFileName(notebook), height: height }) })] }));
|
|
35
|
+
}, children: [!hideTitle && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, m: 0 }, children: title })), _jsx(Suspense, { fallback: _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Opening the notebook\u2026" }), children: _jsx(TeamNotebookView, { notebook: notebook, fileName: notebookFileName(notebook), height: height }) })] }));
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* A notebook as a balloon's large visual: read-only in the balloon, and,
|
|
@@ -43,7 +43,7 @@ export function notebookBalloonVisual(notebook, title, height) {
|
|
|
43
43
|
return {
|
|
44
44
|
id: `notebook:${notebook.filename ?? notebook.name}`,
|
|
45
45
|
title,
|
|
46
|
-
render:
|
|
46
|
+
render: place => (_jsx(TeamNotebook, { notebook: notebook, title: title, height: height, hideTitle: place === 'overlay' })),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
export default TeamNotebook;
|
|
@@ -8,6 +8,7 @@ import { useSignalValue } from '@datalayer/reactor/react';
|
|
|
8
8
|
import { notebookStore } from '@datalayer/jupyter-react';
|
|
9
9
|
import { EphemeralNotebook } from '../../chat/notebook/EphemeralNotebook';
|
|
10
10
|
import { createBrowserSandboxService } from '../../loop/plugins/agents/browserService';
|
|
11
|
+
import { teamNotebookKernel } from './teamNotebookKernel';
|
|
11
12
|
/** Each notebook drawn has its own id in jupyter-react's store. */
|
|
12
13
|
let drawn = 0;
|
|
13
14
|
/** Save a notebook as an `.ipynb` file, as Jupyter writes one. */
|
|
@@ -57,6 +58,20 @@ export function TeamNotebookView({ notebook, fileName, height, }) {
|
|
|
57
58
|
const manager = service.getServiceManager() ??
|
|
58
59
|
undefined;
|
|
59
60
|
const running = snapshot.state === 'running' && manager !== undefined;
|
|
61
|
+
// Say which kernel the notebook runs on, while it does: the browser
|
|
62
|
+
// agent's *Code Sandbox Details…* lists its variables.
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const kernel = running ? service.getKernelConnection() : null;
|
|
65
|
+
if (!kernel) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
teamNotebookKernel.value = kernel;
|
|
69
|
+
return () => {
|
|
70
|
+
if (teamNotebookKernel.peek() === kernel) {
|
|
71
|
+
teamNotebookKernel.value = null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}, [running, service]);
|
|
60
75
|
return (_jsx(QueryClientProvider, { client: queries, children: _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsxs(Box, { sx: {
|
|
61
76
|
display: 'flex',
|
|
62
77
|
alignItems: 'center',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JupyterLab's theme variables, light and dark, as its theme extensions state
|
|
3
|
+
* them. Generated by scripts/codegen/generate_jupyter_theme_variables.mjs: do
|
|
4
|
+
* not edit.
|
|
5
|
+
*
|
|
6
|
+
* @module components/teams/jupyterThemeVariables
|
|
7
|
+
*/
|
|
8
|
+
export declare const JUPYTER_LIGHT_VARIABLES: Readonly<Record<string, string>>;
|
|
9
|
+
export declare const JUPYTER_DARK_VARIABLES: Readonly<Record<string, string>>;
|