@datalayer/agent-runtimes 1.0.18 → 1.0.20
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 +14 -6
- package/lib/AgentNode.js +567 -67
- package/lib/App.js +2 -2
- package/lib/agent-node/AgentNodeGallery.d.ts +80 -0
- package/lib/agent-node/AgentNodeGallery.js +627 -0
- package/lib/agent-notebook-main.js +0 -1
- package/lib/api/runtimes/runtimes.d.ts +57 -0
- package/lib/api/runtimes/runtimes.js +55 -1
- package/lib/chat/Chat.d.ts +14 -1
- package/lib/chat/Chat.js +22 -7
- package/lib/chat/base/ChatBase.js +86 -23
- package/lib/chat/document/EphemeralDocument.d.ts +49 -1
- package/lib/chat/document/EphemeralDocument.js +141 -15
- package/lib/chat/header/ChatHeaderBase.d.ts +3 -1
- package/lib/chat/header/ChatHeaderBase.js +5 -3
- package/lib/chat/messages/ChatMessageList.d.ts +19 -0
- package/lib/chat/messages/ChatMessageList.js +180 -9
- package/lib/chat/notebook/EphemeralNotebook.d.ts +40 -1
- package/lib/chat/notebook/EphemeralNotebook.js +49 -13
- package/lib/chat/prompt/InputPrompt.js +1 -1
- package/lib/chat/tools/SubagentActivity.d.ts +20 -0
- package/lib/chat/tools/SubagentActivity.js +154 -0
- package/lib/chat/tools/index.d.ts +1 -0
- package/lib/chat/tools/index.js +1 -0
- package/lib/chat/usage/TokenUsageBar.js +3 -3
- package/lib/client/AgentRuntimesClient.d.ts +5 -0
- package/lib/client/AgentsMixin.js +3 -1
- package/lib/client/mixins/RuntimesMixin.d.ts +12 -0
- package/lib/client/mixins/RuntimesMixin.js +24 -1
- package/lib/components/AgentSummary.d.ts +1 -0
- package/lib/components/AgentSummary.js +66 -10
- package/lib/components/runtimes/RuntimePickerBase.js +64 -29
- package/lib/components/runtimes/RuntimePickerCell.js +1 -1
- package/lib/components/runtimes/RuntimePickerNotebook.js +1 -1
- package/lib/components/runtimes/RuntimeSimplePicker.js +3 -2
- package/lib/components/runtimes/RuntimeUtils.js +2 -2
- package/lib/context/GraphFlowChart.js +1 -1
- package/lib/context/TokenUsageChart.js +1 -1
- package/lib/context/TurnGraphChart.js +2 -2
- package/lib/examples/A2UiAgentExample.js +4 -4
- package/lib/examples/A2UiComponentsGalleryExample.js +3 -3
- package/lib/examples/A2UiContactCardExample.js +3 -3
- package/lib/examples/A2UiRestaurantExample.js +2 -2
- package/lib/examples/A2UiViewerExample.js +3 -3
- package/lib/examples/AgUiAgenticExample.js +3 -3
- package/lib/examples/AgUiBackendToolRenderingExample.js +2 -2
- package/lib/examples/AgUiHaikuGenUiExample.js +3 -3
- package/lib/examples/AgUiHumanInTheLoopExample.js +5 -5
- package/lib/examples/AgUiSharedStateExample.js +3 -3
- package/lib/examples/AgUiToolsBasedGenUiExample.js +7 -5
- package/lib/examples/AgentCheckpointsExample.js +6 -5
- package/lib/examples/{AgentSandboxExample.d.ts → AgentCodeSandboxExample.d.ts} +4 -4
- package/lib/examples/{AgentSandboxExample.js → AgentCodeSandboxExample.js} +10 -9
- package/lib/examples/AgentCodeSandboxesExample.js +2 -2
- package/lib/examples/AgentCodemodeExample.js +3 -3
- package/lib/examples/AgentCompactionExample.d.ts +12 -0
- package/lib/examples/AgentCompactionExample.js +244 -0
- package/lib/examples/AgentEvalsExample.js +10 -20
- package/lib/examples/AgentGuardrailsExample.js +2 -2
- package/lib/examples/AgentHooksExample.js +1 -1
- package/lib/examples/AgentInferenceProviderExample.js +6 -8
- package/lib/examples/AgentLoopExample.js +3 -3
- package/lib/examples/AgentMCPExample.js +3 -3
- package/lib/examples/AgentMemoryExample.d.ts +4 -2
- package/lib/examples/AgentMemoryExample.js +93 -10
- package/lib/examples/AgentMonitoringExample.js +2 -2
- package/lib/examples/AgentNotificationsExample.js +1 -1
- package/lib/examples/AgentOtelExample.d.ts +2 -3
- package/lib/examples/AgentOtelExample.js +7 -12
- package/lib/examples/AgentOutputsExample.js +5 -5
- package/lib/examples/AgentParametersExample.js +3 -3
- package/lib/examples/AgentSkillsExample.js +4 -4
- package/lib/examples/AgentSubagentsExample.d.ts +2 -2
- package/lib/examples/AgentSubagentsExample.js +43 -34
- package/lib/examples/AgentToolApprovalsExample.js +1 -1
- package/lib/examples/AgentTriggersExample.js +9 -11
- package/lib/examples/AgentspecsExample.js +13 -5
- package/lib/examples/CellExample.d.ts +6 -0
- package/lib/examples/CellExample.js +84 -0
- package/lib/examples/ChatCustomExample.js +1 -1
- package/lib/examples/ChatExample.js +1 -1
- package/lib/examples/ChatStandaloneExample.js +2 -2
- package/lib/examples/HomeExample.js +29 -28
- package/lib/examples/Lexical2Example.js +2 -2
- package/lib/examples/LexicalAgentExample.js +1 -1
- package/lib/examples/LexicalAgentSidebarExample.js +2 -2
- package/lib/examples/NotebookAgentSidebarExample.d.ts +1 -1
- package/lib/examples/NotebookAgentSidebarExample.js +5 -4
- package/lib/examples/NotebookCollaborationExample.js +3 -3
- package/lib/examples/NotebookExample.js +2 -2
- package/lib/examples/components/ExampleWrapper.js +5 -0
- package/lib/examples/components/Header.js +2 -2
- package/lib/examples/components/HeaderControls.js +1 -1
- package/lib/examples/components/LexicalEditor.js +1 -1
- package/lib/examples/components/MainContent.js +1 -1
- package/lib/examples/components/MockFileBrowser.js +8 -8
- package/lib/examples/components/TimeTravel.js +1 -1
- package/lib/examples/components/haiku/HaikuDisplay.js +2 -2
- package/lib/examples/example-selector.js +4 -3
- package/lib/examples/hooks/useExampleAgentRuntime.js +11 -1
- package/lib/examples/hooks/useExampleAgentRuntimes.d.ts +7 -0
- package/lib/examples/hooks/useExampleAgentRuntimes.js +82 -0
- package/lib/examples/index.d.ts +2 -1
- package/lib/examples/index.js +2 -1
- package/lib/examples/main.js +359 -46
- package/lib/examples/utils/agentSummaryStore.d.ts +17 -0
- package/lib/examples/utils/themedProvider.js +32 -2
- package/lib/examples/utils/useExampleAgentRuntimesUrl.js +2 -1
- package/lib/examples2/NotebookMutationsKernel.js +6 -3
- package/lib/examples2/NotebookMutationsServiceManager.js +6 -3
- package/lib/examples2/ReactRouterExample.js +5 -5
- package/lib/examples2/main.js +38 -0
- package/lib/hooks/useAIAgentsWebSocket.js +35 -0
- package/lib/hooks/useAgentRuntimes.d.ts +22 -0
- package/lib/hooks/useAgentRuntimes.js +192 -5
- package/lib/hooks/useCheckpoints.js +2 -2
- package/lib/hooks/useToolApprovals.d.ts +10 -4
- package/lib/hooks/useToolApprovals.js +11 -5
- package/lib/identity/AgentIdentity.js +1 -2
- package/lib/models/Runtime.d.ts +10 -8
- package/lib/models/RuntimeDTO.d.ts +5 -3
- package/lib/models/RuntimeDTO.js +3 -3
- package/lib/runtimes/actions.js +3 -1
- package/lib/runtimes/apis.d.ts +7 -0
- package/lib/shims/primerAddonsBox.d.ts +1 -0
- package/lib/shims/primerAddonsBox.js +7 -0
- package/lib/specs/agents/agents.d.ts +51 -0
- package/lib/specs/agents/agents.js +3389 -94
- package/lib/specs/models.d.ts +2 -0
- package/lib/specs/models.js +37 -0
- package/lib/stores/agentRuntimeStore.d.ts +32 -1
- package/lib/stores/agentRuntimeStore.js +65 -1
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/types/agents.d.ts +9 -3
- package/lib/types/agentspecs.d.ts +2 -0
- package/lib/types/chat.d.ts +83 -0
- package/lib/types/models.d.ts +5 -0
- package/lib/types/stream.d.ts +58 -1
- package/package.json +6 -5
- package/scripts/codegen/generate_agents.py +7 -0
- package/scripts/codegen/generate_models.py +22 -9
- package/scripts/gen_workers.py +340 -0
- package/lib/examples/CellSimpleExample.d.ts +0 -6
- package/lib/examples/CellSimpleExample.js +0 -20
- package/scripts/codegen/__pycache__/generate_sandbox_agents.cpython-313.pyc +0 -0
package/lib/AgentNode.js
CHANGED
|
@@ -3,25 +3,116 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
7
7
|
import { useQueryClient } from '@tanstack/react-query';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { AiAgentIcon, AlienIcon } from '@datalayer/icons-react';
|
|
9
|
+
import { DatalayerLogoText, DatalayerThemeProvider, getColorPalette, getLogoColors, setupPrimerPortals, themeConfigs, } from '@datalayer/primer-addons';
|
|
10
|
+
import { AppearanceMenu } from '@datalayer/primer-addons/lib/components/appearance';
|
|
11
|
+
import { ActionList, ActionMenu, Box, Button, Heading, Label, PageHeader, PageLayout, Text, } from '@primer/react';
|
|
12
|
+
import { CommentIcon, HomeIcon, KeyAsteriskIcon, LockIcon, MoonIcon, PeopleIcon, PersonIcon, GearIcon, SignOutIcon, } from '@primer/octicons-react';
|
|
12
13
|
import { SignInSimple } from '@datalayer/core/lib/views/iam';
|
|
14
|
+
import { UserAvatar } from '@datalayer/core/lib/components/avatars';
|
|
13
15
|
import { UserBadge } from '@datalayer/core/lib/views/profile';
|
|
14
16
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
15
17
|
import { useIAMStore } from '@datalayer/core/lib/state';
|
|
16
18
|
import { BillingEntitySelect, } from '@datalayer/core/lib/components/billing';
|
|
17
19
|
import { ShareAccessComponent } from '@datalayer/core/lib/components/sharing';
|
|
18
20
|
import { useAgentNodeThemeStore } from './agent-node/themeStore';
|
|
21
|
+
import { AgentNodeGallery } from './agent-node/AgentNodeGallery';
|
|
19
22
|
import { Chat } from './chat';
|
|
23
|
+
import { DatalayerCollaborationProvider } from './collaboration';
|
|
20
24
|
import '../style/primer-primitives.css';
|
|
21
25
|
setupPrimerPortals();
|
|
22
26
|
const AGENT_RUNTIMES_BASE_URL = (import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL ||
|
|
23
27
|
import.meta.env.VITE_BASE_URL ||
|
|
24
28
|
window.location.origin).replace(/\/$/, '');
|
|
29
|
+
/**
|
|
30
|
+
* LOCAL Jupyter sandbox endpoint shared by the node's agent and the chat's
|
|
31
|
+
* ephemeral notebook/document surfaces. When set (node-local dev, injected via
|
|
32
|
+
* `VITE_JUPYTER_SANDBOX_URL`), the surfaces bind their kernel straight to this
|
|
33
|
+
* Jupyter server — no proxy or tunnel — exactly like `NotebookAgentExample`.
|
|
34
|
+
* Parsed once into a clean base URL + token for `ServerConnection`.
|
|
35
|
+
*/
|
|
36
|
+
const LOCAL_JUPYTER_SANDBOX = (() => {
|
|
37
|
+
const raw = String(import.meta.env.VITE_JUPYTER_SANDBOX_URL || '').trim();
|
|
38
|
+
if (!raw) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const url = new URL(raw);
|
|
43
|
+
const token = url.searchParams.get('token') || undefined;
|
|
44
|
+
url.searchParams.delete('token');
|
|
45
|
+
const baseUrl = `${url.origin}${url.pathname}`.replace(/\/$/, '');
|
|
46
|
+
return { baseUrl, token };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { baseUrl: raw.replace(/\/$/, ''), token: undefined };
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
const DEFAULT_DATALAYER_URL = 'https://prod1.datalayer.run';
|
|
53
|
+
/**
|
|
54
|
+
* localStorage key recording that the user explicitly signed out. When set, the
|
|
55
|
+
* UI must not silently re-authenticate from the env-supplied DATALAYER_API_KEY
|
|
56
|
+
* via /auth/bootstrap, so the sign-out survives a page refresh.
|
|
57
|
+
*/
|
|
58
|
+
const AUTO_BOOTSTRAP_DISABLED_KEY = 'agent-node-auto-bootstrap-disabled';
|
|
59
|
+
/** Persist (or clear) the signed-out intent in localStorage. */
|
|
60
|
+
function setAutoBootstrapDisabled(disabled) {
|
|
61
|
+
try {
|
|
62
|
+
if (disabled) {
|
|
63
|
+
window.localStorage.setItem(AUTO_BOOTSTRAP_DISABLED_KEY, 'true');
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
window.localStorage.removeItem(AUTO_BOOTSTRAP_DISABLED_KEY);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Ignore storage failures (private mode, disabled storage, etc.).
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Read a service URL from the server-injected `datalayer-config-data` script
|
|
75
|
+
* tag. The agent-runtimes Python server injects this tag (populated from the
|
|
76
|
+
* `DATALAYER_*_URL` environment variables, e.g. from `plane local`) when it
|
|
77
|
+
* serves the built agent pages. Reading it at runtime lets the node target the
|
|
78
|
+
* configured services (IAM, runtimes, ...) instead of the build-time-baked
|
|
79
|
+
* `VITE_DATALAYER_URL`, which defaults to production because `make build` runs
|
|
80
|
+
* without the local environment.
|
|
81
|
+
*/
|
|
82
|
+
const getConfigUrlFromDocument = (...keys) => {
|
|
83
|
+
if (typeof document === 'undefined') {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const el = document.getElementById('datalayer-config-data');
|
|
87
|
+
if (!el?.textContent) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const config = JSON.parse(el.textContent);
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
const value = config?.[key];
|
|
94
|
+
if (typeof value === 'string' && value.trim()) {
|
|
95
|
+
return value.replace(/\/$/, '');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Ignore malformed config; fall back to env / default.
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
};
|
|
104
|
+
/** Resolve the base Datalayer URL: injected config → VITE env → production. */
|
|
105
|
+
const resolveDatalayerUrl = () => getConfigUrlFromDocument('datalayerUrl', 'iamUrl') ||
|
|
106
|
+
import.meta.env?.VITE_DATALAYER_URL ||
|
|
107
|
+
DEFAULT_DATALAYER_URL;
|
|
108
|
+
/** Resolve the runtimes URL: injected config → VITE env → base Datalayer URL. */
|
|
109
|
+
const resolveRuntimesUrl = () => getConfigUrlFromDocument('runtimesUrl') ||
|
|
110
|
+
import.meta.env?.VITE_DATALAYER_RUNTIMES_URL ||
|
|
111
|
+
resolveDatalayerUrl();
|
|
112
|
+
/** Resolve the spacer URL: injected config → VITE env → base Datalayer URL. */
|
|
113
|
+
const resolveSpacerUrl = () => getConfigUrlFromDocument('spacerUrl') ||
|
|
114
|
+
import.meta.env?.VITE_DATALAYER_SPACER_URL ||
|
|
115
|
+
resolveDatalayerUrl();
|
|
25
116
|
const MODE_CARDS = [
|
|
26
117
|
{
|
|
27
118
|
mode: 'private',
|
|
@@ -52,21 +143,33 @@ const DEFAULT_CONFIGURATION = {
|
|
|
52
143
|
* but assembled exclusively from core building blocks so agent-runtimes does
|
|
53
144
|
* not pull in the `ui` package.
|
|
54
145
|
*/
|
|
55
|
-
function AgentNodeProfileView({ token }) {
|
|
146
|
+
function AgentNodeProfileView({ token, onTokenExpired, avatarFallbackBackground, avatarFallbackForeground, }) {
|
|
56
147
|
const user = useIAMStore(state => state.user);
|
|
148
|
+
const hasRealAvatar = (url) => {
|
|
149
|
+
if (!url) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (url.startsWith('https://www.gravatar.com/avatar')) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
};
|
|
57
157
|
const display = useMemo(() => {
|
|
58
158
|
if (!user)
|
|
59
159
|
return null;
|
|
60
160
|
const u = user;
|
|
61
|
-
const displayName =
|
|
62
|
-
u.
|
|
63
|
-
u.
|
|
64
|
-
u.
|
|
65
|
-
u.
|
|
66
|
-
|
|
161
|
+
const displayName = u.display_name ||
|
|
162
|
+
u.displayName ||
|
|
163
|
+
u.profile?.display_name ||
|
|
164
|
+
u.profile?.displayName ||
|
|
165
|
+
u.full_name ||
|
|
166
|
+
u.fullName ||
|
|
167
|
+
[u.first_name, u.last_name].filter(Boolean).join(' ').trim() ||
|
|
168
|
+
'';
|
|
67
169
|
const username = u.username || u.handle || (u.email ? String(u.email).split('@')[0] : '');
|
|
170
|
+
const headingDisplayName = displayName || username || u.email || 'Datalayer user';
|
|
68
171
|
const initials = u.initials ||
|
|
69
|
-
String(
|
|
172
|
+
String(headingDisplayName)
|
|
70
173
|
.split(/\s+/)
|
|
71
174
|
.filter(Boolean)
|
|
72
175
|
.slice(0, 2)
|
|
@@ -76,6 +179,7 @@ function AgentNodeProfileView({ token }) {
|
|
|
76
179
|
id: String(u.id || u.uid || ''),
|
|
77
180
|
username,
|
|
78
181
|
displayName,
|
|
182
|
+
headingDisplayName,
|
|
79
183
|
initials,
|
|
80
184
|
origin: String(u.origin || 'datalayer'),
|
|
81
185
|
handle: u.handle ? `@${u.handle}` : '',
|
|
@@ -92,29 +196,52 @@ function AgentNodeProfileView({ token }) {
|
|
|
92
196
|
display: 'flex',
|
|
93
197
|
flexDirection: 'column',
|
|
94
198
|
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: "
|
|
96
|
-
width: 72,
|
|
97
|
-
height: 72,
|
|
98
|
-
borderRadius: '50%',
|
|
99
|
-
bg: 'canvas.subtle',
|
|
100
|
-
display: 'inline-flex',
|
|
101
|
-
alignItems: 'center',
|
|
102
|
-
justifyContent: 'center',
|
|
103
|
-
color: 'fg.muted',
|
|
104
|
-
}, children: _jsx(PersonIcon, { size: 30 }) })), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Heading, { sx: { fontSize: 3, mb: 1 }, children: display.displayName }), display.handle && (_jsx(Text, { sx: { color: 'fg.muted' }, children: display.handle })), display.email && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: display.email }))] })] }), display.id && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(Label, { size: "large", variant: "secondary", children: display.id }) })), _jsxs(Box, { sx: {
|
|
199
|
+
}, 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: "Profile" }), _jsxs(Box, { sx: {
|
|
105
200
|
display: 'grid',
|
|
106
|
-
gridTemplateColumns:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
201
|
+
gridTemplateColumns: [
|
|
202
|
+
'1fr',
|
|
203
|
+
null,
|
|
204
|
+
'minmax(280px, 1fr) minmax(320px, 2fr)',
|
|
205
|
+
],
|
|
206
|
+
gap: 4,
|
|
207
|
+
alignItems: 'start',
|
|
208
|
+
}, children: [_jsxs(Box, { sx: {
|
|
209
|
+
border: '1px solid',
|
|
210
|
+
borderColor: 'border.default',
|
|
211
|
+
borderRadius: 2,
|
|
212
|
+
p: 3,
|
|
213
|
+
display: 'flex',
|
|
214
|
+
flexDirection: 'column',
|
|
215
|
+
gap: 3,
|
|
216
|
+
}, children: [_jsx(Box, { sx: { textAlign: 'left' }, children: _jsx(UserBadge, { token: token, variant: "small", onTokenExpired: onTokenExpired }) }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 3 }, children: [hasRealAvatar(display.avatarUrl) ? (_jsx(UserAvatar, { avatarUrl: display.avatarUrl, size: 72, square: false })) : (_jsx(Box, { sx: {
|
|
217
|
+
width: 72,
|
|
218
|
+
height: 72,
|
|
219
|
+
borderRadius: '50%',
|
|
220
|
+
bg: avatarFallbackBackground || 'accent.subtle',
|
|
221
|
+
display: 'flex',
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
justifyContent: 'center',
|
|
224
|
+
'--datalayer-icon-fg': avatarFallbackForeground || 'accent.fg',
|
|
225
|
+
}, children: _jsx(AlienIcon, { size: 34, themed: true, colormode: true }) })), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Heading, { sx: { fontSize: 3, mb: 1 }, children: display.headingDisplayName }), display.handle && (_jsx(Text, { sx: { color: 'fg.muted' }, children: display.handle }))] })] }), display.id && (_jsx(Box, { children: _jsx(Label, { size: "large", variant: "secondary", children: display.id }) }))] }), _jsxs(Box, { sx: {
|
|
226
|
+
border: '1px solid',
|
|
227
|
+
borderColor: 'border.default',
|
|
228
|
+
borderRadius: 2,
|
|
229
|
+
p: 3,
|
|
230
|
+
display: 'grid',
|
|
231
|
+
gridTemplateColumns: '1fr 2fr',
|
|
232
|
+
rowGap: 2,
|
|
233
|
+
columnGap: 3,
|
|
234
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'bold' }, children: "Username" }), _jsx(Text, { children: display.username || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Display name" }), _jsx(Text, { children: display.displayName || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Email" }), _jsx(Text, { children: display.email || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Initials" }), _jsx(Text, { children: display.initials || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Origin" }), _jsx(Text, { children: display.origin || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Roles" }), _jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: display.roles.length > 0 ? (display.roles.map(role => (_jsx(Label, { size: "small", variant: "secondary", children: role }, role)))) : (_jsx(Text, { children: "-" })) })] })] })] })) }));
|
|
111
235
|
}
|
|
112
236
|
export function AgentNode() {
|
|
113
237
|
const { token, setAuth, clearAuth } = useSimpleAuthStore();
|
|
114
238
|
const tokenForCore = token ?? undefined;
|
|
239
|
+
const signInLoginUrl = `${resolveDatalayerUrl()}/api/iam/v1/login`;
|
|
115
240
|
const queryClient = useQueryClient();
|
|
116
241
|
const iamUser = useIAMStore(state => state.user);
|
|
117
242
|
const { colorMode, theme: themeVariant } = useAgentNodeThemeStore();
|
|
243
|
+
const setColorMode = useAgentNodeThemeStore(state => state.setColorMode);
|
|
244
|
+
const setTheme = useAgentNodeThemeStore(state => state.setTheme);
|
|
118
245
|
const cfg = themeConfigs[themeVariant];
|
|
119
246
|
const logoColors = getLogoColors(themeVariant, colorMode);
|
|
120
247
|
const resolvedMode = colorMode === 'auto'
|
|
@@ -125,16 +252,243 @@ export function AgentNode() {
|
|
|
125
252
|
: colorMode === 'dark'
|
|
126
253
|
? 'dark'
|
|
127
254
|
: 'light';
|
|
255
|
+
const avatarPalette = getColorPalette(themeVariant, resolvedMode);
|
|
128
256
|
const authGradient = cfg.cardGradient[resolvedMode];
|
|
129
257
|
const [step, setStep] = useState('auth');
|
|
130
|
-
const [
|
|
258
|
+
const [selectedAgentId, setSelectedAgentId] = useState('default');
|
|
259
|
+
// Persisted across refreshes: once the user explicitly signs out we must
|
|
260
|
+
// not silently re-authenticate them from the env-supplied DATALAYER_API_KEY
|
|
261
|
+
// via /auth/bootstrap. Initialise from localStorage so the intent survives a
|
|
262
|
+
// page reload (React state alone resets to false on refresh).
|
|
263
|
+
const [disableAutoBootstrap, setDisableAutoBootstrap] = useState(() => {
|
|
264
|
+
try {
|
|
265
|
+
return (window.localStorage.getItem(AUTO_BOOTSTRAP_DISABLED_KEY) === 'true');
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
131
271
|
const [configuration, setConfiguration] = useState(DEFAULT_CONFIGURATION);
|
|
132
272
|
const [, setSelectedBillingEntity] = useState(undefined);
|
|
133
273
|
const [isSaving, setIsSaving] = useState(false);
|
|
134
274
|
const [error, setError] = useState(null);
|
|
275
|
+
// ── Ephemeral notebook RTC (shared room with the SaaS UI) ───────────────
|
|
276
|
+
// The node provisions a spacer notebook room on registration and stores its
|
|
277
|
+
// uid in the configuration. When present, both the node-local chat and the
|
|
278
|
+
// SaaS gallery view join that room so the agent's notebook edits transit via
|
|
279
|
+
// RTC instead of the tunnel. Absent (e.g. before sign-in) the ephemeral
|
|
280
|
+
// notebook stays in-memory, unchanged.
|
|
281
|
+
const ephemeralCollaborationDocumentId = token && configuration.collaboration_notebook_uid
|
|
282
|
+
? configuration.collaboration_notebook_uid
|
|
283
|
+
: undefined;
|
|
284
|
+
const ephemeralNotebookCollaborationProvider = useMemo(() => {
|
|
285
|
+
if (!ephemeralCollaborationDocumentId || !token) {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
const spacerUrl = resolveSpacerUrl();
|
|
289
|
+
return new DatalayerCollaborationProvider({
|
|
290
|
+
datalayerUrl: spacerUrl,
|
|
291
|
+
token,
|
|
292
|
+
});
|
|
293
|
+
}, [ephemeralCollaborationDocumentId, token]);
|
|
294
|
+
// ── Ephemeral document RTC (shared Lexical/Loro room with the SaaS UI) ────
|
|
295
|
+
// The node provisions a spacer *lexical* room (distinct from the notebook
|
|
296
|
+
// room) on registration and stores its uid as `collaboration_document_uid`.
|
|
297
|
+
// Both peers join that Loro room over the spacer lexical WebSocket. The token
|
|
298
|
+
// is embedded in the ws URL (createWebsocketProvider forwards it as a query
|
|
299
|
+
// param) so the spacer authenticates the connection.
|
|
300
|
+
const collaborationDocumentUser = useIAMStore(state => state.user);
|
|
301
|
+
const ephemeralDocumentCollaboration = useMemo(() => {
|
|
302
|
+
const documentRoomId = configuration.collaboration_document_uid;
|
|
303
|
+
if (!token || !documentRoomId) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const spacerUrl = resolveSpacerUrl();
|
|
307
|
+
const wsSpacer = String(spacerUrl).replace(/^http/, 'ws');
|
|
308
|
+
const websocketUrl = `${wsSpacer}/api/spacer/v1/lexical/ws?token=${encodeURIComponent(token)}`;
|
|
309
|
+
const u = collaborationDocumentUser;
|
|
310
|
+
return {
|
|
311
|
+
websocketUrl,
|
|
312
|
+
roomId: documentRoomId,
|
|
313
|
+
identity: u
|
|
314
|
+
? {
|
|
315
|
+
userId: u.uid,
|
|
316
|
+
handle: u.handle,
|
|
317
|
+
displayName: u.displayName,
|
|
318
|
+
initials: u.initials,
|
|
319
|
+
avatarUrl: u.avatarUrl,
|
|
320
|
+
}
|
|
321
|
+
: undefined,
|
|
322
|
+
};
|
|
323
|
+
}, [
|
|
324
|
+
configuration.collaboration_document_uid,
|
|
325
|
+
token,
|
|
326
|
+
collaborationDocumentUser,
|
|
327
|
+
]);
|
|
135
328
|
const [inferenceProvider, setInferenceProvider] = useState('datalayer');
|
|
136
329
|
const [inferenceModels, setInferenceModels] = useState([]);
|
|
137
330
|
const [inferenceDefaultModel, setInferenceDefaultModel] = useState(null);
|
|
331
|
+
const [configurationLoaded, setConfigurationLoaded] = useState(false);
|
|
332
|
+
const [isActiveAgentRunning, setIsActiveAgentRunning] = useState(false);
|
|
333
|
+
const chatRestoreAttemptRef = useRef('');
|
|
334
|
+
const hasActiveAgent = Boolean(String(configuration.active_agent_id || '').trim()) &&
|
|
335
|
+
isActiveAgentRunning;
|
|
336
|
+
const isSaasOnlyChat = configuration.chat_access_mode === 'saas_only' ||
|
|
337
|
+
configuration.deployment_target === 'aws';
|
|
338
|
+
// ── Local sandbox runtime override for the ephemeral surfaces ─────────────
|
|
339
|
+
// In the node-local webapp the agent's Jupyter sandbox is a LOCAL server, not
|
|
340
|
+
// a Kubernetes pod resolvable by `pod_name`, so the ephemeral notebook and
|
|
341
|
+
// document surfaces cannot bind their kernel through the runtimes pod lookup.
|
|
342
|
+
// Instead we read the live sandbox Jupyter endpoint from `/health/startup`
|
|
343
|
+
// and pass it as an explicit override so the surfaces connect a service
|
|
344
|
+
// manager straight to the sandbox kernel. This is what makes the surfaces
|
|
345
|
+
// actually render and the chat header's kernel indicator reflect the sandbox
|
|
346
|
+
// kernel (rather than the browser/Pyodide placeholder).
|
|
347
|
+
const [sandboxRuntimeOverride, setSandboxRuntimeOverride] = useState(undefined);
|
|
348
|
+
// Environment name shown in the chat header's kernel indicator details, so
|
|
349
|
+
// it reads e.g. "Local Agent Sandbox (python3)" instead of the indicator's
|
|
350
|
+
// generic "browser-runtime" placeholder once the local sandbox is bound.
|
|
351
|
+
const [sandboxEnvironmentName, setSandboxEnvironmentName] = useState(undefined);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
const selected = String(selectedAgentId || '').trim();
|
|
354
|
+
if (step !== 'chat' || !selected || selected === 'default') {
|
|
355
|
+
setSandboxRuntimeOverride(undefined);
|
|
356
|
+
setSandboxEnvironmentName(undefined);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// Fast path: in node-local dev the agent's Jupyter sandbox is a LOCAL
|
|
360
|
+
// server whose URL/token are known up front (VITE_JUPYTER_SANDBOX_URL).
|
|
361
|
+
// Bind the ephemeral surfaces straight to it — no /health/startup polling,
|
|
362
|
+
// no proxy, no tunnel — mirroring NotebookAgentExample.
|
|
363
|
+
if (LOCAL_JUPYTER_SANDBOX?.baseUrl) {
|
|
364
|
+
setSandboxEnvironmentName('Local Agent Sandbox');
|
|
365
|
+
setSandboxRuntimeOverride(prev => {
|
|
366
|
+
if (prev?.baseUrl === LOCAL_JUPYTER_SANDBOX.baseUrl &&
|
|
367
|
+
(prev?.token || '') === (LOCAL_JUPYTER_SANDBOX.token || '')) {
|
|
368
|
+
return prev;
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
baseUrl: LOCAL_JUPYTER_SANDBOX.baseUrl,
|
|
372
|
+
token: LOCAL_JUPYTER_SANDBOX.token || undefined,
|
|
373
|
+
podName: 'agent-node-sandbox',
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
let cancelled = false;
|
|
379
|
+
let intervalId = null;
|
|
380
|
+
const stopPolling = () => {
|
|
381
|
+
if (intervalId !== null) {
|
|
382
|
+
window.clearInterval(intervalId);
|
|
383
|
+
intervalId = null;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const poll = async () => {
|
|
387
|
+
try {
|
|
388
|
+
const resp = await fetch(`${AGENT_RUNTIMES_BASE_URL}/health/startup`);
|
|
389
|
+
if (!resp.ok) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const payload = await resp.json();
|
|
393
|
+
const sandbox = payload?.sandbox;
|
|
394
|
+
const jupyterUrl = String(sandbox?.jupyter_url || '').trim();
|
|
395
|
+
if (sandbox?.variant !== 'jupyter' || !jupyterUrl) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const jupyterToken = String(sandbox?.jupyter_token || '').trim();
|
|
399
|
+
if (cancelled) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const kernelName = String(sandbox?.kernel_name || '').trim();
|
|
403
|
+
setSandboxEnvironmentName(kernelName
|
|
404
|
+
? `Local Agent Sandbox (${kernelName})`
|
|
405
|
+
: 'Local Agent Sandbox');
|
|
406
|
+
setSandboxRuntimeOverride(prev => {
|
|
407
|
+
if (prev?.baseUrl === jupyterUrl &&
|
|
408
|
+
(prev?.token || '') === jupyterToken) {
|
|
409
|
+
return prev;
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
baseUrl: jupyterUrl,
|
|
413
|
+
token: jupyterToken || undefined,
|
|
414
|
+
podName: 'agent-node-sandbox',
|
|
415
|
+
};
|
|
416
|
+
});
|
|
417
|
+
// Endpoint resolved — stop the fast poll.
|
|
418
|
+
stopPolling();
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// Sandbox not ready yet; keep polling.
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
void poll();
|
|
425
|
+
intervalId = window.setInterval(() => {
|
|
426
|
+
void poll();
|
|
427
|
+
}, 2000);
|
|
428
|
+
return () => {
|
|
429
|
+
cancelled = true;
|
|
430
|
+
stopPolling();
|
|
431
|
+
};
|
|
432
|
+
}, [step, selectedAgentId]);
|
|
433
|
+
// ── Agent spec info for the chat header (title/description/suggestions) ──
|
|
434
|
+
// Mirrors the `ui` runtime chat view: the chat header shows the agent spec's
|
|
435
|
+
// display name and welcome message, and the empty state offers the spec's
|
|
436
|
+
// suggested prompts. The node-local agent is launched from a library spec
|
|
437
|
+
// whose id is used as the agent id (see AgentNodeGallery `launch`), so the
|
|
438
|
+
// spec is looked up by that same id via `GET /api/v1/agents/library/{id}`.
|
|
439
|
+
const [agentSpecInfo, setAgentSpecInfo] = useState(null);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
const agentId = String(selectedAgentId || '').trim();
|
|
442
|
+
if (step !== 'chat' || !agentId || agentId === 'default') {
|
|
443
|
+
setAgentSpecInfo(null);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
let cancelled = false;
|
|
447
|
+
(async () => {
|
|
448
|
+
try {
|
|
449
|
+
const resp = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agents/library/${encodeURIComponent(agentId)}`, {
|
|
450
|
+
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
451
|
+
});
|
|
452
|
+
if (!resp.ok) {
|
|
453
|
+
if (!cancelled) {
|
|
454
|
+
setAgentSpecInfo(null);
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const spec = await resp.json().catch(() => null);
|
|
459
|
+
if (cancelled || !spec) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
setAgentSpecInfo({
|
|
463
|
+
name: typeof spec.name === 'string' ? spec.name : undefined,
|
|
464
|
+
description: typeof spec.description === 'string' ? spec.description : undefined,
|
|
465
|
+
welcomeMessage: typeof spec.welcomeMessage === 'string'
|
|
466
|
+
? spec.welcomeMessage
|
|
467
|
+
: typeof spec.welcome_message === 'string'
|
|
468
|
+
? spec.welcome_message
|
|
469
|
+
: undefined,
|
|
470
|
+
suggestions: Array.isArray(spec.suggestions)
|
|
471
|
+
? spec.suggestions.filter((s) => typeof s === 'string')
|
|
472
|
+
: undefined,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
if (!cancelled) {
|
|
477
|
+
setAgentSpecInfo(null);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
})();
|
|
481
|
+
return () => {
|
|
482
|
+
cancelled = true;
|
|
483
|
+
};
|
|
484
|
+
}, [step, selectedAgentId, token]);
|
|
485
|
+
const chatTitle = agentSpecInfo?.name || 'Agent Node Chat';
|
|
486
|
+
const chatDescription = agentSpecInfo?.welcomeMessage ||
|
|
487
|
+
agentSpecInfo?.description ||
|
|
488
|
+
'Node-local chat';
|
|
489
|
+
const chatSuggestions = agentSpecInfo?.suggestions && agentSpecInfo.suggestions.length > 0
|
|
490
|
+
? agentSpecInfo.suggestions.map(s => ({ title: s, message: s }))
|
|
491
|
+
: undefined;
|
|
138
492
|
const [banner, setBanner] = useState(null);
|
|
139
493
|
const bannerTimerRef = useRef(null);
|
|
140
494
|
const showBanner = useCallback((kind, message) => {
|
|
@@ -158,9 +512,7 @@ export function AgentNode() {
|
|
|
158
512
|
};
|
|
159
513
|
}, []);
|
|
160
514
|
const pushCredentials = useCallback((authToken, payloadToken = authToken) => {
|
|
161
|
-
const
|
|
162
|
-
'https://prod1.datalayer.run';
|
|
163
|
-
const runtimesUrl = import.meta.env?.VITE_DATALAYER_RUNTIMES_URL || datalayerUrl;
|
|
515
|
+
const runtimesUrl = resolveRuntimesUrl();
|
|
164
516
|
const headers = {
|
|
165
517
|
'Content-Type': 'application/json',
|
|
166
518
|
};
|
|
@@ -213,6 +565,7 @@ export function AgentNode() {
|
|
|
213
565
|
try {
|
|
214
566
|
const response = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/configuration`);
|
|
215
567
|
if (!response.ok) {
|
|
568
|
+
setConfigurationLoaded(true);
|
|
216
569
|
return;
|
|
217
570
|
}
|
|
218
571
|
const payload = await response.json();
|
|
@@ -221,10 +574,16 @@ export function AgentNode() {
|
|
|
221
574
|
...(payload?.configuration || {}),
|
|
222
575
|
};
|
|
223
576
|
setConfiguration(loadedConfiguration);
|
|
577
|
+
if (loadedConfiguration.active_agent_id) {
|
|
578
|
+
setSelectedAgentId(loadedConfiguration.active_agent_id);
|
|
579
|
+
}
|
|
224
580
|
}
|
|
225
581
|
catch {
|
|
226
582
|
// Ignore initial-load failures in local development.
|
|
227
583
|
}
|
|
584
|
+
finally {
|
|
585
|
+
setConfigurationLoaded(true);
|
|
586
|
+
}
|
|
228
587
|
};
|
|
229
588
|
load();
|
|
230
589
|
}, []);
|
|
@@ -319,7 +678,7 @@ export function AgentNode() {
|
|
|
319
678
|
}
|
|
320
679
|
if (data?.has_key && data?.token) {
|
|
321
680
|
setAuth(String(data.token), String(data.handle || 'api-key-user'));
|
|
322
|
-
setStep('
|
|
681
|
+
setStep('gallery');
|
|
323
682
|
}
|
|
324
683
|
}
|
|
325
684
|
catch {
|
|
@@ -343,10 +702,10 @@ export function AgentNode() {
|
|
|
343
702
|
}, [token, pushCredentials]);
|
|
344
703
|
useEffect(() => {
|
|
345
704
|
import('@datalayer/core/lib/state').then(({ iamStore, coreStore }) => {
|
|
346
|
-
const datalayerUrl =
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
705
|
+
const datalayerUrl = resolveDatalayerUrl();
|
|
706
|
+
const runtimesUrl = resolveRuntimesUrl();
|
|
707
|
+
const aiInferenceUrl = getConfigUrlFromDocument('aiInferenceUrl') ||
|
|
708
|
+
import.meta.env?.VITE_DATALAYER_AI_INFERENCE_URL ||
|
|
350
709
|
datalayerUrl;
|
|
351
710
|
// Seed all per-service URLs to match the main UI login behavior.
|
|
352
711
|
const coreApi = coreStore.getState();
|
|
@@ -387,11 +746,75 @@ export function AgentNode() {
|
|
|
387
746
|
setStep('auth');
|
|
388
747
|
}
|
|
389
748
|
}, [token, step]);
|
|
749
|
+
useEffect(() => {
|
|
750
|
+
if (!token || !String(configuration.active_agent_id || '').trim()) {
|
|
751
|
+
setIsActiveAgentRunning(false);
|
|
752
|
+
}
|
|
753
|
+
}, [token, configuration.active_agent_id]);
|
|
754
|
+
useEffect(() => {
|
|
755
|
+
if (!token || !configurationLoaded) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const activeAgentId = (configuration.active_agent_id || '').trim();
|
|
759
|
+
if (!activeAgentId) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const restoreKey = `${activeAgentId}`;
|
|
763
|
+
if (chatRestoreAttemptRef.current === restoreKey) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
chatRestoreAttemptRef.current = restoreKey;
|
|
767
|
+
let cancelled = false;
|
|
768
|
+
(async () => {
|
|
769
|
+
try {
|
|
770
|
+
const response = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agents`, {
|
|
771
|
+
headers: {
|
|
772
|
+
Authorization: `Bearer ${token}`,
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
if (!response.ok) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const payload = await response.json().catch(() => null);
|
|
779
|
+
if (cancelled) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const agents = Array.isArray(payload)
|
|
783
|
+
? payload
|
|
784
|
+
: payload?.agents || payload?.items || [];
|
|
785
|
+
const running = agents.some(agent => {
|
|
786
|
+
const id = String(agent?.agent_id || agent?.id || '').trim();
|
|
787
|
+
return id === activeAgentId;
|
|
788
|
+
});
|
|
789
|
+
setIsActiveAgentRunning(running);
|
|
790
|
+
if (running) {
|
|
791
|
+
setSelectedAgentId(activeAgentId);
|
|
792
|
+
setConfiguration(prev => prev.mode === 'private' ? prev : { ...prev, mode: 'private' });
|
|
793
|
+
setStep('chat');
|
|
794
|
+
}
|
|
795
|
+
else if (selectedAgentId === activeAgentId) {
|
|
796
|
+
setSelectedAgentId('default');
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
setIsActiveAgentRunning(false);
|
|
801
|
+
// Best-effort restore only.
|
|
802
|
+
}
|
|
803
|
+
})();
|
|
804
|
+
return () => {
|
|
805
|
+
cancelled = true;
|
|
806
|
+
};
|
|
807
|
+
}, [
|
|
808
|
+
token,
|
|
809
|
+
configurationLoaded,
|
|
810
|
+
configuration.active_agent_id,
|
|
811
|
+
selectedAgentId,
|
|
812
|
+
]);
|
|
390
813
|
useEffect(() => {
|
|
391
814
|
// If a persisted session is already authenticated on first load,
|
|
392
|
-
// skip the auth screen and land on the
|
|
815
|
+
// skip the auth screen and land on the agents gallery.
|
|
393
816
|
if (token && step === 'auth') {
|
|
394
|
-
setStep('
|
|
817
|
+
setStep('gallery');
|
|
395
818
|
}
|
|
396
819
|
}, [token, step]);
|
|
397
820
|
useEffect(() => {
|
|
@@ -399,16 +822,26 @@ export function AgentNode() {
|
|
|
399
822
|
setStep('config');
|
|
400
823
|
}
|
|
401
824
|
}, [step, configuration.mode]);
|
|
825
|
+
useEffect(() => {
|
|
826
|
+
if (step === 'chat' && !hasActiveAgent) {
|
|
827
|
+
setStep('gallery');
|
|
828
|
+
}
|
|
829
|
+
}, [step, hasActiveAgent]);
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
if (step === 'chat' && isSaasOnlyChat) {
|
|
832
|
+
setStep('gallery');
|
|
833
|
+
}
|
|
834
|
+
}, [step, isSaasOnlyChat]);
|
|
402
835
|
const handleSignIn = (newToken, handle) => {
|
|
836
|
+
setAutoBootstrapDisabled(false);
|
|
403
837
|
setDisableAutoBootstrap(false);
|
|
404
838
|
setAuth(newToken, handle);
|
|
405
|
-
setStep('
|
|
839
|
+
setStep('gallery');
|
|
406
840
|
};
|
|
407
841
|
// API keys are exchanged for a session token before login so billing and
|
|
408
842
|
// plans endpoints (/api/iam/v1/plans/*) resolve the correct paid plan.
|
|
409
843
|
const handleApiKeySignIn = async (apiKey) => {
|
|
410
|
-
const datalayerUrl =
|
|
411
|
-
'https://prod1.datalayer.run';
|
|
844
|
+
const datalayerUrl = resolveDatalayerUrl();
|
|
412
845
|
try {
|
|
413
846
|
const resp = await fetch(`${datalayerUrl}/api/iam/v1/login`, {
|
|
414
847
|
method: 'POST',
|
|
@@ -428,6 +861,9 @@ export function AgentNode() {
|
|
|
428
861
|
}
|
|
429
862
|
};
|
|
430
863
|
const handleSignOut = () => {
|
|
864
|
+
// Persist the signed-out intent so a refresh does not re-bootstrap the
|
|
865
|
+
// session from the env-supplied DATALAYER_API_KEY.
|
|
866
|
+
setAutoBootstrapDisabled(true);
|
|
431
867
|
setDisableAutoBootstrap(true);
|
|
432
868
|
// Clear backend fallback credentials while still authenticated.
|
|
433
869
|
void pushCredentials(token, null).catch(() => {
|
|
@@ -467,9 +903,12 @@ export function AgentNode() {
|
|
|
467
903
|
...(payload?.configuration || {}),
|
|
468
904
|
};
|
|
469
905
|
setConfiguration(saved);
|
|
906
|
+
if (saved.active_agent_id) {
|
|
907
|
+
setSelectedAgentId(saved.active_agent_id);
|
|
908
|
+
}
|
|
470
909
|
showBanner('success', 'Agent Node configuration saved.');
|
|
471
910
|
if (saved.mode === 'private') {
|
|
472
|
-
setStep('
|
|
911
|
+
setStep('gallery');
|
|
473
912
|
}
|
|
474
913
|
}
|
|
475
914
|
catch (reason) {
|
|
@@ -486,8 +925,11 @@ export function AgentNode() {
|
|
|
486
925
|
return true;
|
|
487
926
|
if (!token)
|
|
488
927
|
return false;
|
|
489
|
-
if (nextStep === '
|
|
490
|
-
return
|
|
928
|
+
if (nextStep === 'gallery')
|
|
929
|
+
return true;
|
|
930
|
+
if (nextStep === 'chat') {
|
|
931
|
+
return (configuration.mode === 'private' && hasActiveAgent && !isSaasOnlyChat);
|
|
932
|
+
}
|
|
491
933
|
return true;
|
|
492
934
|
};
|
|
493
935
|
const StepEntry = ({ entryStep, label, leadingVisual, }) => {
|
|
@@ -508,21 +950,13 @@ export function AgentNode() {
|
|
|
508
950
|
minHeight: '100vh',
|
|
509
951
|
bg: 'canvas.default',
|
|
510
952
|
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: {
|
|
953
|
+
}, 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: {
|
|
954
|
+
display: 'flex',
|
|
955
|
+
alignItems: 'center',
|
|
956
|
+
gap: 2,
|
|
957
|
+
fontSize: 2,
|
|
958
|
+
lineHeight: '22px',
|
|
959
|
+
}, children: [token && (_jsxs(_Fragment, { children: [_jsx(StepEntry, { entryStep: "chat", label: "Chat", leadingVisual: CommentIcon }), _jsx(StepEntry, { entryStep: "gallery", label: "Agents", leadingVisual: AiAgentIcon }), _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(AppearanceMenu, { colorMode: colorMode, themeVariant: themeVariant, onColorModeChange: setColorMode, onThemeChange: nextTheme => setTheme(nextTheme, false), shape: "circle" })] }) })] }) }), _jsxs(PageLayout.Content, { children: [_jsx(Box, { "aria-live": "polite", sx: {
|
|
526
960
|
position: 'fixed',
|
|
527
961
|
top: 0,
|
|
528
962
|
left: 0,
|
|
@@ -560,7 +994,7 @@ export function AgentNode() {
|
|
|
560
994
|
? 'opacity 600ms ease, transform 700ms cubic-bezier(0.16, 1, 0.3, 1)'
|
|
561
995
|
: 'opacity 250ms ease, transform 350ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
562
996
|
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: {
|
|
997
|
+
}, 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
998
|
border: '1px solid',
|
|
565
999
|
borderColor: 'border.default',
|
|
566
1000
|
borderTop: '3px solid',
|
|
@@ -582,13 +1016,17 @@ export function AgentNode() {
|
|
|
582
1016
|
'& > div > div': {
|
|
583
1017
|
width: '100%',
|
|
584
1018
|
bg: 'canvas.default',
|
|
1019
|
+
border: '1px solid',
|
|
585
1020
|
borderColor: `${cfg.brandColor}66`,
|
|
586
1021
|
boxShadow: `0 0 0 1px ${cfg.brandColor}2B`,
|
|
1022
|
+
borderRadius: 2,
|
|
1023
|
+
px: [3, 4],
|
|
1024
|
+
py: [3, 4],
|
|
587
1025
|
},
|
|
588
1026
|
'& h2, & h3': {
|
|
589
1027
|
color: cfg.brandColor,
|
|
590
1028
|
},
|
|
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: {
|
|
1029
|
+
}, 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
1030
|
border: '1px solid',
|
|
593
1031
|
borderColor: 'border.default',
|
|
594
1032
|
borderRadius: 2,
|
|
@@ -605,7 +1043,12 @@ export function AgentNode() {
|
|
|
605
1043
|
display: 'flex',
|
|
606
1044
|
flexDirection: 'column',
|
|
607
1045
|
gap: 3,
|
|
608
|
-
}, children: [_jsxs(
|
|
1046
|
+
}, children: [_jsxs(Box, { sx: {
|
|
1047
|
+
border: '1px solid',
|
|
1048
|
+
borderColor: 'border.default',
|
|
1049
|
+
borderRadius: 2,
|
|
1050
|
+
p: 3,
|
|
1051
|
+
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Mode" }), _jsx(Box, { sx: {
|
|
609
1052
|
display: 'grid',
|
|
610
1053
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
611
1054
|
gap: 3,
|
|
@@ -645,7 +1088,12 @@ export function AgentNode() {
|
|
|
645
1088
|
? cfg.brandColor
|
|
646
1089
|
: 'fg.default',
|
|
647
1090
|
}, children: [_jsx(Icon, { size: 20 }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: card.name })] }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: card.description })] }, card.mode));
|
|
648
|
-
}) })] }), _jsxs(
|
|
1091
|
+
}) })] }), _jsxs(Box, { sx: {
|
|
1092
|
+
border: '1px solid',
|
|
1093
|
+
borderColor: 'border.default',
|
|
1094
|
+
borderRadius: 2,
|
|
1095
|
+
p: 3,
|
|
1096
|
+
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Inference" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1, mb: 2 }, children: "Used inference provider for newly launched agent sessions." }), _jsxs(Box, { sx: {
|
|
649
1097
|
display: 'flex',
|
|
650
1098
|
gap: 2,
|
|
651
1099
|
flexWrap: 'wrap',
|
|
@@ -663,15 +1111,67 @@ export function AgentNode() {
|
|
|
663
1111
|
display: 'flex',
|
|
664
1112
|
flexDirection: 'column',
|
|
665
1113
|
gap: 1,
|
|
666
|
-
}, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Bedrock Anthropic model" }), inferenceModels.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No model list available." })) : (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { children: inferenceDefaultModel || inferenceModels[0] }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { selectionVariant: "single", children: inferenceModels.map(model => (_jsx(ActionList.Item, { selected: inferenceDefaultModel === model, inactiveText: "Selection is locked", children: model }, model))) }) })] }))] }))] }), iamUser ? (
|
|
1114
|
+
}, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Bedrock Anthropic model" }), inferenceModels.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No model list available." })) : (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { children: inferenceDefaultModel || inferenceModels[0] }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { selectionVariant: "single", children: inferenceModels.map(model => (_jsx(ActionList.Item, { selected: inferenceDefaultModel === model, inactiveText: "Selection is locked", children: model }, model))) }) })] }))] }))] }), iamUser ? (_jsxs(Box, { sx: {
|
|
1115
|
+
border: '1px solid',
|
|
1116
|
+
borderColor: 'border.default',
|
|
1117
|
+
borderRadius: 2,
|
|
1118
|
+
p: 3,
|
|
1119
|
+
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Billing Entity" }), _jsx(BillingEntitySelect, { value: configuration.billing_entity_uid || '', onChange: handleBillingEntityChange, onSelectedAccountChange: handleSelectedAccountChange, onAccountsResolved: handleAccountsResolved })] })) : (_jsxs(Box, { sx: {
|
|
1120
|
+
border: '1px solid',
|
|
1121
|
+
borderColor: 'border.default',
|
|
1122
|
+
borderRadius: 2,
|
|
1123
|
+
p: 3,
|
|
1124
|
+
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Billing Entity" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Loading billing entitys..." })] }))] }), _jsx(Box, { sx: {
|
|
667
1125
|
display: 'flex',
|
|
668
1126
|
flexDirection: 'column',
|
|
669
1127
|
gap: 2,
|
|
670
|
-
}, children:
|
|
1128
|
+
}, children: _jsxs(Box, { sx: {
|
|
1129
|
+
border: '1px solid',
|
|
1130
|
+
borderColor: 'border.default',
|
|
1131
|
+
borderRadius: 2,
|
|
1132
|
+
p: 3,
|
|
1133
|
+
}, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, 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, avatarFallbackBackground: avatarPalette.bgAlt, avatarFallbackForeground: avatarPalette.primary })), step === 'gallery' && (_jsx(AgentNodeGallery, { baseUrl: AGENT_RUNTIMES_BASE_URL, token: token, activeAgentId: configuration.active_agent_id, onLaunchError: message => {
|
|
1134
|
+
showBanner('error', message);
|
|
1135
|
+
}, onLaunched: agentId => {
|
|
1136
|
+
setSelectedAgentId(agentId);
|
|
1137
|
+
setIsActiveAgentRunning(true);
|
|
1138
|
+
const nextConfiguration = {
|
|
1139
|
+
...configuration,
|
|
1140
|
+
active_agent_id: agentId,
|
|
1141
|
+
mode: 'private',
|
|
1142
|
+
};
|
|
1143
|
+
setConfiguration(nextConfiguration);
|
|
1144
|
+
// Persist mode=private so Chat stays enabled and the node
|
|
1145
|
+
// advertises the active agent state to the sync loop.
|
|
1146
|
+
void fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/configuration`, {
|
|
1147
|
+
method: 'POST',
|
|
1148
|
+
headers: {
|
|
1149
|
+
'Content-Type': 'application/json',
|
|
1150
|
+
},
|
|
1151
|
+
body: JSON.stringify(nextConfiguration),
|
|
1152
|
+
}).catch(() => {
|
|
1153
|
+
// Best-effort persistence; local state already switched.
|
|
1154
|
+
});
|
|
1155
|
+
if (isSaasOnlyChat) {
|
|
1156
|
+
showBanner('info', 'This node is configured for SaaS chat. Open Agent Nodes in Datalayer to chat with it.');
|
|
1157
|
+
setStep('gallery');
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
setStep('chat');
|
|
1161
|
+
}
|
|
1162
|
+
}, onTerminated: () => {
|
|
1163
|
+
setSelectedAgentId('default');
|
|
1164
|
+
setIsActiveAgentRunning(false);
|
|
1165
|
+
setConfiguration(prev => ({
|
|
1166
|
+
...prev,
|
|
1167
|
+
active_agent_id: undefined,
|
|
1168
|
+
}));
|
|
1169
|
+
setStep('gallery');
|
|
1170
|
+
} })), step === 'chat' && (_jsx(Box, { sx: {
|
|
671
1171
|
border: '1px solid',
|
|
672
1172
|
borderColor: 'border.default',
|
|
673
1173
|
borderRadius: 2,
|
|
674
1174
|
overflow: 'hidden',
|
|
675
|
-
}, children: _jsx(Chat, { protocol: "ag-ui", baseUrl: AGENT_RUNTIMES_BASE_URL, agentId:
|
|
1175
|
+
}, children: isSaasOnlyChat ? (_jsxs(Box, { sx: { p: 4 }, children: [_jsx(Heading, { sx: { fontSize: 2, mb: 2 }, children: "Chat From SaaS" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "This Agent Node deployment is configured for SaaS-only chat. Use the Datalayer Agent Nodes view to open chat sessions over the runtimes tunnel." })] })) : (_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, themeVariant: themeVariant, colorMode: colorMode, ephemeralNotebookCollaborationProvider: ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId: ephemeralCollaborationDocumentId, ephemeralDocumentCollaboration: ephemeralDocumentCollaboration, historyEndpoint: `${AGENT_RUNTIMES_BASE_URL}/api/v1/history` })) }))] })] }) }) }));
|
|
676
1176
|
}
|
|
677
1177
|
export default AgentNode;
|