@datalayer/agent-runtimes 1.3.83 → 1.3.85
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 +5 -4
- package/lib/chat/ChatFloating.js +8 -6
- package/lib/chat/ChatLanguage.d.ts +17 -0
- package/lib/chat/ChatLanguage.js +29 -0
- package/lib/chat/assistant/AssistantStage.d.ts +4 -4
- package/lib/chat/assistant/AssistantStage.js +4 -2
- package/lib/chat/assistant/BalloonParts.d.ts +2 -1
- package/lib/chat/assistant/BalloonParts.js +12 -5
- package/lib/chat/assistant/BalloonVisual.js +3 -1
- package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
- package/lib/chat/assistant/ConversationBalloon.js +3 -2
- package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
- package/lib/chat/assistant/SpeechBalloon.js +10 -7
- package/lib/chat/assistant/toolLine.d.ts +8 -4
- package/lib/chat/assistant/toolLine.js +13 -9
- package/lib/chat/base/ChatBase.js +30 -11
- package/lib/chat/base/loopElement.d.ts +53 -0
- package/lib/chat/base/loopElement.js +78 -0
- package/lib/chat/base/loopWindow.d.ts +31 -0
- package/lib/chat/base/loopWindow.js +74 -0
- package/lib/chat/display/EmptyState.js +3 -1
- package/lib/chat/index.d.ts +2 -0
- package/lib/chat/index.js +3 -0
- package/lib/chat/messages/ChatMessages.js +3 -1
- package/lib/chat/messages/TurnFooter.js +3 -1
- package/lib/chat/prompt/InputPrompt.js +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +3 -1
- package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
- package/lib/chat/tools/ToolApprovalBanner.js +5 -3
- package/lib/chat/tools/ToolApprovalDialog.js +5 -3
- package/lib/chat/tools/ToolCallDisplay.js +29 -25
- package/lib/chat/words.d.ts +102 -0
- package/lib/chat/words.js +460 -0
- package/lib/components/a2ui/custom/index.d.ts +39 -0
- package/lib/components/a2ui/custom/index.js +95 -0
- package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
- package/lib/components/a2ui/custom/sandbox.js +188 -0
- package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
- package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
- package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
- package/lib/components/a2ui/datalayer/Form.js +9 -2
- package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
- package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
- package/lib/components/a2ui/datalayer/index.d.ts +1 -1
- package/lib/components/a2ui/datalayer/index.js +1 -1
- package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
- package/lib/components/a2ui/datalayer/schema.js +12 -1
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +12 -1
- package/lib/components/a2ui/visibleWhen.js +24 -3
- package/lib/components/inspector/AgentInspector.d.ts +25 -44
- package/lib/components/inspector/AgentInspector.js +27 -215
- package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
- package/lib/components/inspector/AgentInspectorDialog.js +2 -2
- package/lib/components/inspector/a2aSpans.d.ts +51 -0
- package/lib/components/inspector/a2aSpans.js +373 -0
- package/lib/components/inspector/agentSpans.d.ts +120 -0
- package/lib/components/inspector/agentSpans.js +253 -0
- package/lib/components/inspector/chatSpans.d.ts +36 -0
- package/lib/components/inspector/chatSpans.js +50 -0
- package/lib/components/inspector/classify.d.ts +11 -8
- package/lib/components/inspector/classify.js +23 -24
- package/lib/components/inspector/index.d.ts +7 -7
- package/lib/components/inspector/index.js +7 -7
- package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
- package/lib/components/teams/SceneTranscript.d.ts +29 -0
- package/lib/components/teams/SceneTranscript.js +54 -0
- package/lib/components/teams/SceneView.d.ts +41 -0
- package/lib/components/teams/SceneView.js +28 -0
- package/lib/components/teams/index.d.ts +3 -0
- package/lib/components/teams/index.js +3 -0
- package/lib/components/teams/sceneTranscript.d.ts +74 -0
- package/lib/components/teams/sceneTranscript.js +294 -0
- package/lib/components/teams/teamConnections.d.ts +2 -2
- package/lib/components/teams/teamConnections.js +3 -3
- package/lib/components/teams/useA2ATeam.d.ts +4 -4
- package/lib/components/teams/useA2ATeam.js +50 -46
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/loop/apps/AppComposer.d.ts +40 -4
- package/lib/loop/apps/AppComposer.js +194 -19
- package/lib/loop/apps/AppRenderer.d.ts +21 -3
- package/lib/loop/apps/AppRenderer.js +88 -48
- package/lib/loop/apps/appspec.d.ts +13 -1
- package/lib/loop/apps/appspec.js +305 -12
- package/lib/loop/apps/checks.d.ts +14 -0
- package/lib/loop/apps/checks.js +276 -5
- package/lib/loop/apps/composer.d.ts +21 -10
- package/lib/loop/apps/composer.js +41 -8
- package/lib/loop/apps/customComponents.d.ts +41 -0
- package/lib/loop/apps/customComponents.js +260 -0
- package/lib/loop/apps/hostTools.d.ts +8 -1
- package/lib/loop/apps/hostTools.js +9 -0
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/apps/language.d.ts +68 -0
- package/lib/loop/apps/language.js +275 -0
- package/lib/loop/apps/settingsInputs.d.ts +29 -0
- package/lib/loop/apps/settingsInputs.js +147 -0
- package/lib/loop/apps/uploads.d.ts +43 -0
- package/lib/loop/apps/uploads.js +66 -0
- package/lib/loop/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +15 -1
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +25 -3
- package/lib/loop/embed/AppEmbed.js +106 -18
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +84 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/embedSession.d.ts +14 -0
- package/lib/loop/embed/embedSession.js +34 -0
- package/lib/loop/embed/hostBridge.d.ts +50 -3
- package/lib/loop/embed/hostBridge.js +22 -2
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
- package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
- package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
- package/lib/loop/plugins/app-components/index.d.ts +26 -0
- package/lib/loop/plugins/app-components/index.js +56 -0
- package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
- package/lib/loop/plugins/app-elements/AppElements.js +79 -0
- package/lib/loop/plugins/app-elements/index.d.ts +20 -0
- package/lib/loop/plugins/app-elements/index.js +43 -0
- package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
- package/lib/loop/plugins/app-page/AppPage.js +106 -8
- package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
- package/lib/loop/plugins/app-page/appPageModel.js +122 -4
- package/lib/loop/plugins/app-page/index.d.ts +1 -0
- package/lib/loop/plugins/app-page/index.js +1 -0
- package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
- package/lib/loop/plugins/app-page/pageRun.js +111 -0
- package/lib/loop/plugins/assistant/index.js +8 -6
- package/lib/loop/plugins/chat/ChatView.js +16 -6
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/chat/index.js +2 -1
- package/lib/models/PageTag.d.ts +1 -1
- package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
- package/lib/protocols/BrowserAgentAdapter.js +13 -10
- package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/runtimes/browser/protocol.d.ts +1 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1518 -216
- package/lib/specs/appspecSchema.js +630 -3
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/specs/uiPlugins.js +6 -1
- package/lib/types/agentspecs.d.ts +200 -1
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/teams.d.ts +13 -2
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +20 -2
- package/scripts/codegen/generate_teams.py +15 -2
- package/lib/components/inspector/JsonTree.d.ts +0 -24
- package/lib/components/inspector/JsonTree.js +0 -89
- package/lib/components/inspector/a2aInspect.d.ts +0 -56
- package/lib/components/inspector/a2aInspect.js +0 -478
- package/lib/components/inspector/agentInspector.d.ts +0 -179
- package/lib/components/inspector/agentInspector.js +0 -267
- package/lib/components/inspector/chatInspect.d.ts +0 -38
- package/lib/components/inspector/chatInspect.js +0 -65
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `app-components` — the components an application's developer wrote (LOOP
|
|
7
|
+
* P-17), registered as that application's A2UI contributions: each a block
|
|
8
|
+
* of its Canvas's palette (`loop.canvas.block`, as a UI plugin's components
|
|
9
|
+
* are) and a renderer of the catalog its page, its elements and its answers
|
|
10
|
+
* are drawn with (`loop.a2ui.component`). One plugin per application, in its
|
|
11
|
+
* workspace's preset only: the catalog grows for it, it does not open.
|
|
12
|
+
*
|
|
13
|
+
* Its components are reviewed before they get here — `checkAppspec` refuses
|
|
14
|
+
* an invalid declaration — and drawn in a sandboxed frame of no origin
|
|
15
|
+
* (`components/a2ui/custom`).
|
|
16
|
+
*
|
|
17
|
+
* @module loop/plugins/app-components
|
|
18
|
+
*/
|
|
19
|
+
import { contribution, definePlugin, } from '@datalayer/reactor';
|
|
20
|
+
import { customImplementation } from '../../../components/a2ui/custom';
|
|
21
|
+
import { customComponentEntry } from '../../apps/customComponents';
|
|
22
|
+
import { LoopA2uiComponent } from '../../core/a2uiComponents';
|
|
23
|
+
import { LoopCanvasBlock } from '../../core/canvasBlocks';
|
|
24
|
+
/** The name of the plugin that contributes an application's own components. */
|
|
25
|
+
export const appComponentsPluginName = (appId) => `@datalayer/loop-plugin-app-components-${appId}`;
|
|
26
|
+
/** Whether an application's developer wrote components of its own. */
|
|
27
|
+
export const hasCustomComponents = (app) => (app.interface.customComponents ?? []).length > 0;
|
|
28
|
+
/**
|
|
29
|
+
* The plugin contributing the components an application's developer wrote:
|
|
30
|
+
* a block and a renderer for each. `fetcher` is how their modules are
|
|
31
|
+
* fetched; the page's `fetch` unless given (tests).
|
|
32
|
+
*/
|
|
33
|
+
export function defineAppComponentsPlugin(app, fetcher) {
|
|
34
|
+
const own = app.interface.customComponents ?? [];
|
|
35
|
+
if (own.length === 0) {
|
|
36
|
+
throw new Error(`“${app.name}” has no component of its own to contribute.`);
|
|
37
|
+
}
|
|
38
|
+
return definePlugin({
|
|
39
|
+
name: appComponentsPluginName(app.id),
|
|
40
|
+
displayName: `${app.name}'s components`,
|
|
41
|
+
description: `The components ${app.name}'s developer wrote: ${own.map(component => component.name).join(', ')}.`,
|
|
42
|
+
octicon: 'apps',
|
|
43
|
+
contributes: own.flatMap(component => [
|
|
44
|
+
contribution(LoopCanvasBlock, {
|
|
45
|
+
id: component.name,
|
|
46
|
+
uiPlugin: `app:${app.id}`,
|
|
47
|
+
component: customComponentEntry(component, app.version),
|
|
48
|
+
}, { id: component.name }),
|
|
49
|
+
contribution(LoopA2uiComponent, {
|
|
50
|
+
id: component.name,
|
|
51
|
+
app: app.id,
|
|
52
|
+
implementation: customImplementation(component, app.version, fetcher),
|
|
53
|
+
}, { id: component.name }),
|
|
54
|
+
]),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The elements an application's code opened beside the conversation (LOOP
|
|
3
|
+
* P-18): a side panel on the trailing edge, and a page of its own over the
|
|
4
|
+
* workspace — each drawn from the same A2UI surface an answer is drawn from
|
|
5
|
+
* (`InlineSurface`), its buttons sent to the application's actions as an
|
|
6
|
+
* answer's are (`answerAction`), and closed with its own button.
|
|
7
|
+
*
|
|
8
|
+
* Drawn from the workspace's root slot, positioned against the workspace:
|
|
9
|
+
* nothing is drawn, and nothing is laid out, while nothing is open.
|
|
10
|
+
*
|
|
11
|
+
* @module loop/plugins/app-elements/AppElements
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX } from 'react';
|
|
14
|
+
import { type LoopElement } from '../../../chat/base/loopElement';
|
|
15
|
+
import type { LoopWorkspaceContext } from '../../core';
|
|
16
|
+
/** How wide the side panel is, at most. */
|
|
17
|
+
export declare const APP_PANEL_WIDTH = 380;
|
|
18
|
+
/** What is open now: the panels in the order they opened, and the last page. */
|
|
19
|
+
export declare function elementsByPlace(open: readonly LoopElement[]): {
|
|
20
|
+
panels: LoopElement[];
|
|
21
|
+
page: LoopElement | null;
|
|
22
|
+
};
|
|
23
|
+
/** Every element open in a side panel or on a page, drawn where it belongs. */
|
|
24
|
+
export declare function AppElements({ workspace, }: {
|
|
25
|
+
workspace?: LoopWorkspaceContext;
|
|
26
|
+
}): JSX.Element | null;
|
|
27
|
+
export default AppElements;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useSyncExternalStore } from 'react';
|
|
3
|
+
import { IconButton, Text } from '@primer/react';
|
|
4
|
+
import { XIcon } from '@primer/octicons-react';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { closeLoopElement, onLoopElements, openLoopElements, } from '../../../chat/base/loopElement';
|
|
7
|
+
import { InlineSurface, SURFACE_CATALOG_ID, } from '../a2ui-surface/InlineSurface';
|
|
8
|
+
import { answerAction, readA2uiToolResult } from '../a2ui-surface/toolResult';
|
|
9
|
+
import { useOwnCatalog } from '../a2ui-surface/ownComponents';
|
|
10
|
+
/** How wide the side panel is, at most. */
|
|
11
|
+
export const APP_PANEL_WIDTH = 380;
|
|
12
|
+
/** What is open now: the panels in the order they opened, and the last page. */
|
|
13
|
+
export function elementsByPlace(open) {
|
|
14
|
+
const pages = open.filter(element => element.where === 'page');
|
|
15
|
+
return {
|
|
16
|
+
panels: open.filter(element => element.where === 'panel'),
|
|
17
|
+
page: pages.length > 0 ? pages[pages.length - 1] : null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function ElementBody({ element, send, }) {
|
|
21
|
+
const parsed = useMemo(() => readA2uiToolResult(element.shows, SURFACE_CATALOG_ID), [element.shows]);
|
|
22
|
+
// With the components its developer wrote (P-17).
|
|
23
|
+
const catalog = useOwnCatalog();
|
|
24
|
+
const onAction = (action) => {
|
|
25
|
+
// A button of what its code shows: its action goes to the application,
|
|
26
|
+
// as a button of an answer does (LOOP P-04).
|
|
27
|
+
const pressed = answerAction(action);
|
|
28
|
+
if (pressed)
|
|
29
|
+
send?.(pressed.message, pressed.forwardedProps);
|
|
30
|
+
};
|
|
31
|
+
if (!parsed?.messages || parsed.messages.length === 0) {
|
|
32
|
+
return _jsx(Text, { sx: { color: 'fg.muted' }, children: "Nothing to show." });
|
|
33
|
+
}
|
|
34
|
+
return (_jsx(InlineSurface
|
|
35
|
+
// Drawn again whole when the code changes it.
|
|
36
|
+
, { messages: parsed.messages, onAction: onAction, catalog: catalog }, JSON.stringify(element.shows)));
|
|
37
|
+
}
|
|
38
|
+
function ElementHeader({ element }) {
|
|
39
|
+
return (_jsxs(Box, { sx: {
|
|
40
|
+
display: 'flex',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
justifyContent: 'space-between',
|
|
43
|
+
gap: 2,
|
|
44
|
+
mb: 2,
|
|
45
|
+
}, children: [_jsx(Text, { as: "h2", sx: { fontSize: 2, fontWeight: 'bold', m: 0 }, children: element.title }), _jsx(IconButton, { icon: XIcon, variant: "invisible", size: "small", "aria-label": `Close ${element.title}`, onClick: () => closeLoopElement(element.id) })] }));
|
|
46
|
+
}
|
|
47
|
+
/** Every element open in a side panel or on a page, drawn where it belongs. */
|
|
48
|
+
export function AppElements({ workspace, }) {
|
|
49
|
+
const open = useSyncExternalStore(onLoopElements, openLoopElements, openLoopElements);
|
|
50
|
+
const { panels, page } = elementsByPlace(open);
|
|
51
|
+
const send = workspace?.viewControls.send;
|
|
52
|
+
if (panels.length === 0 && page === null)
|
|
53
|
+
return null;
|
|
54
|
+
return (_jsxs(_Fragment, { children: [panels.length > 0 ? (_jsx(Box, { as: "aside", "aria-label": "Side panel", sx: {
|
|
55
|
+
position: 'absolute',
|
|
56
|
+
top: 0,
|
|
57
|
+
right: 0,
|
|
58
|
+
bottom: 0,
|
|
59
|
+
width: `min(${APP_PANEL_WIDTH}px, 100%)`,
|
|
60
|
+
zIndex: 1,
|
|
61
|
+
overflowY: 'auto',
|
|
62
|
+
borderLeft: '1px solid',
|
|
63
|
+
borderColor: 'border.default',
|
|
64
|
+
bg: 'canvas.subtle',
|
|
65
|
+
boxShadow: 'shadow.medium',
|
|
66
|
+
p: 3,
|
|
67
|
+
display: 'flex',
|
|
68
|
+
flexDirection: 'column',
|
|
69
|
+
gap: 3,
|
|
70
|
+
}, children: panels.map(element => (_jsxs(Box, { as: "section", "aria-label": element.title, children: [_jsx(ElementHeader, { element: element }), _jsx(ElementBody, { element: element, send: send })] }, element.id))) })) : null, page !== null ? (_jsxs(Box, { role: "dialog", "aria-label": page.title, sx: {
|
|
71
|
+
position: 'absolute',
|
|
72
|
+
inset: 0,
|
|
73
|
+
zIndex: 2,
|
|
74
|
+
overflowY: 'auto',
|
|
75
|
+
bg: 'canvas.default',
|
|
76
|
+
p: [3, 4],
|
|
77
|
+
}, children: [_jsx(ElementHeader, { element: page }), _jsx(ElementBody, { element: page, send: send })] })) : null] }));
|
|
78
|
+
}
|
|
79
|
+
export default AppElements;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `app-elements` — what an application's code opens beside the
|
|
3
|
+
* conversation (LOOP P-18): `session.show(..., where="panel")` a side panel,
|
|
4
|
+
* `where="page"` a page of its own over the workspace, each closed by the
|
|
5
|
+
* code (`session.close`) or by the person. Inline, an element is a message,
|
|
6
|
+
* which the chat draws (P-04); this plugin draws the other two.
|
|
7
|
+
*
|
|
8
|
+
* The chat keeps what is open (`chat/base/loopElement`), from the session
|
|
9
|
+
* API's `loop.element` events; this plugin draws it from the workspace's
|
|
10
|
+
* root slot, positioned against the workspace, so that nothing is laid out
|
|
11
|
+
* while nothing is open.
|
|
12
|
+
*
|
|
13
|
+
* @module loop/plugins/app-elements
|
|
14
|
+
*/
|
|
15
|
+
import { type ReactorPlugin } from '@datalayer/reactor';
|
|
16
|
+
export declare const APP_ELEMENTS_PLUGIN_NAME = "@datalayer/loop-plugin-app-elements";
|
|
17
|
+
/** The plugin that draws an application's side panel and pages. */
|
|
18
|
+
export declare const AppElementsPlugin: ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
19
|
+
export { AppElements, APP_PANEL_WIDTH, elementsByPlace } from './AppElements';
|
|
20
|
+
export default AppElementsPlugin;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `app-elements` — what an application's code opens beside the
|
|
7
|
+
* conversation (LOOP P-18): `session.show(..., where="panel")` a side panel,
|
|
8
|
+
* `where="page"` a page of its own over the workspace, each closed by the
|
|
9
|
+
* code (`session.close`) or by the person. Inline, an element is a message,
|
|
10
|
+
* which the chat draws (P-04); this plugin draws the other two.
|
|
11
|
+
*
|
|
12
|
+
* The chat keeps what is open (`chat/base/loopElement`), from the session
|
|
13
|
+
* API's `loop.element` events; this plugin draws it from the workspace's
|
|
14
|
+
* root slot, positioned against the workspace, so that nothing is laid out
|
|
15
|
+
* while nothing is open.
|
|
16
|
+
*
|
|
17
|
+
* @module loop/plugins/app-elements
|
|
18
|
+
*/
|
|
19
|
+
import { definePlugin } from '@datalayer/reactor';
|
|
20
|
+
import { clearLoopElements } from '../../../chat/base/loopElement';
|
|
21
|
+
import { LoopSlots } from '../../core';
|
|
22
|
+
import { AppElements } from './AppElements';
|
|
23
|
+
export const APP_ELEMENTS_PLUGIN_NAME = '@datalayer/loop-plugin-app-elements';
|
|
24
|
+
/** The plugin that draws an application's side panel and pages. */
|
|
25
|
+
export const AppElementsPlugin = definePlugin({
|
|
26
|
+
name: APP_ELEMENTS_PLUGIN_NAME,
|
|
27
|
+
displayName: 'Side panel and pages',
|
|
28
|
+
description: 'What an application opens beside the conversation: a side panel, or a page of its own.',
|
|
29
|
+
octicon: 'sidebar-expand',
|
|
30
|
+
build: () => ({
|
|
31
|
+
components: [
|
|
32
|
+
{
|
|
33
|
+
id: 'app-elements',
|
|
34
|
+
slot: LoopSlots.root,
|
|
35
|
+
Component: AppElements,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
// A workspace that goes takes what it had open with it.
|
|
40
|
+
register: () => clearLoopElements,
|
|
41
|
+
});
|
|
42
|
+
export { AppElements, APP_PANEL_WIDTH, elementsByPlace } from './AppElements';
|
|
43
|
+
export default AppElementsPlugin;
|
|
@@ -9,12 +9,21 @@
|
|
|
9
9
|
* palette (`loop.canvas.block`, R-01b) and nothing else, so that what the
|
|
10
10
|
* Canvas can place and what the page can draw are one list: a page that
|
|
11
11
|
* uses a block no enabled plugin contributes says so, in place of the page.
|
|
12
|
+
* The components its developer wrote (LOOP P-17) are among them, contributed
|
|
13
|
+
* by its own plugin (`app-components`) with their renderers.
|
|
14
|
+
*
|
|
15
|
+
* A widget's page written in its code (LOOP P-05) runs in a session of its
|
|
16
|
+
* own, beside the conversation: as an input changes, the page sends them all
|
|
17
|
+
* to the session's `page` action and shows what its code answered at
|
|
18
|
+
* `/outputs/<name>`, in place — no message is sent (`./pageRun`).
|
|
12
19
|
*
|
|
13
20
|
* @module loop/plugins/app-page/AppPage
|
|
14
21
|
*/
|
|
15
22
|
import type { JSX } from 'react';
|
|
16
23
|
import type { AppSpec } from '../../../types/agentspecs';
|
|
17
24
|
import { type LoopWorkspaceContext } from '../../core';
|
|
25
|
+
/** How long the page waits after an input changed before it runs (P-05). */
|
|
26
|
+
export declare const PAGE_RUN_DELAY_MS = 300;
|
|
18
27
|
/** The components a page's messages place, each once. */
|
|
19
28
|
export declare function componentsOnPage(messages: readonly unknown[]): string[];
|
|
20
29
|
/**
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
3
4
|
import { Box } from '@datalayer/primer-addons';
|
|
4
5
|
import { signal } from '@datalayer/reactor';
|
|
5
6
|
import { useContributions, useSignalValue } from '@datalayer/reactor/react';
|
|
6
7
|
import { catalogOfBlocks } from '../../../components/a2ui';
|
|
7
8
|
import { LoopCanvasBlock, LoopChatTurn, } from '../../core';
|
|
8
9
|
import { InlineSurface, SURFACE_CATALOG_ID, } from '../a2ui-surface/InlineSurface';
|
|
9
|
-
import {
|
|
10
|
+
import { useOwnComponents } from '../a2ui-surface/ownComponents';
|
|
11
|
+
import { appPageAction, appPageData, appPageMessages, pageInputsOf, pageOf, } from './appPageModel';
|
|
12
|
+
import { openPageSession, outputsData, runPageIn, } from './pageRun';
|
|
13
|
+
/** How long the page waits after an input changed before it runs (P-05). */
|
|
14
|
+
export const PAGE_RUN_DELAY_MS = 300;
|
|
10
15
|
/** No chat in the workspace: the page reads a turn that never starts. */
|
|
11
16
|
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
12
17
|
const NO_CONVERSATION = signal([]);
|
|
@@ -42,13 +47,15 @@ export function AppPage({ app, workspace }) {
|
|
|
42
47
|
const blocks = useContributions(LoopCanvasBlock);
|
|
43
48
|
const contributed = [...new Set(blocks.map(entry => entry.value.id))].sort();
|
|
44
49
|
const drawnWith = contributed.join(',');
|
|
50
|
+
// The renderers of the components its developer wrote (P-17).
|
|
51
|
+
const own = useOwnComponents();
|
|
45
52
|
const drawing = useMemo(() => {
|
|
46
53
|
const missing = blocksMissing(componentsOnPage(messages), contributed);
|
|
47
54
|
if (missing) {
|
|
48
55
|
return { problem: missing };
|
|
49
56
|
}
|
|
50
57
|
try {
|
|
51
|
-
return { catalog: catalogOfBlocks(contributed) };
|
|
58
|
+
return { catalog: catalogOfBlocks(contributed, own) };
|
|
52
59
|
}
|
|
53
60
|
catch (error) {
|
|
54
61
|
return {
|
|
@@ -56,9 +63,97 @@ export function AppPage({ app, workspace }) {
|
|
|
56
63
|
};
|
|
57
64
|
}
|
|
58
65
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
-
}, [messages, drawnWith]);
|
|
60
|
-
const data = useMemo(() => appPageData(app, turn, conversation), [app, turn, conversation]);
|
|
66
|
+
}, [messages, drawnWith, own]);
|
|
61
67
|
const [refusal, setRefusal] = useState(null);
|
|
68
|
+
// A widget's page written in its code (P-05): its outputs, as last answered.
|
|
69
|
+
const page = pageOf(app);
|
|
70
|
+
const [outputs, setOutputs] = useState({});
|
|
71
|
+
const data = useMemo(() => ({
|
|
72
|
+
...appPageData(app, turn, conversation),
|
|
73
|
+
...outputsData(outputs),
|
|
74
|
+
}), [app, turn, conversation, outputs]);
|
|
75
|
+
const token = useIAMStore(state => state.token) ?? '';
|
|
76
|
+
const pageContext = useRef({ serverUrl: '', agentId: '' });
|
|
77
|
+
pageContext.current = {
|
|
78
|
+
serverUrl: workspace.sandbox?.agentBaseUrl || workspace.serverUrl || '',
|
|
79
|
+
agentId: workspace.agentId || app.id,
|
|
80
|
+
token: token || undefined,
|
|
81
|
+
};
|
|
82
|
+
const pageSession = useRef(null);
|
|
83
|
+
const pageRunning = useRef(false);
|
|
84
|
+
const pageNext = useRef(null);
|
|
85
|
+
const pageSent = useRef('');
|
|
86
|
+
const alive = useRef(true);
|
|
87
|
+
// The latest inputs win: one run at a time, the last ones written next.
|
|
88
|
+
const runPage = useCallback((inputs) => {
|
|
89
|
+
pageNext.current = inputs;
|
|
90
|
+
if (pageRunning.current) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
pageRunning.current = true;
|
|
94
|
+
void (async () => {
|
|
95
|
+
while (pageNext.current && alive.current) {
|
|
96
|
+
const next = pageNext.current;
|
|
97
|
+
pageNext.current = null;
|
|
98
|
+
const context = pageContext.current;
|
|
99
|
+
try {
|
|
100
|
+
pageSession.current ??= openPageSession(context);
|
|
101
|
+
const turn = await runPageIn(context, await pageSession.current, next);
|
|
102
|
+
if (!alive.current)
|
|
103
|
+
break;
|
|
104
|
+
if ('refused' in turn) {
|
|
105
|
+
setRefusal(turn.refused);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
setRefusal(null);
|
|
109
|
+
setOutputs(turn.outputs);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// No session: the next change opens one again.
|
|
114
|
+
pageSession.current = null;
|
|
115
|
+
if (alive.current) {
|
|
116
|
+
setRefusal(error instanceof Error ? error.message : String(error));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
pageRunning.current = false;
|
|
121
|
+
})();
|
|
122
|
+
}, []);
|
|
123
|
+
// A live page runs as its inputs change, from the start (their defaults).
|
|
124
|
+
const unwatch = useRef(null);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
alive.current = true;
|
|
127
|
+
return () => {
|
|
128
|
+
alive.current = false;
|
|
129
|
+
unwatch.current?.();
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
const onSurface = useCallback((surface) => {
|
|
133
|
+
if (!page?.live) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
unwatch.current?.();
|
|
137
|
+
let timer;
|
|
138
|
+
const changed = (value) => {
|
|
139
|
+
const inputs = pageInputsOf(app, value);
|
|
140
|
+
const said = JSON.stringify(inputs);
|
|
141
|
+
if (said === pageSent.current) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
timer = setTimeout(() => {
|
|
146
|
+
pageSent.current = said;
|
|
147
|
+
runPage(inputs);
|
|
148
|
+
}, PAGE_RUN_DELAY_MS);
|
|
149
|
+
};
|
|
150
|
+
const watching = surface.dataModel.subscribe('/inputs', changed);
|
|
151
|
+
changed(watching.value);
|
|
152
|
+
unwatch.current = () => {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
watching.unsubscribe();
|
|
155
|
+
};
|
|
156
|
+
}, [app, page?.live, runPage]);
|
|
62
157
|
// The workspace changes as the chat reports itself; the handler reads the latest.
|
|
63
158
|
const workspaceRef = useRef(workspace);
|
|
64
159
|
workspaceRef.current = workspace;
|
|
@@ -72,7 +167,10 @@ export function AppPage({ app, workspace }) {
|
|
|
72
167
|
return;
|
|
73
168
|
}
|
|
74
169
|
setRefusal(null);
|
|
75
|
-
if ('
|
|
170
|
+
if ('runPage' in outcome) {
|
|
171
|
+
runPage(outcome.runPage);
|
|
172
|
+
}
|
|
173
|
+
else if ('stop' in outcome) {
|
|
76
174
|
controls.stop?.();
|
|
77
175
|
}
|
|
78
176
|
else if ('newChat' in outcome) {
|
|
@@ -102,9 +200,9 @@ export function AppPage({ app, workspace }) {
|
|
|
102
200
|
surface?.dataModel.set(path, '');
|
|
103
201
|
}
|
|
104
202
|
}
|
|
105
|
-
}, [app]);
|
|
203
|
+
}, [app, runPage]);
|
|
106
204
|
return (_jsx(Box, { "data-testid": "app-page", sx: { height: '100%', minHeight: 0, overflow: 'auto', p: 3 }, children: 'problem' in drawing ? (_jsx(Box, { role: "status", sx: { color: 'fg.muted', fontSize: 1 }, children: drawing.problem })) : (_jsx(InlineSurface
|
|
107
205
|
// A new list of blocks is a new surface, drawn with it.
|
|
108
|
-
, { messages: messages, data: data, onAction: onAction, validationError: refusal, catalog: drawing.catalog }, drawnWith)) }));
|
|
206
|
+
, { messages: messages, data: data, onAction: onAction, validationError: refusal, catalog: drawing.catalog, ...(page ? { onSurface } : {}) }, drawnWith)) }));
|
|
109
207
|
}
|
|
110
208
|
export default AppPage;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* @module loop/plugins/app-page/appPageModel
|
|
25
25
|
*/
|
|
26
|
-
import type { AppFormField, AppFormSchema, AppSpec } from '../../../types/agentspecs';
|
|
26
|
+
import type { AppFormField, AppFormSchema, AppPageSpec, AppSpec } from '../../../types/agentspecs';
|
|
27
27
|
import type { ChatTurnSnapshot, ConversationEntry } from '../../core';
|
|
28
28
|
/** The id of the editor surface an application's page is drawn in. */
|
|
29
29
|
export declare const APP_PAGE_SURFACE = "app-page";
|
|
@@ -92,6 +92,12 @@ export declare function settingDefaults(settings: AppFormSchema | undefined): Re
|
|
|
92
92
|
* shown and written. Empty lists for a decision, or a kind not known.
|
|
93
93
|
*/
|
|
94
94
|
export declare function appKindPaths(app: Pick<AppSpec, 'kind' | 'interface'>): AppKindPaths;
|
|
95
|
+
/** The page its code writes (P-05): a widget's, or none. */
|
|
96
|
+
export declare const pageOf: (app: Pick<AppSpec, "kind" | "interface">) => AppPageSpec | undefined;
|
|
97
|
+
/** The path an output of its page is shown at (P-05). */
|
|
98
|
+
export declare const outputPath: (name: string) => string;
|
|
99
|
+
/** The action that runs a widget's page written in its code (P-05). */
|
|
100
|
+
export declare const PAGE_ACTION = "page";
|
|
95
101
|
/**
|
|
96
102
|
* Whether the workspace draws the application's page: a chat, a widget or a
|
|
97
103
|
* worker whose layout has one (`page`, `split`). A `chat` layout is the
|
|
@@ -149,6 +155,12 @@ export declare function givenFiles(files: unknown): {
|
|
|
149
155
|
* them against the settings' form.
|
|
150
156
|
*/
|
|
151
157
|
export declare function settingsOf(app: Pick<AppSpec, 'interface'>, inputs: unknown): Record<string, unknown>;
|
|
158
|
+
/**
|
|
159
|
+
* The inputs of a widget's page written in its code (P-05), as the page
|
|
160
|
+
* holds them, typed as its form says, for the session's `page` action: the
|
|
161
|
+
* runtime checks them against the same form.
|
|
162
|
+
*/
|
|
163
|
+
export declare function pageInputsOf(app: Pick<AppSpec, 'kind' | 'interface'>, inputs: unknown): Record<string, unknown>;
|
|
152
164
|
/**
|
|
153
165
|
* What the page did besides its message, for the session API: the action of
|
|
154
166
|
* the block pressed, the files given with it, the settings it holds. Sent
|
|
@@ -182,6 +194,10 @@ export type AppPageOutcome = {
|
|
|
182
194
|
stop: true;
|
|
183
195
|
} | {
|
|
184
196
|
newChat: true;
|
|
197
|
+
}
|
|
198
|
+
/** A widget's page, to run on these inputs (P-05): no message is sent. */
|
|
199
|
+
| {
|
|
200
|
+
runPage: Record<string, unknown>;
|
|
185
201
|
} | {
|
|
186
202
|
refused: string;
|
|
187
203
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
import { pageOutputShows } from '../../apps/appspec';
|
|
5
6
|
/** The id of the editor surface an application's page is drawn in. */
|
|
6
7
|
export const APP_PAGE_SURFACE = 'app-page';
|
|
7
8
|
/** The id of the A2UI surface itself, inside it. */
|
|
@@ -178,12 +179,49 @@ export function appKindPaths(app) {
|
|
|
178
179
|
meaning: `The setting “${settingTitle(id, field)}”.`,
|
|
179
180
|
words: settingTitle(id, field),
|
|
180
181
|
}));
|
|
182
|
+
// A widget's page written in its code (P-05): its inputs taken, its
|
|
183
|
+
// outputs published, and the action that runs it.
|
|
184
|
+
const page = pageOf(app);
|
|
185
|
+
if (!page) {
|
|
186
|
+
return {
|
|
187
|
+
...kind,
|
|
188
|
+
publishes: [...kind.publishes, ...inputs],
|
|
189
|
+
accepts: [...kind.accepts, ...inputs],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const pageInputs = settingFields(page.inputs).map(([id, field]) => ({
|
|
193
|
+
path: inputPath(id),
|
|
194
|
+
meaning: `The input “${settingTitle(id, field)}” of its page.`,
|
|
195
|
+
words: settingTitle(id, field),
|
|
196
|
+
}));
|
|
197
|
+
const outputs = page.outputs.map(output => ({
|
|
198
|
+
path: outputPath(output.name),
|
|
199
|
+
meaning: `What its page shows as “${output.title || output.name}”, drawn with a ${output.component}, for its inputs.`,
|
|
200
|
+
words: output.title || output.name,
|
|
201
|
+
...(output.component === 'Table' || output.component === 'Chart'
|
|
202
|
+
? { list: true }
|
|
203
|
+
: {}),
|
|
204
|
+
}));
|
|
181
205
|
return {
|
|
182
206
|
...kind,
|
|
183
|
-
publishes: [...kind.publishes, ...inputs],
|
|
184
|
-
accepts: [...kind.accepts, ...inputs],
|
|
207
|
+
publishes: [...kind.publishes, ...inputs, ...pageInputs, ...outputs],
|
|
208
|
+
accepts: [...kind.accepts, ...inputs, ...pageInputs],
|
|
209
|
+
actions: [...kind.actions, PAGE],
|
|
185
210
|
};
|
|
186
211
|
}
|
|
212
|
+
/** The page its code writes (P-05): a widget's, or none. */
|
|
213
|
+
export const pageOf = (app) => app.kind === 'widget' ? app.interface.page : undefined;
|
|
214
|
+
/** The path an output of its page is shown at (P-05). */
|
|
215
|
+
export const outputPath = (name) => `/outputs/${name}`;
|
|
216
|
+
/** The action that runs a widget's page written in its code (P-05). */
|
|
217
|
+
export const PAGE_ACTION = 'page';
|
|
218
|
+
const PAGE = {
|
|
219
|
+
name: PAGE_ACTION,
|
|
220
|
+
meaning: 'Run its page on its inputs: every value written under /inputs, its outputs shown in place at /outputs.',
|
|
221
|
+
words: 'Run its page',
|
|
222
|
+
context: [],
|
|
223
|
+
asks: {},
|
|
224
|
+
};
|
|
187
225
|
/** The components its surface has, or none. */
|
|
188
226
|
const surfaceComponents = (app) => app.interface.surface?.components ?? [];
|
|
189
227
|
/**
|
|
@@ -257,7 +295,10 @@ export function appPageInitialData(app) {
|
|
|
257
295
|
// Each setting at its default, as the settings' form holds it; a
|
|
258
296
|
// ChoicePicker a builder placed at a setting holds a list of the values
|
|
259
297
|
// chosen.
|
|
260
|
-
const inputs =
|
|
298
|
+
const inputs = {
|
|
299
|
+
...settingDefaults(app.interface.settings),
|
|
300
|
+
...settingDefaults(pageOf(app)?.inputs),
|
|
301
|
+
};
|
|
261
302
|
for (const node of app.interface.surface?.components ?? []) {
|
|
262
303
|
const bound = node.value?.path;
|
|
263
304
|
const id = typeof bound === 'string' && bound.startsWith('/inputs/')
|
|
@@ -269,6 +310,9 @@ export function appPageInitialData(app) {
|
|
|
269
310
|
}
|
|
270
311
|
tree.inputs = inputs;
|
|
271
312
|
}
|
|
313
|
+
if (pageOf(app)) {
|
|
314
|
+
tree.outputs = {};
|
|
315
|
+
}
|
|
272
316
|
if (paths.accepts.some(entry => entry.path === DRAFT.path)) {
|
|
273
317
|
tree.draft = '';
|
|
274
318
|
}
|
|
@@ -362,11 +406,23 @@ export function givenFiles(files) {
|
|
|
362
406
|
* them against the settings' form.
|
|
363
407
|
*/
|
|
364
408
|
export function settingsOf(app, inputs) {
|
|
409
|
+
return formValuesOf(app.interface.settings, inputs);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* The inputs of a widget's page written in its code (P-05), as the page
|
|
413
|
+
* holds them, typed as its form says, for the session's `page` action: the
|
|
414
|
+
* runtime checks them against the same form.
|
|
415
|
+
*/
|
|
416
|
+
export function pageInputsOf(app, inputs) {
|
|
417
|
+
return formValuesOf(pageOf(app)?.inputs, inputs);
|
|
418
|
+
}
|
|
419
|
+
/** A form's values as the page holds them, each typed as its field says. */
|
|
420
|
+
function formValuesOf(form, inputs) {
|
|
365
421
|
const values = inputs && typeof inputs === 'object'
|
|
366
422
|
? inputs
|
|
367
423
|
: {};
|
|
368
424
|
const settings = {};
|
|
369
|
-
for (const [id, field] of settingFields(
|
|
425
|
+
for (const [id, field] of settingFields(form)) {
|
|
370
426
|
const held = values[id];
|
|
371
427
|
if (held === undefined || held === null) {
|
|
372
428
|
continue;
|
|
@@ -425,6 +481,8 @@ export function appPageAction(app, event, read, lastInputs = '') {
|
|
|
425
481
|
...(settings ? { settings } : {}),
|
|
426
482
|
});
|
|
427
483
|
switch (event.name) {
|
|
484
|
+
case PAGE_ACTION:
|
|
485
|
+
return { runPage: pageInputsOf(app, read('/inputs')) };
|
|
428
486
|
case 'stop':
|
|
429
487
|
return { stop: true };
|
|
430
488
|
case 'new':
|
|
@@ -486,17 +544,72 @@ const button = (id, label, name, primary = false) => [
|
|
|
486
544
|
* filled — no action, no button: they go with the next run.
|
|
487
545
|
*/
|
|
488
546
|
function settingsForm(app) {
|
|
547
|
+
const page = pageOf(app);
|
|
548
|
+
if (page) {
|
|
549
|
+
return pageForm(app, page);
|
|
550
|
+
}
|
|
489
551
|
return app.interface.settings && settingFields(app.interface.settings).length
|
|
490
552
|
? [
|
|
491
553
|
{
|
|
492
554
|
id: 'settings',
|
|
493
555
|
component: 'Form',
|
|
494
556
|
schema: app.interface.settings,
|
|
557
|
+
// Drawn with the inputs its uiSchema names (P-20).
|
|
558
|
+
...(app.interface.settingsUi ? { ui: app.interface.settingsUi } : {}),
|
|
495
559
|
values: { path: '/inputs' },
|
|
496
560
|
},
|
|
497
561
|
]
|
|
498
562
|
: [];
|
|
499
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* A widget's page written in its code (P-05): one Form of its settings and
|
|
566
|
+
* its inputs, written at `/inputs` as they are filled — the page runs its
|
|
567
|
+
* code on them as they change — then each output, under its title, drawn
|
|
568
|
+
* with its component from `/outputs/<name>`; a Run button when it is not
|
|
569
|
+
* live.
|
|
570
|
+
*/
|
|
571
|
+
function pageForm(app, page) {
|
|
572
|
+
const settings = app.interface.settings;
|
|
573
|
+
const schema = {
|
|
574
|
+
...page.inputs,
|
|
575
|
+
type: 'object',
|
|
576
|
+
properties: { ...settings?.properties, ...page.inputs.properties },
|
|
577
|
+
required: [...(settings?.required ?? []), ...(page.inputs.required ?? [])],
|
|
578
|
+
};
|
|
579
|
+
const ui = { ...app.interface.settingsUi, ...page.inputsUi };
|
|
580
|
+
const outputs = page.outputs.flatMap(output => [
|
|
581
|
+
...(output.title
|
|
582
|
+
? [
|
|
583
|
+
{
|
|
584
|
+
id: `output-${output.name}-title`,
|
|
585
|
+
component: 'Text',
|
|
586
|
+
text: output.title,
|
|
587
|
+
variant: 'h4',
|
|
588
|
+
},
|
|
589
|
+
]
|
|
590
|
+
: []),
|
|
591
|
+
{
|
|
592
|
+
...output.props,
|
|
593
|
+
id: `output-${output.name}`,
|
|
594
|
+
component: output.component,
|
|
595
|
+
[pageOutputShows(output.component, app.interface.customComponents) ??
|
|
596
|
+
'text']: {
|
|
597
|
+
path: outputPath(output.name),
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
]);
|
|
601
|
+
return [
|
|
602
|
+
{
|
|
603
|
+
id: 'inputs',
|
|
604
|
+
component: 'Form',
|
|
605
|
+
schema,
|
|
606
|
+
...(Object.keys(ui).length > 0 ? { ui } : {}),
|
|
607
|
+
values: { path: '/inputs' },
|
|
608
|
+
},
|
|
609
|
+
...(page.live ? [] : button('run-page', 'Run', PAGE_ACTION, true)),
|
|
610
|
+
...outputs,
|
|
611
|
+
];
|
|
612
|
+
}
|
|
500
613
|
/**
|
|
501
614
|
* The page drawn when the application has a page layout and no surface
|
|
502
615
|
* composed: what its kind publishes, its settings as inputs, and the
|
|
@@ -510,6 +623,11 @@ export function defaultAppSurface(app) {
|
|
|
510
623
|
body.push(text('question', '/question', 'h3'), text('answer', '/answer'), text('status', '/status', 'caption'), ...inputs);
|
|
511
624
|
break;
|
|
512
625
|
case 'widget':
|
|
626
|
+
if (pageOf(app)) {
|
|
627
|
+
// Its page is its code's (P-05): the inputs, and what it shows for them.
|
|
628
|
+
body.push(...inputs);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
513
631
|
body.push(...inputs, { id: 'actions', component: 'Row', children: ['run', 'stop'] }, ...button('run', 'Run', 'run', true), ...button('stop', 'Stop', 'stop'), text('status', '/status', 'caption'), text('output', '/output'));
|
|
514
632
|
break;
|
|
515
633
|
case 'worker':
|
|
@@ -41,4 +41,5 @@ export type AppHostPageProps = {
|
|
|
41
41
|
export declare function defineAppHostPagePlugin(app: AppSpec, Page: ComponentType<AppHostPageProps>): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
42
42
|
export { AppPage, type AppPageProps } from './AppPage';
|
|
43
43
|
export * from './appPageModel';
|
|
44
|
+
export * from './pageRun';
|
|
44
45
|
export default defineAppPagePlugin;
|