@datalayer/agent-runtimes 1.0.5 → 1.0.7
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 +157 -10
- package/lib/AgentNode.d.ts +3 -0
- package/lib/AgentNode.js +676 -0
- package/lib/agent-node/themeStore.d.ts +3 -0
- package/lib/agent-node/themeStore.js +156 -0
- package/lib/agent-node-main.d.ts +1 -0
- package/lib/agent-node-main.js +14 -0
- package/lib/chat/Chat.js +48 -27
- package/lib/chat/ChatFloating.js +1 -1
- package/lib/chat/ChatSidebar.js +81 -49
- package/lib/chat/base/ChatBase.js +388 -74
- package/lib/chat/display/FloatingBrandButton.js +8 -1
- package/lib/chat/header/ChatHeader.d.ts +3 -1
- package/lib/chat/header/ChatHeader.js +15 -12
- package/lib/chat/header/ChatHeaderBase.d.ts +29 -9
- package/lib/chat/header/ChatHeaderBase.js +26 -3
- package/lib/chat/indicators/SandboxStatusIndicator.js +82 -47
- package/lib/chat/messages/ChatMessageList.js +46 -1
- package/lib/chat/messages/ChatMessages.js +6 -2
- package/lib/chat/prompt/InputFooter.d.ts +3 -1
- package/lib/chat/prompt/InputFooter.js +8 -5
- package/lib/chat/prompt/InputPrompt.d.ts +3 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptFooter.d.ts +3 -1
- package/lib/chat/prompt/InputPromptFooter.js +3 -3
- package/lib/client/AgentsMixin.js +14 -0
- package/lib/config/AgentConfiguration.d.ts +22 -0
- package/lib/config/AgentConfiguration.js +319 -64
- package/lib/examples/AgUiSharedStateExample.js +2 -1
- package/lib/examples/AgentCheckpointsExample.js +3 -3
- package/lib/examples/AgentCodemodeExample.d.ts +3 -3
- package/lib/examples/AgentCodemodeExample.js +24 -12
- package/lib/examples/AgentEvalsExample.js +330 -40
- package/lib/examples/AgentGuardrailsExample.js +16 -5
- package/lib/examples/AgentHooksExample.js +27 -9
- package/lib/examples/AgentInferenceProviderExample.d.ts +3 -0
- package/lib/examples/AgentInferenceProviderExample.js +329 -0
- package/lib/examples/AgentMCPExample.js +6 -5
- package/lib/examples/AgentMemoryExample.d.ts +1 -2
- package/lib/examples/AgentMemoryExample.js +71 -22
- package/lib/examples/AgentMonitoringExample.js +5 -5
- package/lib/examples/AgentNotificationsExample.d.ts +1 -2
- package/lib/examples/AgentNotificationsExample.js +71 -22
- package/lib/examples/AgentOtelExample.js +31 -40
- package/lib/examples/AgentOutputsExample.d.ts +1 -1
- package/lib/examples/AgentOutputsExample.js +67 -16
- package/lib/examples/AgentParametersExample.js +10 -8
- package/lib/examples/AgentSandboxExample.d.ts +1 -1
- package/lib/examples/AgentSandboxExample.js +7 -6
- package/lib/examples/AgentSkillsExample.js +6 -6
- package/lib/examples/AgentSubagentsExample.d.ts +1 -1
- package/lib/examples/AgentSubagentsExample.js +6 -6
- package/lib/examples/AgentToolApprovalsExample.js +27 -11
- package/lib/examples/AgentTriggersExample.js +5 -5
- package/lib/examples/{AgentSpecsExample.d.ts → AgentspecsExample.d.ts} +2 -2
- package/lib/examples/AgentspecsExample.js +1096 -0
- package/lib/examples/ChatCustomExample.js +6 -5
- package/lib/examples/ChatExample.js +6 -5
- package/lib/examples/Lexical2Example.js +1 -1
- package/lib/examples/LexicalAgentExample.js +1 -1
- package/lib/examples/NotebookAgentExample.js +3 -3
- package/lib/examples/components/ExampleWrapper.d.ts +6 -7
- package/lib/examples/components/ExampleWrapper.js +27 -10
- package/lib/examples/example-selector.js +2 -1
- package/lib/examples/index.d.ts +2 -1
- package/lib/examples/index.js +2 -1
- package/lib/examples/lexical/initial-content.json +6 -6
- package/lib/examples/main.js +56 -16
- package/lib/examples/utils/agentId.d.ts +1 -1
- package/lib/examples/utils/agentId.js +1 -1
- package/lib/examples/utils/useExampleAgentRuntimesUrl.d.ts +5 -0
- package/lib/examples/utils/useExampleAgentRuntimesUrl.js +19 -0
- package/lib/hooks/useAIAgentsWebSocket.js +35 -0
- package/lib/hooks/useAgentRuntimes.d.ts +32 -3
- package/lib/hooks/useAgentRuntimes.js +114 -19
- package/lib/index.d.ts +1 -1
- package/lib/specs/agents/agents.d.ts +20 -13
- package/lib/specs/agents/agents.js +1267 -581
- package/lib/specs/benchmarks.d.ts +20 -0
- package/lib/specs/benchmarks.js +205 -0
- package/lib/specs/envvars.d.ts +0 -1
- package/lib/specs/envvars.js +0 -11
- package/lib/specs/evals.d.ts +10 -9
- package/lib/specs/evals.js +128 -88
- package/lib/specs/index.d.ts +0 -1
- package/lib/specs/index.js +0 -1
- package/lib/specs/models.d.ts +0 -2
- package/lib/specs/models.js +0 -15
- package/lib/specs/skills.d.ts +0 -1
- package/lib/specs/skills.js +0 -18
- package/lib/stores/agentRuntimeStore.d.ts +5 -1
- package/lib/stores/agentRuntimeStore.js +22 -8
- package/lib/stores/conversationStore.js +2 -2
- package/lib/types/agents-lifecycle.d.ts +18 -0
- package/lib/types/agents.d.ts +6 -0
- package/lib/types/agentspecs.d.ts +4 -0
- package/lib/types/benchmarks.d.ts +43 -0
- package/lib/types/benchmarks.js +5 -0
- package/lib/types/chat.d.ts +16 -0
- package/lib/types/evals.d.ts +26 -17
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/package.json +9 -5
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_benchmarks.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +89 -43
- package/scripts/codegen/generate_benchmarks.py +441 -0
- package/scripts/codegen/generate_evals.py +94 -16
- package/scripts/codegen/generate_events.py +0 -1
- package/lib/examples/AgentSpecsExample.js +0 -694
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { create } from 'zustand';
|
|
6
|
+
import { createJSONStorage, persist } from 'zustand/middleware';
|
|
7
|
+
import { themeConfigs, } from '@datalayer/primer-addons';
|
|
8
|
+
const STORAGE_KEY = 'agent-runtimes-agent-node-theme';
|
|
9
|
+
const COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
10
|
+
const colorModeCycle = {
|
|
11
|
+
light: 'dark',
|
|
12
|
+
dark: 'auto',
|
|
13
|
+
auto: 'light',
|
|
14
|
+
};
|
|
15
|
+
const normalizeThemeVariant = (value) => {
|
|
16
|
+
// Backward compatibility: "ocean" maps to the Earth theme variant.
|
|
17
|
+
if (value === 'ocean') {
|
|
18
|
+
return 'earth';
|
|
19
|
+
}
|
|
20
|
+
if (value === 'datalayer' ||
|
|
21
|
+
value === 'spatial' ||
|
|
22
|
+
value === 'lovely' ||
|
|
23
|
+
value === 'matrix' ||
|
|
24
|
+
value === 'earth') {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
return 'earth';
|
|
28
|
+
};
|
|
29
|
+
const cookieStorage = {
|
|
30
|
+
getItem: (name) => {
|
|
31
|
+
if (typeof document === 'undefined') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const encodedName = `${encodeURIComponent(name)}=`;
|
|
35
|
+
const parts = document.cookie.split('; ');
|
|
36
|
+
for (const part of parts) {
|
|
37
|
+
if (part.startsWith(encodedName)) {
|
|
38
|
+
return decodeURIComponent(part.slice(encodedName.length));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
setItem: (name, value) => {
|
|
44
|
+
if (typeof document === 'undefined') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE_SECONDS}; samesite=lax`;
|
|
48
|
+
},
|
|
49
|
+
removeItem: (name) => {
|
|
50
|
+
if (typeof document === 'undefined') {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
document.cookie = `${encodeURIComponent(name)}=; path=/; max-age=0; samesite=lax`;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
export const useAgentNodeThemeStore = create()(persist(set => ({
|
|
57
|
+
// "ocean dark" default: Earth theme (ocean palette) + dark mode.
|
|
58
|
+
colorMode: 'dark',
|
|
59
|
+
theme: 'earth',
|
|
60
|
+
activeVariant: null,
|
|
61
|
+
variants: {},
|
|
62
|
+
toggleColorMode: () => set(state => {
|
|
63
|
+
const next = colorModeCycle[state.colorMode];
|
|
64
|
+
if (state.activeVariant && state.variants[state.activeVariant]) {
|
|
65
|
+
return {
|
|
66
|
+
colorMode: next,
|
|
67
|
+
variants: {
|
|
68
|
+
...state.variants,
|
|
69
|
+
[state.activeVariant]: {
|
|
70
|
+
...state.variants[state.activeVariant],
|
|
71
|
+
colorMode: next,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { colorMode: next };
|
|
77
|
+
}),
|
|
78
|
+
setColorMode: (mode) => set(state => {
|
|
79
|
+
if (state.activeVariant && state.variants[state.activeVariant]) {
|
|
80
|
+
return {
|
|
81
|
+
colorMode: mode,
|
|
82
|
+
variants: {
|
|
83
|
+
...state.variants,
|
|
84
|
+
[state.activeVariant]: {
|
|
85
|
+
...state.variants[state.activeVariant],
|
|
86
|
+
colorMode: mode,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { colorMode: mode };
|
|
92
|
+
}),
|
|
93
|
+
setTheme: (theme, applyDefaultColorMode = true) => set(state => {
|
|
94
|
+
const normalizedTheme = normalizeThemeVariant(theme);
|
|
95
|
+
const nextColorMode = applyDefaultColorMode
|
|
96
|
+
? themeConfigs[normalizedTheme].defaultColorMode
|
|
97
|
+
: state.colorMode;
|
|
98
|
+
if (state.activeVariant && state.variants[state.activeVariant]) {
|
|
99
|
+
return {
|
|
100
|
+
theme: normalizedTheme,
|
|
101
|
+
colorMode: nextColorMode,
|
|
102
|
+
variants: {
|
|
103
|
+
...state.variants,
|
|
104
|
+
[state.activeVariant]: {
|
|
105
|
+
theme: normalizedTheme,
|
|
106
|
+
colorMode: nextColorMode,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return { theme: normalizedTheme, colorMode: nextColorMode };
|
|
112
|
+
}),
|
|
113
|
+
registerVariants: defs => set(state => {
|
|
114
|
+
const merged = { ...state.variants };
|
|
115
|
+
for (const [key, val] of Object.entries(defs)) {
|
|
116
|
+
if (!merged[key]) {
|
|
117
|
+
merged[key] = val;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { variants: merged };
|
|
121
|
+
}),
|
|
122
|
+
setVariant: variant => set(state => {
|
|
123
|
+
const variantState = state.variants[variant];
|
|
124
|
+
if (!variantState) {
|
|
125
|
+
return { activeVariant: variant };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
activeVariant: variant,
|
|
129
|
+
theme: normalizeThemeVariant(variantState.theme),
|
|
130
|
+
colorMode: variantState.colorMode,
|
|
131
|
+
};
|
|
132
|
+
}),
|
|
133
|
+
}), {
|
|
134
|
+
name: STORAGE_KEY,
|
|
135
|
+
version: 1,
|
|
136
|
+
storage: createJSONStorage(() => cookieStorage),
|
|
137
|
+
partialize: state => ({
|
|
138
|
+
colorMode: state.colorMode,
|
|
139
|
+
theme: state.theme,
|
|
140
|
+
activeVariant: state.activeVariant,
|
|
141
|
+
variants: state.variants,
|
|
142
|
+
}),
|
|
143
|
+
merge: (persisted, current) => {
|
|
144
|
+
const persistedState = (persisted ?? {});
|
|
145
|
+
const persistedTheme = normalizeThemeVariant(persistedState.theme);
|
|
146
|
+
return {
|
|
147
|
+
...current,
|
|
148
|
+
...persistedState,
|
|
149
|
+
theme: persistedTheme,
|
|
150
|
+
variants: {
|
|
151
|
+
...current.variants,
|
|
152
|
+
...(persistedState.variants ?? {}),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
import { createRoot } from 'react-dom/client';
|
|
7
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
8
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
9
|
+
import { AgentNode } from './AgentNode';
|
|
10
|
+
const queryClient = new QueryClient();
|
|
11
|
+
const rootElement = document.getElementById('root');
|
|
12
|
+
if (rootElement) {
|
|
13
|
+
createRoot(rootElement).render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(AgentNode, {}) }) }));
|
|
14
|
+
}
|
package/lib/chat/Chat.js
CHANGED
|
@@ -18,7 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
*
|
|
19
19
|
* @module chat/Chat
|
|
20
20
|
*/
|
|
21
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
21
|
+
import { useEffect, useMemo, useState, useRef } from 'react';
|
|
22
22
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
23
23
|
import { Text, Button, Spinner } from '@primer/react';
|
|
24
24
|
import { AlertIcon, SyncIcon } from '@primer/octicons-react';
|
|
@@ -63,22 +63,25 @@ const queryClient = new QueryClient();
|
|
|
63
63
|
* Get transport endpoint path
|
|
64
64
|
*/
|
|
65
65
|
function getEndpointPath(protocol, agentId) {
|
|
66
|
+
const resolvedAgentId = typeof agentId === 'string' && agentId.trim().length > 0
|
|
67
|
+
? agentId.trim()
|
|
68
|
+
: 'default';
|
|
66
69
|
switch (protocol) {
|
|
67
70
|
case 'vercel-ai':
|
|
68
|
-
return `/api/v1/vercel-ai/${
|
|
71
|
+
return `/api/v1/vercel-ai/${resolvedAgentId}`;
|
|
69
72
|
case 'vercel-ai-jupyter':
|
|
70
73
|
// Jupyter server endpoint - same protocol as vercel-ai
|
|
71
74
|
// Note: no leading slash - will be joined with baseUrl that may have trailing slash
|
|
72
75
|
return 'agent_runtimes/chat';
|
|
73
76
|
case 'ag-ui':
|
|
74
|
-
return `/api/v1/ag-ui/${
|
|
77
|
+
return `/api/v1/ag-ui/${resolvedAgentId}/`;
|
|
75
78
|
case 'a2a':
|
|
76
79
|
// A2A requires trailing slash for FastA2A compatibility
|
|
77
|
-
return `/api/v1/a2a/agents/${
|
|
80
|
+
return `/api/v1/a2a/agents/${resolvedAgentId}/`;
|
|
78
81
|
case 'acp':
|
|
79
|
-
return `/api/v1/acp/ws/${
|
|
82
|
+
return `/api/v1/acp/ws/${resolvedAgentId}`;
|
|
80
83
|
default:
|
|
81
|
-
return `/api/v1/agents/${
|
|
84
|
+
return `/api/v1/agents/${resolvedAgentId}/chat`;
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
/**
|
|
@@ -134,11 +137,17 @@ function getProtocolType(protocol) {
|
|
|
134
137
|
* ```
|
|
135
138
|
*/
|
|
136
139
|
export function Chat({ protocol: transport, extensions: _extensions, baseUrl = 'http://localhost:8765', wsUrl, agentId, authToken: authTokenProp, placeholder = 'Type your message...', title, subtitle, brandIcon, autoConnect: _autoConnect = true, streaming: _streaming = true, onMessageSent: _onMessageSent, onMessageReceived: _onMessageReceived, onDisconnect, onLogout: _onLogout, onCollapsePanel: _onCollapsePanel, className, height = '600px', showHeader = true, showNewChatButton = true, showClearButton = true, showModelSelector = true, showToolsMenu = true, showInput = true, disableInputPrompt = false, showSkillsMenu = true, codemodeEnabled = false, onToggleCodemode, showTokenUsage = true, initialModel, availableModels, mcpServers, initialSkills, clearOnMount: _clearOnMount = true, suggestions, submitOnSuggestionClick = true, description, headerContent, headerActions, autoFocus = false, identityProviders, onIdentityConnect, onIdentityDisconnect, runtimeId, historyEndpoint, pendingPrompt, errorBanner, showInformation = true, chatViewMode, onChatViewModeChange, frontendTools, onToolCallStart, onToolCallComplete, renderToolResult, hideMessagesAfterToolUI = false, contextSnapshot, mcpStatusData, codemodeStatusData, sandboxStatusData, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, }) {
|
|
140
|
+
const resolvedAgentId = typeof agentId === 'string' && agentId.trim().length > 0
|
|
141
|
+
? agentId.trim()
|
|
142
|
+
: 'default';
|
|
137
143
|
const [error, setError] = useState(null);
|
|
138
144
|
const [isInitializing, setIsInitializing] = useState(true);
|
|
139
145
|
const [showDetails, setShowDetails] = useState(false);
|
|
140
146
|
const [messageCount, setMessageCount] = useState(0);
|
|
141
147
|
const [focusTrigger, setFocusTrigger] = useState(0);
|
|
148
|
+
const renderCountRef = useRef(0);
|
|
149
|
+
renderCountRef.current += 1;
|
|
150
|
+
console.log(`[AgentRuntimes][Chat] render count: ${renderCountRef.current}`);
|
|
142
151
|
// Get connected identities to pass to backend for skill execution
|
|
143
152
|
const connectedIdentities = useConnectedIdentities();
|
|
144
153
|
// Map identities to the format expected by ChatBase
|
|
@@ -161,8 +170,9 @@ export function Chat({ protocol: transport, extensions: _extensions, baseUrl = '
|
|
|
161
170
|
return () => clearTimeout(timer);
|
|
162
171
|
}
|
|
163
172
|
}, [showDetails]);
|
|
164
|
-
// Build protocol config based on transport
|
|
165
|
-
|
|
173
|
+
// Build protocol config based on transport.
|
|
174
|
+
// Keep this pure: never call React state setters inside this memo.
|
|
175
|
+
const protocolConfigResult = useMemo(() => {
|
|
166
176
|
try {
|
|
167
177
|
let endpoint;
|
|
168
178
|
let authToken = authTokenProp;
|
|
@@ -201,45 +211,56 @@ export function Chat({ protocol: transport, extensions: _extensions, baseUrl = '
|
|
|
201
211
|
break;
|
|
202
212
|
}
|
|
203
213
|
case 'acp': {
|
|
204
|
-
endpoint = `${baseUrl}${getEndpointPath(transport,
|
|
214
|
+
endpoint = `${baseUrl}${getEndpointPath(transport, resolvedAgentId)}`;
|
|
205
215
|
const acpWsUrl = wsUrl ||
|
|
206
|
-
`${baseUrl.replace('http', 'ws')}/api/v1/acp/ws/${
|
|
216
|
+
`${baseUrl.replace('http', 'ws')}/api/v1/acp/ws/${resolvedAgentId}`;
|
|
207
217
|
options = { wsUrl: acpWsUrl };
|
|
208
218
|
break;
|
|
209
219
|
}
|
|
210
220
|
default: {
|
|
211
|
-
endpoint = `${baseUrl}${getEndpointPath(transport,
|
|
221
|
+
endpoint = `${baseUrl}${getEndpointPath(transport, resolvedAgentId)}`;
|
|
212
222
|
break;
|
|
213
223
|
}
|
|
214
224
|
}
|
|
215
225
|
return {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
config: {
|
|
227
|
+
type: getProtocolType(transport),
|
|
228
|
+
endpoint,
|
|
229
|
+
agentId: resolvedAgentId,
|
|
230
|
+
authToken,
|
|
231
|
+
options,
|
|
232
|
+
// Enable config query for all protocols to fetch models and tools
|
|
233
|
+
enableConfigQuery: true,
|
|
234
|
+
// For Jupyter-based transports, use Jupyter requestAPI (configEndpoint undefined)
|
|
235
|
+
// For FastAPI-based transports, use direct fetch to the configure endpoint
|
|
236
|
+
configEndpoint: transport === 'vercel-ai-jupyter'
|
|
237
|
+
? undefined // Use Jupyter requestAPI
|
|
238
|
+
: `${baseUrl}/api/v1/configure`,
|
|
239
|
+
},
|
|
240
|
+
error: null,
|
|
228
241
|
};
|
|
229
242
|
}
|
|
230
243
|
catch (err) {
|
|
231
244
|
console.error('[Chat] Error building protocol config:', err);
|
|
232
|
-
|
|
233
|
-
|
|
245
|
+
return {
|
|
246
|
+
config: undefined,
|
|
247
|
+
error: err instanceof Error ? err.message : 'Failed to configure',
|
|
248
|
+
};
|
|
234
249
|
}
|
|
235
|
-
}, [transport, baseUrl, wsUrl,
|
|
250
|
+
}, [transport, baseUrl, wsUrl, resolvedAgentId, authTokenProp]);
|
|
251
|
+
const protocolConfig = protocolConfigResult.config;
|
|
236
252
|
// Set initialized once protocol config is built
|
|
237
253
|
useEffect(() => {
|
|
254
|
+
if (protocolConfigResult.error) {
|
|
255
|
+
setError(protocolConfigResult.error);
|
|
256
|
+
setIsInitializing(false);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
238
259
|
if (protocolConfig) {
|
|
239
260
|
setIsInitializing(false);
|
|
240
261
|
setError(null);
|
|
241
262
|
}
|
|
242
|
-
}, [protocolConfig]);
|
|
263
|
+
}, [protocolConfig, protocolConfigResult.error]);
|
|
243
264
|
// Handle reconnect
|
|
244
265
|
const handleReconnect = () => {
|
|
245
266
|
setError(null);
|
package/lib/chat/ChatFloating.js
CHANGED
|
@@ -431,7 +431,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
431
431
|
}, children: _jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, autoFocus: isOpen, focusTrigger: focusTrigger, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
|
|
432
432
|
showNewChat: showNewChatButton,
|
|
433
433
|
showClear: showClearButton && messages.length > 0,
|
|
434
|
-
showSettings: showSettingsButton,
|
|
434
|
+
showSettings: showSettingsButton && !!onSettingsClick,
|
|
435
435
|
onNewChat: handleNewChat,
|
|
436
436
|
onClear: handleClear,
|
|
437
437
|
onSettings: onSettingsClick,
|
package/lib/chat/ChatSidebar.js
CHANGED
|
@@ -12,10 +12,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
12
12
|
* @module chat/ChatSidebar
|
|
13
13
|
*/
|
|
14
14
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
15
|
-
import {
|
|
15
|
+
import { createPortal } from 'react-dom';
|
|
16
|
+
import { IconButton } from '@primer/react';
|
|
16
17
|
import { Box } from '@datalayer/primer-addons';
|
|
17
18
|
import { SidebarCollapseIcon, SidebarExpandIcon, XIcon, } from '@primer/octicons-react';
|
|
18
|
-
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
19
19
|
import { useChatKeyboardShortcuts, getShortcutDisplay, } from '@datalayer/core/lib/hooks';
|
|
20
20
|
import { ChatBase } from './base/ChatBase';
|
|
21
21
|
import { useChatStore, useChatOpen, useChatMessages, } from '../stores/chatStore';
|
|
@@ -45,6 +45,37 @@ export function ChatSidebar({ title = 'Chat', protocol: protocolProp, defaultOpe
|
|
|
45
45
|
const sidebarRef = useRef(null);
|
|
46
46
|
const inputRef = useRef(null);
|
|
47
47
|
const isMobile = useIsMobile();
|
|
48
|
+
const [desktopViewportHeight, setDesktopViewportHeight] = useState(null);
|
|
49
|
+
// Compute available desktop height from the element's top position so the
|
|
50
|
+
// sidebar always fits within the visible viewport.
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (isMobile) {
|
|
53
|
+
setDesktopViewportHeight(null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const updateHeight = () => {
|
|
57
|
+
const el = sidebarRef.current;
|
|
58
|
+
if (!el)
|
|
59
|
+
return;
|
|
60
|
+
const rect = el.getBoundingClientRect();
|
|
61
|
+
const viewportHeight = window.visualViewport?.height || window.innerHeight || 0;
|
|
62
|
+
const bottomPadding = 4; // keep a small breathing space from viewport edge
|
|
63
|
+
const available = Math.max(220, Math.floor(viewportHeight - rect.top - bottomPadding));
|
|
64
|
+
setDesktopViewportHeight(prev => prev !== null && Math.abs(prev - available) < 2 ? prev : available);
|
|
65
|
+
};
|
|
66
|
+
updateHeight();
|
|
67
|
+
window.addEventListener('resize', updateHeight);
|
|
68
|
+
window.addEventListener('scroll', updateHeight, true);
|
|
69
|
+
const observer = new ResizeObserver(() => updateHeight());
|
|
70
|
+
if (sidebarRef.current) {
|
|
71
|
+
observer.observe(sidebarRef.current);
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
window.removeEventListener('resize', updateHeight);
|
|
75
|
+
window.removeEventListener('scroll', updateHeight, true);
|
|
76
|
+
observer.disconnect();
|
|
77
|
+
};
|
|
78
|
+
}, [isMobile]);
|
|
48
79
|
// Initialize open state from defaultOpen
|
|
49
80
|
useEffect(() => {
|
|
50
81
|
setOpen(defaultOpen);
|
|
@@ -137,58 +168,45 @@ export function ChatSidebar({ title = 'Chat', protocol: protocolProp, defaultOpe
|
|
|
137
168
|
: SidebarExpandIcon, "aria-label": `Close sidebar${shortcutHint ? ` (${shortcutHint})` : ''}`, onClick: handleToggle, variant: "invisible", size: "small" }));
|
|
138
169
|
// Collapsed state
|
|
139
170
|
if (!isOpen) {
|
|
140
|
-
|
|
171
|
+
const collapsedLauncher = (_jsx(Box, { ref: sidebarRef, className: className, sx: {
|
|
141
172
|
position: 'fixed',
|
|
142
173
|
top: 12,
|
|
143
|
-
...(position === 'right'
|
|
174
|
+
...(position === 'right'
|
|
175
|
+
? { right: 'env(safe-area-inset-right)' }
|
|
176
|
+
: { left: 'env(safe-area-inset-left)' }),
|
|
144
177
|
zIndex: 1001,
|
|
145
178
|
display: 'flex',
|
|
146
179
|
flexDirection: 'column',
|
|
147
180
|
alignItems: 'center',
|
|
148
181
|
gap: 2,
|
|
149
|
-
}, children:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
border: '1px solid',
|
|
178
|
-
borderColor: 'border.default',
|
|
179
|
-
borderRadius: 2,
|
|
180
|
-
boxShadow: 'shadow.small',
|
|
181
|
-
}, children: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }) }), shortcutHint && (_jsx(Box, { sx: {
|
|
182
|
-
px: 1,
|
|
183
|
-
py: '2px',
|
|
184
|
-
bg: 'canvas.default',
|
|
185
|
-
border: '1px solid',
|
|
186
|
-
borderColor: 'border.default',
|
|
187
|
-
color: 'fg.muted',
|
|
188
|
-
borderRadius: 1,
|
|
189
|
-
fontSize: 0,
|
|
190
|
-
boxShadow: 'shadow.small',
|
|
191
|
-
}, children: _jsx(Text, { sx: { fontSize: '10px', fontFamily: 'mono' }, children: shortcutHint }) }))] }));
|
|
182
|
+
}, children: _jsxs(Box, { sx: { position: 'relative' }, children: [_jsx(IconButton, { icon: position === 'right' ? SidebarExpandIcon : SidebarCollapseIcon, "aria-label": "Open chat", description: shortcutHint ? `Open chat (${shortcutHint})` : 'Open chat', onClick: handleToggle, variant: "default", size: "small", sx: {
|
|
183
|
+
bg: 'canvas.default',
|
|
184
|
+
border: '1px solid',
|
|
185
|
+
borderColor: 'border.default',
|
|
186
|
+
boxShadow: 'shadow.small',
|
|
187
|
+
} }), messages.length > 0 && (_jsx(Box, { sx: {
|
|
188
|
+
position: 'absolute',
|
|
189
|
+
top: -6,
|
|
190
|
+
right: -6,
|
|
191
|
+
minWidth: 16,
|
|
192
|
+
height: 16,
|
|
193
|
+
px: 1,
|
|
194
|
+
display: 'flex',
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
justifyContent: 'center',
|
|
197
|
+
bg: 'accent.emphasis',
|
|
198
|
+
color: 'fg.onEmphasis',
|
|
199
|
+
borderRadius: '50%',
|
|
200
|
+
fontSize: '10px',
|
|
201
|
+
fontWeight: 'bold',
|
|
202
|
+
boxShadow: 'shadow.small',
|
|
203
|
+
}, children: messages.length > 99 ? '99+' : messages.length }))] }) }));
|
|
204
|
+
// Render the collapsed launcher in a body portal so it stays pinned to the
|
|
205
|
+
// viewport edge even when ancestors use transforms/positioning contexts.
|
|
206
|
+
if (typeof document !== 'undefined' && document.body) {
|
|
207
|
+
return createPortal(collapsedLauncher, document.body);
|
|
208
|
+
}
|
|
209
|
+
return collapsedLauncher;
|
|
192
210
|
}
|
|
193
211
|
// Mobile full-screen overlay
|
|
194
212
|
const mobileStyles = isMobile
|
|
@@ -217,21 +235,35 @@ export function ChatSidebar({ title = 'Chat', protocol: protocolProp, defaultOpe
|
|
|
217
235
|
position: 'relative',
|
|
218
236
|
display: 'flex',
|
|
219
237
|
flexDirection: 'column',
|
|
238
|
+
alignSelf: 'stretch',
|
|
220
239
|
width: isMobile
|
|
221
240
|
? '100%'
|
|
222
241
|
: typeof width === 'number'
|
|
223
242
|
? `${width}px`
|
|
224
243
|
: width,
|
|
225
|
-
height:
|
|
244
|
+
height: isMobile
|
|
245
|
+
? '100%'
|
|
246
|
+
: desktopViewportHeight
|
|
247
|
+
? `${desktopViewportHeight}px`
|
|
248
|
+
: 'calc(100dvh - 8px)',
|
|
249
|
+
minHeight: 0,
|
|
250
|
+
maxHeight: isMobile
|
|
251
|
+
? '100%'
|
|
252
|
+
: desktopViewportHeight
|
|
253
|
+
? `${desktopViewportHeight}px`
|
|
254
|
+
: 'calc(100dvh - 8px)',
|
|
255
|
+
marginBlock: isMobile ? 0 : '4px',
|
|
256
|
+
flex: isMobile ? '1 1 auto' : '0 0 auto',
|
|
226
257
|
bg: 'canvas.default',
|
|
227
258
|
borderLeft: !isMobile && position === 'right' ? '1px solid' : 'none',
|
|
228
259
|
borderRight: !isMobile && position === 'left' ? '1px solid' : 'none',
|
|
229
260
|
borderColor: 'border.default',
|
|
261
|
+
overflow: 'hidden',
|
|
230
262
|
...mobileStyles,
|
|
231
263
|
}, children: _jsx(ChatBase, { title: title, showHeader: showHeader, brandIcon: brandIcon, protocol: protocolProp, headerButtons: {
|
|
232
264
|
showNewChat: showNewChatButton,
|
|
233
265
|
showClear: showClearButton && messages.length > 0,
|
|
234
|
-
showSettings: showSettingsButton,
|
|
266
|
+
showSettings: showSettingsButton && !!onSettingsClick,
|
|
235
267
|
onNewChat: handleNewChat,
|
|
236
268
|
onClear: handleClear,
|
|
237
269
|
onSettings: onSettingsClick,
|