@datalayer/agent-runtimes 1.3.71 → 1.3.72
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/chat/ChatFloating.d.ts +16 -1
- package/lib/chat/ChatFloating.js +62 -17
- package/lib/chat/assistant/AssistantStage.d.ts +17 -2
- package/lib/chat/assistant/AssistantStage.js +3 -3
- package/lib/chat/assistant/BalloonParts.d.ts +55 -0
- package/lib/chat/assistant/BalloonParts.js +140 -0
- package/lib/chat/assistant/ConversationBalloon.d.ts +5 -0
- package/lib/chat/assistant/ConversationBalloon.js +5 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +23 -2
- package/lib/chat/assistant/SpeechBalloon.js +22 -8
- package/lib/chat/assistant/toolLine.d.ts +69 -0
- package/lib/chat/assistant/toolLine.js +118 -0
- package/lib/components/teams/A2ATeamGraph.d.ts +12 -0
- package/lib/components/teams/A2ATeamGraph.js +35 -8
- package/lib/components/teams/NotebookPreview.d.ts +39 -0
- package/lib/components/teams/NotebookPreview.js +68 -0
- package/lib/components/teams/NotebookPreviewView.d.ts +21 -0
- package/lib/components/teams/NotebookPreviewView.js +42 -0
- package/lib/components/teams/TeamNotebook.js +16 -1
- package/lib/components/teams/a2aTeamFlow.d.ts +5 -0
- package/lib/components/teams/a2aTeamFlow.js +11 -0
- package/lib/components/teams/index.d.ts +1 -0
- package/lib/components/teams/index.js +1 -0
- package/lib/components/teams/useA2ATeam.d.ts +8 -0
- package/lib/components/teams/useA2ATeam.js +38 -3
- package/lib/examples/AgentA2ATeamExample.d.ts +8 -4
- package/lib/examples/AgentA2ATeamExample.js +2 -2
- package/lib/examples/ChatAssistantExample.js +4 -2
- package/lib/examples/ChatAssistantGalleryExample.d.ts +5 -1
- package/lib/examples/ChatAssistantGalleryExample.js +40 -10
- package/lib/examples/utils/AssistantGalleryGrid.d.ts +14 -11
- package/lib/examples/utils/AssistantGalleryGrid.js +42 -1
- package/lib/examples/utils/assistantGallery.d.ts +49 -1
- package/lib/examples/utils/assistantGallery.js +52 -1
- package/lib/loop/apps/appspec.js +6 -0
- package/lib/loop/apps/checks.js +2 -0
- package/lib/loop/embed/AppEmbed.js +3 -1
- package/lib/loop/plugins/assistant/index.d.ts +14 -1
- package/lib/loop/plugins/assistant/index.js +39 -13
- package/lib/specs/appspecSchema.js +18 -0
- package/lib/stories/loop/ReferenceScreens.d.ts +1 -1
- package/lib/stories/loop/ReferenceScreens.js +20 -3
- package/lib/types/agentspecs.d.ts +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the balloon shows the conversation: `history`, every message,
|
|
3
|
+
* scrolled, the composer last; `current`, only what it says or does now, in
|
|
4
|
+
* one balloon that replaces its words as the turn goes on.
|
|
5
|
+
*/
|
|
6
|
+
export type BalloonDisplay = 'history' | 'current';
|
|
7
|
+
/** The two displays, in the Appspec's order (`interface.balloon`). */
|
|
8
|
+
export declare const BALLOON_DISPLAYS: readonly BalloonDisplay[];
|
|
9
|
+
/** The floating chat's display when nothing says one. */
|
|
10
|
+
export declare const DEFAULT_BALLOON_DISPLAY: BalloonDisplay;
|
|
11
|
+
/** Where a tool call is: running, returned, or failed. */
|
|
12
|
+
export type ToolLinePhase = 'running' | 'done' | 'failed';
|
|
13
|
+
/** A tool call, as the balloon says it. */
|
|
14
|
+
export interface BalloonToolLine {
|
|
15
|
+
/** The call's id: a start and its end share it. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** The tool's name, as the agent calls it (`list_invoices`). */
|
|
18
|
+
tool: string;
|
|
19
|
+
/** How it is said: the spec's display name, else the name. */
|
|
20
|
+
name: string;
|
|
21
|
+
phase: ToolLinePhase;
|
|
22
|
+
/**
|
|
23
|
+
* Its own words, when its runtime said what it does (`Writing a
|
|
24
|
+
* notebook…`, `Asking Accounting…`): said in place of "Using …".
|
|
25
|
+
*/
|
|
26
|
+
words?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How a tool is said: the display name its runtime spec gives it (`Current
|
|
30
|
+
* Time` for `current_time`), else its own name without the MCP server's
|
|
31
|
+
* prefix (`odoo__list_invoices` is `list_invoices`).
|
|
32
|
+
*/
|
|
33
|
+
export declare function toolDisplayName(tool: string): string;
|
|
34
|
+
/** The three parts a tool line is said in: before the name, the name, after it. */
|
|
35
|
+
export declare function toolLineParts(line: BalloonToolLine): {
|
|
36
|
+
before: string;
|
|
37
|
+
name: string;
|
|
38
|
+
after: string;
|
|
39
|
+
};
|
|
40
|
+
/** A tool line in plain words: "Using list_invoices…". */
|
|
41
|
+
export declare function toolLineText(line: BalloonToolLine): string;
|
|
42
|
+
/**
|
|
43
|
+
* The tool line of the conversation's newest item, when that item is a tool
|
|
44
|
+
* call: running until it is complete or failed. Once words follow it, the
|
|
45
|
+
* newest item is the answer, and there is no tool line.
|
|
46
|
+
*/
|
|
47
|
+
export declare function newestToolLine(items: readonly unknown[]): BalloonToolLine | undefined;
|
|
48
|
+
/** A tool step a peer told over A2A (`a2aPeer`'s `A2APeerToolStep`). */
|
|
49
|
+
export interface ToolStep {
|
|
50
|
+
id?: string;
|
|
51
|
+
name: string;
|
|
52
|
+
ended: boolean;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
/** The tool line of a step a peer told: its start running, its end done or failed. */
|
|
56
|
+
export declare function toolLineOfStep(step: ToolStep, name?: string): BalloonToolLine;
|
|
57
|
+
/**
|
|
58
|
+
* What a screen reader hears of a tool line: once when the call starts and
|
|
59
|
+
* once when it ends — a new id or a new phase — never while it runs.
|
|
60
|
+
*/
|
|
61
|
+
export declare function toolAnnouncement(line: BalloonToolLine | undefined, previous: {
|
|
62
|
+
id: string;
|
|
63
|
+
phase: ToolLinePhase;
|
|
64
|
+
} | undefined): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* How many messages the conversation holds, as the `history` balloon's
|
|
67
|
+
* header counts them: the person's and the agent's, not the tool calls.
|
|
68
|
+
*/
|
|
69
|
+
export declare function conversationCount(items: readonly unknown[]): number;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* What the floating assistant's balloon shows (LOOP T-23): the whole
|
|
7
|
+
* conversation (`history`), or only what it says or does now (`current`);
|
|
8
|
+
* and, either way, the tool it calls, in plain words — "Using
|
|
9
|
+
* **list_invoices**…", then "Done: list_invoices" or "list_invoices failed".
|
|
10
|
+
*
|
|
11
|
+
* @module chat/assistant/toolLine
|
|
12
|
+
*/
|
|
13
|
+
import { BACKEND_TOOL_CATALOG } from '../../specs/backendTools';
|
|
14
|
+
/** The two displays, in the Appspec's order (`interface.balloon`). */
|
|
15
|
+
export const BALLOON_DISPLAYS = [
|
|
16
|
+
'history',
|
|
17
|
+
'current',
|
|
18
|
+
];
|
|
19
|
+
/** The floating chat's display when nothing says one. */
|
|
20
|
+
export const DEFAULT_BALLOON_DISPLAY = 'history';
|
|
21
|
+
let displayNames = null;
|
|
22
|
+
/**
|
|
23
|
+
* How a tool is said: the display name its runtime spec gives it (`Current
|
|
24
|
+
* Time` for `current_time`), else its own name without the MCP server's
|
|
25
|
+
* prefix (`odoo__list_invoices` is `list_invoices`).
|
|
26
|
+
*/
|
|
27
|
+
export function toolDisplayName(tool) {
|
|
28
|
+
if (!displayNames) {
|
|
29
|
+
displayNames = new Map();
|
|
30
|
+
for (const spec of Object.values(BACKEND_TOOL_CATALOG)) {
|
|
31
|
+
const method = spec.runtime?.method;
|
|
32
|
+
if (method && spec.name && !displayNames.has(method)) {
|
|
33
|
+
displayNames.set(method, spec.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const named = displayNames.get(tool);
|
|
38
|
+
if (named) {
|
|
39
|
+
return named;
|
|
40
|
+
}
|
|
41
|
+
const at = tool.indexOf('__');
|
|
42
|
+
return at > 0 ? tool.slice(at + 2) : tool;
|
|
43
|
+
}
|
|
44
|
+
/** The three parts a tool line is said in: before the name, the name, after it. */
|
|
45
|
+
export function toolLineParts(line) {
|
|
46
|
+
if (line.phase === 'running' && line.words) {
|
|
47
|
+
return { before: '', name: '', after: line.words };
|
|
48
|
+
}
|
|
49
|
+
switch (line.phase) {
|
|
50
|
+
case 'running':
|
|
51
|
+
return { before: 'Using ', name: line.name, after: '…' };
|
|
52
|
+
case 'done':
|
|
53
|
+
return { before: 'Done: ', name: line.name, after: '' };
|
|
54
|
+
case 'failed':
|
|
55
|
+
return { before: '', name: line.name, after: ' failed' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** A tool line in plain words: "Using list_invoices…". */
|
|
59
|
+
export function toolLineText(line) {
|
|
60
|
+
const { before, name, after } = toolLineParts(line);
|
|
61
|
+
return `${before}${name}${after}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The tool line of the conversation's newest item, when that item is a tool
|
|
65
|
+
* call: running until it is complete or failed. Once words follow it, the
|
|
66
|
+
* newest item is the answer, and there is no tool line.
|
|
67
|
+
*/
|
|
68
|
+
export function newestToolLine(items) {
|
|
69
|
+
const newest = items[items.length - 1];
|
|
70
|
+
if (!newest || typeof newest.toolName !== 'string' || !newest.toolName) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const phase = newest.status === 'error' ||
|
|
74
|
+
(typeof newest.error === 'string' && newest.error)
|
|
75
|
+
? 'failed'
|
|
76
|
+
: newest.status === 'complete'
|
|
77
|
+
? 'done'
|
|
78
|
+
: 'running';
|
|
79
|
+
return {
|
|
80
|
+
id: String(newest.toolCallId ?? newest.id ?? newest.toolName),
|
|
81
|
+
tool: newest.toolName,
|
|
82
|
+
name: toolDisplayName(newest.toolName),
|
|
83
|
+
phase,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** The tool line of a step a peer told: its start running, its end done or failed. */
|
|
87
|
+
export function toolLineOfStep(step, name = toolDisplayName(step.name)) {
|
|
88
|
+
return {
|
|
89
|
+
id: step.id ?? step.name,
|
|
90
|
+
tool: step.name,
|
|
91
|
+
name,
|
|
92
|
+
phase: step.error ? 'failed' : step.ended ? 'done' : 'running',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* What a screen reader hears of a tool line: once when the call starts and
|
|
97
|
+
* once when it ends — a new id or a new phase — never while it runs.
|
|
98
|
+
*/
|
|
99
|
+
export function toolAnnouncement(line, previous) {
|
|
100
|
+
if (!line) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (previous && previous.id === line.id && previous.phase === line.phase) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return toolLineText(line);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* How many messages the conversation holds, as the `history` balloon's
|
|
110
|
+
* header counts them: the person's and the agent's, not the tool calls.
|
|
111
|
+
*/
|
|
112
|
+
export function conversationCount(items) {
|
|
113
|
+
return items.filter(item => {
|
|
114
|
+
const message = item;
|
|
115
|
+
return ((message.role === 'user' || message.role === 'assistant') &&
|
|
116
|
+
typeof message.toolName !== 'string');
|
|
117
|
+
}).length;
|
|
118
|
+
}
|
|
@@ -27,6 +27,7 @@ import { type Edge, type EdgeProps, type Viewport } from '@xyflow/react';
|
|
|
27
27
|
import '@xyflow/react/dist/base.css';
|
|
28
28
|
import { type A2ATeamCall, type A2ATeamConnection, type A2ATeamFlow } from './a2aTeamFlow';
|
|
29
29
|
import type { A2ATeamPersona } from './useA2ATeam';
|
|
30
|
+
import type { BalloonDisplay } from '../../chat/assistant/toolLine';
|
|
30
31
|
/** One member of the team, as the graph draws it. */
|
|
31
32
|
export type A2ATeamGraphMember = {
|
|
32
33
|
id: string;
|
|
@@ -43,6 +44,17 @@ export type A2ATeamGraphMember = {
|
|
|
43
44
|
onAway?: (away: boolean) => void;
|
|
44
45
|
/** The MCP servers it reaches, drawn under it (`teamConnectionsOf`). */
|
|
45
46
|
connections?: A2ATeamConnection[];
|
|
47
|
+
/**
|
|
48
|
+
* How its balloon shows what it says (LOOP T-23): only what it says or
|
|
49
|
+
* does now (`current`, a team's default) — the words, the tool it calls,
|
|
50
|
+
* a notebook it was given — or a peek (`history`).
|
|
51
|
+
*/
|
|
52
|
+
balloonDisplay?: BalloonDisplay;
|
|
53
|
+
/**
|
|
54
|
+
* The notebook in its balloon was clicked: by default, the page's
|
|
55
|
+
* `TeamNotebook` is scrolled to and focused.
|
|
56
|
+
*/
|
|
57
|
+
onNotebookOpen?: () => void;
|
|
46
58
|
};
|
|
47
59
|
export type A2ATeamGraphProps = {
|
|
48
60
|
/** The member that asks: drawn on the left. */
|
|
@@ -7,9 +7,15 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
7
7
|
import { AssistantStage } from '../../chat/assistant/AssistantStage';
|
|
8
8
|
import { SpecMark } from '../../chat/marks/SpecMark';
|
|
9
9
|
import { toolWords, } from './a2aTeamFlow';
|
|
10
|
+
import { NotebookPreview, focusTeamNotebook } from './NotebookPreview';
|
|
11
|
+
import { toolLineText } from '../../chat/assistant/toolLine';
|
|
10
12
|
/** The graph's own coordinates: the members, and room above them for their balloons. */
|
|
11
13
|
const NODE_WIDTH = 200;
|
|
12
|
-
const BALLOON_ROOM =
|
|
14
|
+
const BALLOON_ROOM = 144;
|
|
15
|
+
/** The room a balloon that holds a notebook needs above its member. */
|
|
16
|
+
const NOTEBOOK_BALLOON_ROOM = 340;
|
|
17
|
+
/** How tall the notebook in a balloon grows before it scrolls. */
|
|
18
|
+
const NOTEBOOK_PREVIEW_HEIGHT = 180;
|
|
13
19
|
const GAP = 220;
|
|
14
20
|
const WIDTH = NODE_WIDTH * 2 + GAP;
|
|
15
21
|
/** A connection's node: half a member's. */
|
|
@@ -70,9 +76,25 @@ const MemberNode = memo(function MemberNode({ data, }) {
|
|
|
70
76
|
// middle of the graph: rightward from the left member.
|
|
71
77
|
place: side === 'left'
|
|
72
78
|
? { position: 'absolute', left: 'auto' }
|
|
73
|
-
: { position: 'absolute', right: 'auto' }, stageRef: stageRef, onDragStart: () => undefined, open: false, onToggle: member.onToggle ?? (() => undefined), balloon: persona.
|
|
74
|
-
? {
|
|
75
|
-
|
|
79
|
+
: { position: 'absolute', right: 'auto' }, stageRef: stageRef, onDragStart: () => undefined, open: false, onToggle: member.onToggle ?? (() => undefined), balloonDisplay: member.balloonDisplay ?? 'current', balloon: persona.tool
|
|
80
|
+
? {
|
|
81
|
+
text: toolLineText(persona.tool),
|
|
82
|
+
tool: persona.tool,
|
|
83
|
+
busy: persona.state !== 'idle',
|
|
84
|
+
}
|
|
85
|
+
: persona.saying
|
|
86
|
+
? {
|
|
87
|
+
text: persona.saying,
|
|
88
|
+
more: persona.saying.endsWith('…'),
|
|
89
|
+
speaking: persona.state === 'speaking',
|
|
90
|
+
busy: persona.state === 'thinking' ||
|
|
91
|
+
persona.state === 'working' ||
|
|
92
|
+
persona.state === 'speaking',
|
|
93
|
+
// A notebook it was given, read-only: the one that
|
|
94
|
+
// runs is under the graph.
|
|
95
|
+
attachment: persona.notebook ? (_jsx(NotebookPreview, { notebook: persona.notebook.data, title: persona.notebook.name || 'The notebook', maxHeight: NOTEBOOK_PREVIEW_HEIGHT, onOpen: member.onNotebookOpen ?? focusTeamNotebook })) : undefined,
|
|
96
|
+
}
|
|
97
|
+
: undefined, insist: persona.insist, onDismiss: away => member.onAway?.(away !== 'none'),
|
|
76
98
|
// A member of the graph: what is clicked around it is the graph.
|
|
77
99
|
stayPut: true })) }), _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] }));
|
|
78
100
|
});
|
|
@@ -201,7 +223,11 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
201
223
|
}));
|
|
202
224
|
const placedKey = JSON.stringify(placed);
|
|
203
225
|
const hasConnections = placed.some(({ connections }) => connections.length);
|
|
204
|
-
|
|
226
|
+
// A notebook in a balloon: more room above the members.
|
|
227
|
+
const room = [entry, peer].some(member => member.persona.notebook)
|
|
228
|
+
? NOTEBOOK_BALLOON_ROOM
|
|
229
|
+
: BALLOON_ROOM;
|
|
230
|
+
const height = Math.ceil((room +
|
|
205
231
|
memberHeight +
|
|
206
232
|
(hasConnections ? CONNECTION_GAP + connectionHeight : 0)) *
|
|
207
233
|
viewport.zoom);
|
|
@@ -209,7 +235,7 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
209
235
|
{
|
|
210
236
|
id: entry.id,
|
|
211
237
|
type: 'member',
|
|
212
|
-
position: { x: 0, y:
|
|
238
|
+
position: { x: 0, y: room },
|
|
213
239
|
width: NODE_WIDTH,
|
|
214
240
|
height: memberHeight,
|
|
215
241
|
draggable: false,
|
|
@@ -221,7 +247,7 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
221
247
|
{
|
|
222
248
|
id: peer.id,
|
|
223
249
|
type: 'member',
|
|
224
|
-
position: { x: NODE_WIDTH + GAP, y:
|
|
250
|
+
position: { x: NODE_WIDTH + GAP, y: room },
|
|
225
251
|
width: NODE_WIDTH,
|
|
226
252
|
height: memberHeight,
|
|
227
253
|
draggable: false,
|
|
@@ -236,7 +262,7 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
236
262
|
type: 'connection',
|
|
237
263
|
position: {
|
|
238
264
|
x: xs[at],
|
|
239
|
-
y:
|
|
265
|
+
y: room + memberHeight + CONNECTION_GAP,
|
|
240
266
|
},
|
|
241
267
|
width: CONNECTION_WIDTH,
|
|
242
268
|
height: connectionHeight,
|
|
@@ -250,6 +276,7 @@ export function A2ATeamGraph({ entry, peer, flow, connected, label = 'A2A', size
|
|
|
250
276
|
entry.id,
|
|
251
277
|
peer.id,
|
|
252
278
|
size,
|
|
279
|
+
room,
|
|
253
280
|
memberHeight,
|
|
254
281
|
placedKey,
|
|
255
282
|
connectionSize,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A notebook, read-only and small: what a member of the team gave, shown in
|
|
3
|
+
* its balloon (LOOP T-23, H-29). It is jupyter-react's notebook in read-only
|
|
4
|
+
* mode (`NotebookPreviewView`): its cells and the outputs it carries, nothing
|
|
5
|
+
* editable, on a service manager with no kernel (`ServiceManagerLess`) —
|
|
6
|
+
* nothing runs, and no Pyodide is loaded. The view is imported only when a
|
|
7
|
+
* notebook is drawn, so a balloon, and the page it is on, stay light until
|
|
8
|
+
* one arrives. It scrolls within its own height, and a click takes the
|
|
9
|
+
* reader to the notebook that runs (`TeamNotebook`).
|
|
10
|
+
*
|
|
11
|
+
* @module components/teams/NotebookPreview
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX } from 'react';
|
|
14
|
+
/** An nbformat 4 document, as far as the preview checks it. */
|
|
15
|
+
export interface NotebookDocument {
|
|
16
|
+
cells: unknown[];
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
nbformat?: number;
|
|
19
|
+
nbformat_minor?: number;
|
|
20
|
+
}
|
|
21
|
+
/** The document a notebook artifact carries, or none when it is not one. */
|
|
22
|
+
export declare function notebookDocument(data: unknown): NotebookDocument | undefined;
|
|
23
|
+
export type NotebookPreviewProps = {
|
|
24
|
+
/** The nbformat document (an artifact's `data`, parsed or not). */
|
|
25
|
+
notebook: unknown;
|
|
26
|
+
/** Its name, said to a screen reader: `Accounting's notebook`. */
|
|
27
|
+
title: string;
|
|
28
|
+
/** How tall it is, in pixels; it scrolls within. */
|
|
29
|
+
maxHeight?: number;
|
|
30
|
+
/** Takes the reader to the notebook that runs; the line under it says so. */
|
|
31
|
+
onOpen?: () => void;
|
|
32
|
+
};
|
|
33
|
+
/** A notebook, read-only and small, with the way to the one that runs. */
|
|
34
|
+
export declare function NotebookPreview({ notebook, title, maxHeight, onOpen, }: NotebookPreviewProps): JSX.Element | null;
|
|
35
|
+
/** Where the notebook that runs is: `TeamNotebook` marks its section. */
|
|
36
|
+
export declare const TEAM_NOTEBOOK_SELECTOR = "[data-team-notebook]";
|
|
37
|
+
/** Takes the reader to the notebook that runs: scrolled to, and focused. */
|
|
38
|
+
export declare function focusTeamNotebook(): void;
|
|
39
|
+
export default NotebookPreview;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Suspense, lazy } from 'react';
|
|
3
|
+
import { Text } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
/** jupyter-react's notebook, read-only, with no kernel: loaded when a notebook is drawn. */
|
|
6
|
+
const NotebookPreviewView = lazy(() => import('./NotebookPreviewView'));
|
|
7
|
+
/** The document a notebook artifact carries, or none when it is not one. */
|
|
8
|
+
export function notebookDocument(data) {
|
|
9
|
+
const parsed = typeof data === 'string'
|
|
10
|
+
? (() => {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(data);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
})()
|
|
18
|
+
: data;
|
|
19
|
+
if (parsed &&
|
|
20
|
+
typeof parsed === 'object' &&
|
|
21
|
+
Array.isArray(parsed.cells)) {
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/** A notebook, read-only and small, with the way to the one that runs. */
|
|
27
|
+
export function NotebookPreview({ notebook, title, maxHeight = 220, onOpen, }) {
|
|
28
|
+
const doc = notebookDocument(notebook);
|
|
29
|
+
if (!doc) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return (_jsxs(Box, { "data-notebook-preview": "", sx: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 }, children: [_jsx(Box, { role: "region", "aria-label": `${title}, read-only`, onClick: onOpen, "data-notebook-preview-cells": doc.cells.length, sx: {
|
|
33
|
+
position: 'relative',
|
|
34
|
+
height: maxHeight,
|
|
35
|
+
overflow: 'hidden',
|
|
36
|
+
border: '1px solid',
|
|
37
|
+
borderColor: 'border.muted',
|
|
38
|
+
borderRadius: 2,
|
|
39
|
+
bg: 'canvas.default',
|
|
40
|
+
cursor: onOpen ? 'pointer' : 'default',
|
|
41
|
+
}, children: _jsx(Suspense, { fallback: _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 0, m: 2 }, children: "Opening the notebook\u2026" }), children: _jsx(NotebookPreviewView, { nbformat: doc, height: maxHeight }) }) }), onOpen ? (_jsx(Text, { as: "button", type: "button", onClick: onOpen, "data-notebook-preview-open": "", sx: {
|
|
42
|
+
alignSelf: 'flex-start',
|
|
43
|
+
p: 0,
|
|
44
|
+
border: 0,
|
|
45
|
+
bg: 'transparent',
|
|
46
|
+
color: 'accent.fg',
|
|
47
|
+
fontSize: 0,
|
|
48
|
+
cursor: 'pointer',
|
|
49
|
+
'&:hover': { textDecoration: 'underline' },
|
|
50
|
+
}, children: "Open it below to run it" })) : null] }));
|
|
51
|
+
}
|
|
52
|
+
/** Where the notebook that runs is: `TeamNotebook` marks its section. */
|
|
53
|
+
export const TEAM_NOTEBOOK_SELECTOR = '[data-team-notebook]';
|
|
54
|
+
/** Takes the reader to the notebook that runs: scrolled to, and focused. */
|
|
55
|
+
export function focusTeamNotebook() {
|
|
56
|
+
const section = document.querySelector(TEAM_NOTEBOOK_SELECTOR);
|
|
57
|
+
if (!section) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const still = typeof window !== 'undefined' &&
|
|
61
|
+
!!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
62
|
+
section.scrollIntoView?.({
|
|
63
|
+
behavior: still ? 'auto' : 'smooth',
|
|
64
|
+
block: 'start',
|
|
65
|
+
});
|
|
66
|
+
section.focus({ preventScroll: true });
|
|
67
|
+
}
|
|
68
|
+
export default NotebookPreview;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jupyter-react's notebook, read-only and with no kernel: the notebook a
|
|
3
|
+
* member of the team gave, as its balloon shows it (`NotebookPreview`).
|
|
4
|
+
*
|
|
5
|
+
* `readonly` makes every cell uneditable; `ServiceManagerLess` is a service
|
|
6
|
+
* manager that reaches no server and starts no kernel, so nothing here runs
|
|
7
|
+
* and no Pyodide is loaded — the notebook that runs is `TeamNotebook`'s, on
|
|
8
|
+
* the browser sandbox. Loaded by `NotebookPreview` when a notebook is drawn,
|
|
9
|
+
* never before.
|
|
10
|
+
*
|
|
11
|
+
* @module components/teams/NotebookPreviewView
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX } from 'react';
|
|
14
|
+
import type { NotebookDocument } from './NotebookPreview';
|
|
15
|
+
export type NotebookPreviewViewProps = {
|
|
16
|
+
nbformat: NotebookDocument;
|
|
17
|
+
/** Its height, in pixels: it scrolls within. */
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function NotebookPreviewView({ nbformat, height, }: NotebookPreviewViewProps): JSX.Element;
|
|
21
|
+
export default NotebookPreviewView;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { useTheme } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { JupyterReactTheme, Notebook, ServiceManagerLess, } from '@datalayer/jupyter-react';
|
|
6
|
+
/** Each preview has its own id in jupyter-react's store. */
|
|
7
|
+
let drawn = 0;
|
|
8
|
+
export function NotebookPreviewView({ nbformat, height, }) {
|
|
9
|
+
// A manager with no server and no kernel: nothing can run.
|
|
10
|
+
const serviceManager = useMemo(() => new ServiceManagerLess(), []);
|
|
11
|
+
// A new id, and a copy handed once, for each notebook: the notebook
|
|
12
|
+
// component keeps, and edits in place, the document it is first handed.
|
|
13
|
+
const id = useMemo(() => {
|
|
14
|
+
drawn += 1;
|
|
15
|
+
return `team-notebook-preview-${drawn}`;
|
|
16
|
+
}, [nbformat]);
|
|
17
|
+
const content = useMemo(() => JSON.parse(JSON.stringify(nbformat)), [nbformat]);
|
|
18
|
+
const { colorScheme } = useTheme();
|
|
19
|
+
return (_jsxs(Box, { "data-notebook-preview-theme": "", sx: { height }, children: [_jsx("span", { "data-jupyter-react-theme-root": "notebook-preview", hidden: true }), _jsx(JupyterReactTheme, { colormode: colorScheme?.startsWith('dark') ? 'dark' : 'light', children: _jsx(Box, { "data-notebook-preview-view": id, sx: {
|
|
20
|
+
height,
|
|
21
|
+
// No toolbar: no room kept for one.
|
|
22
|
+
'& .datalayer-NotebookPanel-header': {
|
|
23
|
+
display: 'none',
|
|
24
|
+
minHeight: 0,
|
|
25
|
+
},
|
|
26
|
+
// Small, as a balloon holds it: no prompts, no sidebars, no
|
|
27
|
+
// collapsers, the headings a step down.
|
|
28
|
+
'& .jp-InputPrompt, & .jp-OutputPrompt, & .jp-Collapser, & .jp-cell-toolbar, & .jp-Notebook-footer': { display: 'none !important' },
|
|
29
|
+
'& .jp-Notebook': { padding: '4px !important', fontSize: '12px' },
|
|
30
|
+
'& .jp-Cell': { padding: '2px 0 !important' },
|
|
31
|
+
'& .jp-Cell .jp-InputArea-editor, & .jp-Cell .jp-OutputArea-output': { marginLeft: '0 !important' },
|
|
32
|
+
'& .jp-RenderedHTMLCommon': { paddingRight: 0, fontSize: '12px' },
|
|
33
|
+
'& .jp-RenderedHTMLCommon :is(h1, h2, h3, h4)': {
|
|
34
|
+
fontSize: '14px',
|
|
35
|
+
margin: '2px 0',
|
|
36
|
+
},
|
|
37
|
+
'& .jp-RenderedHTMLCommon .jp-InternalAnchorLink': {
|
|
38
|
+
display: 'none',
|
|
39
|
+
},
|
|
40
|
+
}, children: _jsx(Notebook, { id: id, nbformat: content, readonly: true, serviceManager: serviceManager, startDefaultKernel: false, height: `${height}px`, cellSidebarMargin: 0 }) }) })] }));
|
|
41
|
+
}
|
|
42
|
+
export default NotebookPreviewView;
|
|
@@ -17,6 +17,21 @@ export function notebookFileName(notebook) {
|
|
|
17
17
|
}
|
|
18
18
|
/** A notebook a member of the team gave, under its title, to run in the page. */
|
|
19
19
|
export function TeamNotebook({ notebook, title, height = 560, }) {
|
|
20
|
-
return (_jsxs(Box, { as: "section", "aria-label": title, "data-team-notebook": "",
|
|
20
|
+
return (_jsxs(Box, { as: "section", "aria-label": title, "data-team-notebook": "",
|
|
21
|
+
// Focused when the notebook in a balloon is clicked (`focusTeamNotebook`).
|
|
22
|
+
tabIndex: -1, sx: {
|
|
23
|
+
display: 'flex',
|
|
24
|
+
flexDirection: 'column',
|
|
25
|
+
gap: 2,
|
|
26
|
+
minWidth: 0,
|
|
27
|
+
scrollMarginTop: '16px',
|
|
28
|
+
'&:focus': { outline: 'none' },
|
|
29
|
+
'&:focus-visible': {
|
|
30
|
+
outline: '2px solid',
|
|
31
|
+
outlineColor: 'var(--focus-outlineColor, var(--fgColor-accent))',
|
|
32
|
+
outlineOffset: 4,
|
|
33
|
+
borderRadius: 2,
|
|
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 }) })] }));
|
|
21
36
|
}
|
|
22
37
|
export default TeamNotebook;
|
|
@@ -78,6 +78,11 @@ export declare const CALL_SHOWN_MS = 900;
|
|
|
78
78
|
export declare function connectionOfTool(tool: string, connections: A2ATeamConnection[]): A2ATeamConnection | undefined;
|
|
79
79
|
/** A tool's name in a few words, without its connection's prefix: `list invoices`. */
|
|
80
80
|
export declare function toolWords(tool: string, connection?: A2ATeamConnection): string;
|
|
81
|
+
/**
|
|
82
|
+
* A tool's own name, without its connection's prefix: `list_invoices` for
|
|
83
|
+
* `odoo_accounting_list_invoices` — as a balloon says it (LOOP T-23).
|
|
84
|
+
*/
|
|
85
|
+
export declare function toolOwnName(tool: string, connections: A2ATeamConnection[]): string;
|
|
81
86
|
/**
|
|
82
87
|
* The calls running after an event of the peer's (`member`): a call it
|
|
83
88
|
* starts to one of its connections is added; its end marks it ended, and
|
|
@@ -52,6 +52,17 @@ export function toolWords(tool, connection) {
|
|
|
52
52
|
}
|
|
53
53
|
return name.replace(/[_-]+/g, ' ').trim() || tool;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* A tool's own name, without its connection's prefix: `list_invoices` for
|
|
57
|
+
* `odoo_accounting_list_invoices` — as a balloon says it (LOOP T-23).
|
|
58
|
+
*/
|
|
59
|
+
export function toolOwnName(tool, connections) {
|
|
60
|
+
const prefix = connectionOfTool(tool, connections)?.prefix;
|
|
61
|
+
if (prefix && tool.includes(prefix)) {
|
|
62
|
+
return tool.slice(tool.indexOf(prefix) + prefix.length) || tool;
|
|
63
|
+
}
|
|
64
|
+
return tool;
|
|
65
|
+
}
|
|
55
66
|
/**
|
|
56
67
|
* The calls running after an event of the peer's (`member`): a call it
|
|
57
68
|
* starts to one of its connections is added; its end marks it ended, and
|
|
@@ -3,6 +3,7 @@ import { type BrowserModelOptions } from '../../runtimes/browser/model';
|
|
|
3
3
|
import { type A2APeer, type A2APeerArtifact } from '../../runtimes/browser/a2aPeer';
|
|
4
4
|
import type { AppSpec } from '../../types/agentspecs';
|
|
5
5
|
import { type A2ATeamCall, type A2ATeamConnection, type A2ATeamFlow } from './a2aTeamFlow';
|
|
6
|
+
import { type BalloonToolLine } from '../../chat/assistant/toolLine';
|
|
6
7
|
/** What one member's character does: its state, its balloon, and whether it was sent away. */
|
|
7
8
|
export type A2ATeamPersona = {
|
|
8
9
|
state: AssistantState;
|
|
@@ -11,6 +12,13 @@ export type A2ATeamPersona = {
|
|
|
11
12
|
/** Whether the balloon shows without the pointer over it. */
|
|
12
13
|
insist: boolean;
|
|
13
14
|
away: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The tool it calls now, or just called (LOOP T-23): "Using
|
|
17
|
+
* list_invoices…", "Asking Accounting…"; said in place of its words.
|
|
18
|
+
*/
|
|
19
|
+
tool?: BalloonToolLine;
|
|
20
|
+
/** A notebook it was given: in its balloon, read-only, under its words. */
|
|
21
|
+
notebook?: A2APeerArtifact;
|
|
14
22
|
};
|
|
15
23
|
export declare const AT_REST: A2ATeamPersona;
|
|
16
24
|
/** One turn of the conversation with the entry. */
|