@datalayer/agent-runtimes 1.3.34 → 1.3.36
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 +8 -2
- package/lib/chat/ChatFloating.js +2 -2
- package/lib/chat/base/ChatBase.js +10 -6
- package/lib/chat/display/EmptyState.js +8 -1
- package/lib/chat/presence/Presence.d.ts +15 -1
- package/lib/chat/presence/Presence.js +0 -13
- package/lib/components/a2ui/datalayer/Chart.d.ts +132 -0
- package/lib/components/a2ui/datalayer/Chart.js +173 -0
- package/lib/components/a2ui/datalayer/Chat.d.ts +21 -0
- package/lib/components/a2ui/datalayer/Chat.js +116 -0
- package/lib/components/a2ui/datalayer/Evidence.d.ts +22 -0
- package/lib/components/a2ui/datalayer/Evidence.js +73 -0
- package/lib/components/a2ui/datalayer/FileUpload.d.ts +22 -0
- package/lib/components/a2ui/datalayer/FileUpload.js +107 -0
- package/lib/components/a2ui/datalayer/Form.d.ts +12 -0
- package/lib/components/a2ui/datalayer/Form.js +46 -0
- package/lib/components/a2ui/datalayer/Table.d.ts +20 -0
- package/lib/components/a2ui/datalayer/Table.js +103 -0
- package/lib/components/a2ui/datalayer/drawn.d.ts +10 -0
- package/lib/components/a2ui/datalayer/drawn.js +20 -0
- package/lib/components/a2ui/datalayer/implementation.d.ts +17 -0
- package/lib/components/a2ui/datalayer/implementation.js +10 -0
- package/lib/components/a2ui/datalayer/index.d.ts +23 -0
- package/lib/components/a2ui/datalayer/index.js +44 -0
- package/lib/components/a2ui/datalayer/parts.d.ts +32 -0
- package/lib/components/a2ui/datalayer/parts.js +44 -0
- package/lib/components/a2ui/datalayer/schema.d.ts +34 -0
- package/lib/components/a2ui/datalayer/schema.js +108 -0
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +4 -2
- package/lib/components/a2ui/visibleWhen.js +6 -3
- package/lib/loop/apps/AppRenderer.d.ts +8 -0
- package/lib/loop/apps/AppRenderer.js +21 -1
- package/lib/loop/apps/appspec.d.ts +9 -2
- package/lib/loop/apps/appspec.js +14 -8
- package/lib/loop/apps/checks.js +4 -3
- package/lib/loop/embed/AppEmbed.d.ts +16 -1
- package/lib/loop/embed/AppEmbed.js +38 -9
- package/lib/loop/embed/LoopEmbed.js +2 -0
- package/lib/loop/embed/element.js +10 -1
- package/lib/loop/embed/embedTheme.d.ts +12 -0
- package/lib/loop/embed/embedTheme.js +15 -0
- package/lib/loop/plugins/assistant/index.d.ts +38 -0
- package/lib/loop/plugins/assistant/index.js +140 -0
- package/lib/loop/plugins/assistant-characters/index.d.ts +35 -1
- package/lib/loop/plugins/assistant-characters/index.js +45 -2
- package/lib/loop/plugins/chat/ChatView.js +21 -9
- package/lib/loop/plugins/chat/index.d.ts +6 -0
- package/lib/loop/presets.d.ts +3 -0
- package/lib/loop/presets.js +2 -1
- package/lib/specs/appspecSchema.js +5 -8
- package/lib/types/agentspecs.d.ts +10 -4
- package/lib/types/chat.d.ts +11 -0
- package/package.json +2 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The A2UI schema of a component of Datalayer's own, read from the catalog
|
|
7
|
+
* (LOOP C-13, C-18): each property its JSON Schema declares, typed as it
|
|
8
|
+
* says; each thing it shows or sends a binding into the data model — a path
|
|
9
|
+
* the application publishes, or a value given in place; its `action`, what
|
|
10
|
+
* it dispatches when a person acts on it, written as A2UI writes a Button's.
|
|
11
|
+
*
|
|
12
|
+
* A2UI's renderer reads a component's schema to tell its properties apart —
|
|
13
|
+
* a binding it resolves and writes back through, an action it dispatches, a
|
|
14
|
+
* plain value it hands over — so the schema is built with the zod A2UI reads
|
|
15
|
+
* (v3), from the catalog, and cannot drift from what the Canvas, the YAML
|
|
16
|
+
* and Python check a component against.
|
|
17
|
+
*
|
|
18
|
+
* @module components/a2ui/datalayer/schema
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod/v3';
|
|
21
|
+
import { ActionSchema, DataBindingSchema } from '@a2ui/web_core/v0_9';
|
|
22
|
+
import { COMPONENT_CATALOGUE } from '../../../specs/uiPlugins';
|
|
23
|
+
/** The components of Datalayer's own the catalog has, by id. */
|
|
24
|
+
export const OWN_COMPONENT_IDS = Object.values(COMPONENT_CATALOGUE)
|
|
25
|
+
.filter(component => !component.standard)
|
|
26
|
+
.map(component => component.id);
|
|
27
|
+
/** A component of Datalayer's own, from the catalog; it throws for any other. */
|
|
28
|
+
export function ownComponent(id) {
|
|
29
|
+
const component = COMPONENT_CATALOGUE[id];
|
|
30
|
+
if (!component) {
|
|
31
|
+
throw new Error(`The catalog has no component ${id}.`);
|
|
32
|
+
}
|
|
33
|
+
if (component.standard || !component.properties || !component.bindings) {
|
|
34
|
+
throw new Error(`${id} is A2UI's own: its schema is the basic catalog's, not Datalayer's.`);
|
|
35
|
+
}
|
|
36
|
+
return component;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* What a component shows or sends: a list, a record, words, a number, true
|
|
40
|
+
* or false given in place, or a path into the data model. A2UI resolves it
|
|
41
|
+
* — and gives the component a setter for it when it is a path.
|
|
42
|
+
*/
|
|
43
|
+
const BindingSchema = z
|
|
44
|
+
.union([
|
|
45
|
+
z.array(z.any()),
|
|
46
|
+
z.record(z.string(), z.unknown()),
|
|
47
|
+
z.string(),
|
|
48
|
+
z.number(),
|
|
49
|
+
z.boolean(),
|
|
50
|
+
DataBindingSchema,
|
|
51
|
+
])
|
|
52
|
+
.describe('REF:#/$defs/DynamicValue|Datalayer: what the component shows or sends — a path the application publishes or takes, or a value given in place.');
|
|
53
|
+
/** A property as its JSON Schema types it. */
|
|
54
|
+
function propertySchema(name, property) {
|
|
55
|
+
if (property.enum) {
|
|
56
|
+
return z.enum(property.enum);
|
|
57
|
+
}
|
|
58
|
+
switch (property.type) {
|
|
59
|
+
case 'string':
|
|
60
|
+
return z.string();
|
|
61
|
+
case 'integer':
|
|
62
|
+
case 'number':
|
|
63
|
+
return z.number();
|
|
64
|
+
case 'boolean':
|
|
65
|
+
return z.boolean();
|
|
66
|
+
case 'array':
|
|
67
|
+
return z.array(z.any());
|
|
68
|
+
case 'object':
|
|
69
|
+
return z.record(z.string(), z.unknown());
|
|
70
|
+
default:
|
|
71
|
+
throw new Error(`${name} is typed ${String(property.type)}, which a component's property cannot be.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** The names a component shows and sends through, each once. */
|
|
75
|
+
export function bindingNames(component) {
|
|
76
|
+
const { shows, sends } = component.bindings;
|
|
77
|
+
return [...new Set([...shows, ...sends])];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The A2UI schema of a component of Datalayer's own: its properties as its
|
|
81
|
+
* JSON Schema has them, its bindings, its `action`, and the `weight` every
|
|
82
|
+
* basic component takes inside a Row or a Column. Strict, as the basic
|
|
83
|
+
* catalog's are: a key it does not declare refuses the tree.
|
|
84
|
+
*/
|
|
85
|
+
export function ownComponentSchema(id) {
|
|
86
|
+
const component = ownComponent(id);
|
|
87
|
+
const json = component.properties;
|
|
88
|
+
const required = new Set(json.required ?? []);
|
|
89
|
+
const shape = {};
|
|
90
|
+
for (const [name, property] of Object.entries(json.properties ?? {})) {
|
|
91
|
+
const schema = propertySchema(name, property);
|
|
92
|
+
shape[name] = required.has(name) ? schema : schema.optional();
|
|
93
|
+
}
|
|
94
|
+
for (const name of bindingNames(component)) {
|
|
95
|
+
if (name in shape) {
|
|
96
|
+
throw new Error(`${id} both declares ${name} as a property and binds through it.`);
|
|
97
|
+
}
|
|
98
|
+
shape[name] = BindingSchema.optional();
|
|
99
|
+
}
|
|
100
|
+
shape.action = ActionSchema
|
|
101
|
+
.describe('REF:#/$defs/Action|What it dispatches when a person acts on it.')
|
|
102
|
+
.optional();
|
|
103
|
+
shape.weight = z
|
|
104
|
+
.number()
|
|
105
|
+
.describe('The relative weight of this component within a Row or Column.')
|
|
106
|
+
.optional();
|
|
107
|
+
return z.object(shape).strict();
|
|
108
|
+
}
|
|
@@ -11,7 +11,9 @@ export declare function nodeIsShown(props: Record<string, unknown>): boolean;
|
|
|
11
11
|
/** A component, its schema declaring `visible_when`, drawn behind the guard. */
|
|
12
12
|
export declare function withVisibleWhen(component: ReactComponentImplementation): ReactComponentImplementation;
|
|
13
13
|
/**
|
|
14
|
-
* The catalog Datalayer's renderer draws from: A2UI's basic catalog
|
|
15
|
-
*
|
|
14
|
+
* The catalog Datalayer's renderer draws from: A2UI's basic catalog and
|
|
15
|
+
* Datalayer's own components (Table, Chart, File upload, Chat, Evidence,
|
|
16
|
+
* Form — `./datalayer`), under the basic catalog's id — the payloads name
|
|
17
|
+
* it — with `visible_when` on every component.
|
|
16
18
|
*/
|
|
17
19
|
export declare const datalayerCatalog: Catalog<ReactComponentImplementation, import("@a2ui/web_core/v0_9").FunctionImplementation>;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { basicCatalog, useSignalValue, } from '@a2ui/react/v0_9';
|
|
3
3
|
import { Catalog, DynamicBooleanSchema, ResolvedBinding, isWritable, } from '@a2ui/web_core/v0_9';
|
|
4
4
|
import { VISIBLE_WHEN, isShown } from './visibility';
|
|
5
|
+
import { OWN_COMPONENTS } from './datalayer';
|
|
5
6
|
/**
|
|
6
7
|
* Whether a node's resolved properties let it be drawn: no condition, or one
|
|
7
8
|
* that holds. A2UI resolves a dynamic property to a binding whether the block
|
|
@@ -48,7 +49,9 @@ export function withVisibleWhen(component) {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
/**
|
|
51
|
-
* The catalog Datalayer's renderer draws from: A2UI's basic catalog
|
|
52
|
-
*
|
|
52
|
+
* The catalog Datalayer's renderer draws from: A2UI's basic catalog and
|
|
53
|
+
* Datalayer's own components (Table, Chart, File upload, Chat, Evidence,
|
|
54
|
+
* Form — `./datalayer`), under the basic catalog's id — the payloads name
|
|
55
|
+
* it — with `visible_when` on every component.
|
|
53
56
|
*/
|
|
54
|
-
export const datalayerCatalog = new Catalog(basicCatalog.id, basicCatalog.protocolVersion, [...basicCatalog.components.values()].map(withVisibleWhen), [...basicCatalog.functions.values()], basicCatalog.themeSchema, basicCatalog.instructions);
|
|
57
|
+
export const datalayerCatalog = new Catalog(basicCatalog.id, basicCatalog.protocolVersion, [...basicCatalog.components.values(), ...OWN_COMPONENTS].map(withVisibleWhen), [...basicCatalog.functions.values()], basicCatalog.themeSchema, basicCatalog.instructions);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactorPlugin } from '@datalayer/reactor';
|
|
2
2
|
import type { AppSpec } from '../../types/agentspecs';
|
|
3
|
+
import type { ThemeOverrides } from '../../types/chat';
|
|
3
4
|
import { type LoopEmbedProps } from '../embed/LoopEmbed';
|
|
4
5
|
import type { LoopPresetOptions } from '../presets';
|
|
5
6
|
import type { PresenceState } from '../../chat/presence/presenceStatus';
|
|
@@ -79,5 +80,12 @@ export declare function appDatalayerCreatePayload(app: AppSpec, instance?: AppIn
|
|
|
79
80
|
/** No host plugins: one array, so that it never reads as a change. */
|
|
80
81
|
/** The agentspec a runtime is allocated with before an application's agent is created on it. */
|
|
81
82
|
export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
|
|
83
|
+
/**
|
|
84
|
+
* The application's accent over the theme its conversation wears, in both
|
|
85
|
+
* modes (LOOP T-05): its bubbles and its one button in its own colour, and
|
|
86
|
+
* not in the theme's default — the chat sets its theme again inside it, so
|
|
87
|
+
* what the page around it set does not reach in.
|
|
88
|
+
*/
|
|
89
|
+
export declare function appThemeOverrides(app: AppSpec): ThemeOverrides | undefined;
|
|
82
90
|
export declare function AppRenderer({ app, plugins, instance, onPresence, frame, ...embed }: AppRendererProps): React.JSX.Element;
|
|
83
91
|
export default AppRenderer;
|
|
@@ -34,6 +34,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
34
34
|
* @module loop/apps/AppRenderer
|
|
35
35
|
*/
|
|
36
36
|
import { useMemo } from 'react';
|
|
37
|
+
import { loopAccentStyles } from '@datalayer/primer-addons';
|
|
37
38
|
import { defineAgentCapacityPlugin } from '../plugins/agent-capacity';
|
|
38
39
|
import { APP_PAGE_SURFACE, defineAppPagePlugin, hasAppPage, } from '../plugins/app-page';
|
|
39
40
|
import { LoopEmbed } from '../embed/LoopEmbed';
|
|
@@ -158,6 +159,21 @@ export function appDatalayerCreatePayload(app, instance) {
|
|
|
158
159
|
/** The agentspec a runtime is allocated with before an application's agent is created on it. */
|
|
159
160
|
export const DATALAYER_BOOTSTRAP_AGENTSPEC = 'example-simple';
|
|
160
161
|
const NO_PLUGINS = [];
|
|
162
|
+
/**
|
|
163
|
+
* The application's accent over the theme its conversation wears, in both
|
|
164
|
+
* modes (LOOP T-05): its bubbles and its one button in its own colour, and
|
|
165
|
+
* not in the theme's default — the chat sets its theme again inside it, so
|
|
166
|
+
* what the page around it set does not reach in.
|
|
167
|
+
*/
|
|
168
|
+
export function appThemeOverrides(app) {
|
|
169
|
+
const accent = app.interface?.accent;
|
|
170
|
+
return accent
|
|
171
|
+
? {
|
|
172
|
+
light: loopAccentStyles(accent, 'light'),
|
|
173
|
+
dark: loopAccentStyles(accent, 'dark'),
|
|
174
|
+
}
|
|
175
|
+
: undefined;
|
|
176
|
+
}
|
|
161
177
|
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, frame = false, ...embed }) {
|
|
162
178
|
/*
|
|
163
179
|
* The application plugin, made once per application. `LoopEmbed` rebuilds
|
|
@@ -198,6 +214,10 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
|
|
|
198
214
|
const datalayerCreatePayload = useMemo(() => (app.agent ? appDatalayerCreatePayload(app, instance) : undefined),
|
|
199
215
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
200
216
|
[source, instance?.appUid, instance?.deploymentUid, instance?.version]);
|
|
217
|
+
const accent = app.interface?.accent;
|
|
218
|
+
const themeOverrides = useMemo(() => appThemeOverrides(app),
|
|
219
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
220
|
+
[accent]);
|
|
201
221
|
if (!appPlugin) {
|
|
202
222
|
return (_jsxs("div", { role: "status", style: { padding: 16 }, children: ["\u201C", app.name, "\u201D is run by a team, which the workspace does not run as an application yet."] }));
|
|
203
223
|
}
|
|
@@ -217,7 +237,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
|
|
|
217
237
|
targetFixed: true,
|
|
218
238
|
// Applications first (LOOP T-12): an application's conversation wears
|
|
219
239
|
// the `loop` theme; a host may say otherwise.
|
|
220
|
-
themeVariant: "loop",
|
|
240
|
+
themeVariant: "loop", themeOverrides: themeOverrides,
|
|
221
241
|
// Its own face, name and welcome in the chat (T-08); no counters: a
|
|
222
242
|
// person using an application is not asking about tokens.
|
|
223
243
|
presence: {
|
|
@@ -33,8 +33,15 @@ export declare const APP_KINDS: AppKind[];
|
|
|
33
33
|
export declare const APP_ACCENTS: AppAccent[];
|
|
34
34
|
/** The four ways an application sits in another product's page (LOOP D-07). */
|
|
35
35
|
export declare const APP_EMBED_MODES: AppEmbedMode[];
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* How an Appspec names the character of its floating assistant (LOOP T-24):
|
|
38
|
+
* the id a plugin contributes it under, `paperclip` or `acme-owl` — as
|
|
39
|
+
* agentspecs' `ASSISTANT_CHARACTER_ID`. Which ids exist is what the enabled
|
|
40
|
+
* plugins contribute (`assistantCharacterNamed`), not the spec's to say.
|
|
41
|
+
*/
|
|
42
|
+
export declare const APP_ASSISTANT_CHARACTER_ID: RegExp;
|
|
43
|
+
/** Whether a value is shaped as a character id an Appspec may name. */
|
|
44
|
+
export declare function isAssistantCharacterId(value: unknown): value is AppAssistantCharacter;
|
|
38
45
|
/** The layout a kind starts with. */
|
|
39
46
|
export declare const DEFAULT_LAYOUTS: Record<AppKind, AppLayout>;
|
|
40
47
|
/** What an application keeps of what it did, when it does not say. */
|
package/lib/loop/apps/appspec.js
CHANGED
|
@@ -23,13 +23,19 @@ export const APP_EMBED_MODES = [
|
|
|
23
23
|
'panel',
|
|
24
24
|
'assistant',
|
|
25
25
|
];
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
]
|
|
26
|
+
/**
|
|
27
|
+
* How an Appspec names the character of its floating assistant (LOOP T-24):
|
|
28
|
+
* the id a plugin contributes it under, `paperclip` or `acme-owl` — as
|
|
29
|
+
* agentspecs' `ASSISTANT_CHARACTER_ID`. Which ids exist is what the enabled
|
|
30
|
+
* plugins contribute (`assistantCharacterNamed`), not the spec's to say.
|
|
31
|
+
*/
|
|
32
|
+
export const APP_ASSISTANT_CHARACTER_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
33
|
+
/** Whether a value is shaped as a character id an Appspec may name. */
|
|
34
|
+
export function isAssistantCharacterId(value) {
|
|
35
|
+
return (typeof value === 'string' &&
|
|
36
|
+
value.length <= 64 &&
|
|
37
|
+
APP_ASSISTANT_CHARACTER_ID.test(value));
|
|
38
|
+
}
|
|
33
39
|
const ACTION_CLASS_NAMES = [
|
|
34
40
|
'read',
|
|
35
41
|
'write',
|
|
@@ -218,7 +224,7 @@ function parseInterface(data, kind) {
|
|
|
218
224
|
if (isData(data.surface)) {
|
|
219
225
|
parsed.surface = parseSurface(data.surface);
|
|
220
226
|
}
|
|
221
|
-
if (
|
|
227
|
+
if (isAssistantCharacterId(data.assistant)) {
|
|
222
228
|
parsed.assistant = data.assistant;
|
|
223
229
|
}
|
|
224
230
|
return parsed;
|
package/lib/loop/apps/checks.js
CHANGED
|
@@ -39,7 +39,7 @@ import { getTeamSpec } from '../../specs/teams';
|
|
|
39
39
|
import { getToolSpec } from '../../specs/tools';
|
|
40
40
|
import { getTrack } from '../../specs/tracks';
|
|
41
41
|
import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
|
|
42
|
-
import { parseAppspec } from './appspec';
|
|
42
|
+
import { isAssistantCharacterId, parseAppspec } from './appspec';
|
|
43
43
|
import { classesOf, splitRef, toolBehaviours } from './rules';
|
|
44
44
|
export const NOT_READY = 'Not ready';
|
|
45
45
|
export const NEEDS_ATTENTION = 'Needs attention';
|
|
@@ -378,7 +378,6 @@ const ENUMS = {
|
|
|
378
378
|
],
|
|
379
379
|
visibility: ['private', 'invited', 'organization', 'link', 'public'],
|
|
380
380
|
mode: ['inline', 'bubble', 'panel', 'assistant'],
|
|
381
|
-
character: ['paperclip', 'wizard', 'cat', 'eyes'],
|
|
382
381
|
trigger: ['schedule', 'event', 'once'],
|
|
383
382
|
criterion: ['metric', 'noul', 'choice', 'score'],
|
|
384
383
|
direction: ['higher', 'lower'],
|
|
@@ -506,7 +505,9 @@ export function documentShapeProblems(document) {
|
|
|
506
505
|
mapping(d.interface, 'interface', ui => {
|
|
507
506
|
oneOf(ui.layout, ENUMS.layout, 'interface.layout');
|
|
508
507
|
oneOf(ui.accent, ENUMS.accent, 'interface.accent');
|
|
509
|
-
|
|
508
|
+
if (ui.assistant !== undefined && !isAssistantCharacterId(ui.assistant)) {
|
|
509
|
+
at('interface.assistant', 'names a character by the id a plugin contributes it under, lowercase words joined by a hyphen: `paperclip`, `acme-owl`');
|
|
510
|
+
}
|
|
510
511
|
text(ui.welcome, 'interface.welcome');
|
|
511
512
|
texts(ui.components, 'interface.components');
|
|
512
513
|
records(ui.starters, 'interface.starters', (item, where) => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CSSProperties, JSX, ReactNode } from 'react';
|
|
2
|
+
import type { PluginRef } from '@datalayer/reactor';
|
|
2
3
|
import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
|
|
3
4
|
import { type AppInstance } from '../apps/AppRenderer';
|
|
5
|
+
import { type AssistantCharacterChosen } from '../plugins/assistant-characters';
|
|
4
6
|
import { type EmbedColorMode } from './embedConfig';
|
|
5
7
|
export type AppEmbedProps = {
|
|
6
8
|
/** The application. */
|
|
@@ -29,7 +31,20 @@ export type AppEmbedProps = {
|
|
|
29
31
|
* shadow root's styles stop at its edge. A React host leaves it off.
|
|
30
32
|
*/
|
|
31
33
|
ownPortal?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The host's plugins, for the assistant's character: what they contribute
|
|
36
|
+
* to `loop.assistant.character` is there beside Datalayer's four. The
|
|
37
|
+
* element passes none.
|
|
38
|
+
*/
|
|
39
|
+
plugins?: PluginRef[];
|
|
32
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* The character the assistant mode draws (T-24, D-07): the application's own
|
|
43
|
+
* (`interface.assistant`), else the paper clip, from what Datalayer's
|
|
44
|
+
* characters and the host's plugins contribute — as a page decides it, with
|
|
45
|
+
* no person's choice, since a visitor to another product has none here.
|
|
46
|
+
*/
|
|
47
|
+
export declare function embedAssistantCharacter(app: AppSpec, plugins?: PluginRef[]): AssistantCharacterChosen;
|
|
33
48
|
/** The `loop` theme, with the application's (or the host's) accent and face. */
|
|
34
49
|
export declare function EmbedThemed({ accent, colorMode, font, children, style, }: {
|
|
35
50
|
accent: AppAccent;
|
|
@@ -38,5 +53,5 @@ export declare function EmbedThemed({ accent, colorMode, font, children, style,
|
|
|
38
53
|
children: ReactNode;
|
|
39
54
|
style?: CSSProperties;
|
|
40
55
|
}): JSX.Element;
|
|
41
|
-
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, height, ownPortal, }: AppEmbedProps): JSX.Element;
|
|
56
|
+
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, height, ownPortal, plugins, }: AppEmbedProps): JSX.Element;
|
|
42
57
|
export default AppEmbed;
|
|
@@ -17,7 +17,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
17
17
|
* - **panel** — the conversation at the right edge of the page, at full
|
|
18
18
|
* height;
|
|
19
19
|
* - **assistant** — the application's character on the page, speaking in a
|
|
20
|
-
* balloon (§6.9)
|
|
20
|
+
* balloon (§6.9): the one its Appspec names (`interface.assistant`), else
|
|
21
|
+
* the paper clip, looked up in what is contributed — Datalayer's four and
|
|
22
|
+
* the host's own plugins (T-24); an id nothing contributes is said, never
|
|
23
|
+
* replaced.
|
|
21
24
|
*
|
|
22
25
|
* The three floating modes are `ChatFloating`'s, given the application's
|
|
23
26
|
* name, welcome, starters and accent, and its agent's endpoint — created
|
|
@@ -25,7 +28,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
25
28
|
* rules there as it does inline.
|
|
26
29
|
*
|
|
27
30
|
* 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)
|
|
31
|
+
* host may override with the face and the mode (D-11) — around the
|
|
32
|
+
* conversation and inside it, where the chat sets its theme again.
|
|
29
33
|
*
|
|
30
34
|
* @module loop/embed/AppEmbed
|
|
31
35
|
*/
|
|
@@ -38,8 +42,20 @@ import { useAgentRuntimes } from '../../hooks/useAgentRuntimes';
|
|
|
38
42
|
import { AppRenderer, DATALAYER_BOOTSTRAP_AGENTSPEC, appDatalayerCreatePayload, defineAppPlugin, } from '../apps/AppRenderer';
|
|
39
43
|
import { LoopAgentBlueprint } from '../core';
|
|
40
44
|
import { ensureServerAgent } from '../plugins/agents/switchable';
|
|
45
|
+
import { AssistantCharactersPlugin, assistantCharacterFor, assistantCharactersFrom, } from '../plugins/assistant-characters';
|
|
41
46
|
import { floatingViewOf } from './embedConfig';
|
|
42
|
-
import { embedThemeStyles } from './embedTheme';
|
|
47
|
+
import { embedThemeOverrides, embedThemeStyles } from './embedTheme';
|
|
48
|
+
/** No host plugins: one array, so that it never reads as a change. */
|
|
49
|
+
const NO_PLUGINS = [];
|
|
50
|
+
/**
|
|
51
|
+
* The character the assistant mode draws (T-24, D-07): the application's own
|
|
52
|
+
* (`interface.assistant`), else the paper clip, from what Datalayer's
|
|
53
|
+
* characters and the host's plugins contribute — as a page decides it, with
|
|
54
|
+
* no person's choice, since a visitor to another product has none here.
|
|
55
|
+
*/
|
|
56
|
+
export function embedAssistantCharacter(app, plugins = NO_PLUGINS) {
|
|
57
|
+
return assistantCharacterFor(assistantCharactersFrom([AssistantCharactersPlugin, ...plugins]), { app: app.interface.assistant });
|
|
58
|
+
}
|
|
43
59
|
/** What the application's agent is created with on an agent-runtimes server. */
|
|
44
60
|
function serverCreatePayload(app) {
|
|
45
61
|
const blueprint = (defineAppPlugin(app).contributes ?? []).find((item) => item.point === LoopAgentBlueprint);
|
|
@@ -132,14 +148,14 @@ function Face({ emoji }) {
|
|
|
132
148
|
return (_jsx("span", { "aria-hidden": true, style: { fontSize: 18, lineHeight: 1 }, children: emoji }));
|
|
133
149
|
}
|
|
134
150
|
/** The bubble, the panel and the assistant: `ChatFloating` in that mode. */
|
|
135
|
-
function FloatingChat({ app, view, colorMode, agent, }) {
|
|
151
|
+
function FloatingChat({ app, view, colorMode, themeOverrides, character, agent, }) {
|
|
136
152
|
const welcome = app.interface.welcome || app.description;
|
|
137
153
|
return (_jsx(ChatFloating
|
|
138
154
|
// A new agent is a new conversation.
|
|
139
|
-
, { defaultViewMode: view, assistantCharacter:
|
|
155
|
+
, { defaultViewMode: view, ...(character ? { assistantCharacter: character } : {}), 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
156
|
title: starter.label,
|
|
141
157
|
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'));
|
|
158
|
+
})), themeVariant: "loop", themeOverrides: themeOverrides, 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
159
|
}
|
|
144
160
|
/** Floating, its agent on the host's agent-runtimes server. */
|
|
145
161
|
function ServerFloating({ serverUrl, ...props }) {
|
|
@@ -192,14 +208,27 @@ export function EmbedThemed({ accent, colorMode, font, children, style, }) {
|
|
|
192
208
|
*/
|
|
193
209
|
_jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: loopTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
|
|
194
210
|
}
|
|
195
|
-
export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUrl, instance, height = 640, ownPortal = false, }) {
|
|
211
|
+
export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUrl, instance, height = 640, ownPortal = false, plugins = NO_PLUGINS, }) {
|
|
196
212
|
const system = useSystemMode();
|
|
197
213
|
const resolvedMode = colorMode === 'auto' ? system : colorMode;
|
|
198
214
|
const shownAs = mode ?? app.deployment.embedded?.mode ?? 'inline';
|
|
199
215
|
const view = floatingViewOf(shownAs);
|
|
200
|
-
|
|
216
|
+
const worn = accent ?? app.interface.accent;
|
|
217
|
+
const themeOverrides = useMemo(() => embedThemeOverrides({ accent: worn, font }), [worn, font]);
|
|
218
|
+
const named = app.interface.assistant;
|
|
219
|
+
const assistant = useMemo(() => view === 'assistant' ? embedAssistantCharacter(app, plugins) : undefined,
|
|
220
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
221
|
+
[view, named, plugins]);
|
|
222
|
+
if (assistant && 'problem' in assistant) {
|
|
223
|
+
return (_jsx("p", { role: "status", className: "datalayer-app-said", children: assistant.problem }));
|
|
224
|
+
}
|
|
225
|
+
const character = assistant?.character;
|
|
226
|
+
return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, style: view
|
|
201
227
|
? // Floating: nothing in the page's flow, nothing painted behind.
|
|
202
228
|
{ 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
|
|
229
|
+
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view && serverUrl ? (_jsx(ServerFloating, { app: app, view: view, serverUrl: serverUrl, colorMode: resolvedMode, themeOverrides: themeOverrides, character: character })) : view ? (_jsx(DatalayerFloating, { app: app, view: view, instance: instance, colorMode: resolvedMode, themeOverrides: themeOverrides, character: character })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), instance: instance,
|
|
230
|
+
// The host's accent and face over the application's own, inside
|
|
231
|
+
// its conversation too.
|
|
232
|
+
themeOverrides: themeOverrides }))] }));
|
|
204
233
|
}
|
|
205
234
|
export default AppEmbed;
|
|
@@ -66,6 +66,8 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
|
|
|
66
66
|
preset.datalayerAgentSpecId,
|
|
67
67
|
preset.targetFixed,
|
|
68
68
|
preset.themeVariant,
|
|
69
|
+
// By its content, as the payload below.
|
|
70
|
+
JSON.stringify(preset.themeOverrides ?? null),
|
|
69
71
|
preset.presence?.name,
|
|
70
72
|
preset.presence?.face,
|
|
71
73
|
preset.presence?.welcome,
|
|
@@ -37,7 +37,7 @@ import { loopThemeStyles } from '@datalayer/primer-addons';
|
|
|
37
37
|
import { coreStore } from '@datalayer/core/lib/state/substates/CoreState';
|
|
38
38
|
import { parseAppspec } from '../apps/appspec';
|
|
39
39
|
import { readAppspecYaml } from '../apps/yaml';
|
|
40
|
-
import { AppEmbed } from './AppEmbed';
|
|
40
|
+
import { AppEmbed, embedAssistantCharacter } from './AppEmbed';
|
|
41
41
|
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, } from './embedConfig';
|
|
42
42
|
import { embedShadowCss } from './embedTheme';
|
|
43
43
|
/** What the framed run page and the element say to each other. */
|
|
@@ -362,6 +362,15 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
362
362
|
this.say(error.message);
|
|
363
363
|
return;
|
|
364
364
|
}
|
|
365
|
+
// A character nothing contributes is said in place, as an attribute
|
|
366
|
+
// that is not one is (D-07, T-24).
|
|
367
|
+
if (look.mode === 'assistant') {
|
|
368
|
+
const chosen = embedAssistantCharacter(source.app);
|
|
369
|
+
if ('problem' in chosen) {
|
|
370
|
+
this.say(chosen.problem);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
365
374
|
this.setAttribute('data-embed-mode', look.mode);
|
|
366
375
|
const shadow = this.skeleton(loopThemeStyles.css);
|
|
367
376
|
const mount = document.createElement('div');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ThemeStyles } from '@datalayer/primer-addons';
|
|
2
2
|
import type { AppAccent } from '../../types/agentspecs';
|
|
3
|
+
import type { ThemeOverrides } from '../../types/chat';
|
|
3
4
|
/**
|
|
4
5
|
* The `loop` theme as the embed wears it: the application's accent over it
|
|
5
6
|
* in both modes (T-05), and the host's face when it named one.
|
|
@@ -8,6 +9,17 @@ export declare function embedThemeStyles({ accent, font, }: {
|
|
|
8
9
|
accent: AppAccent;
|
|
9
10
|
font?: string;
|
|
10
11
|
}): ThemeStyles;
|
|
12
|
+
/**
|
|
13
|
+
* The same accent and face laid over the conversation's own theme (T-05,
|
|
14
|
+
* D-11): the chat sets the `loop` theme again inside it, which would put the
|
|
15
|
+
* theme's mint and face back over what the embed set around it. The
|
|
16
|
+
* accent's properties in each mode, and, when the host named a face, every
|
|
17
|
+
* property of the theme that names its own.
|
|
18
|
+
*/
|
|
19
|
+
export declare function embedThemeOverrides({ accent, font, }: {
|
|
20
|
+
accent: AppAccent;
|
|
21
|
+
font?: string;
|
|
22
|
+
}): ThemeOverrides;
|
|
11
23
|
/**
|
|
12
24
|
* The element's own stylesheet, written into its shadow root.
|
|
13
25
|
*
|
|
@@ -30,6 +30,21 @@ export function embedThemeStyles({ accent, font = '', }) {
|
|
|
30
30
|
dark: mode('dark'),
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The same accent and face laid over the conversation's own theme (T-05,
|
|
35
|
+
* D-11): the chat sets the `loop` theme again inside it, which would put the
|
|
36
|
+
* theme's mint and face back over what the embed set around it. The
|
|
37
|
+
* accent's properties in each mode, and, when the host named a face, every
|
|
38
|
+
* property of the theme that names its own.
|
|
39
|
+
*/
|
|
40
|
+
export function embedThemeOverrides({ accent, font = '', }) {
|
|
41
|
+
const mode = (which) => {
|
|
42
|
+
const theme = loopThemeStyles[which];
|
|
43
|
+
const faced = Object.fromEntries(Object.entries(withFace(theme, font)).filter(([name, value]) => typeof value === 'string' && value !== theme[name]));
|
|
44
|
+
return { ...faced, ...loopAccentStyles(accent, which) };
|
|
45
|
+
};
|
|
46
|
+
return { light: mode('light'), dark: mode('dark') };
|
|
47
|
+
}
|
|
33
48
|
/**
|
|
34
49
|
* The element's own stylesheet, written into its shadow root.
|
|
35
50
|
*
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The floating assistant in the LOOP workspace (LOOP T-24): the character a
|
|
3
|
+
* page draws over its workspace, chosen from what the enabled plugins
|
|
4
|
+
* contribute to `loop.assistant.character` — the application's own
|
|
5
|
+
* (`interface.assistant`) when its Appspec names one, else the person's
|
|
6
|
+
* choice from their settings, else the paper clip. It acts out the chat's
|
|
7
|
+
* turn (T-22), says the agent's newest words in its balloon while the
|
|
8
|
+
* conversation is out of sight (T-23), opens the conversation when clicked,
|
|
9
|
+
* is dragged about and sent away as the chat's own assistant is (T-27).
|
|
10
|
+
*
|
|
11
|
+
* The workspace's chat is the conversation: on a page layout, its side panel
|
|
12
|
+
* is opened and closed; where the conversation is always on screen, a click
|
|
13
|
+
* takes the person to its composer.
|
|
14
|
+
*
|
|
15
|
+
* @module loop/plugins/assistant
|
|
16
|
+
*/
|
|
17
|
+
import type { JSX } from 'react';
|
|
18
|
+
import { type AssistantState } from '../../../chat/assistant/state';
|
|
19
|
+
import { type ChatTurnSnapshot } from '../../core';
|
|
20
|
+
export declare const LOOP_ASSISTANT_PLUGIN_NAME = "@datalayer/loop-plugin-assistant";
|
|
21
|
+
export type LoopAssistantConfig = {
|
|
22
|
+
/** The character the application's Appspec names; it wins. */
|
|
23
|
+
app?: string;
|
|
24
|
+
/** The character the person chose in their settings. */
|
|
25
|
+
person?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* What the character acts out, from the workspace's chat turn (T-22): it
|
|
29
|
+
* thinks while the agent thinks, works while a tool runs (the turn's
|
|
30
|
+
* `activity`), speaks while the answer is written, and idles otherwise.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assistantStateOfTurn(turn: ChatTurnSnapshot, moment?: {
|
|
33
|
+
arriving?: boolean;
|
|
34
|
+
leaving?: boolean;
|
|
35
|
+
}): AssistantState;
|
|
36
|
+
export declare function LoopAssistant({ app, person, }: LoopAssistantConfig): JSX.Element | null;
|
|
37
|
+
export declare const LoopAssistantPlugin: import("@datalayer/reactor").ReactorPlugin<LoopAssistantConfig, unknown, unknown>;
|
|
38
|
+
export default LoopAssistantPlugin;
|