@datalayer/agent-runtimes 1.3.83 → 1.3.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chat/ChatFloating.d.ts +5 -4
- package/lib/chat/ChatFloating.js +8 -6
- package/lib/chat/ChatLanguage.d.ts +17 -0
- package/lib/chat/ChatLanguage.js +29 -0
- package/lib/chat/assistant/AssistantStage.d.ts +4 -4
- package/lib/chat/assistant/AssistantStage.js +4 -2
- package/lib/chat/assistant/BalloonParts.d.ts +2 -1
- package/lib/chat/assistant/BalloonParts.js +12 -5
- package/lib/chat/assistant/BalloonVisual.js +3 -1
- package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
- package/lib/chat/assistant/ConversationBalloon.js +3 -2
- package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
- package/lib/chat/assistant/SpeechBalloon.js +10 -7
- package/lib/chat/assistant/toolLine.d.ts +8 -4
- package/lib/chat/assistant/toolLine.js +13 -9
- package/lib/chat/base/ChatBase.js +30 -11
- package/lib/chat/base/loopElement.d.ts +53 -0
- package/lib/chat/base/loopElement.js +78 -0
- package/lib/chat/base/loopWindow.d.ts +31 -0
- package/lib/chat/base/loopWindow.js +74 -0
- package/lib/chat/display/EmptyState.js +3 -1
- package/lib/chat/index.d.ts +2 -0
- package/lib/chat/index.js +3 -0
- package/lib/chat/messages/ChatMessages.js +3 -1
- package/lib/chat/messages/TurnFooter.js +3 -1
- package/lib/chat/prompt/InputPrompt.js +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +3 -1
- package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
- package/lib/chat/tools/ToolApprovalBanner.js +5 -3
- package/lib/chat/tools/ToolApprovalDialog.js +5 -3
- package/lib/chat/tools/ToolCallDisplay.js +29 -25
- package/lib/chat/words.d.ts +102 -0
- package/lib/chat/words.js +460 -0
- package/lib/components/a2ui/custom/index.d.ts +39 -0
- package/lib/components/a2ui/custom/index.js +95 -0
- package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
- package/lib/components/a2ui/custom/sandbox.js +188 -0
- package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
- package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
- package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
- package/lib/components/a2ui/datalayer/Form.js +9 -2
- package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
- package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
- package/lib/components/a2ui/datalayer/index.d.ts +1 -1
- package/lib/components/a2ui/datalayer/index.js +1 -1
- package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
- package/lib/components/a2ui/datalayer/schema.js +12 -1
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +12 -1
- package/lib/components/a2ui/visibleWhen.js +24 -3
- package/lib/components/inspector/AgentInspector.d.ts +25 -44
- package/lib/components/inspector/AgentInspector.js +27 -215
- package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
- package/lib/components/inspector/AgentInspectorDialog.js +2 -2
- package/lib/components/inspector/a2aSpans.d.ts +51 -0
- package/lib/components/inspector/a2aSpans.js +373 -0
- package/lib/components/inspector/agentSpans.d.ts +120 -0
- package/lib/components/inspector/agentSpans.js +253 -0
- package/lib/components/inspector/chatSpans.d.ts +36 -0
- package/lib/components/inspector/chatSpans.js +50 -0
- package/lib/components/inspector/classify.d.ts +11 -8
- package/lib/components/inspector/classify.js +23 -24
- package/lib/components/inspector/index.d.ts +7 -7
- package/lib/components/inspector/index.js +7 -7
- package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
- package/lib/components/teams/SceneTranscript.d.ts +29 -0
- package/lib/components/teams/SceneTranscript.js +54 -0
- package/lib/components/teams/SceneView.d.ts +41 -0
- package/lib/components/teams/SceneView.js +28 -0
- package/lib/components/teams/index.d.ts +3 -0
- package/lib/components/teams/index.js +3 -0
- package/lib/components/teams/sceneTranscript.d.ts +74 -0
- package/lib/components/teams/sceneTranscript.js +294 -0
- package/lib/components/teams/teamConnections.d.ts +2 -2
- package/lib/components/teams/teamConnections.js +3 -3
- package/lib/components/teams/useA2ATeam.d.ts +4 -4
- package/lib/components/teams/useA2ATeam.js +50 -46
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/loop/apps/AppComposer.d.ts +40 -4
- package/lib/loop/apps/AppComposer.js +194 -19
- package/lib/loop/apps/AppRenderer.d.ts +21 -3
- package/lib/loop/apps/AppRenderer.js +88 -48
- package/lib/loop/apps/appspec.d.ts +13 -1
- package/lib/loop/apps/appspec.js +305 -12
- package/lib/loop/apps/checks.d.ts +14 -0
- package/lib/loop/apps/checks.js +276 -5
- package/lib/loop/apps/composer.d.ts +21 -10
- package/lib/loop/apps/composer.js +41 -8
- package/lib/loop/apps/customComponents.d.ts +41 -0
- package/lib/loop/apps/customComponents.js +260 -0
- package/lib/loop/apps/hostTools.d.ts +8 -1
- package/lib/loop/apps/hostTools.js +9 -0
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/apps/language.d.ts +68 -0
- package/lib/loop/apps/language.js +275 -0
- package/lib/loop/apps/settingsInputs.d.ts +29 -0
- package/lib/loop/apps/settingsInputs.js +147 -0
- package/lib/loop/apps/uploads.d.ts +43 -0
- package/lib/loop/apps/uploads.js +66 -0
- package/lib/loop/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +15 -1
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +25 -3
- package/lib/loop/embed/AppEmbed.js +106 -18
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +84 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/embedSession.d.ts +14 -0
- package/lib/loop/embed/embedSession.js +34 -0
- package/lib/loop/embed/hostBridge.d.ts +50 -3
- package/lib/loop/embed/hostBridge.js +22 -2
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
- package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
- package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
- package/lib/loop/plugins/app-components/index.d.ts +26 -0
- package/lib/loop/plugins/app-components/index.js +56 -0
- package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
- package/lib/loop/plugins/app-elements/AppElements.js +79 -0
- package/lib/loop/plugins/app-elements/index.d.ts +20 -0
- package/lib/loop/plugins/app-elements/index.js +43 -0
- package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
- package/lib/loop/plugins/app-page/AppPage.js +106 -8
- package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
- package/lib/loop/plugins/app-page/appPageModel.js +122 -4
- package/lib/loop/plugins/app-page/index.d.ts +1 -0
- package/lib/loop/plugins/app-page/index.js +1 -0
- package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
- package/lib/loop/plugins/app-page/pageRun.js +111 -0
- package/lib/loop/plugins/assistant/index.js +8 -6
- package/lib/loop/plugins/chat/ChatView.js +16 -6
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/chat/index.js +2 -1
- package/lib/models/PageTag.d.ts +1 -1
- package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
- package/lib/protocols/BrowserAgentAdapter.js +13 -10
- package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/runtimes/browser/protocol.d.ts +1 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1518 -216
- package/lib/specs/appspecSchema.js +630 -3
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/specs/uiPlugins.js +6 -1
- package/lib/types/agentspecs.d.ts +200 -1
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/teams.d.ts +13 -2
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +20 -2
- package/scripts/codegen/generate_teams.py +15 -2
- package/lib/components/inspector/JsonTree.d.ts +0 -24
- package/lib/components/inspector/JsonTree.js +0 -89
- package/lib/components/inspector/a2aInspect.d.ts +0 -56
- package/lib/components/inspector/a2aInspect.js +0 -478
- package/lib/components/inspector/agentInspector.d.ts +0 -179
- package/lib/components/inspector/agentInspector.js +0 -267
- package/lib/components/inspector/chatInspect.d.ts +0 -38
- package/lib/components/inspector/chatInspect.js +0 -65
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
* list_invoices…" while Accounting calls Odoo. Then the report appears. The
|
|
20
20
|
* page's own state is `useA2ATeam`'s, which the landing's home page runs too.
|
|
21
21
|
*
|
|
22
|
-
* Under the graph, each member has its own Agent Inspector
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
22
|
+
* Under the graph, each member has its own Agent Inspector, core's OTEL
|
|
23
|
+
* view of the page's OpenTelemetry spans: Sales' — its model turns
|
|
24
|
+
* (`invoke_agent`), its `ask_accounting` call (`execute_tool`), the A2A
|
|
25
|
+
* requests it sends and what came back, event by event — and Accounting's —
|
|
26
|
+
* the A2A requests sent to it (`peer.service`), its Odoo MCP calls,
|
|
27
|
+
* `write_notebook`, as the A2A stream tells them. Both read one tracer
|
|
28
|
+
* (`useAgentInspector`), fed by the team (`useA2ATeam`'s `inspector`) and by
|
|
29
|
+
* the fetch the A2A client sends with (`traceA2AFetch`); an A2A request
|
|
30
|
+
* shows in both. Right-click a member for its menu: *Inspect the agent…*
|
|
31
|
+
* opens its inspector over the page.
|
|
31
32
|
*
|
|
32
33
|
* Sales' suggestions — its Appspec's starters — are chips in its balloon
|
|
33
34
|
* while it waits for a question: one clicked is asked, as if typed.
|
|
@@ -10,9 +10,9 @@ import { useExampleAgentRuntimesUrl } from './utils/useExampleAgentRuntimesUrl';
|
|
|
10
10
|
import { useBrowserInference } from '../hooks/useBrowserInference';
|
|
11
11
|
import { readJwtClaims, tokenLifetimeMs, useAnonymousSessionStore, } from '../runtimes/browser/anonymousToken';
|
|
12
12
|
import { connectA2APeer } from '../runtimes/browser/a2aPeer';
|
|
13
|
-
import { A2ATeamGraph, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
|
|
13
|
+
import { A2ATeamGraph, SceneView, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
|
|
14
14
|
import { ACCOUNTING_APP_0_0_1, SALES_APP_0_0_1 } from '../specs/apps';
|
|
15
|
-
import { AgentInspector, classifyToolCall,
|
|
15
|
+
import { AgentInspector, classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector';
|
|
16
16
|
import { SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1 } from '../specs/teams/teams';
|
|
17
17
|
const TEAM = SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1;
|
|
18
18
|
const SALES = SALES_APP_0_0_1;
|
|
@@ -46,8 +46,18 @@ function AgentA2ATeam() {
|
|
|
46
46
|
}, [accountingKey]);
|
|
47
47
|
const [accountingKeyEnded, setAccountingKeyEnded] = useState(() => accountingKeyClock !== undefined &&
|
|
48
48
|
accountingKeyClock.expiresAt <= Date.now());
|
|
49
|
-
// One
|
|
50
|
-
const {
|
|
49
|
+
// One tracer of what both members do, each inspector showing its own.
|
|
50
|
+
const { tracer: inspector } = useAgentInspector();
|
|
51
|
+
// The members as the transcript names them: the spans' services, and the
|
|
52
|
+
// connections a tool call is of (Accounting → Odoo).
|
|
53
|
+
const sceneMembers = useMemo(() => [
|
|
54
|
+
{ id: SALES.id, name: SALES.name },
|
|
55
|
+
{
|
|
56
|
+
id: ACCOUNTING.id,
|
|
57
|
+
name: ACCOUNTING.name,
|
|
58
|
+
connections: ACCOUNTING_CONNECTIONS,
|
|
59
|
+
},
|
|
60
|
+
], []);
|
|
51
61
|
const [peer, setPeer] = useState(null);
|
|
52
62
|
const [peerError, setPeerError] = useState(null);
|
|
53
63
|
const [draft, setDraft] = useState('');
|
|
@@ -61,9 +71,9 @@ function AgentA2ATeam() {
|
|
|
61
71
|
connectA2APeer({
|
|
62
72
|
url: accountingUrl,
|
|
63
73
|
key: accountingKey,
|
|
64
|
-
// The A2A traffic,
|
|
65
|
-
fetch:
|
|
66
|
-
|
|
74
|
+
// The A2A traffic, as spans: Sales sends, Accounting answers.
|
|
75
|
+
fetch: traceA2AFetch(globalThis.fetch.bind(globalThis), {
|
|
76
|
+
tracer: inspector,
|
|
67
77
|
asker: SALES.name,
|
|
68
78
|
peer: ACCOUNTING.name,
|
|
69
79
|
classifyTool: teamToolClassifier(ACCOUNTING_CONNECTIONS, classifyToolCall),
|
|
@@ -116,66 +126,66 @@ function AgentA2ATeam() {
|
|
|
116
126
|
px: 2,
|
|
117
127
|
pt: 2,
|
|
118
128
|
mb: 3,
|
|
119
|
-
}, children: _jsx(A2ATeamGraph, { entry: {
|
|
120
|
-
|
|
121
|
-
name: SALES.name,
|
|
122
|
-
emoji: SALES.emoji,
|
|
123
|
-
character: SALES.interface.assistant ?? 'paperclip',
|
|
124
|
-
where: salesMember.runsIn === 'browser'
|
|
125
|
-
? 'in your browser'
|
|
126
|
-
: 'on a runtime',
|
|
127
|
-
persona: team.entryPersona,
|
|
128
|
-
onToggle: () => composer.current?.focus(),
|
|
129
|
-
conversationLabel: `Ask ${SALES.name}`,
|
|
130
|
-
onAway: team.setEntryAway,
|
|
131
|
-
history: team.entryHistory,
|
|
132
|
-
expandTarget: notebookArea,
|
|
133
|
-
notebookTitle: `${ACCOUNTING.name}\u2019s notebook`,
|
|
134
|
-
suggestions: team.ready && !team.busy ? suggestions : undefined,
|
|
135
|
-
onSuggestion: suggestion => send(suggestion.prompt),
|
|
136
|
-
inspector,
|
|
137
|
-
onStop: team.busy ? team.stop : undefined,
|
|
138
|
-
about: {
|
|
129
|
+
}, children: _jsx(SceneView, { members: sceneMembers, tracer: inspector, emptyText: "Ask Sales: what it says to Accounting, what Accounting reads in Odoo and answers, line by line, shows here.", children: _jsx(A2ATeamGraph, { entry: {
|
|
130
|
+
id: SALES.id,
|
|
139
131
|
name: SALES.name,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
where:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
132
|
+
emoji: SALES.emoji,
|
|
133
|
+
character: SALES.interface.assistant ?? 'paperclip',
|
|
134
|
+
where: salesMember.runsIn === 'browser'
|
|
135
|
+
? 'in your browser'
|
|
136
|
+
: 'on a runtime',
|
|
137
|
+
persona: team.entryPersona,
|
|
138
|
+
onToggle: () => composer.current?.focus(),
|
|
139
|
+
conversationLabel: `Ask ${SALES.name}`,
|
|
140
|
+
onAway: team.setEntryAway,
|
|
141
|
+
history: team.entryHistory,
|
|
142
|
+
expandTarget: notebookArea,
|
|
143
|
+
notebookTitle: `${ACCOUNTING.name}\u2019s notebook`,
|
|
144
|
+
suggestions: team.ready && !team.busy ? suggestions : undefined,
|
|
145
|
+
onSuggestion: suggestion => send(suggestion.prompt),
|
|
146
|
+
inspector,
|
|
147
|
+
onStop: team.busy ? team.stop : undefined,
|
|
148
|
+
about: {
|
|
149
|
+
name: SALES.name,
|
|
150
|
+
spec: `${SALES.id}:${SALES.version}`,
|
|
151
|
+
model: SALES.model || undefined,
|
|
152
|
+
where: 'in your browser',
|
|
153
|
+
description: SALES.description || undefined,
|
|
154
|
+
},
|
|
155
|
+
}, peer: {
|
|
156
|
+
id: ACCOUNTING.id,
|
|
159
157
|
name: ACCOUNTING.name,
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
// Its face as its card says it, once it is reached.
|
|
159
|
+
emoji: peer?.face?.emoji ?? ACCOUNTING.emoji,
|
|
160
|
+
character: ACCOUNTING.interface.assistant ?? 'wizard',
|
|
162
161
|
where: accountingWhere,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
persona: team.peerPersona,
|
|
163
|
+
onAway: team.setPeerAway,
|
|
164
|
+
history: team.peerHistory,
|
|
165
|
+
connections: ACCOUNTING_CONNECTIONS,
|
|
166
|
+
inspector,
|
|
167
|
+
sandbox: accountingSandbox,
|
|
168
|
+
about: {
|
|
169
|
+
name: ACCOUNTING.name,
|
|
170
|
+
spec: `${ACCOUNTING.id}:${ACCOUNTING.version}`,
|
|
171
|
+
model: ACCOUNTING.model || undefined,
|
|
172
|
+
where: accountingWhere,
|
|
173
|
+
// What its A2A card says, once it is reached.
|
|
174
|
+
description: peer?.card.description || ACCOUNTING.description || undefined,
|
|
175
|
+
skills: peer?.card.skills.map(skill => skill.name),
|
|
176
|
+
protocol: peer ? 'a2a' : undefined,
|
|
177
|
+
url: peer ? accountingUrl : undefined,
|
|
178
|
+
},
|
|
179
|
+
}, flow: team.flow, calls: team.calls,
|
|
180
|
+
// Room for the balloons: suggestions, *more* and a notebook.
|
|
181
|
+
balloonRoom: 320, connected: peer !== null, label: peer ? `A2A · ${peer.skill.name}` : 'A2A · not connected' }) }) }), _jsx(Box, { ref: notebookArea, sx: { mb: team.notebook ? 3 : 0 }, "data-team-notebook-placement": "under-graph" }), _jsx(Box, { sx: {
|
|
172
182
|
display: 'grid',
|
|
173
183
|
gridTemplateColumns: ['1fr', '1fr', '1fr 1fr'],
|
|
174
184
|
gap: 3,
|
|
175
185
|
mb: 3,
|
|
176
|
-
}, "data-team-inspectors": "", children: [SALES, ACCOUNTING].map(member => (_jsxs(Box, { sx: { minWidth: 0 }, "data-team-inspector": member.id, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: [member.emoji, " ", member.name, "\u2019s Agent Inspector"] }), _jsx(AgentInspector, {
|
|
177
|
-
? 'Its model turns, its call to Accounting and the A2A
|
|
178
|
-
: '
|
|
186
|
+
}, "data-team-inspectors": "", children: [SALES, ACCOUNTING].map(member => (_jsxs(Box, { sx: { minWidth: 0 }, "data-team-inspector": member.id, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: [member.emoji, " ", member.name, "\u2019s Agent Inspector"] }), _jsx(AgentInspector, { tracer: inspector, agent: member.name, compact: true, maxHeight: 360, exportName: `agent-inspector-${member.id}`, emptyText: member === SALES
|
|
187
|
+
? 'Its model turns, its call to Accounting and the A2A requests it sends, with what came back, show here.'
|
|
188
|
+
: 'The A2A requests sent to it, its Odoo MCP calls and what it sends back show here.' })] }, member.id))) }), _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsxs(Box, { sx: { flex: '1 1 420px', minWidth: 0 }, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: ["Ask ", SALES.name] }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mt: 0 }, children: ["Or choose one of ", SALES.name, "\u2019s suggestions in its balloon."] }), _jsx(Textarea, { ref: composer, block: true, rows: 3, value: draft, placeholder: "Ask for a financial report\u2026", onChange: event => setDraft(event.target.value), onKeyDown: event => {
|
|
179
189
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
180
190
|
event.preventDefault();
|
|
181
191
|
send(draft);
|
|
@@ -15,7 +15,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
* and so are the characters clippy.js publishes, fetched when picked.
|
|
16
16
|
*/
|
|
17
17
|
import { useEffect, useRef, useState } from 'react';
|
|
18
|
-
import { useAgentInspector } from '../components/inspector/
|
|
18
|
+
import { useAgentInspector } from '../components/inspector/agentSpans';
|
|
19
19
|
import { buildReactorFromPlugins } from '@datalayer/reactor';
|
|
20
20
|
import { Button, Heading, Text, ToggleSwitch } from '@primer/react';
|
|
21
21
|
import { Box } from '@datalayer/primer-addons';
|
|
@@ -96,7 +96,7 @@ const AssistantExample = () => {
|
|
|
96
96
|
// How the balloon shows the conversation (T-23): history, or current.
|
|
97
97
|
const [balloon, setBalloon] = useState('history');
|
|
98
98
|
// What the agent does, for the menu's *Inspect the agent…*.
|
|
99
|
-
const {
|
|
99
|
+
const { tracer: inspector } = useAgentInspector();
|
|
100
100
|
// Pixel, the test sprite, read once through the clippy.js reader.
|
|
101
101
|
const [pixel, setPixel] = useState();
|
|
102
102
|
useEffect(() => {
|
|
@@ -9,17 +9,53 @@
|
|
|
9
9
|
* one menu each; the option picked goes with every run as
|
|
10
10
|
* `forwardedProps.loop.modes` (`LoopRunProps`), and the runtime tells the
|
|
11
11
|
* agent its instructions, on the model it names, for that run.
|
|
12
|
+
* - What a person may **send** without being asked (LOOP P-21,
|
|
13
|
+
* `interface.uploads`): a paper clip beside the composer takes images,
|
|
14
|
+
* files and recordings of the kinds and sizes the Appspec says — one it
|
|
15
|
+
* does not take is refused in a sentence and never read — and the files
|
|
16
|
+
* held go with the next message as `forwardedProps.loop.files`, then are
|
|
17
|
+
* let go. The runtime refuses again what was sent all the same.
|
|
18
|
+
* - Its **profiles** (LOOP P-20, `interface.profiles`): a picker beside the
|
|
19
|
+
* composer, on the first unless another is picked. The profile picked goes
|
|
20
|
+
* with every run as `forwardedProps.loop.profile`, and its starters (else
|
|
21
|
+
* the application's) are what the empty chat offers, by category
|
|
22
|
+
* (`LoopChatExtras.openers`). Once the first message is sent, the
|
|
23
|
+
* conversation keeps its profile: the picker says so and picks no other.
|
|
24
|
+
*
|
|
25
|
+
* Given the application as the person reads it (`translatedAppspec`, LOOP
|
|
26
|
+
* P-26), its words are theirs; the composer's own, the chat's language's
|
|
27
|
+
* (`useChatLanguage`).
|
|
12
28
|
*
|
|
13
29
|
* @module loop/apps/AppComposer
|
|
14
30
|
*/
|
|
15
|
-
import type
|
|
16
|
-
import type { AppSpec } from '../../types/agentspecs';
|
|
31
|
+
import { type JSX } from 'react';
|
|
32
|
+
import type { AppSpec, AppStarterSpec } from '../../types/agentspecs';
|
|
33
|
+
import { type GivenFile } from '../../components/a2ui/datalayer/FileUpload';
|
|
34
|
+
import { type ChatSuggestionItem } from '../core';
|
|
35
|
+
/** What the composer holds to send: the files, and why the last were refused. */
|
|
36
|
+
export type HeldUploads = {
|
|
37
|
+
files: GivenFile[];
|
|
38
|
+
refused: string | null;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Files chosen in the composer, held for the next message: the refusal the
|
|
42
|
+
* runtime would say for what the application does not take, else the files
|
|
43
|
+
* read. Nothing is read when one is refused.
|
|
44
|
+
*/
|
|
45
|
+
export declare function holdUploads(app: Pick<AppSpec, 'name' | 'interface'>, held: readonly GivenFile[], chosen: readonly File[], read?: (file: File) => Promise<GivenFile>): Promise<HeldUploads>;
|
|
17
46
|
/** The plugin's name, before the application's id. */
|
|
18
47
|
export declare const APP_COMPOSER_PLUGIN_NAME = "@datalayer/loop-plugin-app-composer";
|
|
48
|
+
/** Starters as the empty chat offers them: each under its category (P-20). */
|
|
49
|
+
export declare function startersAsOpeners(starters: readonly AppStarterSpec[]): ChatSuggestionItem[];
|
|
50
|
+
/**
|
|
51
|
+
* Whether an application needs its composer plugin: commands, modes,
|
|
52
|
+
* uploads or profiles to offer there.
|
|
53
|
+
*/
|
|
54
|
+
export declare const needsAppComposer: (app: AppSpec) => boolean;
|
|
19
55
|
/**
|
|
20
56
|
* The plugin that puts an application's commands in the composer's `/` menu
|
|
21
|
-
* and its modes
|
|
22
|
-
*
|
|
57
|
+
* and its modes, its profiles and its paper clip beside the composer.
|
|
58
|
+
* Nothing for an application with none: its plugin contributes nothing.
|
|
23
59
|
*/
|
|
24
60
|
export declare function defineAppComposerPlugin(app: AppSpec): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, {
|
|
25
61
|
components: {
|
|
@@ -1,17 +1,118 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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's commands and modes in the composer (LOOP P-19).
|
|
8
|
+
*
|
|
9
|
+
* - Each of its **commands** is a workspace command (`LoopCommand`), listed
|
|
10
|
+
* in the composer's `/` menu (`composer`): picking it writes `/<name> `,
|
|
11
|
+
* and sending runs it — its prompt goes to the agent, the words typed
|
|
12
|
+
* after it in place of `{input}`.
|
|
13
|
+
* - Its **modes** are switches beside the composer (`LoopSlots.promptAction`),
|
|
14
|
+
* one menu each; the option picked goes with every run as
|
|
15
|
+
* `forwardedProps.loop.modes` (`LoopRunProps`), and the runtime tells the
|
|
16
|
+
* agent its instructions, on the model it names, for that run.
|
|
17
|
+
* - What a person may **send** without being asked (LOOP P-21,
|
|
18
|
+
* `interface.uploads`): a paper clip beside the composer takes images,
|
|
19
|
+
* files and recordings of the kinds and sizes the Appspec says — one it
|
|
20
|
+
* does not take is refused in a sentence and never read — and the files
|
|
21
|
+
* held go with the next message as `forwardedProps.loop.files`, then are
|
|
22
|
+
* let go. The runtime refuses again what was sent all the same.
|
|
23
|
+
* - Its **profiles** (LOOP P-20, `interface.profiles`): a picker beside the
|
|
24
|
+
* composer, on the first unless another is picked. The profile picked goes
|
|
25
|
+
* with every run as `forwardedProps.loop.profile`, and its starters (else
|
|
26
|
+
* the application's) are what the empty chat offers, by category
|
|
27
|
+
* (`LoopChatExtras.openers`). Once the first message is sent, the
|
|
28
|
+
* conversation keeps its profile: the picker says so and picks no other.
|
|
29
|
+
*
|
|
30
|
+
* Given the application as the person reads it (`translatedAppspec`, LOOP
|
|
31
|
+
* P-26), its words are theirs; the composer's own, the chat's language's
|
|
32
|
+
* (`useChatLanguage`).
|
|
33
|
+
*
|
|
34
|
+
* @module loop/apps/AppComposer
|
|
35
|
+
*/
|
|
36
|
+
import { useRef } from 'react';
|
|
37
|
+
import { ActionList, ActionMenu, IconButton, Text, Token } from '@primer/react';
|
|
38
|
+
import { PaperclipIcon } from '@primer/octicons-react';
|
|
39
|
+
import { computed, contribution, definePlugin, signal, } from '@datalayer/reactor';
|
|
4
40
|
import { useSignalValue } from '@datalayer/reactor/react';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
41
|
+
import { readFile, } from '../../components/a2ui/datalayer/FileUpload';
|
|
42
|
+
import { LoopChatExtras, LoopCommand, LoopRunProps, LoopSlots, } from '../core';
|
|
43
|
+
import { commandPrompt, modeChoice, modeDefaults, profileChoice, startersFor, } from './composer';
|
|
44
|
+
import { interfaceWords } from './language';
|
|
45
|
+
import { useChatLanguage } from '../../chat/ChatLanguage';
|
|
46
|
+
import { uploadsAccept, uploadsInWords, uploadsRefusal } from './uploads';
|
|
47
|
+
/**
|
|
48
|
+
* Files chosen in the composer, held for the next message: the refusal the
|
|
49
|
+
* runtime would say for what the application does not take, else the files
|
|
50
|
+
* read. Nothing is read when one is refused.
|
|
51
|
+
*/
|
|
52
|
+
export async function holdUploads(app, held, chosen, read = readFile) {
|
|
53
|
+
const refused = uploadsRefusal(app, [
|
|
54
|
+
...held,
|
|
55
|
+
...chosen.map(file => ({
|
|
56
|
+
name: file.name,
|
|
57
|
+
type: file.type,
|
|
58
|
+
size: file.size,
|
|
59
|
+
})),
|
|
60
|
+
]);
|
|
61
|
+
if (refused) {
|
|
62
|
+
return { files: [...held], refused };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
files: [...held, ...(await Promise.all(chosen.map(read)))],
|
|
66
|
+
refused: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
7
69
|
/** The plugin's name, before the application's id. */
|
|
8
70
|
export const APP_COMPOSER_PLUGIN_NAME = '@datalayer/loop-plugin-app-composer';
|
|
71
|
+
/** Starters as the empty chat offers them: each under its category (P-20). */
|
|
72
|
+
export function startersAsOpeners(starters) {
|
|
73
|
+
return starters.map(starter => ({
|
|
74
|
+
text: starter.label,
|
|
75
|
+
message: starter.message,
|
|
76
|
+
...(starter.category ? { group: starter.category } : {}),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether an application needs its composer plugin: commands, modes,
|
|
81
|
+
* uploads or profiles to offer there.
|
|
82
|
+
*/
|
|
83
|
+
export const needsAppComposer = (app) => (app.interface.commands ?? []).length > 0 ||
|
|
84
|
+
(app.interface.modes ?? []).length > 0 ||
|
|
85
|
+
(app.interface.profiles ?? []).length > 0 ||
|
|
86
|
+
Boolean(app.interface.uploads);
|
|
9
87
|
/**
|
|
10
88
|
* The plugin that puts an application's commands in the composer's `/` menu
|
|
11
|
-
* and its modes
|
|
12
|
-
*
|
|
89
|
+
* and its modes, its profiles and its paper clip beside the composer.
|
|
90
|
+
* Nothing for an application with none: its plugin contributes nothing.
|
|
13
91
|
*/
|
|
14
92
|
export function defineAppComposerPlugin(app) {
|
|
93
|
+
const profiles = app.interface.profiles ?? [];
|
|
94
|
+
/** The profile picked, and whether the conversation has started with it. */
|
|
95
|
+
const profile = signal(profileChoice(app)?.id);
|
|
96
|
+
const started = signal(false);
|
|
97
|
+
const openers = computed(() => ({
|
|
98
|
+
openers: startersAsOpeners(startersFor(app, profile.value)),
|
|
99
|
+
}));
|
|
100
|
+
function ProfilePicker() {
|
|
101
|
+
// In the chat's language, as the chat's own words (P-26).
|
|
102
|
+
const words = interfaceWords(useChatLanguage());
|
|
103
|
+
const now = profileChoice(app, useSignalValue(profile));
|
|
104
|
+
const kept = useSignalValue(started);
|
|
105
|
+
if (!now) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const said = `${words.profile}: ${now.label}`;
|
|
109
|
+
if (kept) {
|
|
110
|
+
return (_jsx(Text, { title: `${said} (${words.profileKept})`, sx: { fontSize: 0, color: 'fg.muted', px: 2 }, children: said }));
|
|
111
|
+
}
|
|
112
|
+
return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", variant: "invisible", "aria-label": said, children: said }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsx(ActionList, { selectionVariant: "single", children: _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: words.profile }), profiles.map(item => (_jsxs(ActionList.Item, { selected: item.id === now.id, onSelect: () => {
|
|
113
|
+
profile.value = profileChoice(app, item.id)?.id;
|
|
114
|
+
}, children: [item.label, item.description ? (_jsx(ActionList.Description, { variant: "block", children: item.description })) : null] }, item.id)))] }) }) })] }));
|
|
115
|
+
}
|
|
15
116
|
const modes = app.interface.modes ?? [];
|
|
16
117
|
/** The option of each mode the person is in. */
|
|
17
118
|
const chosen = signal(modeDefaults(app));
|
|
@@ -28,13 +129,49 @@ export function defineAppComposerPlugin(app) {
|
|
|
28
129
|
}, children: [option.label, option.description ? (_jsx(ActionList.Description, { variant: "block", children: option.description })) : null] }, option.id)))] }) }) })] }, mode.id));
|
|
29
130
|
}) }));
|
|
30
131
|
}
|
|
132
|
+
const uploads = app.interface.uploads;
|
|
133
|
+
/** The files held for the next message, and the last refusal (P-21). */
|
|
134
|
+
const held = signal({ files: [], refused: null });
|
|
135
|
+
function Attach() {
|
|
136
|
+
const words = interfaceWords(useChatLanguage());
|
|
137
|
+
const now = useSignalValue(held);
|
|
138
|
+
const input = useRef(null);
|
|
139
|
+
if (!uploads) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: PaperclipIcon, size: "small", variant: "invisible", "aria-label": `${words.attach}. ${uploadsInWords(uploads)}`, onClick: () => input.current?.click() }), _jsx("input", { ref: input, type: "file", hidden: true, multiple: uploads.maxFiles > 1, accept: uploadsAccept(uploads), "aria-label": words.attach, onChange: event => {
|
|
143
|
+
const chosen = Array.from(event.target.files ?? []);
|
|
144
|
+
event.target.value = '';
|
|
145
|
+
void holdUploads(app, held.peek().files, chosen).then(next => {
|
|
146
|
+
held.value = next;
|
|
147
|
+
});
|
|
148
|
+
} }), now.files.map(file => (_jsx(Token, { text: file.name, size: "small", onRemove: () => {
|
|
149
|
+
held.value = {
|
|
150
|
+
files: held.peek().files.filter(kept => kept !== file),
|
|
151
|
+
refused: null,
|
|
152
|
+
};
|
|
153
|
+
} }, file.name))), now.refused ? (_jsx(Text, { role: "alert", sx: { fontSize: 0, color: 'danger.fg' }, children: now.refused })) : null] }));
|
|
154
|
+
}
|
|
31
155
|
return definePlugin({
|
|
32
156
|
name: `${APP_COMPOSER_PLUGIN_NAME}-${app.id}`,
|
|
33
|
-
displayName: `${app.name}: commands and
|
|
34
|
-
description: `The commands and
|
|
157
|
+
displayName: `${app.name}: commands, modes, profiles and uploads`,
|
|
158
|
+
description: `The commands, modes, profiles and files ${app.name} takes in its composer.`,
|
|
35
159
|
octicon: 'command-palette',
|
|
36
160
|
emoji: '\u{2318}',
|
|
37
161
|
contributes: [
|
|
162
|
+
...(profiles.length > 0
|
|
163
|
+
? [
|
|
164
|
+
contribution(LoopRunProps, {
|
|
165
|
+
id: 'app-profile',
|
|
166
|
+
// Sent with every run; from the first, the conversation keeps it.
|
|
167
|
+
props: () => {
|
|
168
|
+
started.value = true;
|
|
169
|
+
return { loop: { profile: profile.peek() } };
|
|
170
|
+
},
|
|
171
|
+
}, { id: 'app-profile' }),
|
|
172
|
+
contribution(LoopChatExtras, { id: 'app-profile-starters', extras: openers }, { id: 'app-profile-starters' }),
|
|
173
|
+
]
|
|
174
|
+
: []),
|
|
38
175
|
...(app.interface.commands ?? []).map(command => contribution(LoopCommand, {
|
|
39
176
|
name: command.name,
|
|
40
177
|
description: command.description,
|
|
@@ -50,18 +187,56 @@ export function defineAppComposerPlugin(app) {
|
|
|
50
187
|
}, { id: 'app-modes' }),
|
|
51
188
|
]
|
|
52
189
|
: []),
|
|
53
|
-
|
|
54
|
-
build: () => ({
|
|
55
|
-
components: modes.length > 0
|
|
190
|
+
...(uploads
|
|
56
191
|
? [
|
|
57
|
-
{
|
|
58
|
-
id: 'app-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
192
|
+
contribution(LoopRunProps, {
|
|
193
|
+
id: 'app-uploads',
|
|
194
|
+
// The files held go with this message, once, and are let go.
|
|
195
|
+
props: () => {
|
|
196
|
+
const files = held.peek().files;
|
|
197
|
+
if (files.length === 0) {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
held.value = { files: [], refused: null };
|
|
201
|
+
return { loop: { files } };
|
|
202
|
+
},
|
|
203
|
+
}, { id: 'app-uploads' }),
|
|
63
204
|
]
|
|
64
|
-
: [],
|
|
205
|
+
: []),
|
|
206
|
+
],
|
|
207
|
+
build: () => ({
|
|
208
|
+
components: [
|
|
209
|
+
...(profiles.length > 0
|
|
210
|
+
? [
|
|
211
|
+
{
|
|
212
|
+
id: 'app-profile',
|
|
213
|
+
slot: LoopSlots.promptAction,
|
|
214
|
+
order: 3,
|
|
215
|
+
Component: ProfilePicker,
|
|
216
|
+
},
|
|
217
|
+
]
|
|
218
|
+
: []),
|
|
219
|
+
...(modes.length > 0
|
|
220
|
+
? [
|
|
221
|
+
{
|
|
222
|
+
id: 'app-modes',
|
|
223
|
+
slot: LoopSlots.promptAction,
|
|
224
|
+
order: 5,
|
|
225
|
+
Component: ModeSwitches,
|
|
226
|
+
},
|
|
227
|
+
]
|
|
228
|
+
: []),
|
|
229
|
+
...(uploads
|
|
230
|
+
? [
|
|
231
|
+
{
|
|
232
|
+
id: 'app-uploads',
|
|
233
|
+
slot: LoopSlots.promptAction,
|
|
234
|
+
order: 4,
|
|
235
|
+
Component: Attach,
|
|
236
|
+
},
|
|
237
|
+
]
|
|
238
|
+
: []),
|
|
239
|
+
],
|
|
65
240
|
}),
|
|
66
241
|
});
|
|
67
242
|
}
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
* address and embedded;
|
|
29
29
|
* - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
|
|
30
30
|
* window, its title the application's face and name;
|
|
31
|
+
* - its **language** (LOOP P-26): what a person reads of it — its name,
|
|
32
|
+
* welcome, starters, settings, commands, modes and profiles — in the first
|
|
33
|
+
* language they prefer that its Appspec is translated into (`language`,
|
|
34
|
+
* else the browser's), its own words otherwise (`translatedAppspec`);
|
|
35
|
+
* what it does is the same in every language;
|
|
31
36
|
* - its **sidebar**, when the host asks (`sidebar`, R-01b): its rules and
|
|
32
37
|
* the approvals waiting for the person (`app-rules`), what it did
|
|
33
38
|
* (`app-activity`), and its computer, live (`app-computer`, R-23), beside
|
|
@@ -62,7 +67,7 @@ export declare const agentIdOf: (app: Pick<AppSpec, "agent" | "team">) => string
|
|
|
62
67
|
* An application run by a team is not supported in the workspace yet: a team
|
|
63
68
|
* is not an agent spec, and is refused here rather than launched as one.
|
|
64
69
|
*/
|
|
65
|
-
export declare function defineAppPlugin(app: AppSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
70
|
+
export declare function defineAppPlugin(app: AppSpec, shown?: AppSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
66
71
|
/** Which instance of an application runs, as the platform knows it (LOOP R-07). */
|
|
67
72
|
export type AppInstance = {
|
|
68
73
|
/** Its `app` item. */
|
|
@@ -132,6 +137,12 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
|
|
|
132
137
|
* Canvas's palette. Compared by content.
|
|
133
138
|
*/
|
|
134
139
|
pluginsOff?: readonly string[];
|
|
140
|
+
/**
|
|
141
|
+
* The language the person reads it in, as BCP 47 tags it (`fr`, `pt-BR`):
|
|
142
|
+
* the platform's setting when the host knows it. The browser's languages
|
|
143
|
+
* when unsaid (LOOP P-26).
|
|
144
|
+
*/
|
|
145
|
+
language?: string;
|
|
135
146
|
};
|
|
136
147
|
/** What `interface.layout` sets on the workspace. */
|
|
137
148
|
export type AppLayoutOptions = Pick<LoopPresetOptions, 'editors' | 'showViewSelector' | 'defaultEditor' | 'pageLayout' | 'pageLayoutArrangement' | 'pageLayoutPrompt' | 'pageLayoutPromptAnchor' | 'pageLayoutTurnPanelFooter'>;
|
|
@@ -164,7 +175,9 @@ export type AppPreset = {
|
|
|
164
175
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
165
176
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
166
177
|
* comment on each answer when its record keeps feedback (V-18); its
|
|
167
|
-
* commands in the composer's `/` menu and its modes beside it (P-19);
|
|
178
|
+
* commands in the composer's `/` menu and its modes beside it (P-19); the
|
|
179
|
+
* side panel and the pages its code opens (`app-elements`, P-18); the
|
|
180
|
+
* components its developer wrote (`app-components`, P-17); what it
|
|
168
181
|
* keeps and for how long, under its prompt before the first message, when
|
|
169
182
|
* `kept` says it (R-31); with
|
|
170
183
|
* `sidebar`, its rules and approvals card, its activity feed and its
|
|
@@ -186,6 +199,11 @@ export declare function appPreset(app: AppSpec, options?: {
|
|
|
186
199
|
computer?: boolean;
|
|
187
200
|
/** What its record is kept under: its activity's. */
|
|
188
201
|
appUid?: string;
|
|
202
|
+
/**
|
|
203
|
+
* The application as the person reads it (P-26): its words in their
|
|
204
|
+
* language. Its own when unsaid.
|
|
205
|
+
*/
|
|
206
|
+
shown?: AppSpec;
|
|
189
207
|
/**
|
|
190
208
|
* Say what it keeps before the first message (R-31): at its address and
|
|
191
209
|
* embedded, not in its builder's Preview — to a `visitor` not signed
|
|
@@ -221,5 +239,5 @@ export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
|
|
|
221
239
|
* colours: over another theme (T-30) nothing is laid, the theme's own rule.
|
|
222
240
|
*/
|
|
223
241
|
export declare function appThemeOverrides(app: AppSpec, variant: string): ThemeOverrides | undefined;
|
|
224
|
-
export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, paused, sidebar, computer, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
|
|
242
|
+
export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, paused, sidebar, computer, frame, page, pluginsOff, language, ...embed }: AppRendererProps): React.JSX.Element;
|
|
225
243
|
export default AppRenderer;
|