@datalayer/agent-runtimes 1.0.5 → 1.0.6
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 +16 -10
- 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
|
@@ -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,6 +137,9 @@ 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);
|
|
@@ -201,21 +207,21 @@ export function Chat({ protocol: transport, extensions: _extensions, baseUrl = '
|
|
|
201
207
|
break;
|
|
202
208
|
}
|
|
203
209
|
case 'acp': {
|
|
204
|
-
endpoint = `${baseUrl}${getEndpointPath(transport,
|
|
210
|
+
endpoint = `${baseUrl}${getEndpointPath(transport, resolvedAgentId)}`;
|
|
205
211
|
const acpWsUrl = wsUrl ||
|
|
206
|
-
`${baseUrl.replace('http', 'ws')}/api/v1/acp/ws/${
|
|
212
|
+
`${baseUrl.replace('http', 'ws')}/api/v1/acp/ws/${resolvedAgentId}`;
|
|
207
213
|
options = { wsUrl: acpWsUrl };
|
|
208
214
|
break;
|
|
209
215
|
}
|
|
210
216
|
default: {
|
|
211
|
-
endpoint = `${baseUrl}${getEndpointPath(transport,
|
|
217
|
+
endpoint = `${baseUrl}${getEndpointPath(transport, resolvedAgentId)}`;
|
|
212
218
|
break;
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
return {
|
|
216
222
|
type: getProtocolType(transport),
|
|
217
223
|
endpoint,
|
|
218
|
-
agentId,
|
|
224
|
+
agentId: resolvedAgentId,
|
|
219
225
|
authToken,
|
|
220
226
|
options,
|
|
221
227
|
// Enable config query for all protocols to fetch models and tools
|
|
@@ -232,7 +238,7 @@ export function Chat({ protocol: transport, extensions: _extensions, baseUrl = '
|
|
|
232
238
|
setError(err instanceof Error ? err.message : 'Failed to configure');
|
|
233
239
|
return undefined;
|
|
234
240
|
}
|
|
235
|
-
}, [transport, baseUrl, wsUrl,
|
|
241
|
+
}, [transport, baseUrl, wsUrl, resolvedAgentId, authTokenProp]);
|
|
236
242
|
// Set initialized once protocol config is built
|
|
237
243
|
useEffect(() => {
|
|
238
244
|
if (protocolConfig) {
|
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,
|