@datalayer/agent-runtimes 1.3.28 → 1.3.30
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/AgentNode.js +14 -33
- package/lib/chat/ChatFloating.d.ts +12 -2
- package/lib/chat/ChatFloating.js +196 -25
- package/lib/chat/assistant/AssistantStage.d.ts +70 -0
- package/lib/chat/assistant/AssistantStage.js +192 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +26 -0
- package/lib/chat/assistant/SpeechBalloon.js +46 -0
- package/lib/chat/assistant/SpriteCharacter.d.ts +26 -0
- package/lib/chat/assistant/SpriteCharacter.js +73 -0
- package/lib/chat/assistant/characters.d.ts +30 -0
- package/lib/chat/assistant/characters.js +41 -0
- package/lib/chat/assistant/formats/acs.d.ts +88 -0
- package/lib/chat/assistant/formats/acs.js +493 -0
- package/lib/chat/assistant/formats/agentCompression.d.ts +5 -0
- package/lib/chat/assistant/formats/agentCompression.js +92 -0
- package/lib/chat/assistant/formats/blobs.d.ts +6 -0
- package/lib/chat/assistant/formats/blobs.js +52 -0
- package/lib/chat/assistant/formats/clippy.d.ts +21 -0
- package/lib/chat/assistant/formats/clippy.js +167 -0
- package/lib/chat/assistant/formats/index.d.ts +13 -0
- package/lib/chat/assistant/formats/index.js +15 -0
- package/lib/chat/assistant/formats/objectLiteral.d.ts +23 -0
- package/lib/chat/assistant/formats/objectLiteral.js +233 -0
- package/lib/chat/assistant/formats/stateAnimations.d.ts +16 -0
- package/lib/chat/assistant/formats/stateAnimations.js +115 -0
- package/lib/chat/assistant/formats/types.d.ts +56 -0
- package/lib/chat/assistant/formats/types.js +11 -0
- package/lib/chat/assistant/state.d.ts +62 -0
- package/lib/chat/assistant/state.js +116 -0
- package/lib/chat/base/ChatBase.js +41 -32
- package/lib/chat/base/modelChoice.d.ts +13 -0
- package/lib/chat/base/modelChoice.js +13 -0
- package/lib/chat/header/ChatViewModeToggle.js +6 -3
- package/lib/{loop/plugins/chat → chat/presence}/Presence.js +1 -1
- package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.d.ts +7 -1
- package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.js +18 -0
- package/lib/chat/viewModes.d.ts +6 -0
- package/lib/chat/viewModes.js +10 -1
- package/lib/examples/ChatAssistantExample.d.ts +8 -0
- package/lib/examples/ChatAssistantExample.js +59 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/main.js +3 -0
- package/lib/loop/apps/AppRenderer.d.ts +34 -2
- package/lib/loop/apps/AppRenderer.js +96 -29
- package/lib/loop/apps/agent.d.ts +15 -0
- package/lib/loop/apps/agent.js +42 -0
- package/lib/loop/apps/appspec.d.ts +5 -1
- package/lib/loop/apps/appspec.js +21 -1
- package/lib/loop/apps/checks.js +3 -1
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/core/index.d.ts +23 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/embed/AppEmbed.d.ts +42 -0
- package/lib/loop/embed/AppEmbed.js +205 -0
- package/lib/loop/embed/datalayer-app.d.ts +1 -0
- package/lib/loop/embed/datalayer-app.js +16 -0
- package/lib/loop/embed/element.d.ts +57 -0
- package/lib/loop/embed/element.js +405 -0
- package/lib/loop/embed/embedConfig.d.ts +120 -0
- package/lib/loop/embed/embedConfig.js +148 -0
- package/lib/loop/embed/embedTheme.d.ts +24 -0
- package/lib/loop/embed/embedTheme.js +83 -0
- package/lib/loop/index.d.ts +6 -2
- package/lib/loop/index.js +8 -2
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +21 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +34 -6
- package/lib/loop/plugins/agent-capacity/index.d.ts +4 -0
- package/lib/loop/plugins/agent-capacity/index.js +3 -1
- package/lib/loop/plugins/agents/switchable.d.ts +7 -0
- package/lib/loop/plugins/agents/switchable.js +91 -88
- package/lib/loop/plugins/app-page/AppPage.d.ts +16 -0
- package/lib/loop/plugins/app-page/AppPage.js +53 -0
- package/lib/loop/plugins/app-page/appPage.d.ts +180 -0
- package/lib/loop/plugins/app-page/appPage.js +443 -0
- package/lib/loop/plugins/app-page/index.d.ts +8 -0
- package/lib/loop/plugins/app-page/index.js +34 -0
- package/lib/loop/plugins/assistant-characters/index.d.ts +30 -0
- package/lib/loop/plugins/assistant-characters/index.js +41 -0
- package/lib/loop/plugins/chat/ChatView.js +50 -39
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +31 -0
- package/lib/loop/plugins/page-layout/SplitLayout.js +129 -0
- package/lib/loop/plugins/page-layout/index.d.ts +13 -0
- package/lib/loop/plugins/page-layout/index.js +15 -2
- package/lib/loop/presets.d.ts +12 -0
- package/lib/loop/presets.js +5 -3
- package/lib/specs/actions.js +14 -0
- package/lib/specs/agents/agents.js +4 -0
- package/lib/specs/apps.d.ts +13 -1
- package/lib/specs/apps.js +2715 -373
- package/lib/specs/appspecSchema.js +21 -3
- package/lib/types/agentspecs.d.ts +21 -1
- package/lib/types/chat.d.ts +1 -1
- package/lib/types/config.d.ts +12 -0
- package/package.json +2 -1
- package/scripts/codegen/generate_agents.py +50 -2
- package/scripts/codegen/generate_apps.py +36 -6
- /package/lib/{loop/plugins/chat → chat/presence}/Presence.d.ts +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
2
|
+
import { type ParsedAppspec } from '../apps/appspec';
|
|
3
|
+
/** What the framed run page and the element say to each other. */
|
|
4
|
+
export declare const EMBED_MESSAGES: {
|
|
5
|
+
readonly size: "datalayer-app:size";
|
|
6
|
+
readonly decision: "datalayer-app:decision";
|
|
7
|
+
/** To the frame: an embed token, the first or a new one. */
|
|
8
|
+
readonly token: "datalayer-app:token";
|
|
9
|
+
/** From the frame: the token lapsed; a new one is needed. */
|
|
10
|
+
readonly tokenExpired: "datalayer-app:token-expired";
|
|
11
|
+
};
|
|
12
|
+
/** Where an application comes from, once read. */
|
|
13
|
+
export type EmbedSource = {
|
|
14
|
+
kind: 'spec';
|
|
15
|
+
app: AppSpec;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'frame';
|
|
18
|
+
src: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'said';
|
|
21
|
+
text: string;
|
|
22
|
+
expired?: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** An Appspec from its text, YAML or JSON; refused in a sentence when it is neither. */
|
|
25
|
+
export declare function appspecOfText(text: string): AppSpec;
|
|
26
|
+
/**
|
|
27
|
+
* An application the embed can run, or a sentence saying why not: one with
|
|
28
|
+
* an id, and an agent to answer — what the spec tolerates in a draft, an
|
|
29
|
+
* embed cannot run.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runnable({ app, problems }: ParsedAppspec): AppSpec;
|
|
32
|
+
/** The run page an application on Datalayer is framed from. */
|
|
33
|
+
export declare function frameSrcOf(origin: string, app: string, token?: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* An application on Datalayer, read with its embed token: what the space
|
|
36
|
+
* keeps of it (`model_s`, the stored definition), whose `spec` is the
|
|
37
|
+
* Appspec. A decision is framed; so is an application that has no spec.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readEmbeddedApp({ api, origin, app, token, fetcher, }: {
|
|
40
|
+
api: string;
|
|
41
|
+
origin: string;
|
|
42
|
+
app: string;
|
|
43
|
+
token: string;
|
|
44
|
+
fetcher?: typeof fetch;
|
|
45
|
+
}): Promise<EmbedSource>;
|
|
46
|
+
export type DatalayerAppElementOptions = {
|
|
47
|
+
/** The element's name; `datalayer-app` by default. */
|
|
48
|
+
tag?: string;
|
|
49
|
+
/** Where the script was loaded from: the default origin, and where its stylesheet is. */
|
|
50
|
+
scriptUrl?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Define the element, once. Returns its class, or `undefined` where there is
|
|
54
|
+
* no `customElements` (a server render) — a second call returns the class
|
|
55
|
+
* already defined.
|
|
56
|
+
*/
|
|
57
|
+
export declare function defineDatalayerAppElement(options?: DatalayerAppElementOptions): CustomElementConstructor | undefined;
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* `<datalayer-app>`: an application in another product's page, as one
|
|
8
|
+
* script tag and one element (LOOP D-08), in the mode its Appspec says
|
|
9
|
+
* (D-07), its theme isolated from the host's and overridable in the three
|
|
10
|
+
* ways that matter (T-13, D-11).
|
|
11
|
+
*
|
|
12
|
+
* <script src="https://datalayer.app/embed/datalayer-app.js" async></script>
|
|
13
|
+
* <datalayer-app app="01M…" origin="https://datalayer.app"></datalayer-app>
|
|
14
|
+
*
|
|
15
|
+
* What it shows comes from one of three places, first found first:
|
|
16
|
+
*
|
|
17
|
+
* 1. an Appspec written in the element, as YAML or JSON
|
|
18
|
+
* (`<script type="application/yaml">…</script>`);
|
|
19
|
+
* 2. an Appspec at an address (`spec="https://…/support-desk.yaml"`);
|
|
20
|
+
* 3. an application on Datalayer (`app`), read with its embed token
|
|
21
|
+
* (`token`). A decision, or an application given without a token, is
|
|
22
|
+
* framed as before: Datalayer's run page in an iframe, sized to it, its
|
|
23
|
+
* `decision` and `token-expired` events raised on the element.
|
|
24
|
+
*
|
|
25
|
+
* Every other application is drawn here, in the element's shadow root, by
|
|
26
|
+
* `AppEmbed` — inline, or floating over the page as a bubble, a panel or the
|
|
27
|
+
* assistant. The bundle's stylesheet is linked inside that root, the
|
|
28
|
+
* styled-components rules are written into it, and Primer's overlays are
|
|
29
|
+
* drawn in it, so nothing of the application's styles reaches the page and
|
|
30
|
+
* nothing of the page's reaches the application.
|
|
31
|
+
*
|
|
32
|
+
* @module loop/embed/element
|
|
33
|
+
*/
|
|
34
|
+
import { createRoot } from 'react-dom/client';
|
|
35
|
+
import { StyleSheetManager } from 'styled-components';
|
|
36
|
+
import { loopThemeStyles } from '@datalayer/primer-addons';
|
|
37
|
+
import { coreStore } from '@datalayer/core/lib/state/substates/CoreState';
|
|
38
|
+
import { parseAppspec } from '../apps/appspec';
|
|
39
|
+
import { readAppspecYaml } from '../apps/yaml';
|
|
40
|
+
import { AppEmbed } from './AppEmbed';
|
|
41
|
+
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, } from './embedConfig';
|
|
42
|
+
import { embedShadowCss } from './embedTheme';
|
|
43
|
+
/** What the framed run page and the element say to each other. */
|
|
44
|
+
export const EMBED_MESSAGES = {
|
|
45
|
+
size: 'datalayer-app:size',
|
|
46
|
+
decision: 'datalayer-app:decision',
|
|
47
|
+
/** To the frame: an embed token, the first or a new one. */
|
|
48
|
+
token: 'datalayer-app:token',
|
|
49
|
+
/** From the frame: the token lapsed; a new one is needed. */
|
|
50
|
+
tokenExpired: 'datalayer-app:token-expired',
|
|
51
|
+
};
|
|
52
|
+
/** An Appspec from its text, YAML or JSON; refused in a sentence when it is neither. */
|
|
53
|
+
export function appspecOfText(text) {
|
|
54
|
+
// Written in a page, a document is indented with its markup: the
|
|
55
|
+
// indentation every line shares is the page's, not the document's.
|
|
56
|
+
const lines = text.replace(/\t/g, ' ').split('\n');
|
|
57
|
+
const indent = Math.min(...lines
|
|
58
|
+
.filter(line => line.trim())
|
|
59
|
+
.map(line => line.length - line.trimStart().length));
|
|
60
|
+
const trimmed = lines
|
|
61
|
+
.map(line => line.slice(Number.isFinite(indent) ? indent : 0))
|
|
62
|
+
.join('\n')
|
|
63
|
+
.trim();
|
|
64
|
+
if (!trimmed) {
|
|
65
|
+
throw new EmbedAttributeError('datalayer-app: the Appspec is empty.');
|
|
66
|
+
}
|
|
67
|
+
let parsed;
|
|
68
|
+
if (trimmed.startsWith('{')) {
|
|
69
|
+
let document;
|
|
70
|
+
try {
|
|
71
|
+
document = JSON.parse(trimmed);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new EmbedAttributeError(`datalayer-app: the Appspec is not JSON (${error.message}).`);
|
|
75
|
+
}
|
|
76
|
+
parsed = parseAppspec(document);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
parsed = readAppspecYaml(trimmed);
|
|
80
|
+
}
|
|
81
|
+
return runnable(parsed);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* An application the embed can run, or a sentence saying why not: one with
|
|
85
|
+
* an id, and an agent to answer — what the spec tolerates in a draft, an
|
|
86
|
+
* embed cannot run.
|
|
87
|
+
*/
|
|
88
|
+
export function runnable({ app, problems }) {
|
|
89
|
+
if (!app.id) {
|
|
90
|
+
throw new EmbedAttributeError(`datalayer-app: the Appspec names no application${problems.length ? ` (${problems[0]})` : ''}.`);
|
|
91
|
+
}
|
|
92
|
+
if (app.kind !== 'decision' && !app.agent) {
|
|
93
|
+
throw new EmbedAttributeError(`datalayer-app: “${app.name || app.id}” names no agent to answer; an embedded application is run by an agent (a team is not supported yet).`);
|
|
94
|
+
}
|
|
95
|
+
return app;
|
|
96
|
+
}
|
|
97
|
+
/** The run page an application on Datalayer is framed from. */
|
|
98
|
+
export function frameSrcOf(origin, app, token = '') {
|
|
99
|
+
// The token in the fragment, which no server is sent: it reaches the page
|
|
100
|
+
// and no access log.
|
|
101
|
+
return `${origin.replace(/\/+$/, '')}/public/apps/${encodeURIComponent(app)}/run?embed=1${token ? `#token=${encodeURIComponent(token)}` : ''}`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* An application on Datalayer, read with its embed token: what the space
|
|
105
|
+
* keeps of it (`model_s`, the stored definition), whose `spec` is the
|
|
106
|
+
* Appspec. A decision is framed; so is an application that has no spec.
|
|
107
|
+
*/
|
|
108
|
+
export async function readEmbeddedApp({ api, origin, app, token, fetcher = fetch, }) {
|
|
109
|
+
const response = await fetcher(`${api.replace(/\/+$/, '')}/api/spacer/v1/apps/${encodeURIComponent(app)}/embedded`, { headers: { Authorization: `Bearer ${token}` } });
|
|
110
|
+
if (response.status === 401) {
|
|
111
|
+
return {
|
|
112
|
+
kind: 'said',
|
|
113
|
+
text: 'datalayer-app: the embed token expired or is not for this application.',
|
|
114
|
+
expired: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
return {
|
|
119
|
+
kind: 'said',
|
|
120
|
+
text: `datalayer-app: the application could not be read (${response.status}).`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const body = (await response.json());
|
|
124
|
+
let definition;
|
|
125
|
+
try {
|
|
126
|
+
definition = JSON.parse(String(body.app?.model_s ?? '{}'));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
definition = {};
|
|
130
|
+
}
|
|
131
|
+
if (!definition.spec) {
|
|
132
|
+
return { kind: 'frame', src: frameSrcOf(origin, app, token) };
|
|
133
|
+
}
|
|
134
|
+
const spec = runnable(parseAppspec(definition.spec));
|
|
135
|
+
return spec.kind === 'decision'
|
|
136
|
+
? { kind: 'frame', src: frameSrcOf(origin, app, token) }
|
|
137
|
+
: { kind: 'spec', app: spec };
|
|
138
|
+
}
|
|
139
|
+
/** The script that is running, when it is a script tag. */
|
|
140
|
+
function currentScriptUrl() {
|
|
141
|
+
try {
|
|
142
|
+
const script = document.currentScript;
|
|
143
|
+
return script?.src ? new URL(script.src, location.href).href : '';
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Define the element, once. Returns its class, or `undefined` where there is
|
|
151
|
+
* no `customElements` (a server render) — a second call returns the class
|
|
152
|
+
* already defined.
|
|
153
|
+
*/
|
|
154
|
+
export function defineDatalayerAppElement(options = {}) {
|
|
155
|
+
if (typeof window === 'undefined' || !('customElements' in window)) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const tag = options.tag ?? EMBED_TAG;
|
|
159
|
+
const existing = customElements.get(tag);
|
|
160
|
+
if (existing) {
|
|
161
|
+
return existing;
|
|
162
|
+
}
|
|
163
|
+
const scriptUrl = options.scriptUrl ?? currentScriptUrl();
|
|
164
|
+
const scriptOrigin = scriptUrl ? new URL(scriptUrl).origin : '';
|
|
165
|
+
// The bundle's stylesheet, beside its script.
|
|
166
|
+
const stylesheetUrl = scriptUrl
|
|
167
|
+
? scriptUrl.replace(/\.js(\?.*)?$/, '.css')
|
|
168
|
+
: '';
|
|
169
|
+
class DatalayerAppElement extends HTMLElement {
|
|
170
|
+
static get observedAttributes() {
|
|
171
|
+
return EMBED_OBSERVED_ATTRIBUTES;
|
|
172
|
+
}
|
|
173
|
+
root = null;
|
|
174
|
+
frame = null;
|
|
175
|
+
source = null;
|
|
176
|
+
loading = 0;
|
|
177
|
+
constructor() {
|
|
178
|
+
super();
|
|
179
|
+
this.attachShadow({ mode: 'open' });
|
|
180
|
+
this.onMessage = this.onMessage.bind(this);
|
|
181
|
+
}
|
|
182
|
+
/** An embed token, the attribute's. Set it to hand a new one over. */
|
|
183
|
+
get token() {
|
|
184
|
+
return this.getAttribute('token') ?? '';
|
|
185
|
+
}
|
|
186
|
+
set token(value) {
|
|
187
|
+
if (value) {
|
|
188
|
+
this.setAttribute('token', String(value));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.removeAttribute('token');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
connectedCallback() {
|
|
195
|
+
window.addEventListener('message', this.onMessage);
|
|
196
|
+
void this.load();
|
|
197
|
+
}
|
|
198
|
+
disconnectedCallback() {
|
|
199
|
+
window.removeEventListener('message', this.onMessage);
|
|
200
|
+
this.unmount();
|
|
201
|
+
}
|
|
202
|
+
attributeChangedCallback(name, before, after) {
|
|
203
|
+
if (!this.isConnected || before === after) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// A new token is handed to the framed page that is already there,
|
|
207
|
+
// which keeps what the person entered.
|
|
208
|
+
if (name === 'token' && this.frame?.contentWindow && after) {
|
|
209
|
+
this.frame.contentWindow.postMessage({ type: EMBED_MESSAGES.token, token: after }, this.origin());
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// How it looks is drawn again; what it is, read again.
|
|
213
|
+
if (['mode', 'accent', 'theme', 'font', 'height'].includes(name)) {
|
|
214
|
+
this.draw();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
void this.load();
|
|
218
|
+
}
|
|
219
|
+
origin() {
|
|
220
|
+
return (this.getAttribute('origin') ||
|
|
221
|
+
scriptOrigin ||
|
|
222
|
+
location.origin).replace(/\/+$/, '');
|
|
223
|
+
}
|
|
224
|
+
inputs() {
|
|
225
|
+
const attributes = {};
|
|
226
|
+
for (const name of EMBED_OBSERVED_ATTRIBUTES) {
|
|
227
|
+
attributes[name] = this.getAttribute(name);
|
|
228
|
+
}
|
|
229
|
+
const computed = getComputedStyle(this);
|
|
230
|
+
const variables = {};
|
|
231
|
+
for (const [key, property] of Object.entries(EMBED_HOST_VARIABLES)) {
|
|
232
|
+
const value = computed.getPropertyValue(property).trim();
|
|
233
|
+
if (value) {
|
|
234
|
+
variables[key] = value.replace(/^["']|["']$/g, '');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { attributes, variables };
|
|
238
|
+
}
|
|
239
|
+
/** Where the application comes from: the element's own Appspec, an address, or Datalayer. */
|
|
240
|
+
async read() {
|
|
241
|
+
const inline = this.querySelector('script[type="application/yaml"], script[type="application/json"], script[type="text/yaml"]');
|
|
242
|
+
if (inline?.textContent?.trim()) {
|
|
243
|
+
return { kind: 'spec', app: appspecOfText(inline.textContent) };
|
|
244
|
+
}
|
|
245
|
+
const spec = this.getAttribute('spec');
|
|
246
|
+
if (spec) {
|
|
247
|
+
const response = await fetch(new URL(spec, location.href).href);
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
return {
|
|
250
|
+
kind: 'said',
|
|
251
|
+
text: `datalayer-app: the Appspec at ${spec} could not be read (${response.status}).`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return { kind: 'spec', app: appspecOfText(await response.text()) };
|
|
255
|
+
}
|
|
256
|
+
const app = this.getAttribute('app');
|
|
257
|
+
if (!app) {
|
|
258
|
+
return {
|
|
259
|
+
kind: 'said',
|
|
260
|
+
text: 'datalayer-app: the "app" attribute names the application to show, or "spec" the address of its Appspec.',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const token = this.token;
|
|
264
|
+
if (!token) {
|
|
265
|
+
// A public application: Datalayer's run page, framed.
|
|
266
|
+
return { kind: 'frame', src: frameSrcOf(this.origin(), app) };
|
|
267
|
+
}
|
|
268
|
+
return readEmbeddedApp({
|
|
269
|
+
api: this.getAttribute('api') ||
|
|
270
|
+
coreStore.getState().configuration?.spacerUrl ||
|
|
271
|
+
'https://prod1.datalayer.run',
|
|
272
|
+
origin: this.origin(),
|
|
273
|
+
app,
|
|
274
|
+
token,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
async load() {
|
|
278
|
+
const turn = ++this.loading;
|
|
279
|
+
let source;
|
|
280
|
+
try {
|
|
281
|
+
source = await this.read();
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
source = {
|
|
285
|
+
kind: 'said',
|
|
286
|
+
text: error instanceof Error
|
|
287
|
+
? error.message
|
|
288
|
+
: `datalayer-app: ${String(error)}`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
// A later read wins over an earlier one still in flight.
|
|
292
|
+
if (turn !== this.loading || !this.isConnected) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.source = source;
|
|
296
|
+
this.draw();
|
|
297
|
+
if (source.kind === 'said' && source.expired) {
|
|
298
|
+
this.dispatchEvent(new CustomEvent('token-expired', {
|
|
299
|
+
detail: { app: this.getAttribute('app') ?? '' },
|
|
300
|
+
bubbles: true,
|
|
301
|
+
composed: true,
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
unmount() {
|
|
306
|
+
this.root?.unmount();
|
|
307
|
+
this.root = null;
|
|
308
|
+
this.frame = null;
|
|
309
|
+
}
|
|
310
|
+
/** The shadow root's skeleton: the bundle's stylesheet, the element's own, and what goes in it. */
|
|
311
|
+
skeleton(themeCss) {
|
|
312
|
+
const shadow = this.shadowRoot;
|
|
313
|
+
this.unmount();
|
|
314
|
+
shadow.replaceChildren();
|
|
315
|
+
if (stylesheetUrl) {
|
|
316
|
+
const link = document.createElement('link');
|
|
317
|
+
link.rel = 'stylesheet';
|
|
318
|
+
link.href = stylesheetUrl;
|
|
319
|
+
shadow.appendChild(link);
|
|
320
|
+
}
|
|
321
|
+
const style = document.createElement('style');
|
|
322
|
+
style.textContent = embedShadowCss(themeCss);
|
|
323
|
+
shadow.appendChild(style);
|
|
324
|
+
return shadow;
|
|
325
|
+
}
|
|
326
|
+
say(text) {
|
|
327
|
+
this.setAttribute('data-embed-mode', 'inline');
|
|
328
|
+
const shadow = this.skeleton();
|
|
329
|
+
const note = document.createElement('p');
|
|
330
|
+
note.className = 'datalayer-app-said';
|
|
331
|
+
note.setAttribute('role', 'status');
|
|
332
|
+
note.textContent = text;
|
|
333
|
+
shadow.appendChild(note);
|
|
334
|
+
}
|
|
335
|
+
draw() {
|
|
336
|
+
const source = this.source;
|
|
337
|
+
if (!source) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (source.kind === 'said') {
|
|
341
|
+
this.say(source.text);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (source.kind === 'frame') {
|
|
345
|
+
this.setAttribute('data-embed-mode', 'inline');
|
|
346
|
+
const shadow = this.skeleton();
|
|
347
|
+
const frame = document.createElement('iframe');
|
|
348
|
+
frame.className = 'datalayer-app-frame';
|
|
349
|
+
frame.src = source.src;
|
|
350
|
+
frame.title = 'Datalayer app';
|
|
351
|
+
frame.style.height = `${inlineHeightOf(this.getAttribute('height'))}px`;
|
|
352
|
+
frame.setAttribute('allow', 'clipboard-write');
|
|
353
|
+
shadow.appendChild(frame);
|
|
354
|
+
this.frame = frame;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
let look;
|
|
358
|
+
try {
|
|
359
|
+
look = embedLookOf(this.inputs(), source.app);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
this.say(error.message);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.setAttribute('data-embed-mode', look.mode);
|
|
366
|
+
const shadow = this.skeleton(loopThemeStyles.css);
|
|
367
|
+
const mount = document.createElement('div');
|
|
368
|
+
mount.className = 'datalayer-app-root';
|
|
369
|
+
shadow.appendChild(mount);
|
|
370
|
+
this.root = createRoot(mount);
|
|
371
|
+
const server = this.getAttribute('server') || undefined;
|
|
372
|
+
this.root.render(_jsx(StyleSheetManager, { target: shadow, children: _jsx(AppEmbed, { app: source.app, mode: look.mode, accent: look.accent, colorMode: look.colorMode, font: look.font, serverUrl: server, height: inlineHeightOf(this.getAttribute('height')), ownPortal: true }) }));
|
|
373
|
+
}
|
|
374
|
+
onMessage(event) {
|
|
375
|
+
if (!this.frame || event.source !== this.frame.contentWindow) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (event.origin !== this.origin()) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const data = (event.data ?? {});
|
|
382
|
+
if (data.type === EMBED_MESSAGES.size &&
|
|
383
|
+
typeof data.height === 'number' &&
|
|
384
|
+
data.height > 0) {
|
|
385
|
+
this.frame.style.height = `${Math.ceil(data.height)}px`;
|
|
386
|
+
}
|
|
387
|
+
else if (data.type === EMBED_MESSAGES.decision) {
|
|
388
|
+
this.dispatchEvent(new CustomEvent('decision', {
|
|
389
|
+
detail: data.record,
|
|
390
|
+
bubbles: true,
|
|
391
|
+
composed: true,
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
else if (data.type === EMBED_MESSAGES.tokenExpired) {
|
|
395
|
+
this.dispatchEvent(new CustomEvent('token-expired', {
|
|
396
|
+
detail: { app: this.getAttribute('app') ?? '' },
|
|
397
|
+
bubbles: true,
|
|
398
|
+
composed: true,
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
customElements.define(tag, DatalayerAppElement);
|
|
404
|
+
return DatalayerAppElement;
|
|
405
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An application in another product's page, as its host writes it (LOOP
|
|
3
|
+
* D-07, D-08, D-11, T-13): one script tag and one element, `<datalayer-app>`,
|
|
4
|
+
* whose attributes — or, for the theme, the host's CSS variables — say what
|
|
5
|
+
* it shows and how it looks.
|
|
6
|
+
*
|
|
7
|
+
* Pure: what the element's attributes mean, the four modes and the chat each
|
|
8
|
+
* floats in, what a host may override and how it is checked, and the snippet
|
|
9
|
+
* the *Ship* tab copies. Nothing here touches the DOM, so all of it is read
|
|
10
|
+
* and tested without one.
|
|
11
|
+
*
|
|
12
|
+
* @module loop/embed/embedConfig
|
|
13
|
+
*/
|
|
14
|
+
import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
|
|
15
|
+
/** The element's name: the one a host writes. */
|
|
16
|
+
export declare const EMBED_TAG = "datalayer-app";
|
|
17
|
+
/** Where the script is served on a Datalayer origin; the stylesheet beside it. */
|
|
18
|
+
export declare const EMBED_SCRIPT_PATH = "/embed/datalayer-app.js";
|
|
19
|
+
export declare const EMBED_STYLESHEET_PATH = "/embed/datalayer-app.css";
|
|
20
|
+
/**
|
|
21
|
+
* The element's attributes, each in a sentence — what the documentation and
|
|
22
|
+
* the element's own refusals say.
|
|
23
|
+
*/
|
|
24
|
+
export declare const EMBED_ATTRIBUTES: {
|
|
25
|
+
readonly app: "The application’s id on Datalayer.";
|
|
26
|
+
readonly origin: "Where Datalayer is; the origin the script was loaded from by default.";
|
|
27
|
+
readonly token: "An embed token for an application that is not public, issued to your server for each visit.";
|
|
28
|
+
readonly spec: "The address of an Appspec (YAML or JSON) to run, in place of `app`.";
|
|
29
|
+
readonly mode: "inline, bubble, panel or assistant; the Appspec’s `deployment.embedded.mode` by default, then inline.";
|
|
30
|
+
readonly accent: "green, rose, sky, lime, sun or violet; the Appspec’s `interface.accent` by default.";
|
|
31
|
+
readonly theme: "light, dark or auto (the visitor’s system); auto by default.";
|
|
32
|
+
readonly font: "A CSS font family for the application’s text; Inter, then the system’s sans-serif, by default.";
|
|
33
|
+
readonly server: "An agent-runtimes server the application’s agent runs on; a Datalayer runtime by default.";
|
|
34
|
+
readonly api: "Datalayer’s API, where an application is read with its token; the platform’s by default.";
|
|
35
|
+
readonly height: "The height of an inline application, in pixels; 640 by default.";
|
|
36
|
+
};
|
|
37
|
+
export type EmbedAttribute = keyof typeof EMBED_ATTRIBUTES;
|
|
38
|
+
/** Every attribute the element watches. */
|
|
39
|
+
export declare const EMBED_OBSERVED_ATTRIBUTES: EmbedAttribute[];
|
|
40
|
+
/**
|
|
41
|
+
* What a host may set from its own stylesheet, on the element or above it
|
|
42
|
+
* (D-11): the accent, the face and the mode. An attribute says the same and
|
|
43
|
+
* wins over the variable.
|
|
44
|
+
*/
|
|
45
|
+
export declare const EMBED_HOST_VARIABLES: {
|
|
46
|
+
readonly accent: "--datalayer-app-accent";
|
|
47
|
+
readonly font: "--datalayer-app-font";
|
|
48
|
+
readonly theme: "--datalayer-app-theme";
|
|
49
|
+
};
|
|
50
|
+
export type EmbedColorMode = 'light' | 'dark' | 'auto';
|
|
51
|
+
export declare const EMBED_COLOR_MODES: EmbedColorMode[];
|
|
52
|
+
/** The chat each floating mode is drawn as (`ChatFloating`'s view modes). */
|
|
53
|
+
export type EmbedFloatingView = 'floating-small' | 'panel' | 'assistant';
|
|
54
|
+
/**
|
|
55
|
+
* The four modes (D-07), and the chat they float in: a **bubble** is the
|
|
56
|
+
* round button that opens a popup — the copilot pattern; a **panel** is the
|
|
57
|
+
* conversation at the page's right edge, at full height; the **assistant** is
|
|
58
|
+
* the character that speaks in a balloon (§6.9). **Inline** has no chat of
|
|
59
|
+
* its own to float: the application, its page and its conversation, sit in
|
|
60
|
+
* the host's page where the element is.
|
|
61
|
+
*/
|
|
62
|
+
export declare const EMBED_FLOATING_VIEWS: Record<Exclude<AppEmbedMode, 'inline'>, EmbedFloatingView>;
|
|
63
|
+
/** The chat a mode floats in; none for inline. */
|
|
64
|
+
export declare function floatingViewOf(mode: AppEmbedMode): EmbedFloatingView | undefined;
|
|
65
|
+
/** A value the element refuses, said in a sentence for the page to show. */
|
|
66
|
+
export declare class EmbedAttributeError extends Error {
|
|
67
|
+
constructor(message: string);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A font family a host names, checked: names, quotes, commas and spaces, as
|
|
71
|
+
* a `font-family` is written — nothing that could close the declaration or
|
|
72
|
+
* fetch anything (`;`, braces, `url(`).
|
|
73
|
+
*/
|
|
74
|
+
export declare function checkedFont(value: string | null | undefined): string;
|
|
75
|
+
/** What the element was told, as text: its attributes and the host's variables. */
|
|
76
|
+
export type EmbedInputs = {
|
|
77
|
+
attributes: Partial<Record<EmbedAttribute, string | null>>;
|
|
78
|
+
/** The host's CSS variables, read on the element (`EMBED_HOST_VARIABLES`). */
|
|
79
|
+
variables?: Partial<Record<keyof typeof EMBED_HOST_VARIABLES, string>>;
|
|
80
|
+
};
|
|
81
|
+
/** How an application is shown and dressed in a host's page. */
|
|
82
|
+
export type EmbedLook = {
|
|
83
|
+
mode: AppEmbedMode;
|
|
84
|
+
/** The accent the host chose, or the application's own. */
|
|
85
|
+
accent: AppAccent;
|
|
86
|
+
colorMode: EmbedColorMode;
|
|
87
|
+
/** The host's face; empty for the theme's own. */
|
|
88
|
+
font: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* What the element shows and how, from what the host wrote and what the
|
|
92
|
+
* application says: an attribute first, then the host's CSS variable, then
|
|
93
|
+
* the Appspec, then the default. A value that is not one is refused with a
|
|
94
|
+
* sentence (`EmbedAttributeError`), never quietly replaced.
|
|
95
|
+
*/
|
|
96
|
+
export declare function embedLookOf(inputs: EmbedInputs, app?: Pick<AppSpec, 'interface' | 'deployment'>): EmbedLook;
|
|
97
|
+
/** The height of an inline application, in pixels. */
|
|
98
|
+
export declare function inlineHeightOf(value: string | null | undefined): number;
|
|
99
|
+
/** What a snippet says beside the application's id. */
|
|
100
|
+
export type EmbedSnippetOptions = {
|
|
101
|
+
/** Where Datalayer is: the script's origin, and the element's. */
|
|
102
|
+
origin: string;
|
|
103
|
+
/** The application's id. */
|
|
104
|
+
app: string;
|
|
105
|
+
/** Written when given — the *Ship* tab writes the Appspec's own. */
|
|
106
|
+
mode?: AppEmbedMode;
|
|
107
|
+
/** An embed token, for an application that is not public. */
|
|
108
|
+
token?: string;
|
|
109
|
+
accent?: AppAccent;
|
|
110
|
+
theme?: EmbedColorMode;
|
|
111
|
+
font?: string;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* The snippet a host pastes (D-08): one script tag and one element. A public
|
|
115
|
+
* application needs no token; a private one takes the token its owner was
|
|
116
|
+
* issued for it, written as given, so the snippet works for as long as the
|
|
117
|
+
* token lives — a host's own page writes the one its server is issued for
|
|
118
|
+
* each visit.
|
|
119
|
+
*/
|
|
120
|
+
export declare function embedSnippetOf(options: EmbedSnippetOptions): string;
|