@datalayer/agent-runtimes 1.0.17 → 1.0.19
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/README.md +3 -3
- package/lib/AgentNode.js +440 -41
- package/lib/agent-node/AgentNodeGallery.d.ts +80 -0
- package/lib/agent-node/AgentNodeGallery.js +464 -0
- package/lib/agent-notebook-main.js +0 -1
- package/lib/chat/Chat.d.ts +14 -1
- package/lib/chat/Chat.js +22 -7
- package/lib/chat/base/ChatBase.js +38 -12
- package/lib/chat/document/EphemeralDocument.d.ts +45 -1
- package/lib/chat/document/EphemeralDocument.js +135 -11
- package/lib/chat/notebook/EphemeralNotebook.d.ts +36 -1
- package/lib/chat/notebook/EphemeralNotebook.js +42 -9
- package/lib/examples/AgentCodeSandboxesExample.d.ts +3 -0
- package/lib/examples/AgentCodeSandboxesExample.js +192 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/identity/AgentIdentity.js +1 -2
- package/lib/shims/primerAddonsBox.d.ts +1 -0
- package/lib/shims/primerAddonsBox.js +7 -0
- package/lib/specs/agents/agents.d.ts +8 -0
- package/lib/specs/agents/agents.js +440 -0
- package/lib/types/agentspecs.d.ts +1 -1
- package/lib/types/chat.d.ts +71 -0
- package/lib/types/config.d.ts +1 -1
- package/package.json +4 -3
- package/scripts/codegen/generate_sandbox_agents.py +145 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
package/lib/AgentNode.js
CHANGED
|
@@ -5,10 +5,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
*/
|
|
6
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { useQueryClient } from '@tanstack/react-query';
|
|
8
|
-
import {
|
|
8
|
+
import { DatalayerLogoText, DatalayerThemeProvider, getLogoColors, setupPrimerPortals, themeConfigs, } from '@datalayer/primer-addons';
|
|
9
9
|
import { AppearanceControlsWithStore } from '@datalayer/primer-addons/lib/components/appearance';
|
|
10
|
-
import { ActionList, ActionMenu, Avatar, Button, FormControl, Heading, Label, PageHeader, PageLayout, Text, } from '@primer/react';
|
|
11
|
-
import { CommentIcon, HomeIcon, KeyAsteriskIcon, LockIcon, MoonIcon, PeopleIcon, PersonIcon, GearIcon,
|
|
10
|
+
import { ActionList, ActionMenu, Avatar, Box, Button, FormControl, Heading, Label, PageHeader, PageLayout, Text, } from '@primer/react';
|
|
11
|
+
import { CommentIcon, HomeIcon, KeyAsteriskIcon, LockIcon, MoonIcon, PeopleIcon, PersonIcon, GearIcon, RocketIcon, SignOutIcon, } from '@primer/octicons-react';
|
|
12
12
|
import { SignInSimple } from '@datalayer/core/lib/views/iam';
|
|
13
13
|
import { UserBadge } from '@datalayer/core/lib/views/profile';
|
|
14
14
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
@@ -16,12 +16,101 @@ import { useIAMStore } from '@datalayer/core/lib/state';
|
|
|
16
16
|
import { BillingEntitySelect, } from '@datalayer/core/lib/components/billing';
|
|
17
17
|
import { ShareAccessComponent } from '@datalayer/core/lib/components/sharing';
|
|
18
18
|
import { useAgentNodeThemeStore } from './agent-node/themeStore';
|
|
19
|
+
import { AgentNodeGallery } from './agent-node/AgentNodeGallery';
|
|
19
20
|
import { Chat } from './chat';
|
|
21
|
+
import { DatalayerCollaborationProvider } from './collaboration';
|
|
20
22
|
import '../style/primer-primitives.css';
|
|
21
23
|
setupPrimerPortals();
|
|
22
24
|
const AGENT_RUNTIMES_BASE_URL = (import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL ||
|
|
23
25
|
import.meta.env.VITE_BASE_URL ||
|
|
24
26
|
window.location.origin).replace(/\/$/, '');
|
|
27
|
+
/**
|
|
28
|
+
* LOCAL Jupyter sandbox endpoint shared by the node's agent and the chat's
|
|
29
|
+
* ephemeral notebook/document surfaces. When set (node-local dev, injected via
|
|
30
|
+
* `VITE_JUPYTER_SANDBOX_URL`), the surfaces bind their kernel straight to this
|
|
31
|
+
* Jupyter server — no proxy or tunnel — exactly like `NotebookAgentExample`.
|
|
32
|
+
* Parsed once into a clean base URL + token for `ServerConnection`.
|
|
33
|
+
*/
|
|
34
|
+
const LOCAL_JUPYTER_SANDBOX = (() => {
|
|
35
|
+
const raw = String(import.meta.env.VITE_JUPYTER_SANDBOX_URL || '').trim();
|
|
36
|
+
if (!raw) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(raw);
|
|
41
|
+
const token = url.searchParams.get('token') || undefined;
|
|
42
|
+
url.searchParams.delete('token');
|
|
43
|
+
const baseUrl = `${url.origin}${url.pathname}`.replace(/\/$/, '');
|
|
44
|
+
return { baseUrl, token };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { baseUrl: raw.replace(/\/$/, ''), token: undefined };
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
const DEFAULT_DATALAYER_URL = 'https://prod1.datalayer.run';
|
|
51
|
+
/**
|
|
52
|
+
* localStorage key recording that the user explicitly signed out. When set, the
|
|
53
|
+
* UI must not silently re-authenticate from the env-supplied DATALAYER_API_KEY
|
|
54
|
+
* via /auth/bootstrap, so the sign-out survives a page refresh.
|
|
55
|
+
*/
|
|
56
|
+
const AUTO_BOOTSTRAP_DISABLED_KEY = 'agent-node-auto-bootstrap-disabled';
|
|
57
|
+
/** Persist (or clear) the signed-out intent in localStorage. */
|
|
58
|
+
function setAutoBootstrapDisabled(disabled) {
|
|
59
|
+
try {
|
|
60
|
+
if (disabled) {
|
|
61
|
+
window.localStorage.setItem(AUTO_BOOTSTRAP_DISABLED_KEY, 'true');
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
window.localStorage.removeItem(AUTO_BOOTSTRAP_DISABLED_KEY);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Ignore storage failures (private mode, disabled storage, etc.).
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read a service URL from the server-injected `datalayer-config-data` script
|
|
73
|
+
* tag. The agent-runtimes Python server injects this tag (populated from the
|
|
74
|
+
* `DATALAYER_*_URL` environment variables, e.g. from `plane local`) when it
|
|
75
|
+
* serves the built agent pages. Reading it at runtime lets the node target the
|
|
76
|
+
* configured services (IAM, runtimes, ...) instead of the build-time-baked
|
|
77
|
+
* `VITE_DATALAYER_URL`, which defaults to production because `make build` runs
|
|
78
|
+
* without the local environment.
|
|
79
|
+
*/
|
|
80
|
+
const getConfigUrlFromDocument = (...keys) => {
|
|
81
|
+
if (typeof document === 'undefined') {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const el = document.getElementById('datalayer-config-data');
|
|
85
|
+
if (!el?.textContent) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const config = JSON.parse(el.textContent);
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
const value = config?.[key];
|
|
92
|
+
if (typeof value === 'string' && value.trim()) {
|
|
93
|
+
return value.replace(/\/$/, '');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore malformed config; fall back to env / default.
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
};
|
|
102
|
+
/** Resolve the base Datalayer URL: injected config → VITE env → production. */
|
|
103
|
+
const resolveDatalayerUrl = () => getConfigUrlFromDocument('datalayerUrl', 'iamUrl') ||
|
|
104
|
+
import.meta.env?.VITE_DATALAYER_URL ||
|
|
105
|
+
DEFAULT_DATALAYER_URL;
|
|
106
|
+
/** Resolve the runtimes URL: injected config → VITE env → base Datalayer URL. */
|
|
107
|
+
const resolveRuntimesUrl = () => getConfigUrlFromDocument('runtimesUrl') ||
|
|
108
|
+
import.meta.env?.VITE_DATALAYER_RUNTIMES_URL ||
|
|
109
|
+
resolveDatalayerUrl();
|
|
110
|
+
/** Resolve the spacer URL: injected config → VITE env → base Datalayer URL. */
|
|
111
|
+
const resolveSpacerUrl = () => getConfigUrlFromDocument('spacerUrl') ||
|
|
112
|
+
import.meta.env?.VITE_DATALAYER_SPACER_URL ||
|
|
113
|
+
resolveDatalayerUrl();
|
|
25
114
|
const MODE_CARDS = [
|
|
26
115
|
{
|
|
27
116
|
mode: 'private',
|
|
@@ -52,7 +141,7 @@ const DEFAULT_CONFIGURATION = {
|
|
|
52
141
|
* but assembled exclusively from core building blocks so agent-runtimes does
|
|
53
142
|
* not pull in the `ui` package.
|
|
54
143
|
*/
|
|
55
|
-
function AgentNodeProfileView({ token }) {
|
|
144
|
+
function AgentNodeProfileView({ token, onTokenExpired, }) {
|
|
56
145
|
const user = useIAMStore(state => state.user);
|
|
57
146
|
const display = useMemo(() => {
|
|
58
147
|
if (!user)
|
|
@@ -92,7 +181,7 @@ function AgentNodeProfileView({ token }) {
|
|
|
92
181
|
display: 'flex',
|
|
93
182
|
flexDirection: 'column',
|
|
94
183
|
gap: 3,
|
|
95
|
-
}, children: !token ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Sign in to view your profile." })) : !display ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading profile\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(Heading, { sx: { fontSize: 2, mb: 2 }, children: "Identity" }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 3 }, children: [display.avatarUrl ? (_jsx(Avatar, { src: display.avatarUrl, size: 72, alt: display.displayName })) : (_jsx(Box, { sx: {
|
|
184
|
+
}, children: !token ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Sign in to view your profile." })) : !display ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading profile\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(Heading, { sx: { fontSize: 2, mb: 2 }, children: "Identity" }), _jsx(Box, { sx: { textAlign: 'left' }, children: _jsx(UserBadge, { token: token, variant: "small", onTokenExpired: onTokenExpired }) }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 3 }, children: [display.avatarUrl ? (_jsx(Avatar, { src: display.avatarUrl, size: 72, alt: display.displayName })) : (_jsx(Box, { sx: {
|
|
96
185
|
width: 72,
|
|
97
186
|
height: 72,
|
|
98
187
|
borderRadius: '50%',
|
|
@@ -112,6 +201,7 @@ function AgentNodeProfileView({ token }) {
|
|
|
112
201
|
export function AgentNode() {
|
|
113
202
|
const { token, setAuth, clearAuth } = useSimpleAuthStore();
|
|
114
203
|
const tokenForCore = token ?? undefined;
|
|
204
|
+
const signInLoginUrl = `${resolveDatalayerUrl()}/api/iam/v1/login`;
|
|
115
205
|
const queryClient = useQueryClient();
|
|
116
206
|
const iamUser = useIAMStore(state => state.user);
|
|
117
207
|
const { colorMode, theme: themeVariant } = useAgentNodeThemeStore();
|
|
@@ -127,14 +217,230 @@ export function AgentNode() {
|
|
|
127
217
|
: 'light';
|
|
128
218
|
const authGradient = cfg.cardGradient[resolvedMode];
|
|
129
219
|
const [step, setStep] = useState('auth');
|
|
130
|
-
const [
|
|
220
|
+
const [selectedAgentId, setSelectedAgentId] = useState('default');
|
|
221
|
+
// Persisted across refreshes: once the user explicitly signs out we must
|
|
222
|
+
// not silently re-authenticate them from the env-supplied DATALAYER_API_KEY
|
|
223
|
+
// via /auth/bootstrap. Initialise from localStorage so the intent survives a
|
|
224
|
+
// page reload (React state alone resets to false on refresh).
|
|
225
|
+
const [disableAutoBootstrap, setDisableAutoBootstrap] = useState(() => {
|
|
226
|
+
try {
|
|
227
|
+
return (window.localStorage.getItem(AUTO_BOOTSTRAP_DISABLED_KEY) === 'true');
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
131
233
|
const [configuration, setConfiguration] = useState(DEFAULT_CONFIGURATION);
|
|
132
234
|
const [, setSelectedBillingEntity] = useState(undefined);
|
|
133
235
|
const [isSaving, setIsSaving] = useState(false);
|
|
134
236
|
const [error, setError] = useState(null);
|
|
237
|
+
// ── Ephemeral notebook RTC (shared room with the SaaS UI) ───────────────
|
|
238
|
+
// The node provisions a spacer notebook room on registration and stores its
|
|
239
|
+
// uid in the configuration. When present, both the node-local chat and the
|
|
240
|
+
// SaaS gallery view join that room so the agent's notebook edits transit via
|
|
241
|
+
// RTC instead of the tunnel. Absent (e.g. before sign-in) the ephemeral
|
|
242
|
+
// notebook stays in-memory, unchanged.
|
|
243
|
+
const ephemeralCollaborationDocumentId = token && configuration.collaboration_notebook_uid
|
|
244
|
+
? configuration.collaboration_notebook_uid
|
|
245
|
+
: undefined;
|
|
246
|
+
const ephemeralNotebookCollaborationProvider = useMemo(() => {
|
|
247
|
+
if (!ephemeralCollaborationDocumentId || !token) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
const spacerUrl = resolveSpacerUrl();
|
|
251
|
+
return new DatalayerCollaborationProvider({
|
|
252
|
+
datalayerUrl: spacerUrl,
|
|
253
|
+
token,
|
|
254
|
+
});
|
|
255
|
+
}, [ephemeralCollaborationDocumentId, token]);
|
|
256
|
+
// ── Ephemeral document RTC (shared Lexical/Loro room with the SaaS UI) ────
|
|
257
|
+
// The node provisions a spacer *lexical* room (distinct from the notebook
|
|
258
|
+
// room) on registration and stores its uid as `collaboration_document_uid`.
|
|
259
|
+
// Both peers join that Loro room over the spacer lexical WebSocket. The token
|
|
260
|
+
// is embedded in the ws URL (createWebsocketProvider forwards it as a query
|
|
261
|
+
// param) so the spacer authenticates the connection.
|
|
262
|
+
const collaborationDocumentUser = useIAMStore(state => state.user);
|
|
263
|
+
const ephemeralDocumentCollaboration = useMemo(() => {
|
|
264
|
+
const documentRoomId = configuration.collaboration_document_uid;
|
|
265
|
+
if (!token || !documentRoomId) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
const spacerUrl = resolveSpacerUrl();
|
|
269
|
+
const wsSpacer = String(spacerUrl).replace(/^http/, 'ws');
|
|
270
|
+
const websocketUrl = `${wsSpacer}/api/spacer/v1/lexical/ws?token=${encodeURIComponent(token)}`;
|
|
271
|
+
const u = collaborationDocumentUser;
|
|
272
|
+
return {
|
|
273
|
+
websocketUrl,
|
|
274
|
+
roomId: documentRoomId,
|
|
275
|
+
identity: u
|
|
276
|
+
? {
|
|
277
|
+
userId: u.uid,
|
|
278
|
+
handle: u.handle,
|
|
279
|
+
displayName: u.displayName,
|
|
280
|
+
initials: u.initials,
|
|
281
|
+
avatarUrl: u.avatarUrl,
|
|
282
|
+
}
|
|
283
|
+
: undefined,
|
|
284
|
+
};
|
|
285
|
+
}, [configuration.collaboration_document_uid, token, collaborationDocumentUser]);
|
|
135
286
|
const [inferenceProvider, setInferenceProvider] = useState('datalayer');
|
|
136
287
|
const [inferenceModels, setInferenceModels] = useState([]);
|
|
137
288
|
const [inferenceDefaultModel, setInferenceDefaultModel] = useState(null);
|
|
289
|
+
const [configurationLoaded, setConfigurationLoaded] = useState(false);
|
|
290
|
+
const chatRestoreAttemptRef = useRef('');
|
|
291
|
+
const hasActiveAgent = Boolean(String(configuration.active_agent_id || '').trim());
|
|
292
|
+
// ── Local sandbox runtime override for the ephemeral surfaces ─────────────
|
|
293
|
+
// In the node-local webapp the agent's Jupyter sandbox is a LOCAL server, not
|
|
294
|
+
// a Kubernetes pod resolvable by `pod_name`, so the ephemeral notebook and
|
|
295
|
+
// document surfaces cannot bind their kernel through the runtimes pod lookup.
|
|
296
|
+
// Instead we read the live sandbox Jupyter endpoint from `/health/startup`
|
|
297
|
+
// and pass it as an explicit override so the surfaces connect a service
|
|
298
|
+
// manager straight to the sandbox kernel. This is what makes the surfaces
|
|
299
|
+
// actually render and the chat header's kernel indicator reflect the sandbox
|
|
300
|
+
// kernel (rather than the browser/Pyodide placeholder).
|
|
301
|
+
const [sandboxRuntimeOverride, setSandboxRuntimeOverride] = useState(undefined);
|
|
302
|
+
// Environment name shown in the chat header's kernel indicator details, so
|
|
303
|
+
// it reads e.g. "Local Agent Sandbox (python3)" instead of the indicator's
|
|
304
|
+
// generic "browser-runtime" placeholder once the local sandbox is bound.
|
|
305
|
+
const [sandboxEnvironmentName, setSandboxEnvironmentName] = useState(undefined);
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
const selected = String(selectedAgentId || '').trim();
|
|
308
|
+
if (step !== 'chat' || !selected || selected === 'default') {
|
|
309
|
+
setSandboxRuntimeOverride(undefined);
|
|
310
|
+
setSandboxEnvironmentName(undefined);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Fast path: in node-local dev the agent's Jupyter sandbox is a LOCAL
|
|
314
|
+
// server whose URL/token are known up front (VITE_JUPYTER_SANDBOX_URL).
|
|
315
|
+
// Bind the ephemeral surfaces straight to it — no /health/startup polling,
|
|
316
|
+
// no proxy, no tunnel — mirroring NotebookAgentExample.
|
|
317
|
+
if (LOCAL_JUPYTER_SANDBOX?.baseUrl) {
|
|
318
|
+
setSandboxEnvironmentName('Local Agent Sandbox');
|
|
319
|
+
setSandboxRuntimeOverride(prev => {
|
|
320
|
+
if (prev?.baseUrl === LOCAL_JUPYTER_SANDBOX.baseUrl &&
|
|
321
|
+
(prev?.token || '') === (LOCAL_JUPYTER_SANDBOX.token || '')) {
|
|
322
|
+
return prev;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
baseUrl: LOCAL_JUPYTER_SANDBOX.baseUrl,
|
|
326
|
+
token: LOCAL_JUPYTER_SANDBOX.token || undefined,
|
|
327
|
+
podName: 'agent-node-sandbox',
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
let cancelled = false;
|
|
333
|
+
let intervalId = null;
|
|
334
|
+
const stopPolling = () => {
|
|
335
|
+
if (intervalId !== null) {
|
|
336
|
+
window.clearInterval(intervalId);
|
|
337
|
+
intervalId = null;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
const poll = async () => {
|
|
341
|
+
try {
|
|
342
|
+
const resp = await fetch(`${AGENT_RUNTIMES_BASE_URL}/health/startup`);
|
|
343
|
+
if (!resp.ok) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const payload = await resp.json();
|
|
347
|
+
const sandbox = payload?.sandbox;
|
|
348
|
+
const jupyterUrl = String(sandbox?.jupyter_url || '').trim();
|
|
349
|
+
if (sandbox?.variant !== 'jupyter' || !jupyterUrl) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const jupyterToken = String(sandbox?.jupyter_token || '').trim();
|
|
353
|
+
if (cancelled) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const kernelName = String(sandbox?.kernel_name || '').trim();
|
|
357
|
+
setSandboxEnvironmentName(kernelName
|
|
358
|
+
? `Local Agent Sandbox (${kernelName})`
|
|
359
|
+
: 'Local Agent Sandbox');
|
|
360
|
+
setSandboxRuntimeOverride(prev => {
|
|
361
|
+
if (prev?.baseUrl === jupyterUrl &&
|
|
362
|
+
(prev?.token || '') === jupyterToken) {
|
|
363
|
+
return prev;
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
baseUrl: jupyterUrl,
|
|
367
|
+
token: jupyterToken || undefined,
|
|
368
|
+
podName: 'agent-node-sandbox',
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
// Endpoint resolved — stop the fast poll.
|
|
372
|
+
stopPolling();
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Sandbox not ready yet; keep polling.
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
void poll();
|
|
379
|
+
intervalId = window.setInterval(() => {
|
|
380
|
+
void poll();
|
|
381
|
+
}, 2000);
|
|
382
|
+
return () => {
|
|
383
|
+
cancelled = true;
|
|
384
|
+
stopPolling();
|
|
385
|
+
};
|
|
386
|
+
}, [step, selectedAgentId]);
|
|
387
|
+
// ── Agent spec info for the chat header (title/description/suggestions) ──
|
|
388
|
+
// Mirrors the `ui` runtime chat view: the chat header shows the agent spec's
|
|
389
|
+
// display name and welcome message, and the empty state offers the spec's
|
|
390
|
+
// suggested prompts. The node-local agent is launched from a library spec
|
|
391
|
+
// whose id is used as the agent id (see AgentNodeGallery `launch`), so the
|
|
392
|
+
// spec is looked up by that same id via `GET /api/v1/agents/library/{id}`.
|
|
393
|
+
const [agentSpecInfo, setAgentSpecInfo] = useState(null);
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
const agentId = String(selectedAgentId || '').trim();
|
|
396
|
+
if (step !== 'chat' || !agentId || agentId === 'default') {
|
|
397
|
+
setAgentSpecInfo(null);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
let cancelled = false;
|
|
401
|
+
(async () => {
|
|
402
|
+
try {
|
|
403
|
+
const resp = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agents/library/${encodeURIComponent(agentId)}`, {
|
|
404
|
+
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
405
|
+
});
|
|
406
|
+
if (!resp.ok) {
|
|
407
|
+
if (!cancelled) {
|
|
408
|
+
setAgentSpecInfo(null);
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const spec = await resp.json().catch(() => null);
|
|
413
|
+
if (cancelled || !spec) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
setAgentSpecInfo({
|
|
417
|
+
name: typeof spec.name === 'string' ? spec.name : undefined,
|
|
418
|
+
description: typeof spec.description === 'string' ? spec.description : undefined,
|
|
419
|
+
welcomeMessage: typeof spec.welcomeMessage === 'string'
|
|
420
|
+
? spec.welcomeMessage
|
|
421
|
+
: typeof spec.welcome_message === 'string'
|
|
422
|
+
? spec.welcome_message
|
|
423
|
+
: undefined,
|
|
424
|
+
suggestions: Array.isArray(spec.suggestions)
|
|
425
|
+
? spec.suggestions.filter((s) => typeof s === 'string')
|
|
426
|
+
: undefined,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
if (!cancelled) {
|
|
431
|
+
setAgentSpecInfo(null);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
})();
|
|
435
|
+
return () => {
|
|
436
|
+
cancelled = true;
|
|
437
|
+
};
|
|
438
|
+
}, [step, selectedAgentId, token]);
|
|
439
|
+
const chatTitle = agentSpecInfo?.name || 'Agent Node Chat';
|
|
440
|
+
const chatDescription = agentSpecInfo?.welcomeMessage || agentSpecInfo?.description || 'Node-local chat';
|
|
441
|
+
const chatSuggestions = agentSpecInfo?.suggestions && agentSpecInfo.suggestions.length > 0
|
|
442
|
+
? agentSpecInfo.suggestions.map(s => ({ title: s, message: s }))
|
|
443
|
+
: undefined;
|
|
138
444
|
const [banner, setBanner] = useState(null);
|
|
139
445
|
const bannerTimerRef = useRef(null);
|
|
140
446
|
const showBanner = useCallback((kind, message) => {
|
|
@@ -158,9 +464,7 @@ export function AgentNode() {
|
|
|
158
464
|
};
|
|
159
465
|
}, []);
|
|
160
466
|
const pushCredentials = useCallback((authToken, payloadToken = authToken) => {
|
|
161
|
-
const
|
|
162
|
-
'https://prod1.datalayer.run';
|
|
163
|
-
const runtimesUrl = import.meta.env?.VITE_DATALAYER_RUNTIMES_URL || datalayerUrl;
|
|
467
|
+
const runtimesUrl = resolveRuntimesUrl();
|
|
164
468
|
const headers = {
|
|
165
469
|
'Content-Type': 'application/json',
|
|
166
470
|
};
|
|
@@ -213,6 +517,7 @@ export function AgentNode() {
|
|
|
213
517
|
try {
|
|
214
518
|
const response = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/configuration`);
|
|
215
519
|
if (!response.ok) {
|
|
520
|
+
setConfigurationLoaded(true);
|
|
216
521
|
return;
|
|
217
522
|
}
|
|
218
523
|
const payload = await response.json();
|
|
@@ -221,10 +526,16 @@ export function AgentNode() {
|
|
|
221
526
|
...(payload?.configuration || {}),
|
|
222
527
|
};
|
|
223
528
|
setConfiguration(loadedConfiguration);
|
|
529
|
+
if (loadedConfiguration.active_agent_id) {
|
|
530
|
+
setSelectedAgentId(loadedConfiguration.active_agent_id);
|
|
531
|
+
}
|
|
224
532
|
}
|
|
225
533
|
catch {
|
|
226
534
|
// Ignore initial-load failures in local development.
|
|
227
535
|
}
|
|
536
|
+
finally {
|
|
537
|
+
setConfigurationLoaded(true);
|
|
538
|
+
}
|
|
228
539
|
};
|
|
229
540
|
load();
|
|
230
541
|
}, []);
|
|
@@ -319,7 +630,7 @@ export function AgentNode() {
|
|
|
319
630
|
}
|
|
320
631
|
if (data?.has_key && data?.token) {
|
|
321
632
|
setAuth(String(data.token), String(data.handle || 'api-key-user'));
|
|
322
|
-
setStep('
|
|
633
|
+
setStep('gallery');
|
|
323
634
|
}
|
|
324
635
|
}
|
|
325
636
|
catch {
|
|
@@ -343,10 +654,10 @@ export function AgentNode() {
|
|
|
343
654
|
}, [token, pushCredentials]);
|
|
344
655
|
useEffect(() => {
|
|
345
656
|
import('@datalayer/core/lib/state').then(({ iamStore, coreStore }) => {
|
|
346
|
-
const datalayerUrl =
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
657
|
+
const datalayerUrl = resolveDatalayerUrl();
|
|
658
|
+
const runtimesUrl = resolveRuntimesUrl();
|
|
659
|
+
const aiInferenceUrl = getConfigUrlFromDocument('aiInferenceUrl') ||
|
|
660
|
+
import.meta.env?.VITE_DATALAYER_AI_INFERENCE_URL ||
|
|
350
661
|
datalayerUrl;
|
|
351
662
|
// Seed all per-service URLs to match the main UI login behavior.
|
|
352
663
|
const coreApi = coreStore.getState();
|
|
@@ -387,11 +698,60 @@ export function AgentNode() {
|
|
|
387
698
|
setStep('auth');
|
|
388
699
|
}
|
|
389
700
|
}, [token, step]);
|
|
701
|
+
useEffect(() => {
|
|
702
|
+
if (!token || !configurationLoaded) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const activeAgentId = (configuration.active_agent_id || '').trim();
|
|
706
|
+
if (!activeAgentId) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const restoreKey = `${activeAgentId}`;
|
|
710
|
+
if (chatRestoreAttemptRef.current === restoreKey) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
chatRestoreAttemptRef.current = restoreKey;
|
|
714
|
+
let cancelled = false;
|
|
715
|
+
(async () => {
|
|
716
|
+
try {
|
|
717
|
+
const response = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agents`, {
|
|
718
|
+
headers: {
|
|
719
|
+
Authorization: `Bearer ${token}`,
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
if (!response.ok) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const payload = await response.json().catch(() => null);
|
|
726
|
+
if (cancelled) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const agents = Array.isArray(payload)
|
|
730
|
+
? payload
|
|
731
|
+
: payload?.agents || payload?.items || [];
|
|
732
|
+
const running = agents.some(agent => {
|
|
733
|
+
const id = String(agent?.agent_id || agent?.id || '').trim();
|
|
734
|
+
return id === activeAgentId;
|
|
735
|
+
});
|
|
736
|
+
if (running) {
|
|
737
|
+
setSelectedAgentId(activeAgentId);
|
|
738
|
+
setConfiguration(prev => prev.mode === 'private' ? prev : { ...prev, mode: 'private' });
|
|
739
|
+
setStep('chat');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
// Best-effort restore only.
|
|
744
|
+
}
|
|
745
|
+
})();
|
|
746
|
+
return () => {
|
|
747
|
+
cancelled = true;
|
|
748
|
+
};
|
|
749
|
+
}, [token, configurationLoaded, configuration.active_agent_id]);
|
|
390
750
|
useEffect(() => {
|
|
391
751
|
// If a persisted session is already authenticated on first load,
|
|
392
|
-
// skip the auth screen and land on the
|
|
752
|
+
// skip the auth screen and land on the agents gallery.
|
|
393
753
|
if (token && step === 'auth') {
|
|
394
|
-
setStep('
|
|
754
|
+
setStep('gallery');
|
|
395
755
|
}
|
|
396
756
|
}, [token, step]);
|
|
397
757
|
useEffect(() => {
|
|
@@ -399,16 +759,21 @@ export function AgentNode() {
|
|
|
399
759
|
setStep('config');
|
|
400
760
|
}
|
|
401
761
|
}, [step, configuration.mode]);
|
|
762
|
+
useEffect(() => {
|
|
763
|
+
if (step === 'chat' && !hasActiveAgent) {
|
|
764
|
+
setStep('gallery');
|
|
765
|
+
}
|
|
766
|
+
}, [step, hasActiveAgent]);
|
|
402
767
|
const handleSignIn = (newToken, handle) => {
|
|
768
|
+
setAutoBootstrapDisabled(false);
|
|
403
769
|
setDisableAutoBootstrap(false);
|
|
404
770
|
setAuth(newToken, handle);
|
|
405
|
-
setStep('
|
|
771
|
+
setStep('gallery');
|
|
406
772
|
};
|
|
407
773
|
// API keys are exchanged for a session token before login so billing and
|
|
408
774
|
// plans endpoints (/api/iam/v1/plans/*) resolve the correct paid plan.
|
|
409
775
|
const handleApiKeySignIn = async (apiKey) => {
|
|
410
|
-
const datalayerUrl =
|
|
411
|
-
'https://prod1.datalayer.run';
|
|
776
|
+
const datalayerUrl = resolveDatalayerUrl();
|
|
412
777
|
try {
|
|
413
778
|
const resp = await fetch(`${datalayerUrl}/api/iam/v1/login`, {
|
|
414
779
|
method: 'POST',
|
|
@@ -428,6 +793,9 @@ export function AgentNode() {
|
|
|
428
793
|
}
|
|
429
794
|
};
|
|
430
795
|
const handleSignOut = () => {
|
|
796
|
+
// Persist the signed-out intent so a refresh does not re-bootstrap the
|
|
797
|
+
// session from the env-supplied DATALAYER_API_KEY.
|
|
798
|
+
setAutoBootstrapDisabled(true);
|
|
431
799
|
setDisableAutoBootstrap(true);
|
|
432
800
|
// Clear backend fallback credentials while still authenticated.
|
|
433
801
|
void pushCredentials(token, null).catch(() => {
|
|
@@ -467,9 +835,12 @@ export function AgentNode() {
|
|
|
467
835
|
...(payload?.configuration || {}),
|
|
468
836
|
};
|
|
469
837
|
setConfiguration(saved);
|
|
838
|
+
if (saved.active_agent_id) {
|
|
839
|
+
setSelectedAgentId(saved.active_agent_id);
|
|
840
|
+
}
|
|
470
841
|
showBanner('success', 'Agent Node configuration saved.');
|
|
471
842
|
if (saved.mode === 'private') {
|
|
472
|
-
setStep('
|
|
843
|
+
setStep('gallery');
|
|
473
844
|
}
|
|
474
845
|
}
|
|
475
846
|
catch (reason) {
|
|
@@ -486,8 +857,11 @@ export function AgentNode() {
|
|
|
486
857
|
return true;
|
|
487
858
|
if (!token)
|
|
488
859
|
return false;
|
|
489
|
-
if (nextStep === '
|
|
490
|
-
return
|
|
860
|
+
if (nextStep === 'gallery')
|
|
861
|
+
return true;
|
|
862
|
+
if (nextStep === 'chat') {
|
|
863
|
+
return configuration.mode === 'private' && hasActiveAgent;
|
|
864
|
+
}
|
|
491
865
|
return true;
|
|
492
866
|
};
|
|
493
867
|
const StepEntry = ({ entryStep, label, leadingVisual, }) => {
|
|
@@ -508,21 +882,13 @@ export function AgentNode() {
|
|
|
508
882
|
minHeight: '100vh',
|
|
509
883
|
bg: 'canvas.default',
|
|
510
884
|
color: 'fg.default',
|
|
511
|
-
}, children: _jsxs(PageLayout, { containerWidth: "full", padding: "normal", sx: { bg: 'canvas.default', color: 'fg.default' }, style: { minHeight: '100vh', overflow: 'visible' }, children: [_jsx(PageLayout.Header, { children: _jsx(PageHeader, { children: _jsx(PageHeader.Actions, { children: _jsxs(Box, { sx: {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
display: 'inline-flex',
|
|
519
|
-
alignItems: 'center',
|
|
520
|
-
gap: 1,
|
|
521
|
-
}, children: [_jsx(Box, { sx: {
|
|
522
|
-
textAlign: 'left',
|
|
523
|
-
display: 'inline-flex',
|
|
524
|
-
alignItems: 'center',
|
|
525
|
-
}, children: _jsx(UserBadge, { token: token, variant: "small", onTokenExpired: handleSignOut }) }), _jsx(StepEntry, { entryStep: "profile", label: "Profile", leadingVisual: PersonIcon }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleSignOut, leadingVisual: SignOutIcon, sx: { color: 'fg.default' }, children: "Sign out" })] })), _jsx(Box, { as: "a", href: "https://datalayer.ai", target: "_blank", rel: "noopener noreferrer", "aria-label": "Open Datalayer website", sx: { display: 'inline-flex', alignItems: 'center' }, children: _jsx(DatalayerLogoText, { size: 24, variant: themeVariant, colorMode: colorMode, primaryColor: logoColors.primary, secondaryColor: logoColors.secondary, textColor: logoColors.textColor, primaryGradient: logoColors.primaryGradient, secondaryGradient: logoColors.secondaryGradient, gradient: true }) })] }) }) }) }), _jsxs(PageLayout.Content, { children: [_jsx(Box, { "aria-live": "polite", sx: {
|
|
885
|
+
}, children: _jsxs(PageLayout, { containerWidth: "full", padding: "normal", sx: { bg: 'canvas.default', color: 'fg.default' }, style: { minHeight: '100vh', overflow: 'visible' }, children: [_jsx(PageLayout.Header, { children: _jsxs(PageHeader, { children: [_jsx(PageHeader.TitleArea, { children: _jsx(PageHeader.Title, { children: _jsx(Box, { as: "a", href: "https://datalayer.ai", target: "_blank", rel: "noopener noreferrer", "aria-label": "Open Datalayer website", sx: { display: 'inline-flex', alignItems: 'center' }, children: _jsx(DatalayerLogoText, { size: 24, inverse: true, variant: themeVariant, colorMode: colorMode, primaryColor: logoColors.primary, secondaryColor: logoColors.secondary, textColor: logoColors.textColor, primaryGradient: logoColors.primaryGradient, secondaryGradient: logoColors.secondaryGradient, gradient: true }) }) }) }), _jsx(PageHeader.Actions, { children: _jsxs(Box, { sx: {
|
|
886
|
+
display: 'flex',
|
|
887
|
+
alignItems: 'center',
|
|
888
|
+
gap: 2,
|
|
889
|
+
fontSize: 2,
|
|
890
|
+
lineHeight: '22px',
|
|
891
|
+
}, children: [token && (_jsxs(_Fragment, { children: [_jsx(StepEntry, { entryStep: "chat", label: "Chat", leadingVisual: CommentIcon }), _jsx(StepEntry, { entryStep: "gallery", label: "Agents", leadingVisual: RocketIcon }), _jsx(StepEntry, { entryStep: "config", label: "Configuration", leadingVisual: GearIcon })] })), !token ? null : (_jsxs(_Fragment, { children: [_jsx(StepEntry, { entryStep: "profile", label: "Profile", leadingVisual: PersonIcon }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleSignOut, leadingVisual: SignOutIcon, sx: { color: 'fg.default' }, children: "Sign out" })] })), _jsx(AppearanceControlsWithStore, { useStore: useAgentNodeThemeStore })] }) })] }) }), _jsxs(PageLayout.Content, { children: [_jsx(Box, { "aria-live": "polite", sx: {
|
|
526
892
|
position: 'fixed',
|
|
527
893
|
top: 0,
|
|
528
894
|
left: 0,
|
|
@@ -560,7 +926,7 @@ export function AgentNode() {
|
|
|
560
926
|
? 'opacity 600ms ease, transform 700ms cubic-bezier(0.16, 1, 0.3, 1)'
|
|
561
927
|
: 'opacity 250ms ease, transform 350ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
562
928
|
textAlign: 'center',
|
|
563
|
-
}, children: _jsx(Text, { sx: { fontSize: 3, fontWeight: 'bold' }, children: banner?.message ?? '' }) }) }), _jsxs(Box, { sx: { mb: 3 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 1 }, children: [_jsx(Box, { sx: { color: 'fg.muted', display: 'inline-flex' }, children: _jsx(KeyAsteriskIcon, { size: 18 }) }), _jsx(Heading, { sx: { fontSize: 3, m: 0 }, children: "Agent Node" })] }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Authenticate, configure Private/Shared/Sleep mode, then chat from this node." })] }), step === 'auth' && (_jsx(Box, { sx: {
|
|
929
|
+
}, children: _jsx(Text, { sx: { fontSize: 3, fontWeight: 'bold' }, children: banner?.message ?? '' }) }) }), !token && (_jsxs(Box, { sx: { mb: 3 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 1 }, children: [_jsx(Box, { sx: { color: 'fg.muted', display: 'inline-flex' }, children: _jsx(KeyAsteriskIcon, { size: 18 }) }), _jsx(Heading, { sx: { fontSize: 3, m: 0 }, children: "Agent Node" })] }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Authenticate, configure Private/Shared/Sleep mode, then chat from this node." })] })), step === 'auth' && (_jsx(Box, { sx: {
|
|
564
930
|
border: '1px solid',
|
|
565
931
|
borderColor: 'border.default',
|
|
566
932
|
borderTop: '3px solid',
|
|
@@ -582,13 +948,17 @@ export function AgentNode() {
|
|
|
582
948
|
'& > div > div': {
|
|
583
949
|
width: '100%',
|
|
584
950
|
bg: 'canvas.default',
|
|
951
|
+
border: '1px solid',
|
|
585
952
|
borderColor: `${cfg.brandColor}66`,
|
|
586
953
|
boxShadow: `0 0 0 1px ${cfg.brandColor}2B`,
|
|
954
|
+
borderRadius: 2,
|
|
955
|
+
px: [3, 4],
|
|
956
|
+
py: [3, 4],
|
|
587
957
|
},
|
|
588
958
|
'& h2, & h3': {
|
|
589
959
|
color: cfg.brandColor,
|
|
590
960
|
},
|
|
591
|
-
}, children: _jsx(SignInSimple, { onSignIn: handleSignIn, onApiKeySignIn: handleApiKeySignIn, title: "Agent Node", description: "Sign in to configure node settings and run authenticated chat sessions.", leadingIcon: _jsx(Box, { sx: { color: cfg.brandColor, display: 'inline-flex' }, children: _jsx(HomeIcon, { size: 24 }) }) }) }) })), step === 'config' && (_jsxs(Box, { sx: {
|
|
961
|
+
}, children: _jsx(SignInSimple, { loginUrl: signInLoginUrl, onSignIn: handleSignIn, onApiKeySignIn: handleApiKeySignIn, title: "Agent Node", description: "Sign in to configure node settings and run authenticated chat sessions.", leadingIcon: _jsx(Box, { sx: { color: cfg.brandColor, display: 'inline-flex' }, children: _jsx(HomeIcon, { size: 24 }) }) }) }) })), step === 'config' && (_jsxs(Box, { sx: {
|
|
592
962
|
border: '1px solid',
|
|
593
963
|
borderColor: 'border.default',
|
|
594
964
|
borderRadius: 2,
|
|
@@ -667,11 +1037,40 @@ export function AgentNode() {
|
|
|
667
1037
|
display: 'flex',
|
|
668
1038
|
flexDirection: 'column',
|
|
669
1039
|
gap: 2,
|
|
670
|
-
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0 }, children: "Share" }), _jsx(ShareAccessComponent, { isOpen: true, displayMode: "inline", requestUrl: `${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/sharing`, resourceLabel: "Agent Node", resourceName: "this Agent Node", onClose: handleSharingInlineClose })] })] }), error && _jsx(Text, { sx: { color: 'danger.fg' }, children: error }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: _jsx(Button, { variant: "primary", onClick: saveConfiguration, disabled: isSaving, children: isSaving ? 'Saving...' : 'Save' }) })] })), step === 'profile' && _jsx(AgentNodeProfileView, { token: token }), step === '
|
|
1040
|
+
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0 }, children: "Share" }), _jsx(ShareAccessComponent, { isOpen: true, displayMode: "inline", requestUrl: `${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/sharing`, resourceLabel: "Agent Node", resourceName: "this Agent Node", onClose: handleSharingInlineClose })] })] }), error && _jsx(Text, { sx: { color: 'danger.fg' }, children: error }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: _jsx(Button, { variant: "primary", onClick: saveConfiguration, disabled: isSaving, children: isSaving ? 'Saving...' : 'Save' }) })] })), step === 'profile' && (_jsx(AgentNodeProfileView, { token: token, onTokenExpired: handleSignOut })), step === 'gallery' && (_jsx(AgentNodeGallery, { baseUrl: AGENT_RUNTIMES_BASE_URL, token: token, activeAgentId: configuration.active_agent_id, onLaunchError: message => {
|
|
1041
|
+
showBanner('error', message);
|
|
1042
|
+
}, onLaunched: agentId => {
|
|
1043
|
+
setSelectedAgentId(agentId);
|
|
1044
|
+
const nextConfiguration = {
|
|
1045
|
+
...configuration,
|
|
1046
|
+
active_agent_id: agentId,
|
|
1047
|
+
mode: 'private',
|
|
1048
|
+
};
|
|
1049
|
+
setConfiguration(nextConfiguration);
|
|
1050
|
+
// Persist mode=private so Chat stays enabled and the node
|
|
1051
|
+
// advertises the active agent state to the sync loop.
|
|
1052
|
+
void fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/configuration`, {
|
|
1053
|
+
method: 'POST',
|
|
1054
|
+
headers: {
|
|
1055
|
+
'Content-Type': 'application/json',
|
|
1056
|
+
},
|
|
1057
|
+
body: JSON.stringify(nextConfiguration),
|
|
1058
|
+
}).catch(() => {
|
|
1059
|
+
// Best-effort persistence; local state already switched.
|
|
1060
|
+
});
|
|
1061
|
+
setStep('chat');
|
|
1062
|
+
}, onTerminated: () => {
|
|
1063
|
+
setSelectedAgentId('default');
|
|
1064
|
+
setConfiguration(prev => ({
|
|
1065
|
+
...prev,
|
|
1066
|
+
active_agent_id: undefined,
|
|
1067
|
+
}));
|
|
1068
|
+
setStep('gallery');
|
|
1069
|
+
} })), step === 'chat' && (_jsx(Box, { sx: {
|
|
671
1070
|
border: '1px solid',
|
|
672
1071
|
borderColor: 'border.default',
|
|
673
1072
|
borderRadius: 2,
|
|
674
1073
|
overflow: 'hidden',
|
|
675
|
-
}, children: _jsx(Chat, { protocol: "ag-ui", baseUrl: AGENT_RUNTIMES_BASE_URL, agentId:
|
|
1074
|
+
}, children: _jsx(Chat, { protocol: "ag-ui", baseUrl: AGENT_RUNTIMES_BASE_URL, agentId: selectedAgentId, title: chatTitle, placeholder: "Send a message...", description: chatDescription, suggestions: chatSuggestions, submitOnSuggestionClick: true, showHeader: true, height: '70vh', showModelSelector: true, showToolsMenu: true, showSkillsMenu: true, showTokenUsage: true, showInformation: true, autoFocus: true, enableEphemeralNotebook: true, enableEphemeralDocument: true, initialEphemeralSurfaceMode: "notebook", runtimeId: selectedAgentId, ephemeralRuntimeOverride: sandboxRuntimeOverride, kernelEnvironmentName: sandboxEnvironmentName, ephemeralNotebookCollaborationProvider: ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId: ephemeralCollaborationDocumentId, ephemeralDocumentCollaboration: ephemeralDocumentCollaboration, historyEndpoint: `${AGENT_RUNTIMES_BASE_URL}/api/v1/history` }) }))] })] }) }) }));
|
|
676
1075
|
}
|
|
677
1076
|
export default AgentNode;
|