@datalayer/agent-runtimes 1.3.10 → 1.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/lib/AgentNode.js +3 -3
- package/lib/api/runtimes/environments.d.ts +22 -1
- package/lib/api/runtimes/environments.js +31 -0
- package/lib/api/runtimes/healthz.js +4 -2
- package/lib/api/spacer/healthz.js +4 -2
- package/lib/chat/ChatFloating.d.ts +8 -1
- package/lib/chat/ChatFloating.js +5 -2
- package/lib/chat/base/ChatBase.js +39 -25
- package/lib/chat/base/modelChoice.d.ts +62 -0
- package/lib/chat/base/modelChoice.js +56 -0
- package/lib/chat/display/ButtonGlow.d.ts +9 -0
- package/lib/chat/display/ButtonGlow.js +60 -0
- package/lib/chat/display/EmptyState.js +4 -1
- package/lib/chat/header/ChatHeaderBase.js +4 -56
- package/lib/chat/header/ChatViewModeToggle.d.ts +29 -0
- package/lib/chat/header/ChatViewModeToggle.js +55 -0
- package/lib/chat/index.d.ts +1 -0
- package/lib/chat/index.js +1 -0
- package/lib/chat/messages/ChatMessageList.js +13 -5
- package/lib/chat/messages/FormSubmissionMessage.d.ts +17 -0
- package/lib/chat/messages/FormSubmissionMessage.js +49 -0
- package/lib/chat/messages/formSubmission.d.ts +41 -0
- package/lib/chat/messages/formSubmission.js +90 -0
- package/lib/chat/prompt/menus/ModelSelector.js +11 -4
- package/lib/chat/styles/streamdownStyles.js +40 -0
- package/lib/client/mixins/RuntimesMixin.js +1 -1
- package/lib/client/mixins/SpacerMixin.js +3 -3
- package/lib/components/a2ui/A2uiMarkdownProvider.d.ts +11 -0
- package/lib/components/a2ui/A2uiMarkdownProvider.js +13 -0
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.js +54 -5
- package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +12 -1
- package/lib/components/code-sandboxes/codeSandboxEnvironments.js +38 -1
- package/lib/components/icons/ArtifactIcon.js +2 -1
- package/lib/examples/A2UiAgentExample.js +36 -223
- package/lib/examples/A2UiJupyterOutputExample.js +11 -1
- package/lib/examples/A2UiViewerExample.d.ts +9 -2
- package/lib/examples/A2UiViewerExample.js +33 -3
- package/lib/examples/DecksAgent.d.ts +1 -1
- package/lib/examples/DecksAgent.js +1 -1
- package/lib/examples/main.js +37 -35
- package/lib/examples/utils/a2uiMarkdownProvider.d.ts +1 -7
- package/lib/examples/utils/a2uiMarkdownProvider.js +6 -8
- package/lib/loop/core/index.d.ts +44 -4
- package/lib/loop/core/index.js +29 -2
- package/lib/loop/embed/LoopEmbed.js +7 -0
- package/lib/loop/index.d.ts +1 -1
- package/lib/loop/index.js +4 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +22 -0
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +64 -0
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +38 -0
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +64 -0
- package/lib/loop/plugins/a2ui-surface/index.d.ts +34 -0
- package/lib/loop/plugins/a2ui-surface/index.js +91 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +52 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.js +106 -0
- package/lib/loop/plugins/chat/ChatView.js +95 -15
- package/lib/loop/plugins/chat/index.d.ts +14 -0
- package/lib/loop/plugins/chat/index.js +1 -0
- package/lib/loop/plugins/document/index.js +4 -0
- package/lib/loop/plugins/notebook/index.js +4 -0
- package/lib/loop/plugins/page-layout/index.d.ts +36 -0
- package/lib/loop/plugins/page-layout/index.js +87 -7
- package/lib/loop/plugins/shell/useEditorPreviews.js +7 -0
- package/lib/loop/presets.d.ts +19 -0
- package/lib/loop/presets.js +9 -1
- package/lib/models/Deck.d.ts +14 -0
- package/lib/models/Deck.js +5 -0
- package/lib/models/Environment.d.ts +117 -0
- package/lib/models/ItemType.d.ts +1 -1
- package/lib/models/Runtime.d.ts +6 -0
- package/lib/models/SpaceItem.d.ts +2 -1
- package/lib/models/environments.generated.d.ts +35 -1
- package/lib/models/environments.generated.js +1 -1
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.js +1 -0
- package/lib/protocols/BrowserAgentAdapter.d.ts +9 -1
- package/lib/protocols/BrowserAgentAdapter.js +11 -4
- package/lib/runtimes/actions.d.ts +5 -0
- package/lib/runtimes/actions.js +19 -0
- package/lib/specs/agents/agents.d.ts +3 -1
- package/lib/specs/agents/agents.js +426 -130
- package/lib/specs/frontendTools.d.ts +1 -0
- package/lib/specs/frontendTools.js +31 -0
- package/lib/specs/mcpServers.js +13 -13
- package/lib/specs/models.js +7 -7
- package/lib/specs/outputs.js +9 -0
- package/lib/specs/skills.js +7 -7
- package/lib/specs/teams/teams.js +1 -1
- package/lib/tools/adapters/copilotkit/CopilotKitToolAdapter.js +1 -1
- package/lib/types/outputs.d.ts +2 -0
- package/package.json +10 -4
- package/patches/@primer+react+37.31.0.patch +390 -0
- package/patches/eslint-plugin-react+7.37.5.patch +65 -0
- package/scripts/apply-patches.sh +15 -1
- package/scripts/codegen/compose.py +4 -4
- package/scripts/codegen/generate_agents.py +1 -2
- package/scripts/codegen/generate_frontend_tools.py +1 -1
- package/scripts/codegen/generate_mcp_servers.py +2 -2
- package/scripts/codegen/generate_models.py +33 -8
- package/scripts/codegen/generate_outputs.py +2 -0
- package/scripts/codegen/generate_skills.py +2 -2
- package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
|
@@ -3,9 +3,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import { Box, IconButton, SegmentedControl, Text, Truncate, } from '@primer/react';
|
|
4
4
|
import { useColorPalette } from '@datalayer/primer-addons';
|
|
5
5
|
import { ScreenFullIcon, ScreenNormalIcon } from '@primer/octicons-react';
|
|
6
|
-
import { signal } from '@datalayer/reactor';
|
|
6
|
+
import { computed, signal } from '@datalayer/reactor';
|
|
7
|
+
/* A signal to read when the layout publishes no live stance: the hook needs one. */
|
|
8
|
+
const NO_STANCE = signal(undefined);
|
|
7
9
|
import { useContributions, useSignalValue, useGate, useReactorPlatform, ReactorLazy, ReactorSlot, useSlotComponents, } from '@datalayer/reactor/react';
|
|
8
10
|
import { ChatBase } from '../../../chat/base/ChatBase';
|
|
11
|
+
import { readServerCatalogue } from '../../../chat/base/modelChoice';
|
|
9
12
|
import { SUGGESTION_CHIP_WIDTH } from '../../../chat/display/EmptyState';
|
|
10
13
|
import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
|
|
11
14
|
import { useAnonymousSessionStore } from '../../../runtimes/browser/anonymousToken';
|
|
@@ -123,6 +126,11 @@ export default function ChatView({ workspace }) {
|
|
|
123
126
|
its own way; first contribution wins. */
|
|
124
127
|
const layoutEntries = useContributions(LoopChatLayout);
|
|
125
128
|
const layout = layoutEntries[0]?.value;
|
|
129
|
+
// The composer's stance: live when the layout publishes one (the page
|
|
130
|
+
// layout, moving the composer as the display mode changes), its starting
|
|
131
|
+
// stance otherwise.
|
|
132
|
+
const liveStance = useSignalValue(layout?.promptStance ?? NO_STANCE);
|
|
133
|
+
const promptStance = liveStance ?? layout?.prompt;
|
|
126
134
|
/* The current turn, kept for whoever shows the conversation without the
|
|
127
135
|
transcript. The chat plugin contributed the feed at build; this view is
|
|
128
136
|
its writer — begin on send, the reply as it arrives, end when it stops. */
|
|
@@ -136,7 +144,16 @@ export default function ChatView({ workspace }) {
|
|
|
136
144
|
handler memoised with empty deps. Set once the spec is resolved below. */
|
|
137
145
|
const agentNameRef = useRef('');
|
|
138
146
|
const extrasEntries = useContributions(LoopChatExtras);
|
|
139
|
-
|
|
147
|
+
/*
|
|
148
|
+
* Every contribution, merged, a later one winning a key both set.
|
|
149
|
+
*
|
|
150
|
+
* Only the first used to be read, which made the extras the property of
|
|
151
|
+
* whichever plugin registered first: the loop's own surface plugin
|
|
152
|
+
* contributes a tool-result renderer, and a host example contributing a
|
|
153
|
+
* banner beside it would have silenced one or the other.
|
|
154
|
+
*/
|
|
155
|
+
const mergedExtras = useMemo(() => computed(() => extrasEntries.reduce((merged, entry) => ({ ...merged, ...entry.value.extras.value }), {})), [extrasEntries]);
|
|
156
|
+
const chatExtras = useSignalValue(extrasEntries.length > 0 ? mergedExtras : EMPTY_CHAT_EXTRAS);
|
|
140
157
|
/* Which editors have ever been mounted; they are never unmounted after —
|
|
141
158
|
see the render below. A ref, not state: adding to it during render is
|
|
142
159
|
idempotent and must not schedule another one. */
|
|
@@ -301,18 +318,43 @@ export default function ChatView({ workspace }) {
|
|
|
301
318
|
busy,
|
|
302
319
|
stop: controlsRef.current?.stop,
|
|
303
320
|
newChat: controlsRef.current?.newChat,
|
|
321
|
+
send: controlsRef.current?.send,
|
|
304
322
|
}
|
|
305
323
|
: null);
|
|
306
324
|
}, [busy, sendReady, setViewControls]);
|
|
307
325
|
// Stop reporting when the view goes away, or the shell would keep a stop
|
|
308
326
|
// button for something that is no longer there.
|
|
309
327
|
useEffect(() => () => setViewControls(null), [setViewControls]);
|
|
328
|
+
/*
|
|
329
|
+
* Said before the adapter could take it.
|
|
330
|
+
*
|
|
331
|
+
* The composer is live while the chat is still starting — the shell shows
|
|
332
|
+
* it as soon as the view mounts — and a message sent in that moment used
|
|
333
|
+
* to be dropped, on the grounds that sending it late and out of context
|
|
334
|
+
* would be worse. It was worse: the message stood in the conversation with
|
|
335
|
+
* no reply and no reason, and the only way to find out was to send it
|
|
336
|
+
* again. Held instead, and sent the moment the adapter is ready, which is
|
|
337
|
+
* the next second or two rather than minutes.
|
|
338
|
+
*/
|
|
339
|
+
const heldForAdapter = useRef([]);
|
|
310
340
|
useEffect(() => workspace.prompts.subscribe(message => {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
341
|
+
const send = controlsRef.current?.send;
|
|
342
|
+
if (!send) {
|
|
343
|
+
heldForAdapter.current.push(message);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
send(message);
|
|
315
347
|
}), [workspace.prompts]);
|
|
348
|
+
// The adapter arrived: whatever was said while it was starting goes now.
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (!sendReady || heldForAdapter.current.length === 0) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const held = heldForAdapter.current.splice(0, heldForAdapter.current.length);
|
|
354
|
+
for (const message of held) {
|
|
355
|
+
controlsRef.current?.send(message);
|
|
356
|
+
}
|
|
357
|
+
}, [sendReady]);
|
|
316
358
|
/*
|
|
317
359
|
* Fired once, on the first real send, for the life of this mounted chat —
|
|
318
360
|
* a ref rather than a signal, because nothing here needs to re-render when
|
|
@@ -627,7 +669,11 @@ export default function ChatView({ workspace }) {
|
|
|
627
669
|
*/
|
|
628
670
|
const suggestionEntries = useContributions(LoopChatSuggestion);
|
|
629
671
|
const hiddenOpeners = chatExtras.hiddenOpeners;
|
|
672
|
+
const hideOpeners = chatExtras.hideOpeners;
|
|
630
673
|
const suggestions = useMemo(() => {
|
|
674
|
+
if (hideOpeners) {
|
|
675
|
+
return [];
|
|
676
|
+
}
|
|
631
677
|
// What the host took off this page, by the chip's words — the same key
|
|
632
678
|
// the two-group merge below uses to tell openers apart.
|
|
633
679
|
const hidden = new Set(hiddenOpeners ?? []);
|
|
@@ -652,7 +698,15 @@ export default function ChatView({ workspace }) {
|
|
|
652
698
|
.filter(item => !listed.has(item.text))
|
|
653
699
|
.map(item => ({ ...item, group: memberName })),
|
|
654
700
|
];
|
|
655
|
-
}, [
|
|
701
|
+
}, [
|
|
702
|
+
suggestionEntries,
|
|
703
|
+
team,
|
|
704
|
+
spec,
|
|
705
|
+
member,
|
|
706
|
+
agentId,
|
|
707
|
+
hiddenOpeners,
|
|
708
|
+
hideOpeners,
|
|
709
|
+
]);
|
|
656
710
|
/*
|
|
657
711
|
* The same openers in the shape the chat's empty state asks for.
|
|
658
712
|
*
|
|
@@ -736,13 +790,18 @@ export default function ChatView({ workspace }) {
|
|
|
736
790
|
}
|
|
737
791
|
// A host example's own frontend tools, folded in last (first name wins),
|
|
738
792
|
// so a bespoke client tool like A2UI's run_jupyter_output_demo joins the
|
|
739
|
-
// agent's toolset without a plugin of its own.
|
|
793
|
+
// agent's toolset without a plugin of its own. Vouched for in the chat
|
|
794
|
+
// view as well: the filter below keeps the tools that leave the editors
|
|
795
|
+
// alone, and a host's tool touches no editor — the Jupyter Output
|
|
796
|
+
// example's chat had lost its one tool to that filter and answered
|
|
797
|
+
// "use the demo button" instead of running the demonstration.
|
|
740
798
|
for (const tool of chatExtras.frontendTools ?? []) {
|
|
741
799
|
if (owners.has(tool.name)) {
|
|
742
800
|
continue;
|
|
743
801
|
}
|
|
744
802
|
owners.set(tool.name, 'chat-extras');
|
|
745
803
|
merged.push(tool);
|
|
804
|
+
keep.add(tool.name);
|
|
746
805
|
}
|
|
747
806
|
/*
|
|
748
807
|
* The view decides the toolset. With an editor on screen the agent has
|
|
@@ -1070,7 +1129,10 @@ export default function ChatView({ workspace }) {
|
|
|
1070
1129
|
.then(response => (response.ok ? response.json() : { models: [] }))
|
|
1071
1130
|
.then(payload => {
|
|
1072
1131
|
if (!cancelled) {
|
|
1073
|
-
|
|
1132
|
+
// In the chat's shape: the route says `available`, the menu and
|
|
1133
|
+
// the opening pick read `isAvailable`, and an unmapped row passes
|
|
1134
|
+
// for usable. See `readServerCatalogue`.
|
|
1135
|
+
setCatalogModels(readServerCatalogue(payload));
|
|
1074
1136
|
}
|
|
1075
1137
|
})
|
|
1076
1138
|
// No catalogue is not an error worth a banner: the menu simply has
|
|
@@ -1183,13 +1245,14 @@ export default function ChatView({ workspace }) {
|
|
|
1183
1245
|
// In a draggable card over the workspace when the host asked for the
|
|
1184
1246
|
// floating placement; the dragging is the prompt's own feature.
|
|
1185
1247
|
placement: floatingPrompt ||
|
|
1186
|
-
|
|
1187
|
-
|
|
1248
|
+
promptStance === 'floating' ||
|
|
1249
|
+
promptStance === 'floating-top' ||
|
|
1250
|
+
promptStance === 'floating-bottom'
|
|
1188
1251
|
? 'floating'
|
|
1189
1252
|
: 'docked',
|
|
1190
1253
|
// Where a floating card starts: the page layout wants it over the top
|
|
1191
1254
|
// of the sheet, like a document's title bar; everyone else, bottom.
|
|
1192
|
-
floatingAnchor:
|
|
1255
|
+
floatingAnchor: promptStance === 'floating-top' ? 'top' : 'bottom',
|
|
1193
1256
|
// The agent is working: `isLoading` disables the editor and turns the
|
|
1194
1257
|
// send button into a stop — the person keeps a way out.
|
|
1195
1258
|
isLoading: busy,
|
|
@@ -1206,8 +1269,9 @@ export default function ChatView({ workspace }) {
|
|
|
1206
1269
|
// Both of the page layout's placements are that bar — one floating over
|
|
1207
1270
|
// the page, one docked above it — so both take the tighter padding.
|
|
1208
1271
|
padding: topPrompt ||
|
|
1209
|
-
|
|
1210
|
-
|
|
1272
|
+
promptStance === 'floating-top' ||
|
|
1273
|
+
promptStance === 'floating-bottom' ||
|
|
1274
|
+
promptStance === 'docked-top'
|
|
1211
1275
|
? 2
|
|
1212
1276
|
: 3,
|
|
1213
1277
|
promptVariant: 'lexical',
|
|
@@ -1340,6 +1404,16 @@ export default function ChatView({ workspace }) {
|
|
|
1340
1404
|
// placeholder does: a dead control with no explanation is worse
|
|
1341
1405
|
// than an absent one.
|
|
1342
1406
|
disabled: chatDisabled, disableReason: disabledReason, protocol: protocol,
|
|
1407
|
+
/*
|
|
1408
|
+
The model this view is on, as the chat's opening pick.
|
|
1409
|
+
|
|
1410
|
+
`ChatBase` keeps the model it sends with, and opened on the
|
|
1411
|
+
first row of whatever catalogue it had before the server
|
|
1412
|
+
answered — Alibaba — while the footer, drawn from
|
|
1413
|
+
`activeModel`, said Claude. Told the same model, both agree
|
|
1414
|
+
from the first message.
|
|
1415
|
+
*/
|
|
1416
|
+
initialModel: activeModel || undefined,
|
|
1343
1417
|
/*
|
|
1344
1418
|
Who is answering, in the words its spec uses.
|
|
1345
1419
|
|
|
@@ -1408,7 +1482,13 @@ export default function ChatView({ workspace }) {
|
|
|
1408
1482
|
props through `renderHeader`. No contribution — or a host
|
|
1409
1483
|
that set `hideHeader` — means no bar.
|
|
1410
1484
|
*/
|
|
1411
|
-
showHeader: !config?.hideHeader && !!HeaderComponent,
|
|
1485
|
+
showHeader: !config?.hideHeader && !!HeaderComponent,
|
|
1486
|
+
// The `+` and the bin in the title bar, when the host asked for
|
|
1487
|
+
// them; the header draws whatever `ChatBase` assembles here.
|
|
1488
|
+
headerButtons: {
|
|
1489
|
+
showNewChat: config?.headerButtons?.newChat ?? false,
|
|
1490
|
+
showClear: config?.headerButtons?.clear ?? false,
|
|
1491
|
+
}, renderHeader: HeaderComponent
|
|
1412
1492
|
? headerProps => (_jsx(HeaderComponent, { workspace: workspace, header: headerProps }))
|
|
1413
1493
|
: undefined,
|
|
1414
1494
|
/*
|
|
@@ -44,6 +44,20 @@ export type ChatPluginConfig = {
|
|
|
44
44
|
* directly beneath repeats the point in a smaller font.
|
|
45
45
|
*/
|
|
46
46
|
hideHeader: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the title bar offers a way to start over.
|
|
49
|
+
*
|
|
50
|
+
* `newChat` is the `+`: a fresh thread, the transcript emptied. `clear`
|
|
51
|
+
* is the bin: the same emptying, behind a confirmation, and drawn only
|
|
52
|
+
* while there is something to empty. Both off by default — a workspace
|
|
53
|
+
* embedded in a page is usually one conversation — and on for an example
|
|
54
|
+
* whose reader will ask for one surface after another and want the last
|
|
55
|
+
* one gone.
|
|
56
|
+
*/
|
|
57
|
+
headerButtons: {
|
|
58
|
+
newChat: boolean;
|
|
59
|
+
clear: boolean;
|
|
60
|
+
};
|
|
47
61
|
/**
|
|
48
62
|
* Whether the chat renders a prompt at all.
|
|
49
63
|
*
|
|
@@ -79,6 +79,10 @@ export const DocumentPlugin = definePlugin({
|
|
|
79
79
|
id: 'document-tools',
|
|
80
80
|
tools: workspace => createLexicalTools(workspace.surfaceId),
|
|
81
81
|
}, { id: 'document-tools' });
|
|
82
|
+
}, error => {
|
|
83
|
+
// The chunk did not load, or the page went away while it did: the
|
|
84
|
+
// chat simply has no document tools.
|
|
85
|
+
console.warn('The document tools could not be loaded', error);
|
|
82
86
|
});
|
|
83
87
|
return {};
|
|
84
88
|
},
|
|
@@ -84,6 +84,10 @@ export const NotebookPlugin = definePlugin({
|
|
|
84
84
|
id: 'notebook-tools',
|
|
85
85
|
tools: workspace => createNotebookTools(workspace.surfaceId),
|
|
86
86
|
}, { id: 'notebook-tools' });
|
|
87
|
+
}, error => {
|
|
88
|
+
// The chunk did not load, or the page went away while it did: the
|
|
89
|
+
// chat simply has no notebook tools.
|
|
90
|
+
console.warn('The notebook tools could not be loaded', error);
|
|
87
91
|
});
|
|
88
92
|
return {};
|
|
89
93
|
},
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { type PageSize } from '@datalayer/primer-addons/lib/reactor';
|
|
2
|
+
import type { ChatViewMode } from '../../../types/chat';
|
|
1
3
|
import { type TurnPanelFooter } from './TurnPanel';
|
|
2
4
|
export { pageLayoutPanelOpen, pageLayoutSheet, openPagePanel as openConversationPanel, } from '@datalayer/primer-addons/lib/reactor';
|
|
3
5
|
export type { TurnPanelFooter } from './TurnPanel';
|
|
6
|
+
export type { PageSize, PageSizeFormat, } from '@datalayer/primer-addons/lib/reactor';
|
|
4
7
|
export declare const LOOP_PAGE_LAYOUT_PLUGIN_NAME = "@datalayer/loop-plugin-page-layout";
|
|
5
8
|
/** What a host may set on the page layout. */
|
|
6
9
|
export type LoopPageLayoutConfig = {
|
|
@@ -22,6 +25,20 @@ export type LoopPageLayoutConfig = {
|
|
|
22
25
|
* kept clear for it — the document application's title bar, movable.
|
|
23
26
|
*/
|
|
24
27
|
prompt: 'docked' | 'floating';
|
|
28
|
+
/**
|
|
29
|
+
* Which edge of the page a floating composer starts at: `top` (the
|
|
30
|
+
* default), where a document's title bar would be, or `bottom`, where a
|
|
31
|
+
* chat's composer waits. Either way it is dragged from there. Only read
|
|
32
|
+
* with a `floating` prompt.
|
|
33
|
+
*/
|
|
34
|
+
promptAnchor?: 'top' | 'bottom';
|
|
35
|
+
/**
|
|
36
|
+
* The sheet's size: free (the default) at the layout's reading width, or
|
|
37
|
+
* a paper — `{ format: 'letter' }`, `{ format: 'a4' }` — whose width the
|
|
38
|
+
* sheet takes and whose height it is at least, with a free `width` or
|
|
39
|
+
* `height` over either. Omitted: free.
|
|
40
|
+
*/
|
|
41
|
+
pageSize?: PageSize;
|
|
25
42
|
/**
|
|
26
43
|
* What the turn panel draws under the reply.
|
|
27
44
|
*
|
|
@@ -31,5 +48,24 @@ export type LoopPageLayoutConfig = {
|
|
|
31
48
|
*/
|
|
32
49
|
turnPanelFooter: TurnPanelFooter;
|
|
33
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* How the page layout shows the chat, when the composer is the floating
|
|
53
|
+
* card: the card itself over the page (`floating-draggable`, where it
|
|
54
|
+
* starts), or the conversation panel with the composer standing in it —
|
|
55
|
+
* beside the page (`sidebar`), over its right edge at full height
|
|
56
|
+
* (`floating`), or as a window in its corner (`floating-small`). The four
|
|
57
|
+
* modes of the chat header's toggle, which the card carries in its own
|
|
58
|
+
* footer. One signal for the workspace, as `pageLayoutPanelOpen` is: the
|
|
59
|
+
* mode is the person's, not a build's.
|
|
60
|
+
*/
|
|
61
|
+
export declare const pageLayoutChatMode: import("@datalayer/reactor").Signal<ChatViewMode>;
|
|
62
|
+
/** Where a mode puts the composer (the band) and the conversation (the panel). */
|
|
63
|
+
export type PageLayoutChatPlacement = {
|
|
64
|
+
bandMode: 'floating' | 'panel';
|
|
65
|
+
panelMode: 'docked' | 'overlay' | 'popup';
|
|
66
|
+
};
|
|
67
|
+
export declare function pageLayoutForChatMode(mode: ChatViewMode): PageLayoutChatPlacement;
|
|
68
|
+
/** Picks a mode: the layout follows, and so does the composer's stance. */
|
|
69
|
+
export declare function setPageLayoutChatMode(mode: ChatViewMode): void;
|
|
34
70
|
export declare const LoopPageLayoutPlugin: import("@datalayer/reactor").ReactorPlugin<LoopPageLayoutConfig, unknown, unknown>;
|
|
35
71
|
export default LoopPageLayoutPlugin;
|
|
@@ -2,7 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { CommentDiscussionIcon } from '@primer/octicons-react';
|
|
3
3
|
import { definePlugin, signal } from '@datalayer/reactor';
|
|
4
4
|
import { useContributions, useSignalValue } from '@datalayer/reactor/react';
|
|
5
|
-
import { PageLayout, PagePanelToggle, } from '@datalayer/primer-addons/lib/reactor';
|
|
5
|
+
import { PageLayout, PagePanelToggle, openPagePanel, } from '@datalayer/primer-addons/lib/reactor';
|
|
6
|
+
import { ChatViewModeToggle } from '../../../chat/header/ChatViewModeToggle';
|
|
6
7
|
import { LoopChatLayout, LoopChatTurn, LoopPromptPanel, LoopSlots, } from '../../core';
|
|
7
8
|
import { TurnPanel } from './TurnPanel';
|
|
8
9
|
/*
|
|
@@ -14,6 +15,53 @@ export { pageLayoutPanelOpen, pageLayoutSheet, openPagePanel as openConversation
|
|
|
14
15
|
export const LOOP_PAGE_LAYOUT_PLUGIN_NAME = '@datalayer/loop-plugin-page-layout';
|
|
15
16
|
/* A signal to read when no chat contributed a turn: the hook needs one. */
|
|
16
17
|
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
18
|
+
/**
|
|
19
|
+
* How the page layout shows the chat, when the composer is the floating
|
|
20
|
+
* card: the card itself over the page (`floating-draggable`, where it
|
|
21
|
+
* starts), or the conversation panel with the composer standing in it —
|
|
22
|
+
* beside the page (`sidebar`), over its right edge at full height
|
|
23
|
+
* (`floating`), or as a window in its corner (`floating-small`). The four
|
|
24
|
+
* modes of the chat header's toggle, which the card carries in its own
|
|
25
|
+
* footer. One signal for the workspace, as `pageLayoutPanelOpen` is: the
|
|
26
|
+
* mode is the person's, not a build's.
|
|
27
|
+
*/
|
|
28
|
+
export const pageLayoutChatMode = signal('floating-draggable');
|
|
29
|
+
/** The composer's stance for the chat view, following the mode above. */
|
|
30
|
+
const promptStance = signal('floating-top');
|
|
31
|
+
/*
|
|
32
|
+
* Which edge the card returns to when the mode puts it back over the page:
|
|
33
|
+
* the build's own `promptAnchor`, kept here because the mode is picked
|
|
34
|
+
* through a module-level function, as `pageLayoutChatMode` itself is. One
|
|
35
|
+
* page layout to a page, the assumption that signal already makes.
|
|
36
|
+
*/
|
|
37
|
+
let floatingStance = 'floating-top';
|
|
38
|
+
export function pageLayoutForChatMode(mode) {
|
|
39
|
+
switch (mode) {
|
|
40
|
+
case 'sidebar':
|
|
41
|
+
return { bandMode: 'panel', panelMode: 'docked' };
|
|
42
|
+
case 'floating':
|
|
43
|
+
return { bandMode: 'panel', panelMode: 'overlay' };
|
|
44
|
+
case 'floating-small':
|
|
45
|
+
return { bandMode: 'panel', panelMode: 'popup' };
|
|
46
|
+
default:
|
|
47
|
+
return { bandMode: 'floating', panelMode: 'docked' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Picks a mode: the layout follows, and so does the composer's stance. */
|
|
51
|
+
export function setPageLayoutChatMode(mode) {
|
|
52
|
+
pageLayoutChatMode.value = mode;
|
|
53
|
+
const { bandMode } = pageLayoutForChatMode(mode);
|
|
54
|
+
promptStance.value = bandMode === 'floating' ? floatingStance : 'docked-top';
|
|
55
|
+
// The composer went into the panel: the panel opens to show it.
|
|
56
|
+
if (bandMode === 'panel') {
|
|
57
|
+
openPagePanel();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** The toggle in the floating composer's footer. */
|
|
61
|
+
function ChatModeAction() {
|
|
62
|
+
const mode = useSignalValue(pageLayoutChatMode);
|
|
63
|
+
return _jsx(ChatViewModeToggle, { value: mode, onChange: setPageLayoutChatMode });
|
|
64
|
+
}
|
|
17
65
|
/**
|
|
18
66
|
* The chat view's parts on the page layout.
|
|
19
67
|
*
|
|
@@ -21,10 +69,16 @@ const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
|
21
69
|
* what the agent is doing — the turn's `activity` — is the line at the top
|
|
22
70
|
* of the sheet, so the change is seen where it happens.
|
|
23
71
|
*/
|
|
24
|
-
function LoopPageLayout({ parts, promptMode, }) {
|
|
72
|
+
function LoopPageLayout({ parts, promptMode, pageSize, }) {
|
|
25
73
|
const turnEntries = useContributions(LoopChatTurn);
|
|
26
74
|
const turn = useSignalValue(turnEntries[0]?.value.turn ?? NO_TURN);
|
|
27
|
-
|
|
75
|
+
const chatMode = useSignalValue(pageLayoutChatMode);
|
|
76
|
+
// The display modes are the floating card's own; a docked composer stays
|
|
77
|
+
// docked above the page, whatever mode was picked elsewhere.
|
|
78
|
+
const placement = promptMode === 'floating'
|
|
79
|
+
? pageLayoutForChatMode(chatMode)
|
|
80
|
+
: { bandMode: 'docked', panelMode: 'docked' };
|
|
81
|
+
return (_jsx(PageLayout, { page: parts.editors, hasPage: parts.hasEditor, panel: parts.transcript, band: parts.prompt, chips: parts.chips, picker: parts.picker, transient: parts.transient, activity: turn.activity, bandMode: placement.bandMode, panelMode: placement.panelMode, pageSize: pageSize }));
|
|
28
82
|
}
|
|
29
83
|
/** The header button that opens and closes the conversation panel. */
|
|
30
84
|
function ConversationToggle() {
|
|
@@ -32,7 +86,13 @@ function ConversationToggle() {
|
|
|
32
86
|
}
|
|
33
87
|
export const LoopPageLayoutPlugin = definePlugin({
|
|
34
88
|
name: LOOP_PAGE_LAYOUT_PLUGIN_NAME,
|
|
35
|
-
config: {
|
|
89
|
+
config: {
|
|
90
|
+
turnPanel: 'below',
|
|
91
|
+
turnPanelFooter: 'full',
|
|
92
|
+
prompt: 'docked',
|
|
93
|
+
promptAnchor: 'top',
|
|
94
|
+
pageSize: undefined,
|
|
95
|
+
},
|
|
36
96
|
displayName: 'Page layout',
|
|
37
97
|
description: 'The editor — or, in the chat view, the conversation — on a centred sheet, the prompt docked above it at the same width or floating over it as a draggable card, the current turn under the prompt, the conversation in a side panel.',
|
|
38
98
|
octicon: 'file',
|
|
@@ -42,15 +102,24 @@ export const LoopPageLayoutPlugin = definePlugin({
|
|
|
42
102
|
The layout, contributed per build because the composer's stance is
|
|
43
103
|
configuration: `docked-top` means the layout owns the composer's width
|
|
44
104
|
— `PageLayout` gives it a mount point the width of the sheet, so the
|
|
45
|
-
prompt and the page read as one column; `floating-top`
|
|
105
|
+
prompt and the page read as one column; `floating-top` and
|
|
106
|
+
`floating-bottom` are the draggable
|
|
46
107
|
card, sized by itself, over a strip of canvas kept clear for it. The
|
|
47
108
|
component closes over the choice once, per build.
|
|
48
109
|
*/
|
|
49
110
|
const promptMode = config.prompt;
|
|
50
|
-
const
|
|
111
|
+
const pageSize = config.pageSize;
|
|
112
|
+
floatingStance =
|
|
113
|
+
config.promptAnchor === 'bottom' ? 'floating-bottom' : 'floating-top';
|
|
114
|
+
if (promptMode === 'floating') {
|
|
115
|
+
promptStance.value = floatingStance;
|
|
116
|
+
}
|
|
117
|
+
const ConfiguredLayout = (parts) => (_jsx(LoopPageLayout, { parts: parts, promptMode: promptMode, pageSize: pageSize }));
|
|
51
118
|
ctx.contribute(LoopChatLayout, {
|
|
52
119
|
id: 'page-layout',
|
|
53
|
-
prompt: promptMode === 'floating' ?
|
|
120
|
+
prompt: promptMode === 'floating' ? floatingStance : 'docked-top',
|
|
121
|
+
// Live only for the card: its display modes move the composer.
|
|
122
|
+
promptStance: promptMode === 'floating' ? promptStance : undefined,
|
|
54
123
|
Component: ConfiguredLayout,
|
|
55
124
|
}, { id: 'page-layout' });
|
|
56
125
|
// The turn panel is configuration-dependent, so it is contributed here
|
|
@@ -73,6 +142,17 @@ export const LoopPageLayoutPlugin = definePlugin({
|
|
|
73
142
|
slot: LoopSlots.header,
|
|
74
143
|
Component: ConversationToggle,
|
|
75
144
|
},
|
|
145
|
+
// The display modes, in the floating card's footer — and only
|
|
146
|
+
// there: a docked composer has none to pick.
|
|
147
|
+
...(promptMode === 'floating'
|
|
148
|
+
? [
|
|
149
|
+
{
|
|
150
|
+
id: 'page-layout-chat-mode',
|
|
151
|
+
slot: LoopSlots.promptAction,
|
|
152
|
+
Component: ChatModeAction,
|
|
153
|
+
},
|
|
154
|
+
]
|
|
155
|
+
: []),
|
|
76
156
|
],
|
|
77
157
|
};
|
|
78
158
|
},
|
|
@@ -179,6 +179,13 @@ function useDocumentRows(surfaceId, enabled) {
|
|
|
179
179
|
};
|
|
180
180
|
refresh();
|
|
181
181
|
unsubscribe = lexicalStore.subscribe(refresh);
|
|
182
|
+
})
|
|
183
|
+
.catch(() => {
|
|
184
|
+
// Lexical could not load (or the page went away while it did): the
|
|
185
|
+
// document offers no preview, which is all this hook was for.
|
|
186
|
+
if (!cancelled) {
|
|
187
|
+
setRows(null);
|
|
188
|
+
}
|
|
182
189
|
});
|
|
183
190
|
return () => {
|
|
184
191
|
cancelled = true;
|
package/lib/loop/presets.d.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { type PluginRef } from '@datalayer/reactor';
|
|
23
23
|
import { type AgentsConfig, type SandboxTarget } from './plugins/agents';
|
|
24
24
|
import { type ChatPluginConfig } from './plugins/chat';
|
|
25
|
+
import { type PageSize } from './plugins/page-layout';
|
|
25
26
|
export type LoopPresetOptions = {
|
|
26
27
|
/** Where the agent runtimes service is. */
|
|
27
28
|
serverUrl?: string;
|
|
@@ -39,6 +40,11 @@ export type LoopPresetOptions = {
|
|
|
39
40
|
showViewSelector?: boolean;
|
|
40
41
|
/** Whether the chat draws its own header. */
|
|
41
42
|
hideChatHeader?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the chat's header offers `+` (a new chat) and the bin (clear).
|
|
45
|
+
* Off by default; see `ChatPluginConfig.headerButtons`.
|
|
46
|
+
*/
|
|
47
|
+
chatHeaderButtons?: boolean;
|
|
42
48
|
/** Where the prompt sits. Passed through to the chat plugin. */
|
|
43
49
|
promptPlacement?: ChatPluginConfig['promptPlacement'];
|
|
44
50
|
/**
|
|
@@ -81,6 +87,19 @@ export type LoopPresetOptions = {
|
|
|
81
87
|
* as a draggable card over the top of the canvas.
|
|
82
88
|
*/
|
|
83
89
|
pageLayoutPrompt?: 'docked' | 'floating';
|
|
90
|
+
/**
|
|
91
|
+
* Which edge a floating composer starts at: `top` (the default) or
|
|
92
|
+
* `bottom`. It is dragged from there either way. Only read with
|
|
93
|
+
* `pageLayoutPrompt: 'floating'`.
|
|
94
|
+
*/
|
|
95
|
+
pageLayoutPromptAnchor?: 'top' | 'bottom';
|
|
96
|
+
/**
|
|
97
|
+
* The page layout's sheet: free (the default) at its reading width, or a
|
|
98
|
+
* paper — `{ format: 'letter' }`, `{ format: 'a4' }` — whose width it
|
|
99
|
+
* takes and whose height it is at least; a free `width` or `height` over
|
|
100
|
+
* either. Only read with `pageLayout`.
|
|
101
|
+
*/
|
|
102
|
+
pageLayoutSize?: PageSize;
|
|
84
103
|
/**
|
|
85
104
|
* What the turn panel draws under the reply: the full turn footer
|
|
86
105
|
* (counters, copy, dismiss — the default), `actions` only, or `none`.
|
package/lib/loop/presets.js
CHANGED
|
@@ -33,6 +33,7 @@ import { AgentspecsPlugin } from './plugins/agentspecs';
|
|
|
33
33
|
import { AgentsPlugin, } from './plugins/agents';
|
|
34
34
|
import { ChatPlugin } from './plugins/chat';
|
|
35
35
|
import { ChatHeaderPlugin } from './plugins/chat-header';
|
|
36
|
+
import { A2uiSurfacePlugin } from './plugins/a2ui-surface';
|
|
36
37
|
import { ChatViewPlugin } from './plugins/chat-view';
|
|
37
38
|
import { DocumentViewPlugin } from './plugins/document-view';
|
|
38
39
|
import { InputPromptPlugin } from './plugins/input-prompt';
|
|
@@ -51,7 +52,7 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
|
|
|
51
52
|
* be installed or signed into, and the switches a demonstration wants are off.
|
|
52
53
|
*/
|
|
53
54
|
export function loopPlugins(options = {}) {
|
|
54
|
-
const { serverUrl, target = 'browser', defaultEditor = 'notebook', showViewSelector = true, hideChatHeader = false, promptPlacement, autoFocusPrompt = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
|
|
55
|
+
const { serverUrl, target = 'browser', defaultEditor = 'notebook', showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
|
|
55
56
|
return [
|
|
56
57
|
// The chat owns the editor beside it, so which one opens is its
|
|
57
58
|
// configuration rather than the workspace's.
|
|
@@ -62,6 +63,7 @@ export function loopPlugins(options = {}) {
|
|
|
62
63
|
// to choose.
|
|
63
64
|
showSurfaceSelector: editors && showViewSelector && !editorSelector,
|
|
64
65
|
hideHeader: hideChatHeader,
|
|
66
|
+
headerButtons: { newChat: chatHeaderButtons, clear: chatHeaderButtons },
|
|
65
67
|
promptPlacement: floatingPrompt ? 'floating' : promptPlacement,
|
|
66
68
|
autoFocusPrompt,
|
|
67
69
|
fullScreenTopOffset,
|
|
@@ -72,6 +74,10 @@ export function loopPlugins(options = {}) {
|
|
|
72
74
|
// still switched individually in the plugins panel.
|
|
73
75
|
configurePlugin(InputPromptPlugin, { firstPromptHook }),
|
|
74
76
|
ChatHeaderPlugin,
|
|
77
|
+
// An agent whose spec binds a tool to the `a2ui-surface` renderer gets
|
|
78
|
+
// its surfaces drawn and submitted without a line of host code. Idle
|
|
79
|
+
// for every other agent: it answers only for tools some spec bound.
|
|
80
|
+
A2uiSurfacePlugin,
|
|
75
81
|
// One footer icon per view, in the composer where the writing hand
|
|
76
82
|
// already is. Each editor's icon withdraws while its editor is not
|
|
77
83
|
// contributed, so mounting these unconditionally costs an absent editor
|
|
@@ -127,6 +133,8 @@ export function loopPlugins(options = {}) {
|
|
|
127
133
|
turnPanel: pageLayoutTurnPanel,
|
|
128
134
|
turnPanelFooter: pageLayoutTurnPanelFooter,
|
|
129
135
|
prompt: pageLayoutPrompt,
|
|
136
|
+
promptAnchor: pageLayoutPromptAnchor,
|
|
137
|
+
pageSize: pageLayoutSize,
|
|
130
138
|
}),
|
|
131
139
|
]
|
|
132
140
|
: []),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IItem } from './Item';
|
|
2
|
+
/**
|
|
3
|
+
* A deck: a presentation described as data, on the `@datalayer/decks`
|
|
4
|
+
* engine. `spec` is the deck specification the engine renders — the
|
|
5
|
+
* item's content, the way `nbformat` is a notebook's — present when the
|
|
6
|
+
* item was read on its own and absent in a listing.
|
|
7
|
+
*/
|
|
8
|
+
export type IBaseDeck = IItem & {
|
|
9
|
+
spec?: any;
|
|
10
|
+
};
|
|
11
|
+
export type IDeck = IBaseDeck & {
|
|
12
|
+
type: 'deck';
|
|
13
|
+
};
|
|
14
|
+
export default IDeck;
|