@datalayer/agent-runtimes 1.3.39 → 1.3.42
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 +30 -1
- package/lib/chat/ChatFloating.js +60 -14
- package/lib/chat/presence/Presence.d.ts +9 -0
- package/lib/chat/presence/Presence.js +11 -2
- package/lib/loop/apps/AppRenderer.d.ts +30 -6
- package/lib/loop/apps/AppRenderer.js +29 -8
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/apps/records.d.ts +42 -0
- package/lib/loop/apps/records.js +81 -0
- package/lib/loop/apps/rules.d.ts +13 -0
- package/lib/loop/apps/rules.js +36 -0
- package/lib/loop/embed/AppEmbed.d.ts +2 -0
- package/lib/loop/embed/AppEmbed.js +47 -119
- package/lib/loop/embed/LoopEmbed.js +1 -0
- package/lib/loop/index.d.ts +2 -0
- package/lib/loop/index.js +2 -0
- package/lib/loop/plugins/app-activity/AppActivity.d.ts +21 -0
- package/lib/loop/plugins/app-activity/AppActivity.js +63 -0
- package/lib/loop/plugins/app-activity/index.d.ts +11 -0
- package/lib/loop/plugins/app-activity/index.js +35 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +40 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +50 -0
- package/lib/loop/plugins/app-rules/index.d.ts +7 -0
- package/lib/loop/plugins/app-rules/index.js +31 -0
- package/lib/loop/plugins/chat/ChatView.js +33 -4
- package/lib/loop/plugins/chat/index.d.ts +23 -0
- package/lib/loop/presets.d.ts +2 -0
- package/lib/loop/presets.js +2 -1
- package/package.json +2 -2
|
@@ -22,10 +22,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
22
22
|
* the host's own plugins (T-24); an id nothing contributes is said, never
|
|
23
23
|
* replaced.
|
|
24
24
|
*
|
|
25
|
-
* The three floating modes are `ChatFloating`'s
|
|
26
|
-
*
|
|
27
|
-
* with
|
|
28
|
-
* rules there as
|
|
25
|
+
* The three floating modes are `ChatFloating`'s chrome around the same
|
|
26
|
+
* `AppRenderer` (R-01): the application's preset per kind and its layout in
|
|
27
|
+
* the window, its agent created with its spec in its payload and spoken to
|
|
28
|
+
* at its session endpoint (R-04), so the runtime enforces its rules there as
|
|
29
|
+
* it does inline; the chrome told by the workspace what it is doing and
|
|
30
|
+
* what it last said, for the balloon and the blink.
|
|
29
31
|
*
|
|
30
32
|
* In the `loop` theme with the application's accent (T-12, T-05), which the
|
|
31
33
|
* host may override with the face and the mode (D-11) — around the
|
|
@@ -34,14 +36,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
34
36
|
* @module loop/embed/AppEmbed
|
|
35
37
|
*/
|
|
36
38
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
37
|
-
import { registerPortalRoot } from '@primer/react';
|
|
38
|
-
import { DatalayerThemeProvider, loopTheme } from '@datalayer/primer-addons';
|
|
39
|
+
import { Text, registerPortalRoot } from '@primer/react';
|
|
39
40
|
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
41
|
+
import { FluentEmoji } from '@datalayer/core/lib/components/emoji';
|
|
42
|
+
import { DatalayerThemeProvider, loopTheme } from '@datalayer/primer-addons';
|
|
40
43
|
import { ChatFloating } from '../../chat/ChatFloating';
|
|
41
|
-
import {
|
|
42
|
-
import { AppRenderer, DATALAYER_BOOTSTRAP_AGENTSPEC, appDatalayerCreatePayload, defineAppPlugin, } from '../apps/AppRenderer';
|
|
43
|
-
import { LoopAgentBlueprint } from '../core';
|
|
44
|
-
import { ensureServerAgent } from '../plugins/agents/switchable';
|
|
44
|
+
import { AppRenderer } from '../apps/AppRenderer';
|
|
45
45
|
import { AssistantCharactersPlugin, assistantCharacterFor, assistantCharactersFrom, } from '../plugins/assistant-characters';
|
|
46
46
|
import { floatingViewOf } from './embedConfig';
|
|
47
47
|
import { embedThemeOverrides, embedThemeStyles } from './embedTheme';
|
|
@@ -56,116 +56,43 @@ const NO_PLUGINS = [];
|
|
|
56
56
|
export function embedAssistantCharacter(app, plugins = NO_PLUGINS) {
|
|
57
57
|
return assistantCharacterFor(assistantCharactersFrom([AssistantCharactersPlugin, ...plugins]), { app: app.interface.assistant });
|
|
58
58
|
}
|
|
59
|
-
/** What
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* The application's agent on the host's agent-runtimes server: created
|
|
66
|
-
* there with the application's spec unless it is there already, as the
|
|
67
|
-
* workspace's Local target does.
|
|
68
|
-
*/
|
|
69
|
-
function useServerAgent(app, serverUrl) {
|
|
70
|
-
const [state, setState] = useState({
|
|
71
|
-
waiting: 'Starting…',
|
|
72
|
-
});
|
|
73
|
-
const source = JSON.stringify(app);
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
let cancelled = false;
|
|
76
|
-
const base = serverUrl.replace(/\/+$/, '');
|
|
77
|
-
setState({ waiting: 'Starting…' });
|
|
78
|
-
ensureServerAgent(base, app.id, serverCreatePayload(app)).then(() => {
|
|
79
|
-
if (!cancelled) {
|
|
80
|
-
setState({
|
|
81
|
-
protocol: {
|
|
82
|
-
type: 'ag-ui',
|
|
83
|
-
// Its session API: each thread a session (LOOP R-04).
|
|
84
|
-
endpoint: `${base}/api/v1/apps/agents/${encodeURIComponent(app.id)}/ag-ui/`,
|
|
85
|
-
agentId: app.id,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}, (error) => {
|
|
90
|
-
if (!cancelled) {
|
|
91
|
-
setState({
|
|
92
|
-
error: error instanceof Error ? error.message : String(error),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
return () => {
|
|
97
|
-
cancelled = true;
|
|
98
|
-
};
|
|
99
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
|
-
}, [serverUrl, source]);
|
|
101
|
-
return state;
|
|
59
|
+
/** What a floating application says to a visitor Datalayer does not know. */
|
|
60
|
+
export const signedOutSentence = (app) => `${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).`;
|
|
61
|
+
/** The application's face, as the floating window's button and header draw it. */
|
|
62
|
+
function Face({ emoji }) {
|
|
63
|
+
// In Fluent Emoji, at a line's size (T-19, T-20).
|
|
64
|
+
return _jsx(FluentEmoji, { emoji: emoji, size: 20, label: "" });
|
|
102
65
|
}
|
|
103
66
|
/**
|
|
104
|
-
* The
|
|
105
|
-
*
|
|
106
|
-
*
|
|
67
|
+
* The bubble, the panel and the assistant: `ChatFloating`'s chrome in that
|
|
68
|
+
* mode — the button, its blink and its balloon, the panel at the edge, the
|
|
69
|
+
* character dragged about and sent away — holding the application as
|
|
70
|
+
* `AppRenderer` draws it inline (R-01): its kind's preset, its layout, its
|
|
71
|
+
* agent created with its spec and spoken to at its session endpoint (R-04).
|
|
72
|
+
* The workspace tells the chrome what the application is doing and what it
|
|
73
|
+
* last said (`onPresence`, `onSaying`).
|
|
107
74
|
*/
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const agentConfig = useMemo(() => ({
|
|
112
|
-
name: app.id,
|
|
113
|
-
protocol: 'ag-ui',
|
|
114
|
-
agentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
115
|
-
createPayload: appDatalayerCreatePayload(app, instance),
|
|
116
|
-
}),
|
|
117
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
|
-
[source, instance?.appUid, instance?.deploymentUid, instance?.version]);
|
|
119
|
-
const { runtime, error } = useAgentRuntimes({
|
|
120
|
-
agentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
121
|
-
agentConfig,
|
|
122
|
-
variant: 'cloud-pydanticai',
|
|
123
|
-
// Only for a visitor Datalayer knows: a runtime is somebody's to pay for.
|
|
124
|
-
autoStart: Boolean(token),
|
|
125
|
-
autoCreateAgent: Boolean(token),
|
|
126
|
-
});
|
|
127
|
-
if (!token) {
|
|
128
|
-
return {
|
|
129
|
-
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).`,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (error) {
|
|
133
|
-
return { error };
|
|
134
|
-
}
|
|
135
|
-
if (!runtime?.agentBaseUrl) {
|
|
136
|
-
return { waiting: 'Starting…' };
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
protocol: {
|
|
140
|
-
type: 'ag-ui',
|
|
141
|
-
// Its session API: each thread a session (LOOP R-04).
|
|
142
|
-
endpoint: `${runtime.agentBaseUrl}/api/v1/apps/agents/${encodeURIComponent(app.id)}/ag-ui/`,
|
|
143
|
-
agentId: app.id,
|
|
144
|
-
authToken: token,
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
/** The application's face, as the floating chat's button and header draw it. */
|
|
149
|
-
function Face({ emoji }) {
|
|
150
|
-
return (_jsx("span", { "aria-hidden": true, style: { fontSize: 18, lineHeight: 1 }, children: emoji }));
|
|
151
|
-
}
|
|
152
|
-
/** The bubble, the panel and the assistant: `ChatFloating` in that mode. */
|
|
153
|
-
function FloatingChat({ app, view, colorMode, themeOverrides, character, agent, }) {
|
|
75
|
+
function FloatingApp({ app, view, colorMode, themeOverrides, character, serverUrl, instance, }) {
|
|
76
|
+
const [presence, setPresence] = useState('idle');
|
|
77
|
+
const [said, setSaid] = useState({ answering: false });
|
|
154
78
|
const welcome = app.interface.welcome || app.description;
|
|
155
|
-
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
79
|
+
// A runtime is somebody's to pay for: on Datalayer, only for a visitor
|
|
80
|
+
// Datalayer knows — said, and nothing launched, for one it does not.
|
|
81
|
+
const token = useIAMStore(state => state.token);
|
|
82
|
+
const signedOut = !serverUrl && !token;
|
|
83
|
+
return (_jsx(ChatFloating, { defaultViewMode: view, ...(character ? { assistantCharacter: character } : {}), title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: `Talk to ${app.name}`, colorMode: colorMode, useStore: false, conversation: {
|
|
84
|
+
body: signedOut ? (_jsx(Text, { as: "p", role: "status", sx: { p: 3, m: 0, fontSize: 1 }, children: signedOutSentence(app) })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), instance: instance,
|
|
85
|
+
// The host's accent and face over the application's own, inside
|
|
86
|
+
// its conversation too, in the embed's mode.
|
|
87
|
+
themeOverrides: themeOverrides, colorMode: colorMode,
|
|
88
|
+
// The window draws its face, its name and close.
|
|
89
|
+
hideChatHeader: true,
|
|
90
|
+
// Mounted closed: the caret goes in when the window opens.
|
|
91
|
+
autoFocusPrompt: false, onPresence: setPresence, onSaying: setSaid })),
|
|
92
|
+
presence,
|
|
93
|
+
saying: said.saying,
|
|
94
|
+
answering: said.answering,
|
|
95
|
+
} }));
|
|
169
96
|
}
|
|
170
97
|
/**
|
|
171
98
|
* Overlays — menus, dialogs — drawn inside the embed's own root, under the
|
|
@@ -228,9 +155,10 @@ export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUr
|
|
|
228
155
|
return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, style: view
|
|
229
156
|
? // Floating: nothing in the page's flow, nothing painted behind.
|
|
230
157
|
{ backgroundColor: 'transparent' }
|
|
231
|
-
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view
|
|
158
|
+
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(FloatingApp, { app: app, view: view, serverUrl: serverUrl, instance: instance, colorMode: resolvedMode, themeOverrides: themeOverrides, character: character })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), instance: instance,
|
|
232
159
|
// The host's accent and face over the application's own, inside
|
|
233
|
-
// its conversation too
|
|
234
|
-
|
|
160
|
+
// its conversation too, in the embed's mode: the host's or its
|
|
161
|
+
// visitor's system's, not a Datalayer setting.
|
|
162
|
+
themeOverrides: themeOverrides, colorMode: resolvedMode }))] }));
|
|
235
163
|
}
|
|
236
164
|
export default AppEmbed;
|
|
@@ -68,6 +68,7 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
|
|
|
68
68
|
preset.themeVariant,
|
|
69
69
|
// By its content, as the payload below.
|
|
70
70
|
JSON.stringify(preset.themeOverrides ?? null),
|
|
71
|
+
preset.colorMode,
|
|
71
72
|
preset.presence?.name,
|
|
72
73
|
preset.presence?.face,
|
|
73
74
|
preset.presence?.welcome,
|
package/lib/loop/index.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export { AgentA2uiPlugin, AGENT_A2UI_PLUGIN_NAME } from './plugins/agent-a2ui';
|
|
|
51
51
|
export { AgentA2uiJupyterOutputPlugin, AGENT_A2UI_JUPYTER_OUTPUT_PLUGIN_NAME, } from './plugins/agent-a2ui-jupyter-output';
|
|
52
52
|
export { defineA2uiScenePlugin, type A2uiSceneOptions, } from './plugins/a2ui-scene';
|
|
53
53
|
export { APP_KIND_PATHS, APP_PAGE_PLUGIN_NAME, APP_PAGE_SURFACE, AppPage, appKindPaths, appPageAction, appPageData, defineAppPagePlugin, hasAppPage, surfaceUnshown, type AppKindPaths, type AppPageAction, type AppPageKind, type AppPagePath, } from './plugins/app-page';
|
|
54
|
+
export { APP_RULES_PLUGIN_NAME, APP_RULES_WORDS, AppRulesCard, defineAppRulesPlugin, } from './plugins/app-rules';
|
|
55
|
+
export { APP_ACTIVITY_PLUGIN_NAME, APP_ACTIVITY_WORDS, AppActivity, defineAppActivityPlugin, } from './plugins/app-activity';
|
|
54
56
|
export { GraphViewPlugin, GRAPH_PLUGIN_NAME, GRAPH_VIEW_TYPE, } from './plugins/graph';
|
|
55
57
|
export { PluginsPanelPlugin, PLUGINS_PANEL_PLUGIN_NAME, PluginsPanel, } from './plugins/plugins-panel';
|
|
56
58
|
export { AGENTS_PLUGIN_NAME, AgentsPlugin, SandboxSelector, browserSource, createBrowserSandboxService, createServerSandboxService, createSwitchableSandboxService, suppliedSource, useOptionalSandboxService, useSandboxService, type AgentsConfig, type AgentsOutput, type SandboxExecution, type SandboxService, type SandboxTarget, type ServiceManagerSource, type SwitchableSandboxService, } from './plugins/agents';
|
package/lib/loop/index.js
CHANGED
|
@@ -64,6 +64,8 @@ export { AgentA2uiPlugin, AGENT_A2UI_PLUGIN_NAME } from './plugins/agent-a2ui';
|
|
|
64
64
|
export { AgentA2uiJupyterOutputPlugin, AGENT_A2UI_JUPYTER_OUTPUT_PLUGIN_NAME, } from './plugins/agent-a2ui-jupyter-output';
|
|
65
65
|
export { defineA2uiScenePlugin, } from './plugins/a2ui-scene';
|
|
66
66
|
export { APP_KIND_PATHS, APP_PAGE_PLUGIN_NAME, APP_PAGE_SURFACE, AppPage, appKindPaths, appPageAction, appPageData, defineAppPagePlugin, hasAppPage, surfaceUnshown, } from './plugins/app-page';
|
|
67
|
+
export { APP_RULES_PLUGIN_NAME, APP_RULES_WORDS, AppRulesCard, defineAppRulesPlugin, } from './plugins/app-rules';
|
|
68
|
+
export { APP_ACTIVITY_PLUGIN_NAME, APP_ACTIVITY_WORDS, AppActivity, defineAppActivityPlugin, } from './plugins/app-activity';
|
|
67
69
|
export { GraphViewPlugin, GRAPH_PLUGIN_NAME, GRAPH_VIEW_TYPE, } from './plugins/graph';
|
|
68
70
|
export { PluginsPanelPlugin, PLUGINS_PANEL_PLUGIN_NAME, PluginsPanel, } from './plugins/plugins-panel';
|
|
69
71
|
export { AGENTS_PLUGIN_NAME, AgentsPlugin, SandboxSelector, browserSource, createBrowserSandboxService, createServerSandboxService, createSwitchableSandboxService, suppliedSource, useOptionalSandboxService, useSandboxService, } from './plugins/agents';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an application did, beside its page (LOOP R-01b, R-15, U-20): its
|
|
3
|
+
* sessions, newest first, each in a sentence, opened on what it did step by
|
|
4
|
+
* step — tool calls, the rules' decisions, the checks that stopped a step,
|
|
5
|
+
* the approvals, the answer. Read from the record ai-agents keeps (R-07),
|
|
6
|
+
* again each time a turn of the conversation ends.
|
|
7
|
+
*
|
|
8
|
+
* @module loop/plugins/app-activity/AppActivity
|
|
9
|
+
*/
|
|
10
|
+
import type { JSX } from 'react';
|
|
11
|
+
/** What the feed says. */
|
|
12
|
+
export declare const APP_ACTIVITY_WORDS: {
|
|
13
|
+
readonly title: "Activity";
|
|
14
|
+
readonly unsaved: "Recorded once it is saved on Datalayer: each session there — its address, or its Preview on Datalayer — is listed here.";
|
|
15
|
+
readonly signedOut: "Its activity is its owner’s: sign in to Datalayer to read it.";
|
|
16
|
+
readonly none: "Nothing yet: a session on Datalayer — its address, or the Preview on Datalayer — is recorded here.";
|
|
17
|
+
};
|
|
18
|
+
export declare function AppActivity({ appUid }: {
|
|
19
|
+
appUid?: string;
|
|
20
|
+
}): JSX.Element;
|
|
21
|
+
export default AppActivity;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Heading, Label, RelativeTime, Spinner, Text } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { signal } from '@datalayer/reactor';
|
|
6
|
+
import { useContributions, useSignalValue } from '@datalayer/reactor/react';
|
|
7
|
+
import { useCoreStore } from '@datalayer/core/lib/state';
|
|
8
|
+
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
9
|
+
import { LoopChatTurn } from '../../core';
|
|
10
|
+
import { RECORD_KIND_WORDS, listSessions, readSession, sessionSentence, } from '../../apps/records';
|
|
11
|
+
/** What the feed says. */
|
|
12
|
+
export const APP_ACTIVITY_WORDS = {
|
|
13
|
+
title: 'Activity',
|
|
14
|
+
unsaved: 'Recorded once it is saved on Datalayer: each session there — its address, or its Preview on Datalayer — is listed here.',
|
|
15
|
+
signedOut: 'Its activity is its owner’s: sign in to Datalayer to read it.',
|
|
16
|
+
none: 'Nothing yet: a session on Datalayer — its address, or the Preview on Datalayer — is recorded here.',
|
|
17
|
+
};
|
|
18
|
+
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
19
|
+
/** Read `load` while `when`, again whenever `key` changes. */
|
|
20
|
+
function useRead(load, key, when = true) {
|
|
21
|
+
const [state, setState] = useState({ loading: when });
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!when) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
setState(previous => ({ ...previous, loading: true }));
|
|
28
|
+
load().then(value => !cancelled && setState({ value, loading: false }), (error) => !cancelled &&
|
|
29
|
+
setState({
|
|
30
|
+
error: error instanceof Error ? error.message : String(error),
|
|
31
|
+
loading: false,
|
|
32
|
+
}));
|
|
33
|
+
return () => {
|
|
34
|
+
cancelled = true;
|
|
35
|
+
};
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [key, when]);
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
function Session({ context, session, }) {
|
|
41
|
+
const [open, setOpen] = useState(false);
|
|
42
|
+
const entries = useRead(() => readSession(context, session.sessionUid), `${session.sessionUid}:${context.token}`, open);
|
|
43
|
+
return (_jsxs(Box, { as: "details", onToggle: (event) => setOpen(event.currentTarget.open), sx: { borderTop: '1px solid', borderColor: 'border.muted', py: 2 }, children: [_jsxs(Box, { as: "summary", sx: { cursor: 'pointer', fontSize: 1 }, children: [session.createdAt ? (_jsx(RelativeTime, { datetime: session.createdAt })) : ('A session'), session.version ? ` · version ${session.version}` : '', entries.value ? ` · ${sessionSentence(entries.value)}` : ''] }), !open ? null : entries.loading ? (_jsx(Spinner, { size: "small" })) : entries.error ? (_jsx(Text, { role: "alert", sx: { fontSize: 1 }, children: entries.error })) : (_jsx(Box, { as: "ol", sx: { pl: 3, mt: 2, mb: 0, fontSize: 1 }, children: (entries.value ?? []).map(entry => (_jsxs(Box, { as: "li", sx: { mb: 1 }, children: [_jsx(Label, { sx: { mr: 2 }, children: RECORD_KIND_WORDS[entry.kind] ?? entry.kind }), entry.summary] }, entry.uid))) }))] }));
|
|
44
|
+
}
|
|
45
|
+
export function AppActivity({ appUid }) {
|
|
46
|
+
const token = useIAMStore(state => state.token) ?? '';
|
|
47
|
+
const aiAgentsUrl = useCoreStore((state) => state.configuration)?.aiAgentsUrl ?? '';
|
|
48
|
+
// Read again as each turn ends: what it just did is in the record then.
|
|
49
|
+
const turnEntries = useContributions(LoopChatTurn);
|
|
50
|
+
const turn = useSignalValue(turnEntries[0]?.value.turn ?? NO_TURN);
|
|
51
|
+
const settled = turn.status !== 'thinking' && turn.status !== 'streaming';
|
|
52
|
+
const [ended, setEnded] = useState(0);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (settled) {
|
|
55
|
+
setEnded(turn.id);
|
|
56
|
+
}
|
|
57
|
+
}, [settled, turn.id]);
|
|
58
|
+
const context = { aiAgentsUrl, token };
|
|
59
|
+
const readable = Boolean(appUid && token);
|
|
60
|
+
const sessions = useRead(() => listSessions(context, appUid ?? ''), `${appUid}:${token}:${aiAgentsUrl}:${ended}`, readable);
|
|
61
|
+
return (_jsxs(Box, { "data-testid": "app-activity", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: APP_ACTIVITY_WORDS.title }), !appUid ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_ACTIVITY_WORDS.unsaved })) : !token ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_ACTIVITY_WORDS.signedOut })) : sessions.loading && !sessions.value ? (_jsx(Spinner, { size: "small" })) : sessions.error ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: sessions.error })) : (sessions.value ?? []).length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_ACTIVITY_WORDS.none })) : ((sessions.value ?? []).map(session => (_jsx(Session, { context: context, session: session }, session.uid))))] }));
|
|
62
|
+
}
|
|
63
|
+
export default AppActivity;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReactorPlugin } from '@datalayer/reactor';
|
|
2
|
+
import type { AppSpec } from '../../../types/agentspecs';
|
|
3
|
+
export declare const APP_ACTIVITY_PLUGIN_NAME = "@datalayer/loop-plugin-app-activity";
|
|
4
|
+
/**
|
|
5
|
+
* The plugin that lists what an application did: its record is kept under
|
|
6
|
+
* its `app` item (`appUid`); without one — an application not saved on
|
|
7
|
+
* Datalayer — the feed says it is recorded once it is.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defineAppActivityPlugin(app: AppSpec, appUid?: string): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
10
|
+
export { AppActivity, APP_ACTIVITY_WORDS } from './AppActivity';
|
|
11
|
+
export default defineAppActivityPlugin;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { definePlugin } from '@datalayer/reactor';
|
|
3
|
+
import { LoopSlots } from '../../core';
|
|
4
|
+
import { AppActivity } from './AppActivity';
|
|
5
|
+
export const APP_ACTIVITY_PLUGIN_NAME = '@datalayer/loop-plugin-app-activity';
|
|
6
|
+
/**
|
|
7
|
+
* The plugin that lists what an application did: its record is kept under
|
|
8
|
+
* its `app` item (`appUid`); without one — an application not saved on
|
|
9
|
+
* Datalayer — the feed says it is recorded once it is.
|
|
10
|
+
*/
|
|
11
|
+
export function defineAppActivityPlugin(app, appUid) {
|
|
12
|
+
// One component per plugin: the slot keeps it, and the application with it.
|
|
13
|
+
function Feed() {
|
|
14
|
+
return _jsx(AppActivity, { appUid: appUid });
|
|
15
|
+
}
|
|
16
|
+
const plugin = definePlugin({
|
|
17
|
+
name: `${APP_ACTIVITY_PLUGIN_NAME}-${app.id}`,
|
|
18
|
+
displayName: `${app.name}: activity`,
|
|
19
|
+
description: `What ${app.name} did, session by session, from its record.`,
|
|
20
|
+
octicon: 'pulse',
|
|
21
|
+
build: () => ({
|
|
22
|
+
components: [
|
|
23
|
+
{
|
|
24
|
+
id: 'app-activity',
|
|
25
|
+
slot: LoopSlots.sidebar,
|
|
26
|
+
order: 20,
|
|
27
|
+
Component: Feed,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
return plugin;
|
|
33
|
+
}
|
|
34
|
+
export { AppActivity, APP_ACTIVITY_WORDS } from './AppActivity';
|
|
35
|
+
export default defineAppActivityPlugin;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An application's rules, and the approvals it waits on, beside its page
|
|
3
|
+
* (LOOP R-01b, U-19).
|
|
4
|
+
*
|
|
5
|
+
* The rules in words — each action, what it covers, what it does about it —
|
|
6
|
+
* and, under them, what is waiting for the person's yes: the approvals its
|
|
7
|
+
* agent asked for, as the existing tool-approvals path carries them (the
|
|
8
|
+
* ai-agents `/ws` stream, the Tool Approvals page's), answered here with
|
|
9
|
+
* *Approve* or *Reject* over the same socket.
|
|
10
|
+
*
|
|
11
|
+
* @module loop/plugins/app-rules/AppRulesCard
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX } from 'react';
|
|
14
|
+
import type { AppRuleSpec, AppSpec } from '../../../types/agentspecs';
|
|
15
|
+
import { type ApprovalRecord } from '../../../hooks/useToolApprovals';
|
|
16
|
+
/** What the card says. */
|
|
17
|
+
export declare const APP_RULES_WORDS: {
|
|
18
|
+
readonly title: "Rules and approvals";
|
|
19
|
+
readonly byDefault: "With no rule of its own: reading is done, anything else waits for you.";
|
|
20
|
+
readonly none: "It has no rule of its own.";
|
|
21
|
+
readonly waiting: "Waiting for you";
|
|
22
|
+
readonly nothingWaiting: "Nothing is waiting for your yes.";
|
|
23
|
+
readonly signedOut: "Its approvals reach you here once you are signed in to Datalayer.";
|
|
24
|
+
readonly offline: "Approvals are not connected yet: what you approve or reject is not sent until they are.";
|
|
25
|
+
readonly approve: "Approve";
|
|
26
|
+
readonly reject: "Reject";
|
|
27
|
+
};
|
|
28
|
+
/** One rule, in words: its action, what it covers, and what it does about it. */
|
|
29
|
+
export declare function ruleInWords(rule: AppRuleSpec): {
|
|
30
|
+
action: string;
|
|
31
|
+
covers: string;
|
|
32
|
+
says: string;
|
|
33
|
+
means: string;
|
|
34
|
+
};
|
|
35
|
+
/** The rule an approval was asked under, as the runtime wrote it in its arguments. */
|
|
36
|
+
export declare const ruleOfApproval: (approval: ApprovalRecord) => string;
|
|
37
|
+
export declare function AppRulesCard({ app }: {
|
|
38
|
+
app: AppSpec;
|
|
39
|
+
}): JSX.Element;
|
|
40
|
+
export default AppRulesCard;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Button, Heading, Label, Text } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
6
|
+
import { BEHAVIOUR_WORDS, coverOf } from '../../apps/rules';
|
|
7
|
+
import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
|
|
8
|
+
/** What the card says. */
|
|
9
|
+
export const APP_RULES_WORDS = {
|
|
10
|
+
title: 'Rules and approvals',
|
|
11
|
+
byDefault: 'With no rule of its own: reading is done, anything else waits for you.',
|
|
12
|
+
none: 'It has no rule of its own.',
|
|
13
|
+
waiting: 'Waiting for you',
|
|
14
|
+
nothingWaiting: 'Nothing is waiting for your yes.',
|
|
15
|
+
signedOut: 'Its approvals reach you here once you are signed in to Datalayer.',
|
|
16
|
+
offline: 'Approvals are not connected yet: what you approve or reject is not sent until they are.',
|
|
17
|
+
approve: 'Approve',
|
|
18
|
+
reject: 'Reject',
|
|
19
|
+
};
|
|
20
|
+
/** One rule, in words: its action, what it covers, and what it does about it. */
|
|
21
|
+
export function ruleInWords(rule) {
|
|
22
|
+
const words = BEHAVIOUR_WORDS[rule.behaviour];
|
|
23
|
+
return {
|
|
24
|
+
action: rule.action,
|
|
25
|
+
covers: rule.appliesTo.map(coverOf).join(', '),
|
|
26
|
+
says: words.says,
|
|
27
|
+
means: words.means,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The rule an approval was asked under, as the runtime wrote it in its arguments. */
|
|
31
|
+
export const ruleOfApproval = (approval) => typeof approval.tool_args?._rule === 'string' ? approval.tool_args._rule : '';
|
|
32
|
+
function Approvals({ app }) {
|
|
33
|
+
// Its agent is created under the application's id: what it asks is under it.
|
|
34
|
+
const filters = useMemo(() => ({ agentId: app.id, status: 'pending' }), [app.id]);
|
|
35
|
+
const query = useToolApprovalsQuery(filters);
|
|
36
|
+
const approve = useApproveToolRequest();
|
|
37
|
+
const reject = useRejectToolRequest();
|
|
38
|
+
const waiting = query.data?.approvals ?? [];
|
|
39
|
+
return (_jsxs(Box, { "data-testid": "app-approvals", children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 1 }, children: APP_RULES_WORDS.waiting }), approve.connectionState !== 'connected' ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0, mb: 1 }, children: APP_RULES_WORDS.offline })) : null, waiting.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_RULES_WORDS.nothingWaiting })) : (_jsx(Box, { as: "ul", sx: { listStyle: 'none', p: 0, m: 0 }, children: waiting.map(approval => (_jsxs(Box, { as: "li", sx: {
|
|
40
|
+
py: 2,
|
|
41
|
+
borderTop: '1px solid',
|
|
42
|
+
borderColor: 'border.muted',
|
|
43
|
+
fontSize: 1,
|
|
44
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: approval.tool_name }), ruleOfApproval(approval) ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: ruleOfApproval(approval) })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 1 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approve.isPending, onClick: () => approve.mutate({ id: approval.id }), children: APP_RULES_WORDS.approve }), _jsx(Button, { size: "small", disabled: reject.isPending, onClick: () => reject.mutate({ id: approval.id }), children: APP_RULES_WORDS.reject })] })] }, approval.id))) }))] }));
|
|
45
|
+
}
|
|
46
|
+
export function AppRulesCard({ app }) {
|
|
47
|
+
const signedIn = Boolean(useIAMStore(state => state.token));
|
|
48
|
+
return (_jsxs(Box, { "data-testid": "app-rules", sx: { mb: 4 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: APP_RULES_WORDS.title }), app.rules.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_RULES_WORDS.none })) : (_jsx(Box, { as: "ul", sx: { listStyle: 'none', p: 0, m: 0 }, children: app.rules.map(ruleInWords).map((rule, index) => (_jsxs(Box, { as: "li", sx: { py: 1, fontSize: 1 }, children: [_jsx(Text, { children: rule.action }), ' ', _jsx(Label, { title: rule.means, children: rule.says }), _jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: rule.covers })] }, index))) })), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1, mb: 0 }, children: APP_RULES_WORDS.byDefault }), signedIn ? (_jsx(Approvals, { app: app })) : (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 3, mb: 0 }, children: APP_RULES_WORDS.signedOut }))] }));
|
|
49
|
+
}
|
|
50
|
+
export default AppRulesCard;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactorPlugin } from '@datalayer/reactor';
|
|
2
|
+
import type { AppSpec } from '../../../types/agentspecs';
|
|
3
|
+
export declare const APP_RULES_PLUGIN_NAME = "@datalayer/loop-plugin-app-rules";
|
|
4
|
+
/** The plugin that lists an application's rules and the approvals waiting for the person. */
|
|
5
|
+
export declare function defineAppRulesPlugin(app: AppSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
6
|
+
export { AppRulesCard, APP_RULES_WORDS, ruleInWords, ruleOfApproval, } from './AppRulesCard';
|
|
7
|
+
export default defineAppRulesPlugin;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { definePlugin } from '@datalayer/reactor';
|
|
3
|
+
import { LoopSlots } from '../../core';
|
|
4
|
+
import { AppRulesCard } from './AppRulesCard';
|
|
5
|
+
export const APP_RULES_PLUGIN_NAME = '@datalayer/loop-plugin-app-rules';
|
|
6
|
+
/** The plugin that lists an application's rules and the approvals waiting for the person. */
|
|
7
|
+
export function defineAppRulesPlugin(app) {
|
|
8
|
+
// One component per plugin: the slot keeps it, and the application with it.
|
|
9
|
+
function Card() {
|
|
10
|
+
return _jsx(AppRulesCard, { app: app });
|
|
11
|
+
}
|
|
12
|
+
const plugin = definePlugin({
|
|
13
|
+
name: `${APP_RULES_PLUGIN_NAME}-${app.id}`,
|
|
14
|
+
displayName: `${app.name}: rules and approvals`,
|
|
15
|
+
description: `When ${app.name} acts alone, when it asks, and what waits for your yes.`,
|
|
16
|
+
octicon: 'shield-check',
|
|
17
|
+
build: () => ({
|
|
18
|
+
components: [
|
|
19
|
+
{
|
|
20
|
+
id: 'app-rules',
|
|
21
|
+
slot: LoopSlots.sidebar,
|
|
22
|
+
order: 10,
|
|
23
|
+
Component: Card,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
return plugin;
|
|
29
|
+
}
|
|
30
|
+
export { AppRulesCard, APP_RULES_WORDS, ruleInWords, ruleOfApproval, } from './AppRulesCard';
|
|
31
|
+
export default defineAppRulesPlugin;
|
|
@@ -43,8 +43,9 @@ import { messageText, turnWritersOf } from './turnState';
|
|
|
43
43
|
import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
|
|
44
44
|
import { useAgentCommandTools } from '../../../tools/adapters/commands';
|
|
45
45
|
import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
|
|
46
|
-
import { PresenceFace, PresenceLine } from '../../../chat/presence/Presence';
|
|
46
|
+
import { FaceDrawing, PresenceFace, PresenceLine, } from '../../../chat/presence/Presence';
|
|
47
47
|
import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
|
|
48
|
+
import { latestSaying, newestIsAnswer, } from '../../../chat/assistant/state';
|
|
48
49
|
/** No editor: the conversation on its own. */
|
|
49
50
|
const NO_SURFACE = '';
|
|
50
51
|
/**
|
|
@@ -203,6 +204,14 @@ export default function ChatView({ workspace }) {
|
|
|
203
204
|
useEffect(() => {
|
|
204
205
|
onPresence?.(presenceNow);
|
|
205
206
|
}, [onPresence, presenceNow]);
|
|
207
|
+
// What it last said, for a host that says it outside the conversation —
|
|
208
|
+
// the embed's floating chrome (LOOP R-01): its balloon and its blink.
|
|
209
|
+
// Through a ref: the handler below is made once.
|
|
210
|
+
const onSayingRef = useRef(config?.presence?.onSaying);
|
|
211
|
+
onSayingRef.current = config?.presence?.onSaying;
|
|
212
|
+
const saidRef = useRef({
|
|
213
|
+
answering: false,
|
|
214
|
+
});
|
|
206
215
|
const handleSendReady = useCallback((controls) => {
|
|
207
216
|
controlsRef.current = controls;
|
|
208
217
|
setSendReady(!!controls);
|
|
@@ -221,6 +230,17 @@ export default function ChatView({ workspace }) {
|
|
|
221
230
|
setPresenceTool(previous => previous.open === open && previous.pendingApproval === pendingApproval
|
|
222
231
|
? previous
|
|
223
232
|
: { open, pendingApproval });
|
|
233
|
+
const said = {
|
|
234
|
+
saying: latestSaying(items),
|
|
235
|
+
answering: newestIsAnswer(items),
|
|
236
|
+
};
|
|
237
|
+
const before = saidRef.current;
|
|
238
|
+
if (before.saying?.id !== said.saying?.id ||
|
|
239
|
+
before.saying?.text !== said.saying?.text ||
|
|
240
|
+
before.answering !== said.answering) {
|
|
241
|
+
saidRef.current = said;
|
|
242
|
+
onSayingRef.current?.(said);
|
|
243
|
+
}
|
|
224
244
|
const feed = turnFeedRef.current;
|
|
225
245
|
if (!feed) {
|
|
226
246
|
return;
|
|
@@ -1100,6 +1120,12 @@ export default function ChatView({ workspace }) {
|
|
|
1100
1120
|
// a new identity would resend the last suggestion.
|
|
1101
1121
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1102
1122
|
}, [suggestion]);
|
|
1123
|
+
/*
|
|
1124
|
+
* An application's session checks who is calling (LOOP R-32, R-04): its
|
|
1125
|
+
* chat says so with the person's token. The embed hands its own.
|
|
1126
|
+
*/
|
|
1127
|
+
const runsApp = Boolean(blueprintTurn?.createPayload?.app_spec);
|
|
1128
|
+
const iamToken = useIAMStore(state => state.token);
|
|
1103
1129
|
const protocol = useMemo(() => inPage
|
|
1104
1130
|
? browserProtocolConfig({
|
|
1105
1131
|
agentId: member?.specId ?? agentId,
|
|
@@ -1132,9 +1158,10 @@ export default function ChatView({ workspace }) {
|
|
|
1132
1158
|
// (LOOP R-04): each thread a session, recorded under its uid,
|
|
1133
1159
|
// run in the name the application runs in, and taking what the
|
|
1134
1160
|
// page did besides the message — the same AG-UI events back.
|
|
1135
|
-
endpoint:
|
|
1161
|
+
endpoint: runsApp
|
|
1136
1162
|
? `${agentServerUrl}/api/v1/apps/agents/${encodeURIComponent(agentId)}/ag-ui/`
|
|
1137
1163
|
: `${agentServerUrl}/api/v1/ag-ui/${agentId}/`,
|
|
1164
|
+
...(runsApp && iamToken ? { authToken: iamToken } : {}),
|
|
1138
1165
|
agentId,
|
|
1139
1166
|
// `/api/v1/configure`, not `/api/v1/configure/config`: the hooks
|
|
1140
1167
|
// strip one trailing `config`/`configure` segment to find the API
|
|
@@ -1152,6 +1179,8 @@ export default function ChatView({ workspace }) {
|
|
|
1152
1179
|
blueprintTurn,
|
|
1153
1180
|
activeModel,
|
|
1154
1181
|
agentServerUrl,
|
|
1182
|
+
runsApp,
|
|
1183
|
+
iamToken,
|
|
1155
1184
|
]);
|
|
1156
1185
|
/*
|
|
1157
1186
|
* The model catalogue, and the builtin tools and skills the agent reports.
|
|
@@ -1475,7 +1504,7 @@ export default function ChatView({ workspace }) {
|
|
|
1475
1504
|
// The theme it wears, when the host names one (an application's).
|
|
1476
1505
|
, {
|
|
1477
1506
|
// The theme it wears, when the host names one (an application's).
|
|
1478
|
-
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides,
|
|
1507
|
+
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode,
|
|
1479
1508
|
// The header says why, beside the title, for the same reason the
|
|
1480
1509
|
// placeholder does: a dead control with no explanation is worse
|
|
1481
1510
|
// than an absent one.
|
|
@@ -1516,7 +1545,7 @@ export default function ChatView({ workspace }) {
|
|
|
1516
1545
|
headerContent: presence ? _jsx(PresenceLine, { state: presenceNow }) : undefined, emptyState: {
|
|
1517
1546
|
// An application's own face, at the page's size of the three
|
|
1518
1547
|
// (LOOP T-08, T-19): the thing a person's eye lands on first.
|
|
1519
|
-
icon: presence?.face ? (_jsx("span", { "aria-hidden": true, style: { fontSize: FACE_LARGE, lineHeight: 1 }, children: presence.face })) : (_jsx(BrandIcon, { size: 48 })),
|
|
1548
|
+
icon: presence?.face ? (_jsx("span", { "aria-hidden": true, style: { fontSize: FACE_LARGE, lineHeight: 1 }, children: _jsx(FaceDrawing, { face: presence.face, size: FACE_LARGE }) })) : (_jsx(BrandIcon, { size: 48 })),
|
|
1520
1549
|
// `ChatEmptyState` reads its heading from here and nowhere
|
|
1521
1550
|
// else — the `title` above reaches the header only — so
|
|
1522
1551
|
// without this the agent introduced itself as "Start a
|