@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
|
@@ -16,13 +16,26 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* created with the application in its payload — so the runtime reaches only
|
|
17
17
|
* what the application connects to and enforces its rules before every tool
|
|
18
18
|
* call — and the application's starters as the openers of the empty chat;
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
19
|
+
* - its **layout**, as `interface.layout` says (`appLayoutOptions`, LOOP
|
|
20
|
+
* T-07), on the workspace's own `page-layout` plugin: `chat` is the
|
|
21
|
+
* conversation alone, with the A2UI surfaces its answers draw; `page` is
|
|
22
|
+
* the page on a sheet with the composer over it and the conversation in a
|
|
23
|
+
* panel the composer's display modes open; `split` is the conversation and
|
|
24
|
+
* the page side by side, a hairline between them to drag;
|
|
25
|
+
* - its **page**, when it has one (`hasAppPage`: a chat, a widget or a worker
|
|
26
|
+
* with a `page` or `split` layout): its A2UI surface drawn by the
|
|
27
|
+
* `app-page` plugin, in place of the notebook and the document, fed from
|
|
28
|
+
* the conversation and answered through it (`APP_KIND_PATHS`). A
|
|
29
|
+
* decision's page stays the Studio's run page (R-02); its layout keeps the
|
|
30
|
+
* editors;
|
|
31
|
+
* - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
|
|
32
|
+
* window, its title the application's face and name.
|
|
21
33
|
*
|
|
22
34
|
* @module loop/apps/AppRenderer
|
|
23
35
|
*/
|
|
24
36
|
import { useMemo } from 'react';
|
|
25
37
|
import { defineAgentCapacityPlugin } from '../plugins/agent-capacity';
|
|
38
|
+
import { APP_PAGE_SURFACE, defineAppPagePlugin, hasAppPage, } from '../plugins/app-page';
|
|
26
39
|
import { LoopEmbed } from '../embed/LoopEmbed';
|
|
27
40
|
import { dumpAppspec } from './appspec';
|
|
28
41
|
/** The id of an agent or a Cog, without its version. */
|
|
@@ -64,8 +77,75 @@ export function defineAppPlugin(app) {
|
|
|
64
77
|
text: starter.label,
|
|
65
78
|
message: starter.message,
|
|
66
79
|
})),
|
|
80
|
+
// In the page, the agent is told what a runtime tells it: its own
|
|
81
|
+
// spec's prompt, then the application's instructions; on the
|
|
82
|
+
// application's model when it names one (`loop/apps/agent`).
|
|
83
|
+
instructions: app.instructions,
|
|
84
|
+
model: app.model,
|
|
67
85
|
});
|
|
68
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* The workspace an application's layout asks for (LOOP T-07).
|
|
89
|
+
*
|
|
90
|
+
* - `chat`: the conversation alone — no editor, no page, no strip.
|
|
91
|
+
* - `page`: the page layout — its page (or, without one, the editors) on the
|
|
92
|
+
* sheet; the composer a card over it, starting at the bottom, whose display
|
|
93
|
+
* modes put it in the conversation panel beside, over or in the corner of
|
|
94
|
+
* the page; the current turn under the composer, its copy and dismiss only.
|
|
95
|
+
* - `split`: the page layout's split — the conversation on the left with its
|
|
96
|
+
* composer, the page on the right, a hairline between them to drag.
|
|
97
|
+
*
|
|
98
|
+
* With a page, the page is the one editor, opened, without a strip to swap
|
|
99
|
+
* it; without one (a decision, whose page is the Studio's), the editors stay.
|
|
100
|
+
*/
|
|
101
|
+
export function appLayoutOptions(app) {
|
|
102
|
+
const layout = app.interface.layout;
|
|
103
|
+
if (layout === 'chat') {
|
|
104
|
+
return { editors: false, showViewSelector: false };
|
|
105
|
+
}
|
|
106
|
+
const withPage = hasAppPage(app);
|
|
107
|
+
return {
|
|
108
|
+
editors: !withPage,
|
|
109
|
+
showViewSelector: !withPage,
|
|
110
|
+
...(withPage ? { defaultEditor: APP_PAGE_SURFACE } : {}),
|
|
111
|
+
pageLayout: true,
|
|
112
|
+
pageLayoutArrangement: layout === 'split' ? 'split' : 'page',
|
|
113
|
+
pageLayoutPrompt: 'floating',
|
|
114
|
+
pageLayoutPromptAnchor: 'bottom',
|
|
115
|
+
pageLayoutTurnPanelFooter: 'actions',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* What an application's agent is created with on a Datalayer runtime: under
|
|
120
|
+
* the application's id, over AG-UI, with the application's own document —
|
|
121
|
+
* so that the runtime registers it and decides every tool call by its rules
|
|
122
|
+
* (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
|
|
123
|
+
* place it runs: the workspace (`AppRenderer`) and the embed's floating
|
|
124
|
+
* modes (`AppEmbed`).
|
|
125
|
+
*/
|
|
126
|
+
export function appDatalayerCreatePayload(app, instance) {
|
|
127
|
+
return {
|
|
128
|
+
// Under the application's id, over AG-UI: what the workspace's
|
|
129
|
+
// chat addresses (`AppRenderer` gives it as `agentId`).
|
|
130
|
+
name: app.id,
|
|
131
|
+
transport: 'ag-ui',
|
|
132
|
+
agent_spec_id: agentIdOf(app),
|
|
133
|
+
app_spec: dumpAppspec(app),
|
|
134
|
+
// Code Mode calls every tool through `execute_code`, which an
|
|
135
|
+
// application without a shell is refused: its tools are called
|
|
136
|
+
// one by one instead, each decided by its rules.
|
|
137
|
+
enable_codemode: Boolean(app.permissions?.computer?.shell),
|
|
138
|
+
...(instance
|
|
139
|
+
? {
|
|
140
|
+
app_instance: {
|
|
141
|
+
app_uid: instance.appUid ?? '',
|
|
142
|
+
deployment_uid: instance.deploymentUid ?? '',
|
|
143
|
+
version: instance.version ?? 0,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
69
149
|
/**
|
|
70
150
|
* An application in a LOOP workspace: its agent, its starters, its layout.
|
|
71
151
|
*
|
|
@@ -76,8 +156,7 @@ export function defineAppPlugin(app) {
|
|
|
76
156
|
/** The agentspec a runtime is allocated with before an application's agent is created on it. */
|
|
77
157
|
export const DATALAYER_BOOTSTRAP_AGENTSPEC = 'example-simple';
|
|
78
158
|
const NO_PLUGINS = [];
|
|
79
|
-
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, ...embed }) {
|
|
80
|
-
const chatOnly = app.interface.layout === 'chat';
|
|
159
|
+
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, frame = false, ...embed }) {
|
|
81
160
|
/*
|
|
82
161
|
* The application plugin, made once per application. `LoopEmbed` rebuilds
|
|
83
162
|
* its whole reactor when its plugins change, so a new plugin on every
|
|
@@ -88,36 +167,20 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, .
|
|
|
88
167
|
const appPlugin = useMemo(() => (app.agent ? defineAppPlugin(app) : null),
|
|
89
168
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
169
|
[source]);
|
|
91
|
-
const
|
|
170
|
+
const withPage = hasAppPage(app);
|
|
171
|
+
const pagePlugin = useMemo(() => (withPage ? defineAppPagePlugin(app) : null),
|
|
172
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
173
|
+
[source, withPage]);
|
|
174
|
+
const allPlugins = useMemo(() => appPlugin
|
|
175
|
+
? [appPlugin, ...(pagePlugin ? [pagePlugin] : []), ...plugins]
|
|
176
|
+
: plugins, [appPlugin, pagePlugin, plugins]);
|
|
92
177
|
/*
|
|
93
178
|
* On Datalayer, the application runs on a runtime: allocated with a plain
|
|
94
179
|
* agentspec, its agent created there with the application's spec — so that
|
|
95
180
|
* the runtime registers it and decides every tool call by its rules (LOOP
|
|
96
181
|
* R-03, R-05), as `loop apps run --cloud` does.
|
|
97
182
|
*/
|
|
98
|
-
const datalayerCreatePayload = useMemo(() => app.agent
|
|
99
|
-
? {
|
|
100
|
-
// Under the application's id, over AG-UI: what the workspace's
|
|
101
|
-
// chat addresses (`agentId` below).
|
|
102
|
-
name: app.id,
|
|
103
|
-
transport: 'ag-ui',
|
|
104
|
-
agent_spec_id: agentIdOf(app),
|
|
105
|
-
app_spec: dumpAppspec(app),
|
|
106
|
-
// Code Mode calls every tool through `execute_code`, which an
|
|
107
|
-
// application without a shell is refused: its tools are called
|
|
108
|
-
// one by one instead, each decided by its rules.
|
|
109
|
-
enable_codemode: Boolean(app.permissions?.computer?.shell),
|
|
110
|
-
...(instance
|
|
111
|
-
? {
|
|
112
|
-
app_instance: {
|
|
113
|
-
app_uid: instance.appUid ?? '',
|
|
114
|
-
deployment_uid: instance.deploymentUid ?? '',
|
|
115
|
-
version: instance.version ?? 0,
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
: {}),
|
|
119
|
-
}
|
|
120
|
-
: undefined,
|
|
183
|
+
const datalayerCreatePayload = useMemo(() => (app.agent ? appDatalayerCreatePayload(app, instance) : undefined),
|
|
121
184
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
185
|
[source, instance?.appUid, instance?.deploymentUid, instance?.version]);
|
|
123
186
|
if (!appPlugin) {
|
|
@@ -129,7 +192,11 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, .
|
|
|
129
192
|
, {
|
|
130
193
|
// The application's own agent: created under its id, so that its
|
|
131
194
|
// spec is applied to an agent of its own, never to the one it extends.
|
|
132
|
-
agentId: app.id,
|
|
195
|
+
agentId: app.id, ...appLayoutOptions(app), ...(frame
|
|
196
|
+
? {
|
|
197
|
+
frameTitle: [app.emoji, app.name].filter(Boolean).join(' '),
|
|
198
|
+
}
|
|
199
|
+
: {}), teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
133
200
|
// Where an application runs is decided by its host — the Studio's
|
|
134
201
|
// Preview, the hosted page — not offered to its user.
|
|
135
202
|
targetFixed: true,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Agentspec } from '../../types/agentspecs';
|
|
2
|
+
/** An agent of the catalogue, or a Cog's agent, by `id` or `id:version`. */
|
|
3
|
+
export declare function resolveAgentspec(reference: string): Agentspec | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* What the agent is told: its spec's system prompt, then what the
|
|
6
|
+
* application tells it on top — as the runtime joins them.
|
|
7
|
+
*/
|
|
8
|
+
export declare function agentInstructions(spec: Pick<Agentspec, 'systemPrompt'> | undefined, extra?: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Why an agent turned in the page cannot run, or undefined when it can.
|
|
11
|
+
*
|
|
12
|
+
* An agent named by a reference that resolves to nothing would answer with
|
|
13
|
+
* no instructions — bare — which is not the agent that was asked for.
|
|
14
|
+
*/
|
|
15
|
+
export declare function inPageAgentRefusal(reference: string | undefined, spec: Agentspec | undefined): string | undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The agent an application runs, as the page turns it (LOOP H-01, E-11).
|
|
7
|
+
*
|
|
8
|
+
* On a runtime, an application's agent is its agent's spec — an agent of the
|
|
9
|
+
* catalogue, or a Cog's, which is an agent equipped with Frames, their
|
|
10
|
+
* context already in its system prompt — and the application's own
|
|
11
|
+
* instructions on top of it (`get_library_agent_spec` and the create route
|
|
12
|
+
* in `agent_runtimes/routes/agents.py`). The page turns the same agent in the
|
|
13
|
+
* browser, so it resolves it the same way: an application on a Cog that the
|
|
14
|
+
* page looked up among the agentspecs alone ran with no instructions at all.
|
|
15
|
+
*
|
|
16
|
+
* @module loop/apps/agent
|
|
17
|
+
*/
|
|
18
|
+
import { getAgentspecs } from '../../specs/agents';
|
|
19
|
+
import { getCog } from '../../specs/cogs';
|
|
20
|
+
/** An agent of the catalogue, or a Cog's agent, by `id` or `id:version`. */
|
|
21
|
+
export function resolveAgentspec(reference) {
|
|
22
|
+
return getAgentspecs(reference) ?? getCog(reference)?.spec;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What the agent is told: its spec's system prompt, then what the
|
|
26
|
+
* application tells it on top — as the runtime joins them.
|
|
27
|
+
*/
|
|
28
|
+
export function agentInstructions(spec, extra) {
|
|
29
|
+
const parts = [spec?.systemPrompt?.trim(), extra?.trim()].filter((part) => Boolean(part));
|
|
30
|
+
return parts.length > 0 ? parts.join('\n\n') : undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Why an agent turned in the page cannot run, or undefined when it can.
|
|
34
|
+
*
|
|
35
|
+
* An agent named by a reference that resolves to nothing would answer with
|
|
36
|
+
* no instructions — bare — which is not the agent that was asked for.
|
|
37
|
+
*/
|
|
38
|
+
export function inPageAgentRefusal(reference, spec) {
|
|
39
|
+
return reference && !spec
|
|
40
|
+
? `There is no agent or Cog named “${reference}” in this page, so it cannot answer here.`
|
|
41
|
+
: undefined;
|
|
42
|
+
}
|
|
@@ -24,13 +24,17 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module loop/apps/appspec
|
|
26
26
|
*/
|
|
27
|
-
import type { AppAccent, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
|
|
27
|
+
import type { AppAccent, AppAssistantCharacter, AppEmbedMode, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
|
|
28
28
|
/** The version of the spec itself. */
|
|
29
29
|
export declare const APP_SCHEMA = "loop.app/v1";
|
|
30
30
|
/** The face of an application that has not chosen one. */
|
|
31
31
|
export declare const DEFAULT_EMOJI = "\uD83D\uDC40";
|
|
32
32
|
export declare const APP_KINDS: AppKind[];
|
|
33
33
|
export declare const APP_ACCENTS: AppAccent[];
|
|
34
|
+
/** The four ways an application sits in another product's page (LOOP D-07). */
|
|
35
|
+
export declare const APP_EMBED_MODES: AppEmbedMode[];
|
|
36
|
+
/** The characters an Appspec may name for its floating assistant (LOOP T-24). */
|
|
37
|
+
export declare const APP_ASSISTANT_CHARACTERS: AppAssistantCharacter[];
|
|
34
38
|
/** The layout a kind starts with. */
|
|
35
39
|
export declare const DEFAULT_LAYOUTS: Record<AppKind, AppLayout>;
|
|
36
40
|
/** What an application keeps of what it did, when it does not say. */
|
package/lib/loop/apps/appspec.js
CHANGED
|
@@ -16,6 +16,20 @@ export const APP_ACCENTS = [
|
|
|
16
16
|
'sun',
|
|
17
17
|
'violet',
|
|
18
18
|
];
|
|
19
|
+
/** The four ways an application sits in another product's page (LOOP D-07). */
|
|
20
|
+
export const APP_EMBED_MODES = [
|
|
21
|
+
'inline',
|
|
22
|
+
'bubble',
|
|
23
|
+
'panel',
|
|
24
|
+
'assistant',
|
|
25
|
+
];
|
|
26
|
+
/** The characters an Appspec may name for its floating assistant (LOOP T-24). */
|
|
27
|
+
export const APP_ASSISTANT_CHARACTERS = [
|
|
28
|
+
'paperclip',
|
|
29
|
+
'wizard',
|
|
30
|
+
'cat',
|
|
31
|
+
'eyes',
|
|
32
|
+
];
|
|
19
33
|
const ACTION_CLASS_NAMES = [
|
|
20
34
|
'read',
|
|
21
35
|
'write',
|
|
@@ -203,6 +217,9 @@ function parseInterface(data, kind) {
|
|
|
203
217
|
if (isData(data.surface)) {
|
|
204
218
|
parsed.surface = parseSurface(data.surface);
|
|
205
219
|
}
|
|
220
|
+
if (APP_ASSISTANT_CHARACTERS.includes(data.assistant)) {
|
|
221
|
+
parsed.assistant = data.assistant;
|
|
222
|
+
}
|
|
206
223
|
return parsed;
|
|
207
224
|
}
|
|
208
225
|
function parseCriterion(data) {
|
|
@@ -239,7 +256,7 @@ function parseDeployment(data) {
|
|
|
239
256
|
}
|
|
240
257
|
if (isData(data.embedded)) {
|
|
241
258
|
deployment.embedded = {
|
|
242
|
-
mode: oneOf(data.embedded.mode,
|
|
259
|
+
mode: oneOf(data.embedded.mode, APP_EMBED_MODES, 'inline'),
|
|
243
260
|
origins: texts(data.embedded.origins),
|
|
244
261
|
};
|
|
245
262
|
}
|
|
@@ -454,6 +471,9 @@ function dumpInterface(spec, kind) {
|
|
|
454
471
|
if (spec.surface) {
|
|
455
472
|
writer.data.surface = dumpSurface(spec.surface);
|
|
456
473
|
}
|
|
474
|
+
if (spec.assistant) {
|
|
475
|
+
writer.data.assistant = spec.assistant;
|
|
476
|
+
}
|
|
457
477
|
return writer.data;
|
|
458
478
|
}
|
|
459
479
|
function dumpDecision(decision) {
|
package/lib/loop/apps/checks.js
CHANGED
|
@@ -370,7 +370,8 @@ const ENUMS = {
|
|
|
370
370
|
'feedback',
|
|
371
371
|
],
|
|
372
372
|
visibility: ['private', 'invited', 'organization', 'link', 'public'],
|
|
373
|
-
mode: ['inline', 'bubble', 'panel'],
|
|
373
|
+
mode: ['inline', 'bubble', 'panel', 'assistant'],
|
|
374
|
+
character: ['paperclip', 'wizard', 'cat', 'eyes'],
|
|
374
375
|
trigger: ['schedule', 'event', 'once'],
|
|
375
376
|
criterion: ['metric', 'noul', 'choice', 'score'],
|
|
376
377
|
direction: ['higher', 'lower'],
|
|
@@ -498,6 +499,7 @@ export function documentShapeProblems(document) {
|
|
|
498
499
|
mapping(d.interface, 'interface', ui => {
|
|
499
500
|
oneOf(ui.layout, ENUMS.layout, 'interface.layout');
|
|
500
501
|
oneOf(ui.accent, ENUMS.accent, 'interface.accent');
|
|
502
|
+
oneOf(ui.assistant, ENUMS.character, 'interface.assistant');
|
|
501
503
|
text(ui.welcome, 'interface.welcome');
|
|
502
504
|
texts(ui.components, 'interface.components');
|
|
503
505
|
records(ui.starters, 'interface.starters', (item, where) => {
|
package/lib/loop/apps/index.d.ts
CHANGED
package/lib/loop/apps/index.js
CHANGED
package/lib/loop/core/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { ExecutionState } from '@datalayer/jupyter-react/kernel-indicator';
|
|
|
4
4
|
import type { ToolbarItem } from '@datalayer/primer-addons';
|
|
5
5
|
import type { FrontendToolDefinition } from '../../types/tools';
|
|
6
6
|
import type { ContextSnapshotData } from '../../types/context';
|
|
7
|
+
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
8
|
+
import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
|
|
7
9
|
/** Lifecycle of the sandbox a workspace is attached to. */
|
|
8
10
|
export type SandboxState = 'idle' | 'starting' | 'running' | 'stopping' | 'error';
|
|
9
11
|
/** What the workspace knows about the sandbox behind it. */
|
|
@@ -481,6 +483,14 @@ export type AgentBlueprintContribution = {
|
|
|
481
483
|
specId?: string;
|
|
482
484
|
/** Extra fields merged into the server's create-agent payload. */
|
|
483
485
|
createPayload?: Record<string, unknown>;
|
|
486
|
+
/**
|
|
487
|
+
* What the agent is told on top of its spec's system prompt — an
|
|
488
|
+
* application's instructions — when the page turns it, as a runtime
|
|
489
|
+
* appends them.
|
|
490
|
+
*/
|
|
491
|
+
instructions?: string;
|
|
492
|
+
/** The model it runs on in the page, in place of its spec's. */
|
|
493
|
+
model?: string;
|
|
484
494
|
};
|
|
485
495
|
export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
|
|
486
496
|
/** One conversation opener a plugin offers on the empty chat. */
|
|
@@ -503,6 +513,19 @@ export type ChatSuggestionContribution = {
|
|
|
503
513
|
suggestions: ChatSuggestionItem[];
|
|
504
514
|
};
|
|
505
515
|
export declare const LoopChatSuggestion: import("@datalayer/reactor").ContributionPoint<ChatSuggestionContribution>;
|
|
516
|
+
/**
|
|
517
|
+
* A character the floating assistant may show (LOOP T-24): a drawing — one
|
|
518
|
+
* of Datalayer's own (T-25) or a plugin's — or a character read from a file
|
|
519
|
+
* a person holds the rights to (T-26). The catalogue of characters is what
|
|
520
|
+
* the enabled plugins contribute here, and nothing else; an application
|
|
521
|
+
* names one by id (`interface.assistant`).
|
|
522
|
+
*/
|
|
523
|
+
export type AssistantCharacterContribution = {
|
|
524
|
+
/** Stable id, what an application names. */
|
|
525
|
+
id: string;
|
|
526
|
+
character: AssistantCharacter | AssistantCharacterData;
|
|
527
|
+
};
|
|
528
|
+
export declare const LoopAssistantCharacter: import("@datalayer/reactor").ContributionPoint<AssistantCharacterContribution>;
|
|
506
529
|
/**
|
|
507
530
|
* The per-example chat extras a host feeds the loop's conversation live.
|
|
508
531
|
*
|
package/lib/loop/core/index.js
CHANGED
|
@@ -141,6 +141,7 @@ export const LoopChatComposer = defineContributionPoint('loop.chat.composer');
|
|
|
141
141
|
export const LoopChatHeader = defineContributionPoint('loop.chat.header');
|
|
142
142
|
export const LoopAgentBlueprint = defineContributionPoint('loop.agent.blueprint');
|
|
143
143
|
export const LoopChatSuggestion = defineContributionPoint('loop.chat.suggestion');
|
|
144
|
+
export const LoopAssistantCharacter = defineContributionPoint('loop.assistant.character');
|
|
144
145
|
export const LoopChatExtras = defineContributionPoint('loop.chat.extras');
|
|
145
146
|
export const LoopOpeningNotebook = defineContributionPoint('loop.notebook.opening');
|
|
146
147
|
export const LoopOpeningDocument = defineContributionPoint('loop.document.opening');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CSSProperties, JSX, ReactNode } from 'react';
|
|
2
|
+
import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
|
|
3
|
+
import { type AppInstance } from '../apps/AppRenderer';
|
|
4
|
+
import { type EmbedColorMode } from './embedConfig';
|
|
5
|
+
export type AppEmbedProps = {
|
|
6
|
+
/** The application. */
|
|
7
|
+
app: AppSpec;
|
|
8
|
+
/** How it sits in the page; the Appspec's `deployment.embedded.mode` by default. */
|
|
9
|
+
mode?: AppEmbedMode;
|
|
10
|
+
/** The host's accent; the application's own by default. */
|
|
11
|
+
accent?: AppAccent;
|
|
12
|
+
/** Light, dark, or the visitor's system. */
|
|
13
|
+
colorMode?: EmbedColorMode;
|
|
14
|
+
/** The host's face, a CSS font family; the theme's by default. */
|
|
15
|
+
font?: string;
|
|
16
|
+
/**
|
|
17
|
+
* An agent-runtimes server the application's agent runs on. Without one,
|
|
18
|
+
* it runs on a Datalayer runtime, launched with the visitor's Datalayer
|
|
19
|
+
* credentials.
|
|
20
|
+
*/
|
|
21
|
+
serverUrl?: string;
|
|
22
|
+
/** What its record is kept under, on Datalayer. */
|
|
23
|
+
instance?: AppInstance;
|
|
24
|
+
/** Height of an inline application. */
|
|
25
|
+
height?: number | string;
|
|
26
|
+
/**
|
|
27
|
+
* Draw overlays — menus, dialogs — inside the embed's own root rather than
|
|
28
|
+
* in Primer's, in the host's `<body>`: what the element asks for, since a
|
|
29
|
+
* shadow root's styles stop at its edge. A React host leaves it off.
|
|
30
|
+
*/
|
|
31
|
+
ownPortal?: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** The `loop` theme, with the application's (or the host's) accent and face. */
|
|
34
|
+
export declare function EmbedThemed({ accent, colorMode, font, children, style, }: {
|
|
35
|
+
accent: AppAccent;
|
|
36
|
+
colorMode: 'light' | 'dark';
|
|
37
|
+
font?: string;
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
style?: CSSProperties;
|
|
40
|
+
}): JSX.Element;
|
|
41
|
+
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, height, ownPortal, }: AppEmbedProps): JSX.Element;
|
|
42
|
+
export default AppEmbed;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } 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
|
+
* An application in another product's page, as a React component (LOOP
|
|
8
|
+
* D-07, D-09, D-11): what the `<datalayer-app>` element draws, and what a
|
|
9
|
+
* host that uses React mounts itself.
|
|
10
|
+
*
|
|
11
|
+
* Four modes, as the Appspec says (`deployment.embedded.mode`) or the host:
|
|
12
|
+
*
|
|
13
|
+
* - **inline** — the application where the component is: `AppRenderer`, its
|
|
14
|
+
* page beside its conversation when it has one;
|
|
15
|
+
* - **bubble** — a round button in the corner that opens the conversation in
|
|
16
|
+
* a popup, the copilot pattern;
|
|
17
|
+
* - **panel** — the conversation at the right edge of the page, at full
|
|
18
|
+
* height;
|
|
19
|
+
* - **assistant** — the application's character on the page, speaking in a
|
|
20
|
+
* balloon (§6.9), the one its Appspec names (`interface.assistant`).
|
|
21
|
+
*
|
|
22
|
+
* The three floating modes are `ChatFloating`'s, given the application's
|
|
23
|
+
* name, welcome, starters and accent, and its agent's endpoint — created
|
|
24
|
+
* with the application's spec in its payload, so the runtime enforces its
|
|
25
|
+
* rules there as it does inline.
|
|
26
|
+
*
|
|
27
|
+
* In the `loop` theme with the application's accent (T-12, T-05), which the
|
|
28
|
+
* host may override with the face and the mode (D-11).
|
|
29
|
+
*
|
|
30
|
+
* @module loop/embed/AppEmbed
|
|
31
|
+
*/
|
|
32
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
33
|
+
import { registerPortalRoot } from '@primer/react';
|
|
34
|
+
import { DatalayerThemeProvider, loopTheme } from '@datalayer/primer-addons';
|
|
35
|
+
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
36
|
+
import { ChatFloating } from '../../chat/ChatFloating';
|
|
37
|
+
import { useAgentRuntimes } from '../../hooks/useAgentRuntimes';
|
|
38
|
+
import { AppRenderer, DATALAYER_BOOTSTRAP_AGENTSPEC, appDatalayerCreatePayload, defineAppPlugin, } from '../apps/AppRenderer';
|
|
39
|
+
import { LoopAgentBlueprint } from '../core';
|
|
40
|
+
import { ensureServerAgent } from '../plugins/agents/switchable';
|
|
41
|
+
import { floatingViewOf } from './embedConfig';
|
|
42
|
+
import { embedThemeStyles } from './embedTheme';
|
|
43
|
+
/** What the application's agent is created with on an agent-runtimes server. */
|
|
44
|
+
function serverCreatePayload(app) {
|
|
45
|
+
const blueprint = (defineAppPlugin(app).contributes ?? []).find((item) => item.point === LoopAgentBlueprint);
|
|
46
|
+
return blueprint?.value.createPayload ?? { name: app.id };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The application's agent on the host's agent-runtimes server: created
|
|
50
|
+
* there with the application's spec unless it is there already, as the
|
|
51
|
+
* workspace's Local target does.
|
|
52
|
+
*/
|
|
53
|
+
function useServerAgent(app, serverUrl) {
|
|
54
|
+
const [state, setState] = useState({
|
|
55
|
+
waiting: 'Starting…',
|
|
56
|
+
});
|
|
57
|
+
const source = JSON.stringify(app);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
61
|
+
setState({ waiting: 'Starting…' });
|
|
62
|
+
ensureServerAgent(base, app.id, serverCreatePayload(app)).then(() => {
|
|
63
|
+
if (!cancelled) {
|
|
64
|
+
setState({
|
|
65
|
+
protocol: {
|
|
66
|
+
type: 'ag-ui',
|
|
67
|
+
endpoint: `${base}/api/v1/ag-ui/${app.id}/`,
|
|
68
|
+
agentId: app.id,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}, (error) => {
|
|
73
|
+
if (!cancelled) {
|
|
74
|
+
setState({
|
|
75
|
+
error: error instanceof Error ? error.message : String(error),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
};
|
|
82
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83
|
+
}, [serverUrl, source]);
|
|
84
|
+
return state;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The application's agent on a Datalayer runtime: allocated with a plain
|
|
88
|
+
* agentspec, the agent created there with the application's spec — the
|
|
89
|
+
* runtime `AppRenderer` launches inline (R-03, R-05).
|
|
90
|
+
*/
|
|
91
|
+
function useDatalayerAgent(app, instance) {
|
|
92
|
+
const token = useIAMStore(state => state.token);
|
|
93
|
+
const source = JSON.stringify(app);
|
|
94
|
+
const agentConfig = useMemo(() => ({
|
|
95
|
+
name: app.id,
|
|
96
|
+
protocol: 'ag-ui',
|
|
97
|
+
agentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
98
|
+
createPayload: appDatalayerCreatePayload(app, instance),
|
|
99
|
+
}),
|
|
100
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
+
[source, instance?.appUid, instance?.deploymentUid, instance?.version]);
|
|
102
|
+
const { runtime, error } = useAgentRuntimes({
|
|
103
|
+
agentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
104
|
+
agentConfig,
|
|
105
|
+
variant: 'cloud-pydanticai',
|
|
106
|
+
// Only for a visitor Datalayer knows: a runtime is somebody's to pay for.
|
|
107
|
+
autoStart: Boolean(token),
|
|
108
|
+
autoCreateAgent: Boolean(token),
|
|
109
|
+
});
|
|
110
|
+
if (!token) {
|
|
111
|
+
return {
|
|
112
|
+
error: `${app.name} runs on Datalayer, which needs you signed in. An application embedded for visitors who are not needs its embed token (LOOP D-14), or an agent-runtimes server of the host's own (the "server" attribute).`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (error) {
|
|
116
|
+
return { error };
|
|
117
|
+
}
|
|
118
|
+
if (!runtime?.agentBaseUrl) {
|
|
119
|
+
return { waiting: 'Starting…' };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
protocol: {
|
|
123
|
+
type: 'ag-ui',
|
|
124
|
+
endpoint: `${runtime.agentBaseUrl}/api/v1/ag-ui/${app.id}/`,
|
|
125
|
+
agentId: app.id,
|
|
126
|
+
authToken: token,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** The application's face, as the floating chat's button and header draw it. */
|
|
131
|
+
function Face({ emoji }) {
|
|
132
|
+
return (_jsx("span", { "aria-hidden": true, style: { fontSize: 18, lineHeight: 1 }, children: emoji }));
|
|
133
|
+
}
|
|
134
|
+
/** The bubble, the panel and the assistant: `ChatFloating` in that mode. */
|
|
135
|
+
function FloatingChat({ app, view, colorMode, agent, }) {
|
|
136
|
+
const welcome = app.interface.welcome || app.description;
|
|
137
|
+
return (_jsx(ChatFloating
|
|
138
|
+
// A new agent is a new conversation.
|
|
139
|
+
, { defaultViewMode: view, assistantCharacter: app.interface.assistant ?? 'paperclip', protocol: agent.protocol, authToken: agent.protocol?.authToken, title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: `Talk to ${app.name}`, suggestions: app.interface.starters.map(starter => ({
|
|
140
|
+
title: starter.label,
|
|
141
|
+
message: starter.message,
|
|
142
|
+
})), themeVariant: "loop", colorMode: colorMode, showTokenUsage: false, showSettingsButton: false, showPoweredBy: false, useStore: false, launching: !agent.protocol && !agent.error, launchingMessage: agent.waiting, disabled: Boolean(agent.error), disableReason: agent.error }, agent.protocol?.endpoint ?? 'waiting'));
|
|
143
|
+
}
|
|
144
|
+
/** Floating, its agent on the host's agent-runtimes server. */
|
|
145
|
+
function ServerFloating({ serverUrl, ...props }) {
|
|
146
|
+
return (_jsx(FloatingChat, { ...props, agent: useServerAgent(props.app, serverUrl) }));
|
|
147
|
+
}
|
|
148
|
+
/** Floating, its agent on a Datalayer runtime. */
|
|
149
|
+
function DatalayerFloating({ instance, ...props }) {
|
|
150
|
+
return (_jsx(FloatingChat, { ...props, agent: useDatalayerAgent(props.app, instance) }));
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Overlays — menus, dialogs — drawn inside the embed's own root, under the
|
|
154
|
+
* theme's element, so that they wear its theme: what the element asks for,
|
|
155
|
+
* since Primer's own portal root is in the host's `<body>`, outside the
|
|
156
|
+
* shadow root and its styles.
|
|
157
|
+
*/
|
|
158
|
+
function OwnPortalRoot() {
|
|
159
|
+
const ref = useRef(null);
|
|
160
|
+
useLayoutEffect(() => {
|
|
161
|
+
if (ref.current) {
|
|
162
|
+
registerPortalRoot(ref.current);
|
|
163
|
+
}
|
|
164
|
+
}, []);
|
|
165
|
+
return _jsx("div", { ref: ref, className: "datalayer-app-portal" });
|
|
166
|
+
}
|
|
167
|
+
/** The visitor's system mode, followed while it changes. */
|
|
168
|
+
function useSystemMode() {
|
|
169
|
+
const query = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
|
170
|
+
? window.matchMedia('(prefers-color-scheme: dark)')
|
|
171
|
+
: null;
|
|
172
|
+
const [dark, setDark] = useState(Boolean(query?.matches));
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!query) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const follow = () => setDark(query.matches);
|
|
178
|
+
query.addEventListener?.('change', follow);
|
|
179
|
+
return () => query.removeEventListener?.('change', follow);
|
|
180
|
+
}, [query]);
|
|
181
|
+
return dark ? 'dark' : 'light';
|
|
182
|
+
}
|
|
183
|
+
/** The `loop` theme, with the application's (or the host's) accent and face. */
|
|
184
|
+
export function EmbedThemed({ accent, colorMode, font, children, style, }) {
|
|
185
|
+
const themeStyles = useMemo(() => embedThemeStyles({ accent, font }), [accent, font]);
|
|
186
|
+
return (
|
|
187
|
+
/*
|
|
188
|
+
* Marked as themed above the provider, so that the provider counts
|
|
189
|
+
* itself nested and leaves the host's <body> and Primer's portal root
|
|
190
|
+
* alone: it would otherwise write the theme's tokens, colour and face on
|
|
191
|
+
* the host's page (T-13). A wrapper of no box.
|
|
192
|
+
*/
|
|
193
|
+
_jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: loopTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
|
|
194
|
+
}
|
|
195
|
+
export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUrl, instance, height = 640, ownPortal = false, }) {
|
|
196
|
+
const system = useSystemMode();
|
|
197
|
+
const resolvedMode = colorMode === 'auto' ? system : colorMode;
|
|
198
|
+
const shownAs = mode ?? app.deployment.embedded?.mode ?? 'inline';
|
|
199
|
+
const view = floatingViewOf(shownAs);
|
|
200
|
+
return (_jsxs(EmbedThemed, { accent: accent ?? app.interface.accent, colorMode: resolvedMode, font: font, style: view
|
|
201
|
+
? // Floating: nothing in the page's flow, nothing painted behind.
|
|
202
|
+
{ backgroundColor: 'transparent' }
|
|
203
|
+
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view && serverUrl ? (_jsx(ServerFloating, { app: app, view: view, serverUrl: serverUrl, colorMode: resolvedMode })) : view ? (_jsx(DatalayerFloating, { app: app, view: view, instance: instance, colorMode: resolvedMode })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), instance: instance }))] }));
|
|
204
|
+
}
|
|
205
|
+
export default AppEmbed;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The one script a host loads (LOOP D-08): it defines `<datalayer-app>` and
|
|
7
|
+
* nothing else. Built on its own by `npm run build:embed`
|
|
8
|
+
* (`vite.embed.config.ts`) into `dist-embed/datalayer-app.js`, with the
|
|
9
|
+
* stylesheet the element links into its shadow root beside it
|
|
10
|
+
* (`dist-embed/datalayer-app.css`); served from a Datalayer origin at
|
|
11
|
+
* `/embed/`.
|
|
12
|
+
*
|
|
13
|
+
* @module loop/embed/datalayer-app
|
|
14
|
+
*/
|
|
15
|
+
import { defineDatalayerAppElement } from './element';
|
|
16
|
+
defineDatalayerAppElement();
|