@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,147 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The nine inputs a setting is drawn with (LOOP P-20): each a JSON Schema
|
|
7
|
+
* field and, where the field's own widget is not the one wanted, the widget a
|
|
8
|
+
* form's uiSchema names for it (`ui:widget`) — what `@datalayer/primer-rjsf`
|
|
9
|
+
* draws. agentspecs' `SETTING_INPUTS`, `FORM_WIDGETS` and
|
|
10
|
+
* `form_ui_problems`, said again for the page, in its sentences.
|
|
11
|
+
*
|
|
12
|
+
* Pure: no React, no network.
|
|
13
|
+
*
|
|
14
|
+
* @module loop/apps/settingsInputs
|
|
15
|
+
*/
|
|
16
|
+
/** The nine inputs: the field each is, and the widget it is drawn with. */
|
|
17
|
+
export const SETTING_INPUTS = {
|
|
18
|
+
Select: { field: 'a field with an `enum`', widget: 'select (its own)' },
|
|
19
|
+
Slider: {
|
|
20
|
+
field: 'a `number` or an `integer` with a `minimum` and a `maximum`',
|
|
21
|
+
widget: 'range',
|
|
22
|
+
},
|
|
23
|
+
Switch: { field: 'a `boolean`', widget: 'switch' },
|
|
24
|
+
TextInput: { field: 'a `string`', widget: 'text (its own), or textarea' },
|
|
25
|
+
Checkbox: { field: 'a `boolean`', widget: 'checkbox (its own)' },
|
|
26
|
+
DatePicker: {
|
|
27
|
+
field: 'a `string` of `format: date`',
|
|
28
|
+
widget: 'date (its own)',
|
|
29
|
+
},
|
|
30
|
+
MultiSelect: {
|
|
31
|
+
field: 'an `array` of `uniqueItems` whose `items` have an `enum`',
|
|
32
|
+
widget: 'select (its own), or checkboxes',
|
|
33
|
+
},
|
|
34
|
+
RadioGroup: { field: 'a field with an `enum`', widget: 'radio' },
|
|
35
|
+
Tags: {
|
|
36
|
+
field: 'an `array` of `string` items without an `enum`',
|
|
37
|
+
widget: 'tags',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
/** The widgets a form's `ui` may name (`ui:widget`). */
|
|
41
|
+
export const FORM_WIDGETS = [
|
|
42
|
+
'select',
|
|
43
|
+
'radio',
|
|
44
|
+
'range',
|
|
45
|
+
'updown',
|
|
46
|
+
'switch',
|
|
47
|
+
'checkbox',
|
|
48
|
+
'text',
|
|
49
|
+
'textarea',
|
|
50
|
+
'date',
|
|
51
|
+
'checkboxes',
|
|
52
|
+
'tags',
|
|
53
|
+
];
|
|
54
|
+
const isData = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
55
|
+
/** Why a widget cannot draw a field, in words; null when it can. */
|
|
56
|
+
export function widgetRefusal(widget, field) {
|
|
57
|
+
const kind = field.type;
|
|
58
|
+
const items = isData(field.items) ? field.items : {};
|
|
59
|
+
switch (widget) {
|
|
60
|
+
case 'select':
|
|
61
|
+
case 'radio':
|
|
62
|
+
if ('enum' in field || (widget === 'radio' && kind === 'boolean')) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (widget === 'select' && kind === 'array' && 'enum' in items) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return 'draws a choice: the field has an `enum`';
|
|
69
|
+
case 'range':
|
|
70
|
+
return (kind === 'number' || kind === 'integer') &&
|
|
71
|
+
'minimum' in field &&
|
|
72
|
+
'maximum' in field
|
|
73
|
+
? null
|
|
74
|
+
: 'is a slider: a `number` or an `integer` with a `minimum` and a `maximum`';
|
|
75
|
+
case 'updown':
|
|
76
|
+
return kind === 'number' || kind === 'integer'
|
|
77
|
+
? null
|
|
78
|
+
: 'draws a `number` or an `integer`';
|
|
79
|
+
case 'switch':
|
|
80
|
+
case 'checkbox':
|
|
81
|
+
return kind === 'boolean' ? null : 'draws a `boolean`';
|
|
82
|
+
case 'text':
|
|
83
|
+
case 'textarea':
|
|
84
|
+
return kind === 'string' ? null : 'draws a `string`';
|
|
85
|
+
case 'date':
|
|
86
|
+
return kind === 'string' ? null : 'draws a `string` of `format: date`';
|
|
87
|
+
case 'checkboxes':
|
|
88
|
+
return kind === 'array' && 'enum' in items
|
|
89
|
+
? null
|
|
90
|
+
: 'draws an `array` whose `items` have an `enum`';
|
|
91
|
+
case 'tags':
|
|
92
|
+
return kind === 'array' && items.type === 'string' && !('enum' in items)
|
|
93
|
+
? null
|
|
94
|
+
: 'draws an `array` of `string` items without an `enum`';
|
|
95
|
+
default:
|
|
96
|
+
return `is no widget: ${FORM_WIDGETS.join(', ')}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const quoted = (value) => `“${String(value)}”`;
|
|
100
|
+
/**
|
|
101
|
+
* What is wrong with how a form's fields are drawn, in agentspecs'
|
|
102
|
+
* `form_ui_problems` sentences: by field name, the `ui:` options of each —
|
|
103
|
+
* `ui:widget` one of {@link FORM_WIDGETS} that draws the field — and
|
|
104
|
+
* `ui:order` at its top.
|
|
105
|
+
*/
|
|
106
|
+
export function formUiProblems(said, schema, ui) {
|
|
107
|
+
if (!isData(ui)) {
|
|
108
|
+
return [`${said} is drawn by field name: its \`ui\` is a mapping.`];
|
|
109
|
+
}
|
|
110
|
+
const fields = isData(schema.properties) ? schema.properties : {};
|
|
111
|
+
const problems = [];
|
|
112
|
+
for (const [name, options] of Object.entries(ui)) {
|
|
113
|
+
if (name === 'ui:order') {
|
|
114
|
+
const order = Array.isArray(options) ? options : null;
|
|
115
|
+
const unknown = (order ?? []).filter(item => item !== '*' && !(String(item) in fields));
|
|
116
|
+
if (order === null || unknown.length > 0) {
|
|
117
|
+
problems.push(`${said} orders fields it does not ask: ${unknown.map(quoted).join(', ') || String(options)}.`);
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (name.startsWith('ui:')) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!(name in fields)) {
|
|
125
|
+
problems.push(`${said} says how to draw ${quoted(name)}, which it does not ask.`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!isData(options)) {
|
|
129
|
+
problems.push(`${said}'s field ${quoted(name)} is drawn with \`ui:\` options, a mapping.`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const wrong = Object.keys(options).filter(key => !key.startsWith('ui:') && key !== 'items');
|
|
133
|
+
if (wrong.length > 0) {
|
|
134
|
+
problems.push(`${said}'s field ${quoted(name)} is drawn with \`ui:\` options, not ${wrong.map(quoted).join(', ')}.`);
|
|
135
|
+
}
|
|
136
|
+
const widget = options['ui:widget'];
|
|
137
|
+
const field = fields[name];
|
|
138
|
+
if (widget === undefined || !isData(field)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const refusal = widgetRefusal(String(widget), field);
|
|
142
|
+
if (refusal) {
|
|
143
|
+
problems.push(`${said}'s field ${quoted(name)} is drawn with ${quoted(widget)}, which ${refusal}.`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return problems;
|
|
147
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a person sends (LOOP P-21), as rules: the kinds of file an
|
|
3
|
+
* application takes in its composer without asking (`interface.uploads`),
|
|
4
|
+
* each with its largest size, and how many at once — and why a file is
|
|
5
|
+
* refused, in the sentence the runtime says when it was sent all the same.
|
|
6
|
+
* agentspecs' own (`AppUploads.refusal`, `too_many`, `upload_kind_takes`),
|
|
7
|
+
* said again for the page.
|
|
8
|
+
*
|
|
9
|
+
* Pure: no React, no network.
|
|
10
|
+
*
|
|
11
|
+
* @module loop/apps/uploads
|
|
12
|
+
*/
|
|
13
|
+
import type { AppSpec, AppUploadsSpec } from '../../types/agentspecs';
|
|
14
|
+
/** The largest file a person may send an application, in megabytes. */
|
|
15
|
+
export declare const MAX_UPLOAD_MB = 25;
|
|
16
|
+
/** What a kind takes when it does not say, in megabytes. */
|
|
17
|
+
export declare const DEFAULT_UPLOAD_MB = 10;
|
|
18
|
+
/** How many files go with one message when it does not say. */
|
|
19
|
+
export declare const DEFAULT_MAX_FILES = 5;
|
|
20
|
+
/** A media type, a family of them, or an extension, lowercase. */
|
|
21
|
+
export declare const UPLOAD_KIND: RegExp;
|
|
22
|
+
/** A file as the person gives it: its name, its type, its size in bytes. */
|
|
23
|
+
export type SentFile = {
|
|
24
|
+
name: string;
|
|
25
|
+
type: string;
|
|
26
|
+
size: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Whether a kind of file takes a file: an extension (`.csv`) by its name, a
|
|
30
|
+
* media type (`application/pdf`) or a family (`image/*`) by its type.
|
|
31
|
+
*/
|
|
32
|
+
export declare function kindTakes(kind: string, file: Pick<SentFile, 'name' | 'type'>): boolean;
|
|
33
|
+
/** Whether any of `kinds` takes a file; any file when there are none. */
|
|
34
|
+
export declare const acceptsFile: (kinds: readonly string[] | undefined, file: Pick<SentFile, "name" | "type">) => boolean;
|
|
35
|
+
/** The `accept` of a file input for what the application takes. */
|
|
36
|
+
export declare const uploadsAccept: (uploads: AppUploadsSpec) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Why files sent without being asked are refused, in a sentence — the
|
|
39
|
+
* runtime's — or `null` when the application takes every one of them.
|
|
40
|
+
*/
|
|
41
|
+
export declare function uploadsRefusal(app: Pick<AppSpec, 'name' | 'interface'>, files: readonly SentFile[]): string | null;
|
|
42
|
+
/** What the composer says it takes, in a sentence. */
|
|
43
|
+
export declare function uploadsInWords(uploads: AppUploadsSpec): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** The largest file a person may send an application, in megabytes. */
|
|
6
|
+
export const MAX_UPLOAD_MB = 25;
|
|
7
|
+
/** What a kind takes when it does not say, in megabytes. */
|
|
8
|
+
export const DEFAULT_UPLOAD_MB = 10;
|
|
9
|
+
/** How many files go with one message when it does not say. */
|
|
10
|
+
export const DEFAULT_MAX_FILES = 5;
|
|
11
|
+
/** A media type, a family of them, or an extension, lowercase. */
|
|
12
|
+
export const UPLOAD_KIND = /^(?:[a-z0-9][a-z0-9.+-]*\/(?:\*|[a-z0-9][a-z0-9.+-]*)|\.[a-z0-9][a-z0-9_+-]*)$/;
|
|
13
|
+
/**
|
|
14
|
+
* Whether a kind of file takes a file: an extension (`.csv`) by its name, a
|
|
15
|
+
* media type (`application/pdf`) or a family (`image/*`) by its type.
|
|
16
|
+
*/
|
|
17
|
+
export function kindTakes(kind, file) {
|
|
18
|
+
const wanted = kind.trim().toLowerCase();
|
|
19
|
+
if (wanted.startsWith('.')) {
|
|
20
|
+
return file.name.toLowerCase().endsWith(wanted);
|
|
21
|
+
}
|
|
22
|
+
const type = (file.type || '').split(';', 1)[0].trim().toLowerCase();
|
|
23
|
+
if (wanted.endsWith('/*')) {
|
|
24
|
+
return type.startsWith(wanted.slice(0, -1));
|
|
25
|
+
}
|
|
26
|
+
return type === wanted;
|
|
27
|
+
}
|
|
28
|
+
/** Whether any of `kinds` takes a file; any file when there are none. */
|
|
29
|
+
export const acceptsFile = (kinds, file) => !kinds?.length || kinds.some(kind => kindTakes(kind, file));
|
|
30
|
+
const megabytes = (size) => `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
31
|
+
/** The `accept` of a file input for what the application takes. */
|
|
32
|
+
export const uploadsAccept = (uploads) => uploads.kinds.map(kind => kind.type).join(',');
|
|
33
|
+
/**
|
|
34
|
+
* Why files sent without being asked are refused, in a sentence — the
|
|
35
|
+
* runtime's — or `null` when the application takes every one of them.
|
|
36
|
+
*/
|
|
37
|
+
export function uploadsRefusal(app, files) {
|
|
38
|
+
if (files.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const uploads = app.interface.uploads;
|
|
42
|
+
if (!uploads) {
|
|
43
|
+
return `${app.name} takes no file sent with a message: its Appspec names none it takes (interface.uploads).`;
|
|
44
|
+
}
|
|
45
|
+
if (files.length > uploads.maxFiles) {
|
|
46
|
+
return `${files.length} files were sent at once: ${app.name} takes at most ${uploads.maxFiles}.`;
|
|
47
|
+
}
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
const kind = uploads.kinds.find(candidate => kindTakes(candidate.type, file));
|
|
50
|
+
if (!kind) {
|
|
51
|
+
const taken = uploads.kinds.map(candidate => candidate.type).join(', ');
|
|
52
|
+
return `${file.name} is not a kind of file ${app.name} takes: it takes ${taken}.`;
|
|
53
|
+
}
|
|
54
|
+
if (file.size > kind.maxMb * 1024 * 1024) {
|
|
55
|
+
return `${file.name} is ${megabytes(file.size)}: ${app.name} takes ${kind.type} of at most ${kind.maxMb} MB.`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** What the composer says it takes, in a sentence. */
|
|
61
|
+
export function uploadsInWords(uploads) {
|
|
62
|
+
const kinds = uploads.kinds
|
|
63
|
+
.map(kind => `${kind.type} up to ${kind.maxMb} MB`)
|
|
64
|
+
.join(', ');
|
|
65
|
+
return `Takes ${kinds}; ${uploads.maxFiles} at most at once.`;
|
|
66
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactComponentImplementation } from '@a2ui/react/v0_9';
|
|
2
|
+
export type A2uiComponentContribution = {
|
|
3
|
+
/** The name a surface gives it (e.g. 'Gauge'). */
|
|
4
|
+
id: string;
|
|
5
|
+
/** The application whose developer wrote it, by id. */
|
|
6
|
+
app: string;
|
|
7
|
+
/** Its renderer, as A2UI's catalog registers one. */
|
|
8
|
+
implementation: ReactComponentImplementation;
|
|
9
|
+
};
|
|
10
|
+
export declare const LoopA2uiComponent: import("@datalayer/reactor").ContributionPoint<A2uiComponentContribution>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A component an application adds to the A2UI catalog it is drawn with (LOOP
|
|
7
|
+
* P-17), as a contribution point, re-exported by `loop/core`: the components
|
|
8
|
+
* its developer wrote, contributed by the application's own plugin, so that
|
|
9
|
+
* its page, the elements its code shows and its answers draw them — in that
|
|
10
|
+
* application's workspace, and in no other.
|
|
11
|
+
*
|
|
12
|
+
* @module loop/core/a2uiComponents
|
|
13
|
+
*/
|
|
14
|
+
import { defineContributionPoint } from '@datalayer/reactor';
|
|
15
|
+
export const LoopA2uiComponent = defineContributionPoint('loop.a2ui.component');
|
package/lib/loop/core/index.d.ts
CHANGED
|
@@ -519,7 +519,7 @@ export type AgentBlueprintContribution = {
|
|
|
519
519
|
* P-19) tell a run turned in the page, and the model it runs on — as the
|
|
520
520
|
* runtime's session API applies them. Unsaid for an agent with no modes.
|
|
521
521
|
*/
|
|
522
|
-
modeEffect?: (chosen: Record<string, string
|
|
522
|
+
modeEffect?: (chosen: Record<string, string>, profile?: string) => AppModeEffect;
|
|
523
523
|
};
|
|
524
524
|
export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
|
|
525
525
|
/** One conversation opener a plugin offers on the empty chat. */
|
|
@@ -529,6 +529,12 @@ export type ChatSuggestionItem = {
|
|
|
529
529
|
/** What is submitted; the chip's words when absent. */
|
|
530
530
|
message?: string;
|
|
531
531
|
emoji?: string;
|
|
532
|
+
/**
|
|
533
|
+
* The heading it is offered under: openers of one group together, under
|
|
534
|
+
* its name, those without one first — an application's starter's category
|
|
535
|
+
* (LOOP P-20).
|
|
536
|
+
*/
|
|
537
|
+
group?: string;
|
|
532
538
|
};
|
|
533
539
|
/**
|
|
534
540
|
* Conversation openers, contributed ahead of the spec's own.
|
|
@@ -573,6 +579,7 @@ export type AssistantMenuContribution = {
|
|
|
573
579
|
};
|
|
574
580
|
export declare const LoopAssistantMenu: import("@datalayer/reactor").ContributionPoint<AssistantMenuContribution>;
|
|
575
581
|
export { LoopCanvasBlock, type CanvasBlockContribution } from './canvasBlocks';
|
|
582
|
+
export { LoopA2uiComponent, type A2uiComponentContribution, } from './a2uiComponents';
|
|
576
583
|
/**
|
|
577
584
|
* The per-example chat extras a host feeds the loop's conversation live.
|
|
578
585
|
*
|
|
@@ -649,6 +656,13 @@ export type LoopChatExtrasValue = {
|
|
|
649
656
|
* offering a first question to somebody who has already asked one.
|
|
650
657
|
*/
|
|
651
658
|
hideOpeners?: boolean;
|
|
659
|
+
/**
|
|
660
|
+
* Openers in place of the contributed ones, live: what an application
|
|
661
|
+
* offers with the profile picked in its composer (LOOP P-20) — the empty
|
|
662
|
+
* chat offers these, and changes them when the profile changes, without
|
|
663
|
+
* rebuilding the reactor. `hiddenOpeners` still applies.
|
|
664
|
+
*/
|
|
665
|
+
openers?: ChatSuggestionItem[];
|
|
652
666
|
/**
|
|
653
667
|
* Told what the agent has used, each time the chat learns it.
|
|
654
668
|
*
|
package/lib/loop/core/index.js
CHANGED
|
@@ -146,6 +146,9 @@ export const LoopAssistantMenu = defineContributionPoint('loop.assistant.menu');
|
|
|
146
146
|
// A block the Canvas may place (LOOP C-12), in a module of its own so that
|
|
147
147
|
// the palette is read without the rest of the contracts.
|
|
148
148
|
export { LoopCanvasBlock } from './canvasBlocks';
|
|
149
|
+
// A component an application adds to the catalog it is drawn with (LOOP
|
|
150
|
+
// P-17): the components its developer wrote, for its workspace alone.
|
|
151
|
+
export { LoopA2uiComponent, } from './a2uiComponents';
|
|
149
152
|
export const LoopChatExtras = defineContributionPoint('loop.chat.extras');
|
|
150
153
|
export const LoopOpeningNotebook = defineContributionPoint('loop.notebook.opening');
|
|
151
154
|
export const LoopOpeningDocument = defineContributionPoint('loop.document.opening');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CSSProperties, JSX, ReactNode } from 'react';
|
|
2
|
-
import type
|
|
2
|
+
import { type PluginRef } from '@datalayer/reactor';
|
|
3
3
|
import type { AppAccent, AppEmbedMode, AppSpec, AppThemeVariant } from '../../types/agentspecs';
|
|
4
4
|
import type { ResumedThread, ThemeOverrides } from '../../types/chat';
|
|
5
5
|
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
@@ -24,6 +24,12 @@ export type AppEmbedProps = {
|
|
|
24
24
|
colorMode?: EmbedColorMode;
|
|
25
25
|
/** The host's face, a CSS font family; the theme's by default. */
|
|
26
26
|
font?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The language the visitor reads it in, as BCP 47 tags it (`fr`, `pt-BR`):
|
|
29
|
+
* its translation into it and the chat's own words (LOOP P-26). The
|
|
30
|
+
* visitor's browser's by default.
|
|
31
|
+
*/
|
|
32
|
+
language?: string;
|
|
27
33
|
/**
|
|
28
34
|
* An agent-runtimes server the application's agent runs on. Without one,
|
|
29
35
|
* it runs on a Datalayer runtime, launched with the visitor's Datalayer
|
|
@@ -91,6 +97,20 @@ export declare function useEmbedSession({ app, serverUrl, embedToken, resume, }:
|
|
|
91
97
|
embedToken?: string;
|
|
92
98
|
resume?: boolean;
|
|
93
99
|
}): EmbedSession;
|
|
100
|
+
/**
|
|
101
|
+
* Window messages from the page (LOOP P-25): on the host's own server, the
|
|
102
|
+
* page is handed how to post one to the application's session
|
|
103
|
+
* (`host.onWindowPort`) — the session the chat goes on with, its uid known
|
|
104
|
+
* from its first message — and what the code answers is raised on the page
|
|
105
|
+
* as `window-message` events. The session returned is the one to draw: its
|
|
106
|
+
* thread told when it starts.
|
|
107
|
+
*/
|
|
108
|
+
export declare function useWindowPort({ serverUrl, embedToken, session, host, }: {
|
|
109
|
+
serverUrl?: string;
|
|
110
|
+
embedToken?: string;
|
|
111
|
+
session: EmbedSession;
|
|
112
|
+
host?: AppEmbedHost;
|
|
113
|
+
}): EmbedSession;
|
|
94
114
|
/**
|
|
95
115
|
* The page and the application talking (LOOP D-10): the tools of the host
|
|
96
116
|
* the Appspec names, given to its agent through a chat-extras plugin made
|
|
@@ -136,6 +156,8 @@ export type AppFloatingProps = {
|
|
|
136
156
|
host?: AppEmbedHost;
|
|
137
157
|
/** The visit's session, picked up again after a reload (D-13). */
|
|
138
158
|
session?: EmbedSession;
|
|
159
|
+
/** The language the visitor reads it in (P-26); the browser's by default. */
|
|
160
|
+
language?: string;
|
|
139
161
|
};
|
|
140
162
|
/**
|
|
141
163
|
* The bubble, the panel and the assistant: `ChatFloating`'s chrome in that
|
|
@@ -146,7 +168,7 @@ export type AppFloatingProps = {
|
|
|
146
168
|
* The workspace tells the chrome what the application is doing and what it
|
|
147
169
|
* last said (`onPresence`, `onSaying`).
|
|
148
170
|
*/
|
|
149
|
-
export declare function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }: AppFloatingProps): JSX.Element;
|
|
171
|
+
export declare function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, language, }: AppFloatingProps): JSX.Element;
|
|
150
172
|
/** The application's theme (`loop` unless it names one), with its (or the host's) accent and face. */
|
|
151
173
|
export declare function EmbedThemed({ accent, colorMode, font, variant, children, style, }: {
|
|
152
174
|
accent: AppAccent;
|
|
@@ -156,5 +178,5 @@ export declare function EmbedThemed({ accent, colorMode, font, variant, children
|
|
|
156
178
|
children: ReactNode;
|
|
157
179
|
style?: CSSProperties;
|
|
158
180
|
}): JSX.Element;
|
|
159
|
-
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, host, resume, }: AppEmbedProps): JSX.Element;
|
|
181
|
+
export declare function AppEmbed({ app, mode, accent, colorMode, font, language, serverUrl, instance, embedToken, height, ownPortal, plugins, host, resume, }: AppEmbedProps): JSX.Element;
|
|
160
182
|
export default AppEmbed;
|
|
@@ -40,6 +40,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
40
40
|
* @module loop/embed/AppEmbed
|
|
41
41
|
*/
|
|
42
42
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
43
|
+
import { contribution, definePlugin } from '@datalayer/reactor';
|
|
43
44
|
import { Text, registerPortalRoot } from '@primer/react';
|
|
44
45
|
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
45
46
|
import { FluentEmoji } from '@datalayer/core/lib/components/emoji';
|
|
@@ -50,9 +51,12 @@ import { AppRenderer, } from '../apps/AppRenderer';
|
|
|
50
51
|
import { AssistantCharactersPlugin, assistantCharacterFor, assistantCharactersFrom, } from '../plugins/assistant-characters';
|
|
51
52
|
import { floatingViewOf } from './embedConfig';
|
|
52
53
|
import { createChatExtrasPlugin } from '../plugins/chat-extras';
|
|
53
|
-
import { hostFrontendTools } from './hostBridge';
|
|
54
|
+
import { hostFrontendTools, hostUserRunProps, } from './hostBridge';
|
|
55
|
+
import { LoopRunProps } from '../core';
|
|
54
56
|
import { embedThemeOverrides, embedThemeStyles } from './embedTheme';
|
|
55
|
-
import { embedSessionKey, reattachSession, sessionGoneSentence, sessionKeeper, visitOfToken, } from './embedSession';
|
|
57
|
+
import { WINDOW_NOT_YET, embedSessionKey, postWindowMessage, reattachSession, sessionGoneSentence, sessionKeeper, visitOfToken, } from './embedSession';
|
|
58
|
+
import { onLoopWindowMessage } from '../../chat/base/loopWindow';
|
|
59
|
+
import { ChatLanguage, useChatWords } from '../../chat/ChatLanguage';
|
|
56
60
|
/**
|
|
57
61
|
* The visit's session, kept and picked up again (LOOP D-13): on the host's
|
|
58
62
|
* server with an embed token that names its visit, the session the chat
|
|
@@ -120,6 +124,55 @@ export function useEmbedSession({ app, serverUrl, embedToken, resume = true, })
|
|
|
120
124
|
...(found.said ? { said: found.said } : {}),
|
|
121
125
|
};
|
|
122
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Window messages from the page (LOOP P-25): on the host's own server, the
|
|
129
|
+
* page is handed how to post one to the application's session
|
|
130
|
+
* (`host.onWindowPort`) — the session the chat goes on with, its uid known
|
|
131
|
+
* from its first message — and what the code answers is raised on the page
|
|
132
|
+
* as `window-message` events. The session returned is the one to draw: its
|
|
133
|
+
* thread told when it starts.
|
|
134
|
+
*/
|
|
135
|
+
export function useWindowPort({ serverUrl, embedToken, session, host, }) {
|
|
136
|
+
const hostRef = useRef(host);
|
|
137
|
+
hostRef.current = host;
|
|
138
|
+
const given = session.thread;
|
|
139
|
+
const uid = useRef(given?.messages.length ? given.id : undefined);
|
|
140
|
+
// A thread of its own where the embed keeps none, so that its uid is known.
|
|
141
|
+
const own = useMemo(() => ({ id: generateMessageId(), messages: [] }), []);
|
|
142
|
+
const base = given ?? (serverUrl && session.ready ? own : undefined);
|
|
143
|
+
const thread = useMemo(() => base && {
|
|
144
|
+
...base,
|
|
145
|
+
onStarted: (id) => {
|
|
146
|
+
uid.current = id;
|
|
147
|
+
base.onStarted?.(id);
|
|
148
|
+
},
|
|
149
|
+
}, [base]);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const port = host?.onWindowPort;
|
|
152
|
+
if (!serverUrl || !port) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
port(async (data) => {
|
|
156
|
+
if (!uid.current) {
|
|
157
|
+
throw new Error(WINDOW_NOT_YET);
|
|
158
|
+
}
|
|
159
|
+
const said = await postWindowMessage({
|
|
160
|
+
serverUrl,
|
|
161
|
+
uid: uid.current,
|
|
162
|
+
...(embedToken ? { token: embedToken } : {}),
|
|
163
|
+
data,
|
|
164
|
+
});
|
|
165
|
+
for (const answer of said) {
|
|
166
|
+
hostRef.current?.onEvent?.({
|
|
167
|
+
type: 'window-message',
|
|
168
|
+
detail: { data: answer },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return () => port(null);
|
|
173
|
+
}, [serverUrl, embedToken, host]);
|
|
174
|
+
return thread ? { ...session, thread } : session;
|
|
175
|
+
}
|
|
123
176
|
/** What the embed says once of its session, above the conversation. */
|
|
124
177
|
function SessionSaid({ said }) {
|
|
125
178
|
return said ? (_jsx(Text, { as: "p", role: "status", sx: { px: 3, py: 2, m: 0, fontSize: 1 }, children: said })) : null;
|
|
@@ -145,7 +198,34 @@ export function useHostBridge(app, host) {
|
|
|
145
198
|
// The tools change with what the Appspec names, and the rules that decide them.
|
|
146
199
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
147
200
|
}, [handle, key]);
|
|
148
|
-
|
|
201
|
+
// Who its user is (D-21): the token the host's server signed goes with
|
|
202
|
+
// every run, for an application that takes only a signed user.
|
|
203
|
+
const signed = bridge?.user === 'signed';
|
|
204
|
+
const userPlugin = useMemo(() => {
|
|
205
|
+
const runProps = hostUserRunProps(app, () => hostRef.current ?? {});
|
|
206
|
+
return runProps
|
|
207
|
+
? definePlugin({
|
|
208
|
+
name: `loop-host-user-${app.id}`,
|
|
209
|
+
displayName: `${app.name}: who its user is`,
|
|
210
|
+
description: `The token naming its user that the host's server signed, sent with every run of ${app.name}.`,
|
|
211
|
+
octicon: 'person',
|
|
212
|
+
emoji: '\u{1F464}',
|
|
213
|
+
contributes: [
|
|
214
|
+
contribution(LoopRunProps, runProps, { id: 'host-user' }),
|
|
215
|
+
],
|
|
216
|
+
})
|
|
217
|
+
: undefined;
|
|
218
|
+
// Made again only when whether it is signed changes.
|
|
219
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
220
|
+
}, [app.id, signed]);
|
|
221
|
+
const plugins = useMemo(() => bridge
|
|
222
|
+
? [handle.plugin, ...(userPlugin ? [userPlugin] : [])]
|
|
223
|
+
: NO_HOST_PLUGINS, [bridge, handle, userPlugin]);
|
|
224
|
+
// What its code tells the page during a turn (LOOP P-25), raised on it.
|
|
225
|
+
useEffect(() => onLoopWindowMessage(data => hostRef.current?.onEvent?.({
|
|
226
|
+
type: 'window-message',
|
|
227
|
+
detail: { data },
|
|
228
|
+
})), []);
|
|
149
229
|
const said = useRef({ answering: false });
|
|
150
230
|
const before = useRef('idle');
|
|
151
231
|
const told = useRef('');
|
|
@@ -199,7 +279,8 @@ function Face({ emoji }) {
|
|
|
199
279
|
* The workspace tells the chrome what the application is doing and what it
|
|
200
280
|
* last said (`onPresence`, `onSaying`).
|
|
201
281
|
*/
|
|
202
|
-
export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }) {
|
|
282
|
+
export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, language, }) {
|
|
283
|
+
const chatText = useChatWords();
|
|
203
284
|
const [presence, setPresence] = useState('idle');
|
|
204
285
|
const [said, setSaid] = useState({ answering: false });
|
|
205
286
|
const bridge = useHostBridge(app, host);
|
|
@@ -219,7 +300,7 @@ export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides
|
|
|
219
300
|
const signedOut = !serverUrl && !token && !renderer?.datalayerVisitors;
|
|
220
301
|
return (_jsx(ChatFloating, { defaultViewMode: view, ...(character ? { assistantCharacter: character } : {}), ...(app.interface.balloon
|
|
221
302
|
? { balloonDisplay: app.interface.balloon }
|
|
222
|
-
: {}), title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip:
|
|
303
|
+
: {}), title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: chatText.talkTo(app.name), colorMode: colorMode, useStore: false, conversation: {
|
|
223
304
|
body: signedOut ? (_jsx(Text, { as: "p", role: "status", sx: { p: 3, m: 0, fontSize: 1 }, children: signedOutSentence(app) })) : session && !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session?.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
|
|
224
305
|
// The host's accent and face over the application's own, inside
|
|
225
306
|
// its conversation too, in the embed's mode.
|
|
@@ -227,7 +308,7 @@ export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides
|
|
|
227
308
|
// The window draws its face, its name and close.
|
|
228
309
|
hideChatHeader: true,
|
|
229
310
|
// Mounted closed: the caret goes in when the window opens.
|
|
230
|
-
autoFocusPrompt: false, onPresence: onPresence, onSaying: onSaying, plugins: bridge.plugins, ...(session?.thread ? { thread: session.thread } : {}), ...renderer })] })),
|
|
311
|
+
autoFocusPrompt: false, onPresence: onPresence, onSaying: onSaying, plugins: bridge.plugins, ...(session?.thread ? { thread: session.thread } : {}), ...(language ? { language } : {}), ...renderer })] })),
|
|
231
312
|
presence,
|
|
232
313
|
saying: said.saying,
|
|
233
314
|
answering: said.answering,
|
|
@@ -276,9 +357,16 @@ export function EmbedThemed({ accent, colorMode, font, variant, children, style,
|
|
|
276
357
|
*/
|
|
277
358
|
_jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: getThemeConfig(variant).primerTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
|
|
278
359
|
}
|
|
279
|
-
export function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height = 640, ownPortal = false, plugins = NO_PLUGINS, host, resume = true, }) {
|
|
360
|
+
export function AppEmbed({ app, mode, accent, colorMode, font, language, serverUrl, instance, embedToken, height = 640, ownPortal = false, plugins = NO_PLUGINS, host, resume = true, }) {
|
|
280
361
|
const bridge = useHostBridge(app, host);
|
|
281
|
-
const
|
|
362
|
+
const kept = useEmbedSession({ app, serverUrl, embedToken, resume });
|
|
363
|
+
// Window messages from the page, on the host's own server (P-25).
|
|
364
|
+
const session = useWindowPort({
|
|
365
|
+
serverUrl,
|
|
366
|
+
embedToken,
|
|
367
|
+
session: kept,
|
|
368
|
+
host,
|
|
369
|
+
});
|
|
282
370
|
const system = useSystemMode();
|
|
283
371
|
// The theme the application names (T-30), `loop` when none; its mode
|
|
284
372
|
// the host's, else the application's, else the visitor's system's.
|
|
@@ -297,15 +385,15 @@ export function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instan
|
|
|
297
385
|
return (_jsx("p", { role: "status", className: "datalayer-app-said", children: assistant.problem }));
|
|
298
386
|
}
|
|
299
387
|
const character = assistant?.character;
|
|
300
|
-
return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, variant: variant, style: view
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
388
|
+
return (_jsx(ChatLanguage, { language: language, children: _jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, variant: variant, style: view
|
|
389
|
+
? // Floating: nothing in the page's flow, nothing painted behind.
|
|
390
|
+
{ backgroundColor: 'transparent' }
|
|
391
|
+
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(AppFloating, { app: app, view: view, serverUrl: serverUrl, instance: instance, embedToken: embedToken, colorMode: resolvedMode, themeVariant: variant, themeOverrides: themeOverrides, character: character, host: host, session: session, ...(language ? { language } : {}) })) : !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
|
|
392
|
+
// The host's accent and face over the application's own, inside
|
|
393
|
+
// its conversation too, in the embed's mode: the host's or its
|
|
394
|
+
// visitor's system's, not a Datalayer setting.
|
|
395
|
+
themeOverrides: themeOverrides, colorMode: resolvedMode, themeVariant: variant,
|
|
396
|
+
// What the page gives it, and what it tells the page (D-10).
|
|
397
|
+
plugins: bridge.plugins, onPresence: bridge.onPresence, onSaying: bridge.onSaying, ...(session.thread ? { thread: session.thread } : {}), ...(language ? { language } : {}) })] }))] }) }));
|
|
310
398
|
}
|
|
311
399
|
export default AppEmbed;
|
|
@@ -31,6 +31,11 @@ export declare function appspecOfText(text: string): AppSpec;
|
|
|
31
31
|
export declare function runnable({ app, problems }: ParsedAppspec): AppSpec;
|
|
32
32
|
/** The run page an application on Datalayer is framed from. */
|
|
33
33
|
export declare function frameSrcOf(origin: string, app: string, token?: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* A framed run page told the visitor's language (P-26): `language=` in its
|
|
36
|
+
* query, before the fragment that holds the token.
|
|
37
|
+
*/
|
|
38
|
+
export declare function frameInLanguage(src: string, language?: string): string;
|
|
34
39
|
/**
|
|
35
40
|
* An application on Datalayer, read with its embed token: what the space
|
|
36
41
|
* keeps of it (`model_s`, the stored definition), whose `spec` is the
|