@datalayer/agent-runtimes 1.3.10 → 1.3.13
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 +1 -2
- package/lib/AgentNode.js +3 -3
- package/lib/api/runtimes/environments.d.ts +22 -1
- package/lib/api/runtimes/environments.js +31 -0
- package/lib/api/runtimes/healthz.js +4 -2
- package/lib/api/spacer/healthz.js +4 -2
- package/lib/chat/ChatFloating.d.ts +8 -1
- package/lib/chat/ChatFloating.js +5 -2
- package/lib/chat/base/ChatBase.js +39 -25
- package/lib/chat/base/modelChoice.d.ts +62 -0
- package/lib/chat/base/modelChoice.js +56 -0
- package/lib/chat/display/ButtonGlow.d.ts +9 -0
- package/lib/chat/display/ButtonGlow.js +60 -0
- package/lib/chat/display/EmptyState.js +4 -1
- package/lib/chat/header/ChatHeaderBase.js +4 -56
- package/lib/chat/header/ChatViewModeToggle.d.ts +29 -0
- package/lib/chat/header/ChatViewModeToggle.js +55 -0
- package/lib/chat/index.d.ts +1 -0
- package/lib/chat/index.js +1 -0
- package/lib/chat/messages/ChatMessageList.js +13 -5
- package/lib/chat/messages/FormSubmissionMessage.d.ts +17 -0
- package/lib/chat/messages/FormSubmissionMessage.js +49 -0
- package/lib/chat/messages/formSubmission.d.ts +41 -0
- package/lib/chat/messages/formSubmission.js +90 -0
- package/lib/chat/prompt/menus/ModelSelector.js +11 -4
- package/lib/chat/styles/streamdownStyles.js +40 -0
- package/lib/client/mixins/RuntimesMixin.js +1 -1
- package/lib/client/mixins/SpacerMixin.js +3 -3
- package/lib/components/a2ui/A2uiMarkdownProvider.d.ts +11 -0
- package/lib/components/a2ui/A2uiMarkdownProvider.js +13 -0
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.js +54 -5
- package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +12 -1
- package/lib/components/code-sandboxes/codeSandboxEnvironments.js +38 -1
- package/lib/components/icons/ArtifactIcon.js +2 -1
- package/lib/examples/A2UiAgentExample.js +36 -223
- package/lib/examples/A2UiJupyterOutputExample.js +11 -1
- package/lib/examples/A2UiViewerExample.d.ts +9 -2
- package/lib/examples/A2UiViewerExample.js +33 -3
- package/lib/examples/DecksAgent.d.ts +1 -1
- package/lib/examples/DecksAgent.js +1 -1
- package/lib/examples/main.js +37 -35
- package/lib/examples/utils/a2uiMarkdownProvider.d.ts +1 -7
- package/lib/examples/utils/a2uiMarkdownProvider.js +6 -8
- package/lib/loop/core/index.d.ts +44 -4
- package/lib/loop/core/index.js +29 -2
- package/lib/loop/embed/LoopEmbed.js +7 -0
- package/lib/loop/index.d.ts +1 -1
- package/lib/loop/index.js +4 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +22 -0
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +64 -0
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +38 -0
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +64 -0
- package/lib/loop/plugins/a2ui-surface/index.d.ts +34 -0
- package/lib/loop/plugins/a2ui-surface/index.js +91 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +52 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.js +106 -0
- package/lib/loop/plugins/chat/ChatView.js +95 -15
- package/lib/loop/plugins/chat/index.d.ts +14 -0
- package/lib/loop/plugins/chat/index.js +1 -0
- package/lib/loop/plugins/document/index.js +4 -0
- package/lib/loop/plugins/notebook/index.js +4 -0
- package/lib/loop/plugins/page-layout/index.d.ts +36 -0
- package/lib/loop/plugins/page-layout/index.js +87 -7
- package/lib/loop/plugins/shell/useEditorPreviews.js +7 -0
- package/lib/loop/presets.d.ts +19 -0
- package/lib/loop/presets.js +9 -1
- package/lib/models/Deck.d.ts +14 -0
- package/lib/models/Deck.js +5 -0
- package/lib/models/Environment.d.ts +117 -0
- package/lib/models/ItemType.d.ts +1 -1
- package/lib/models/Runtime.d.ts +6 -0
- package/lib/models/SpaceItem.d.ts +2 -1
- package/lib/models/environments.generated.d.ts +35 -1
- package/lib/models/environments.generated.js +1 -1
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.js +1 -0
- package/lib/protocols/BrowserAgentAdapter.d.ts +9 -1
- package/lib/protocols/BrowserAgentAdapter.js +11 -4
- package/lib/runtimes/actions.d.ts +5 -0
- package/lib/runtimes/actions.js +19 -0
- package/lib/specs/agents/agents.d.ts +3 -1
- package/lib/specs/agents/agents.js +426 -130
- package/lib/specs/frontendTools.d.ts +1 -0
- package/lib/specs/frontendTools.js +31 -0
- package/lib/specs/mcpServers.js +13 -13
- package/lib/specs/models.js +7 -7
- package/lib/specs/outputs.js +9 -0
- package/lib/specs/skills.js +7 -7
- package/lib/specs/teams/teams.js +1 -1
- package/lib/tools/adapters/copilotkit/CopilotKitToolAdapter.js +1 -1
- package/lib/types/outputs.d.ts +2 -0
- package/package.json +10 -4
- package/patches/@primer+react+37.31.0.patch +390 -0
- package/patches/eslint-plugin-react+7.37.5.patch +65 -0
- package/scripts/apply-patches.sh +15 -1
- package/scripts/codegen/compose.py +4 -4
- package/scripts/codegen/generate_agents.py +1 -2
- package/scripts/codegen/generate_frontend_tools.py +1 -1
- package/scripts/codegen/generate_mcp_servers.py +2 -2
- package/scripts/codegen/generate_models.py +33 -8
- package/scripts/codegen/generate_outputs.py +2 -0
- package/scripts/codegen/generate_skills.py +2 -2
- package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
package/README.md
CHANGED
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
# 🤖 🚀 Agent Runtimes
|
|
12
12
|
|
|
13
|
-
[](https://app.netlify.com/projects/agent-runtimes/deploys)
|
|
13
|
+
[](https://github.com/datalayer/agent-runtimes/actions/workflows/build.yaml)
|
|
15
14
|
[](https://pypi.org/project/agent-runtimes)
|
|
16
15
|
|
|
17
16
|
**Agent Runtimes** is a unified library for deploying, managing, and interacting with AI agents across multiple protocols and frameworks. It provides both a Python server for hosting agents and React components for seamless integration into web and desktop applications.
|
package/lib/AgentNode.js
CHANGED
|
@@ -819,8 +819,8 @@ export function AgentNode() {
|
|
|
819
819
|
const aiAgentsUrl = getConfigUrlFromDocument('aiAgentsUrl') ||
|
|
820
820
|
import.meta.env?.VITE_DATALAYER_AI_AGENTS_URL ||
|
|
821
821
|
'https://r1.datalayer.run';
|
|
822
|
-
const
|
|
823
|
-
import.meta.env?.
|
|
822
|
+
const datalayerMcpServerUrl = getConfigUrlFromDocument('datalayerMcpServerUrl') ||
|
|
823
|
+
import.meta.env?.VITE_DATALAYER_MCP_SERVER_URL ||
|
|
824
824
|
'https://mcp.datalayer.run/mcp';
|
|
825
825
|
// Seed all per-service URLs to match the main UI login behavior.
|
|
826
826
|
const coreApi = coreStore.getState();
|
|
@@ -832,7 +832,7 @@ export function AgentNode() {
|
|
|
832
832
|
libraryUrl: iamUrl,
|
|
833
833
|
aiAgentsUrl,
|
|
834
834
|
aiInferenceUrl: aiInferenceUrl,
|
|
835
|
-
|
|
835
|
+
datalayerMcpServerUrl,
|
|
836
836
|
otelUrl: iamUrl,
|
|
837
837
|
growthUrl: iamUrl,
|
|
838
838
|
successUrl: iamUrl,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EnvironmentData, ListEnvironmentsResponse } from '../../models/EnvironmentDTO';
|
|
2
|
-
import type { ICreateEnvironmentBuildsRequest, ICreateEnvironmentRequest, ICreateEnvironmentVersionRequest, IDatalayerEnvironment, IEnvironmentArtifactsPage, IEnvironmentBuildLogChunk, IEnvironmentBuildLogPage, IEnvironmentBuildRecord, IEnvironmentBuildsCreated, IEnvironmentBuildsPage, IEnvironmentFork, IEnvironmentPublicationRecord, IEnvironmentRecord, IEnvironmentsError, IEnvironmentsPageQuery, IEnvironmentTrial, IEnvironmentValidationReport, IEnvironmentVersionRecord, IEnvironmentVersionsPage, IForkEnvironmentVersionRequest, IListEnvironmentsQuery, IPromoteEnvironmentVersionRequest, ITrialEnvironmentVersionRequest, IUpdateEnvironmentRequest, IUpdateEnvironmentVersionRequest, IValidateEnvironmentVersionRequest } from '../../models/Environment';
|
|
2
|
+
import type { ICreateEnvironmentBuildsRequest, ICreateEnvironmentRequest, ICreateEnvironmentVersionRequest, IDatalayerEnvironment, IEnvironmentArtifactsPage, IEnvironmentBuildLogChunk, IEnvironmentBuildLogPage, IEnvironmentBuildRecord, IEnvironmentBuildsCreated, IEnvironmentBuildsPage, IEnvironmentFork, IEnvironmentPublicationRecord, IEnvironmentQuotas, IEnvironmentRecord, IEnvironmentVersionSandboxes, IEnvironmentsError, IEnvironmentsPageQuery, IEnvironmentTrial, IEnvironmentValidationReport, IEnvironmentVersionRecord, IEnvironmentVersionsPage, IForkEnvironmentVersionRequest, IListEnvironmentsQuery, IPromoteEnvironmentVersionRequest, ITrialEnvironmentVersionRequest, IUpdateEnvironmentRequest, IUpdateEnvironmentVersionRequest, IValidateEnvironmentVersionRequest } from '../../models/Environment';
|
|
3
3
|
/** What every environments call may carry. */
|
|
4
4
|
export interface IEnvironmentsRequestOptions {
|
|
5
5
|
/** Sent as `X-Correlation-Id`; a refusal answers with it. */
|
|
@@ -197,6 +197,27 @@ export declare const createEnvironmentBuilds: (token: string, versionUid: string
|
|
|
197
197
|
* @returns Promise resolving to one page of builds
|
|
198
198
|
*/
|
|
199
199
|
export declare const listEnvironmentBuilds: (token: string, versionUid: string, page?: IEnvironmentsPageQuery, options?: IEnvironmentsRequestOptions, baseUrl?: string) => Promise<IEnvironmentBuildsPage>;
|
|
200
|
+
/**
|
|
201
|
+
* The sandboxes running a version now: yours by uid, anybody else's counted (E2-19).
|
|
202
|
+
* @param token - Authentication token
|
|
203
|
+
* @param versionUid - The version's uid
|
|
204
|
+
* @param options - Correlation id and abort signal
|
|
205
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
206
|
+
* @returns Promise resolving to the version's live sandboxes
|
|
207
|
+
*/
|
|
208
|
+
export declare const getEnvironmentVersionSandboxes: (token: string, versionUid: string, options?: IEnvironmentsRequestOptions, baseUrl?: string) => Promise<IEnvironmentVersionSandboxes>;
|
|
209
|
+
/**
|
|
210
|
+
* An owner's build quotas and what it holds against each (E1-13, E2-19).
|
|
211
|
+
* @param token - Authentication token
|
|
212
|
+
* @param owner - The caller's own when empty; an organization's by its uid
|
|
213
|
+
* @param options - Correlation id and abort signal
|
|
214
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
215
|
+
* @returns Promise resolving to the limits and the holdings
|
|
216
|
+
*/
|
|
217
|
+
export declare const getEnvironmentQuotas: (token: string, owner?: {
|
|
218
|
+
ownerType?: "user" | "organization";
|
|
219
|
+
ownerUid?: string;
|
|
220
|
+
}, options?: IEnvironmentsRequestOptions, baseUrl?: string) => Promise<IEnvironmentQuotas>;
|
|
200
221
|
/**
|
|
201
222
|
* List a version's artifacts, one per variant and region.
|
|
202
223
|
* @param token - Authentication token
|
|
@@ -293,6 +293,37 @@ export const listEnvironmentBuilds = async (token, versionUid, page = {}, option
|
|
|
293
293
|
validateToken(token);
|
|
294
294
|
return send(token, 'GET', registryUrl(baseUrl, `/environment-versions/${segment(versionUid, 'Version UID')}/builds`, { ...page }), options);
|
|
295
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* The sandboxes running a version now: yours by uid, anybody else's counted (E2-19).
|
|
298
|
+
* @param token - Authentication token
|
|
299
|
+
* @param versionUid - The version's uid
|
|
300
|
+
* @param options - Correlation id and abort signal
|
|
301
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
302
|
+
* @returns Promise resolving to the version's live sandboxes
|
|
303
|
+
*/
|
|
304
|
+
export const getEnvironmentVersionSandboxes = async (token, versionUid, options = {}, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
|
|
305
|
+
validateToken(token);
|
|
306
|
+
return send(token, 'GET', registryUrl(baseUrl, `/environment-versions/${segment(versionUid, 'Version UID')}/sandboxes`), options);
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* An owner's build quotas and what it holds against each (E1-13, E2-19).
|
|
310
|
+
* @param token - Authentication token
|
|
311
|
+
* @param owner - The caller's own when empty; an organization's by its uid
|
|
312
|
+
* @param options - Correlation id and abort signal
|
|
313
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
314
|
+
* @returns Promise resolving to the limits and the holdings
|
|
315
|
+
*/
|
|
316
|
+
export const getEnvironmentQuotas = async (token, owner = {}, options = {}, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
|
|
317
|
+
validateToken(token);
|
|
318
|
+
const query = {};
|
|
319
|
+
if (owner.ownerType) {
|
|
320
|
+
query.ownerType = owner.ownerType;
|
|
321
|
+
}
|
|
322
|
+
if (owner.ownerUid) {
|
|
323
|
+
query.ownerUid = owner.ownerUid;
|
|
324
|
+
}
|
|
325
|
+
return send(token, 'GET', registryUrl(baseUrl, '/environment-quotas', query), options);
|
|
326
|
+
};
|
|
296
327
|
/**
|
|
297
328
|
* List a version's artifacts, one per variant and region.
|
|
298
329
|
* @param token - Authentication token
|
|
@@ -36,9 +36,11 @@ export const ping = async (baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
|
|
|
36
36
|
// Check if it's a response error with status code information
|
|
37
37
|
if (error.response) {
|
|
38
38
|
const status = error.response.status;
|
|
39
|
-
throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}
|
|
39
|
+
throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`, { cause: error });
|
|
40
40
|
}
|
|
41
41
|
// Re-throw other errors (network errors, etc.)
|
|
42
|
-
throw new Error(`Health check failed: ${error.message}
|
|
42
|
+
throw new Error(`Health check failed: ${error.message}`, {
|
|
43
|
+
cause: error,
|
|
44
|
+
});
|
|
43
45
|
}
|
|
44
46
|
};
|
|
@@ -36,9 +36,11 @@ export const ping = async (baseUrl = DEFAULT_SERVICE_URLS.SPACER) => {
|
|
|
36
36
|
// Check if it's a response error with status code information
|
|
37
37
|
if (error.response) {
|
|
38
38
|
const status = error.response.status;
|
|
39
|
-
throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}
|
|
39
|
+
throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`, { cause: error });
|
|
40
40
|
}
|
|
41
41
|
// Re-throw other errors (network errors, etc.)
|
|
42
|
-
throw new Error(`Health check failed: ${error.message}
|
|
42
|
+
throw new Error(`Health check failed: ${error.message}`, {
|
|
43
|
+
cause: error,
|
|
44
|
+
});
|
|
43
45
|
}
|
|
44
46
|
};
|
|
@@ -49,6 +49,13 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
49
49
|
buttonTooltip?: string;
|
|
50
50
|
/** Brand color override. Defaults to the theme's `accent.emphasis` token. */
|
|
51
51
|
brandColor?: string;
|
|
52
|
+
/**
|
|
53
|
+
* A soft light breathing around the button while the chat is closed, as
|
|
54
|
+
* if it were alive (see `ButtonGlow`). Still, and dimmer, for a reader who
|
|
55
|
+
* asks the system for reduced motion.
|
|
56
|
+
* @default true
|
|
57
|
+
*/
|
|
58
|
+
buttonGlow?: boolean;
|
|
52
59
|
/** Offset from edge (in pixels) */
|
|
53
60
|
offset?: number;
|
|
54
61
|
/** Animation duration (in ms) */
|
|
@@ -92,5 +99,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
92
99
|
* ChatFloating component
|
|
93
100
|
* A floating chat window built on ChatBase
|
|
94
101
|
*/
|
|
95
|
-
export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, }: ChatFloatingProps): React.JSX.Element;
|
|
102
|
+
export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, }: ChatFloatingProps): React.JSX.Element;
|
|
96
103
|
export default ChatFloating;
|
package/lib/chat/ChatFloating.js
CHANGED
|
@@ -23,6 +23,7 @@ import { XIcon, CommentDiscussionIcon, GrabberIcon, } from '@primer/octicons-rea
|
|
|
23
23
|
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
24
24
|
import { createPortal } from 'react-dom';
|
|
25
25
|
import { ChatBase } from './base/ChatBase';
|
|
26
|
+
import { ButtonGlow } from './display/ButtonGlow';
|
|
26
27
|
import { useViewportDrag } from './useViewportDrag';
|
|
27
28
|
import { disabledChatViewModes, resolveMountPoint } from './viewModes';
|
|
28
29
|
import { useChatOpen, useChatMessages, useChatStore, } from '../stores/chatStore';
|
|
@@ -46,7 +47,7 @@ function useIsMobile(breakpoint = 640) {
|
|
|
46
47
|
* ChatFloating component
|
|
47
48
|
* A floating chat window built on ChatBase
|
|
48
49
|
*/
|
|
49
|
-
export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, }) {
|
|
50
|
+
export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, }) {
|
|
50
51
|
// Store-based state
|
|
51
52
|
const storeIsOpen = useChatOpen();
|
|
52
53
|
const storeMessages = useChatMessages();
|
|
@@ -555,7 +556,9 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
555
556
|
}, children: _jsxs(Box, { sx: {
|
|
556
557
|
position: 'relative',
|
|
557
558
|
display: 'inline-flex',
|
|
558
|
-
|
|
559
|
+
// The glow's halos go behind the button, not behind the page.
|
|
560
|
+
isolation: 'isolate',
|
|
561
|
+
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [buttonGlow && _jsx(ButtonGlow, { color: brandColor }), _jsx(Tooltip, { text: `${buttonTooltip}${shortcutHint ? ` (${shortcutHint})` : ''}`, direction: position.includes('right') ? 'w' : 'e', children: _jsx(IconButton, { icon: buttonIcon
|
|
559
562
|
? buttonIcon
|
|
560
563
|
: CommentDiscussionIcon, "aria-label": buttonTooltip, onClick: handleToggle, size: "large", sx: {
|
|
561
564
|
width: 56,
|
|
@@ -31,7 +31,7 @@ import { useChatStore } from '../../stores/chatStore';
|
|
|
31
31
|
import { useConversationStore } from '../../stores/conversationStore';
|
|
32
32
|
import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/messages';
|
|
33
33
|
import { AgentDetails } from '../../agents/AgentDetails';
|
|
34
|
-
import { AI_MODEL_CATALOGUE } from '../../specs/models';
|
|
34
|
+
import { AI_MODEL_CATALOGUE, DEFAULT_MODEL } from '../../specs/models';
|
|
35
35
|
import { internalQueryClient, isToolCallMessage, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
|
|
36
36
|
import { useConfig, useSkills, useSkillActions, useContextSnapshot, useSandbox, } from '../../hooks';
|
|
37
37
|
import { useAgentRuntimeWebSocket } from '../../hooks/useAgentRuntimes';
|
|
@@ -48,6 +48,7 @@ import { InputPrompt } from '../prompt/InputPrompt';
|
|
|
48
48
|
import { isFloatingChatViewMode } from '../viewModes';
|
|
49
49
|
import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
|
|
50
50
|
import { EphemeralNotebook } from '../notebook/EphemeralNotebook';
|
|
51
|
+
import { initialModelId, isOffered, usableModels } from './modelChoice';
|
|
51
52
|
// EphemeralDocument statically imports `@datalayer/jupyter-lexical` (which
|
|
52
53
|
// initialises Lumino-backed nodes on load). Lazy-load it so notebook-only chats
|
|
53
54
|
// never pull lexical into the bundle or trigger its module side effects.
|
|
@@ -1378,9 +1379,10 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1378
1379
|
? (protocol?.options).model
|
|
1379
1380
|
: undefined;
|
|
1380
1381
|
const offeredModels = useMemo(() => {
|
|
1382
|
+
// Only what can actually be called — see `modelChoice` for the rule.
|
|
1381
1383
|
const fromConfig = availableModels || configQuery.data?.models;
|
|
1382
1384
|
if (fromConfig?.length) {
|
|
1383
|
-
return fromConfig;
|
|
1385
|
+
return usableModels(fromConfig);
|
|
1384
1386
|
}
|
|
1385
1387
|
/*
|
|
1386
1388
|
* The catalogue, filtered to what is worth offering.
|
|
@@ -1402,7 +1404,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1402
1404
|
provider: model.provider,
|
|
1403
1405
|
}));
|
|
1404
1406
|
if (catalogued.length > 0) {
|
|
1405
|
-
return catalogued;
|
|
1407
|
+
return usableModels(catalogued);
|
|
1406
1408
|
}
|
|
1407
1409
|
return browserModel
|
|
1408
1410
|
? [{ id: browserModel, name: browserModel, provider: 'inference' }]
|
|
@@ -1721,7 +1723,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1721
1723
|
const timer = setTimeout(adjustTextareaHeight, 0);
|
|
1722
1724
|
return () => clearTimeout(timer);
|
|
1723
1725
|
}, [adjustTextareaHeight]);
|
|
1724
|
-
// ----
|
|
1726
|
+
// ---- The model, whenever what is offered changes ----
|
|
1725
1727
|
useEffect(() => {
|
|
1726
1728
|
/*
|
|
1727
1729
|
* `offeredModels`, not the two sources it is built from.
|
|
@@ -1731,28 +1733,40 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1731
1733
|
* sources, so that model was offered by the menu and never selected. The
|
|
1732
1734
|
* menu draws nothing without a selection, so a browser agent had a model
|
|
1733
1735
|
* list of one and no model control at all.
|
|
1736
|
+
*
|
|
1737
|
+
* Re-picked, not picked once. Before the server has answered, the offer
|
|
1738
|
+
* is the browser's catalogue, and the chat chose from it — the first row,
|
|
1739
|
+
* Alibaba, which the catalogue marks available because the platform is
|
|
1740
|
+
* entitled to it. The server then answered with the one model this
|
|
1741
|
+
* process can call, the guess was not in it, and nothing looked again:
|
|
1742
|
+
* the trigger read "none selected" while every turn was still sent to
|
|
1743
|
+
* Alibaba and came back asking for its key. A selection stands only while
|
|
1744
|
+
* it is offered and usable; otherwise the preferred model — the agent's
|
|
1745
|
+
* own, from the server — or the first usable one takes its place.
|
|
1734
1746
|
*/
|
|
1735
|
-
if (offeredModels.length
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1747
|
+
if (offeredModels.length === 0 || isOffered(offeredModels, selectedModel)) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
// The host's word first, then the server's default, then the platform's:
|
|
1751
|
+
// the first row of a catalogue is an order of listing, not a choice, and
|
|
1752
|
+
// opening on it sent the first message to Alibaba while the footer —
|
|
1753
|
+
// drawn from the host's own model — said Claude.
|
|
1754
|
+
const opening = initialModelId(offeredModels, initialModel || configQuery.data?.defaultModel || DEFAULT_MODEL);
|
|
1755
|
+
if (opening && opening !== selectedModel) {
|
|
1756
|
+
setSelectedModel(opening);
|
|
1757
|
+
}
|
|
1758
|
+
}, [
|
|
1759
|
+
offeredModels,
|
|
1760
|
+
selectedModel,
|
|
1761
|
+
initialModel,
|
|
1762
|
+
configQuery.data?.defaultModel,
|
|
1763
|
+
]);
|
|
1764
|
+
// ---- Initialize tools when config is available ----
|
|
1765
|
+
const toolsInitializedRef = useRef(false);
|
|
1766
|
+
useEffect(() => {
|
|
1767
|
+
// Once: a later change to the offer must not undo what the reader toggled.
|
|
1768
|
+
if (offeredModels.length > 0 && !toolsInitializedRef.current) {
|
|
1769
|
+
toolsInitializedRef.current = true;
|
|
1756
1770
|
const allToolIds = configQuery.data?.builtinTools?.map(tool => tool.id) || [];
|
|
1757
1771
|
setEnabledTools(allToolIds);
|
|
1758
1772
|
if (configQuery.data?.mcpServers) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which models a chat offers, and which one it opens on.
|
|
3
|
+
*
|
|
4
|
+
* Two rules, kept out of `ChatBase` so they can be read and tested without
|
|
5
|
+
* rendering a chat:
|
|
6
|
+
*
|
|
7
|
+
* - **Only what can be called is offered.** A server answers with its whole
|
|
8
|
+
* catalogue and a flag per model, and the menu used to draw all of it, most
|
|
9
|
+
* rows greyed out with a reason. Thirty models to choose two from reads as
|
|
10
|
+
* a broken menu, and the one thing it guarantees is that a reader's first
|
|
11
|
+
* click lands on something that cannot answer.
|
|
12
|
+
* - **The default has to be one of them.** A default naming a model this
|
|
13
|
+
* deployment cannot use — a spec claiming the default while marked
|
|
14
|
+
* unavailable — opened every chat on it, and the first message came back
|
|
15
|
+
* as the provider's complaint about a missing key.
|
|
16
|
+
*
|
|
17
|
+
* @module chat/base/modelChoice
|
|
18
|
+
*/
|
|
19
|
+
import type { ModelConfig } from '../../types/chat';
|
|
20
|
+
/** Whether a model can be picked: the flag says no, or says nothing. */
|
|
21
|
+
export declare const isUsable: (model: ModelConfig) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The models to offer: the usable ones.
|
|
24
|
+
*
|
|
25
|
+
* When none is usable the list is kept whole. An empty menu says nothing,
|
|
26
|
+
* while the rows and their reasons say which key is missing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function usableModels(models: ModelConfig[]): ModelConfig[];
|
|
29
|
+
/** Whether `id` is on offer and usable — the selection may stand. */
|
|
30
|
+
export declare const isOffered: (offered: ModelConfig[], id?: string) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The model a chat opens on.
|
|
33
|
+
*
|
|
34
|
+
* The preferred one when it is offered *and* usable; otherwise the first
|
|
35
|
+
* usable one; otherwise the first there is. `undefined` when there is none.
|
|
36
|
+
*/
|
|
37
|
+
export declare function initialModelId(offered: ModelConfig[], preferred?: string | null): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* One row of `/api/v1/configure/models`, as the server writes it.
|
|
40
|
+
*
|
|
41
|
+
* Snake case and its own flag names: the route is read by more than this
|
|
42
|
+
* chat, and its shape is the server's to keep.
|
|
43
|
+
*/
|
|
44
|
+
export type ServerCatalogueModel = {
|
|
45
|
+
id: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
available?: boolean;
|
|
48
|
+
missing_env_vars?: string[];
|
|
49
|
+
reason?: string;
|
|
50
|
+
warning?: string | null;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The server's catalogue in the chat's shape.
|
|
54
|
+
*
|
|
55
|
+
* The route says `available`; the chat, the menu and the rules above read
|
|
56
|
+
* `isAvailable`. Handed over unmapped, every row looked usable, the menu
|
|
57
|
+
* offered all thirty-one, and the first message of a chat that opened before
|
|
58
|
+
* its spec was known went to the first row — Alibaba, and its missing key.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readServerCatalogue(payload: {
|
|
61
|
+
models?: ServerCatalogueModel[];
|
|
62
|
+
} | null | undefined): ModelConfig[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** Whether a model can be picked: the flag says no, or says nothing. */
|
|
6
|
+
export const isUsable = (model) => model.isAvailable !== false;
|
|
7
|
+
/**
|
|
8
|
+
* The models to offer: the usable ones.
|
|
9
|
+
*
|
|
10
|
+
* When none is usable the list is kept whole. An empty menu says nothing,
|
|
11
|
+
* while the rows and their reasons say which key is missing.
|
|
12
|
+
*/
|
|
13
|
+
export function usableModels(models) {
|
|
14
|
+
const usable = models.filter(isUsable);
|
|
15
|
+
return usable.length > 0 ? usable : models;
|
|
16
|
+
}
|
|
17
|
+
/** Whether `id` is on offer and usable — the selection may stand. */
|
|
18
|
+
export const isOffered = (offered, id) => Boolean(id) && offered.some(m => m.id === id && isUsable(m));
|
|
19
|
+
/**
|
|
20
|
+
* The model a chat opens on.
|
|
21
|
+
*
|
|
22
|
+
* The preferred one when it is offered *and* usable; otherwise the first
|
|
23
|
+
* usable one; otherwise the first there is. `undefined` when there is none.
|
|
24
|
+
*/
|
|
25
|
+
export function initialModelId(offered, preferred) {
|
|
26
|
+
if (preferred && offered.some(m => m.id === preferred && isUsable(m))) {
|
|
27
|
+
return preferred;
|
|
28
|
+
}
|
|
29
|
+
const first = offered.find(isUsable) ?? offered[0];
|
|
30
|
+
return first?.id;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The server's catalogue in the chat's shape.
|
|
34
|
+
*
|
|
35
|
+
* The route says `available`; the chat, the menu and the rules above read
|
|
36
|
+
* `isAvailable`. Handed over unmapped, every row looked usable, the menu
|
|
37
|
+
* offered all thirty-one, and the first message of a chat that opened before
|
|
38
|
+
* its spec was known went to the first row — Alibaba, and its missing key.
|
|
39
|
+
*/
|
|
40
|
+
export function readServerCatalogue(payload) {
|
|
41
|
+
return (payload?.models ?? []).map(model => {
|
|
42
|
+
const missing = model.missing_env_vars ?? [];
|
|
43
|
+
const reason = model.reason ??
|
|
44
|
+
(missing.length > 0 ? `Set ${missing.join(', ')}` : undefined) ??
|
|
45
|
+
model.warning ??
|
|
46
|
+
undefined;
|
|
47
|
+
return {
|
|
48
|
+
id: model.id,
|
|
49
|
+
name: model.name ?? model.id,
|
|
50
|
+
isAvailable: model.available !== false,
|
|
51
|
+
...(reason && model.available === false
|
|
52
|
+
? { unavailableReason: reason }
|
|
53
|
+
: {}),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ButtonGlowProps {
|
|
2
|
+
/**
|
|
3
|
+
* The colour of the light: the button's own. A theme token or any CSS
|
|
4
|
+
* colour; the theme's `accent.emphasis` when absent.
|
|
5
|
+
*/
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function ButtonGlow({ color }: ButtonGlowProps): import("react").JSX.Element;
|
|
9
|
+
export default ButtonGlow;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A soft light breathing around a round button, as if it were alive.
|
|
8
|
+
*
|
|
9
|
+
* Two blurred halos in the button's colour swell and fade on cycles of
|
|
10
|
+
* different lengths, 3.4 s and 5.3 s, so the light drifts rather than
|
|
11
|
+
* blinks: the two never line up the same way twice in a row. For a reader
|
|
12
|
+
* who asks the system for less motion, the halos hold still, a little dimmer.
|
|
13
|
+
*
|
|
14
|
+
* The halos sit behind the button: the parent must be `position: relative`
|
|
15
|
+
* and make a stacking context of its own (`isolation: 'isolate'`), or they
|
|
16
|
+
* would slip behind the page instead.
|
|
17
|
+
*
|
|
18
|
+
* @module chat/display/ButtonGlow
|
|
19
|
+
*/
|
|
20
|
+
import { Box } from '@datalayer/primer-addons';
|
|
21
|
+
const halo = (color) => ({
|
|
22
|
+
position: 'absolute',
|
|
23
|
+
inset: 0,
|
|
24
|
+
borderRadius: '50%',
|
|
25
|
+
bg: color || 'accent.emphasis',
|
|
26
|
+
zIndex: -1,
|
|
27
|
+
pointerEvents: 'none',
|
|
28
|
+
willChange: 'transform, opacity',
|
|
29
|
+
});
|
|
30
|
+
export function ButtonGlow({ color }) {
|
|
31
|
+
return (_jsxs(_Fragment, { children: [_jsx(Box, { "aria-hidden": true, "data-button-glow": "breath", sx: {
|
|
32
|
+
...halo(color),
|
|
33
|
+
filter: 'blur(10px)',
|
|
34
|
+
animation: 'agentRuntimesGlowBreath 3.4s ease-in-out infinite',
|
|
35
|
+
'@keyframes agentRuntimesGlowBreath': {
|
|
36
|
+
'0%, 100%': { transform: 'scale(0.9)', opacity: 0.35 },
|
|
37
|
+
'50%': { transform: 'scale(1.25)', opacity: 0.7 },
|
|
38
|
+
},
|
|
39
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
40
|
+
animation: 'none',
|
|
41
|
+
transform: 'scale(1.1)',
|
|
42
|
+
opacity: 0.4,
|
|
43
|
+
},
|
|
44
|
+
} }), _jsx(Box, { "aria-hidden": true, "data-button-glow": "aura", sx: {
|
|
45
|
+
...halo(color),
|
|
46
|
+
filter: 'blur(18px)',
|
|
47
|
+
animation: 'agentRuntimesGlowAura 5.3s ease-in-out infinite',
|
|
48
|
+
'@keyframes agentRuntimesGlowAura': {
|
|
49
|
+
'0%, 100%': { transform: 'scale(1)', opacity: 0.15 },
|
|
50
|
+
'40%': { transform: 'scale(1.55)', opacity: 0.4 },
|
|
51
|
+
'70%': { transform: 'scale(1.3)', opacity: 0.25 },
|
|
52
|
+
},
|
|
53
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
54
|
+
animation: 'none',
|
|
55
|
+
transform: 'scale(1.3)',
|
|
56
|
+
opacity: 0.2,
|
|
57
|
+
},
|
|
58
|
+
} })] }));
|
|
59
|
+
}
|
|
60
|
+
export default ButtonGlow;
|
|
@@ -50,6 +50,9 @@ export function ChatEmptyState({ emptyState, brandIcon, description, suggestions
|
|
|
50
50
|
cursor: 'pointer',
|
|
51
51
|
display: 'inline-flex',
|
|
52
52
|
alignItems: 'center',
|
|
53
|
+
// A short opener sits in the middle of its fixed-width chip, not
|
|
54
|
+
// against its left edge with the rest of the chip empty.
|
|
55
|
+
justifyContent: 'center',
|
|
53
56
|
width: SUGGESTION_CHIP_WIDTH,
|
|
54
57
|
flex: `0 0 ${SUGGESTION_CHIP_WIDTH}px`,
|
|
55
58
|
'&:hover': {
|
|
@@ -57,7 +60,7 @@ export function ChatEmptyState({ emptyState, brandIcon, description, suggestions
|
|
|
57
60
|
color: 'var(--button-primary-fgColor-rest)',
|
|
58
61
|
borderColor: 'accent.emphasis',
|
|
59
62
|
},
|
|
60
|
-
}, onClick: () => handleSuggestionClick(suggestion), children: _jsx(Truncate, { title: suggestion.message, maxWidth: "100%", sx: { minWidth: 0 }, children: suggestion.title }) }, index))) }));
|
|
63
|
+
}, onClick: () => handleSuggestionClick(suggestion), children: _jsx(Truncate, { inline: true, title: suggestion.message, maxWidth: "100%", sx: { minWidth: 0 }, children: suggestion.title }) }, index))) }));
|
|
61
64
|
return (_jsx(ThemeProvider, { children: _jsxs(Box, { sx: {
|
|
62
65
|
display: 'flex',
|
|
63
66
|
flexDirection: 'column',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Heading, IconButton, Label, Text,
|
|
2
|
+
import { Heading, IconButton, Label, Text, Truncate } from '@primer/react';
|
|
3
3
|
import { Box } from '@datalayer/primer-addons';
|
|
4
4
|
import { KernelIndicator } from '@datalayer/jupyter-react';
|
|
5
|
-
import { PlusIcon, TrashIcon, GearIcon,
|
|
6
|
-
import {
|
|
5
|
+
import { PlusIcon, TrashIcon, GearIcon, InfoIcon, } from '@primer/octicons-react';
|
|
6
|
+
import { ChatViewModeToggle } from './ChatViewModeToggle';
|
|
7
7
|
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
8
8
|
import { EphemeralSurfaceControl } from '../EphemeralSurfaceControl';
|
|
9
9
|
export function toRuntimeExecutionState(runtimeStatus) {
|
|
@@ -29,16 +29,6 @@ export function toRuntimeExecutionState(runtimeStatus) {
|
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
|
-
// Props
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
/** The drawing of each mode in the header's toggle. */
|
|
35
|
-
const VIEW_MODE_ICONS = {
|
|
36
|
-
floating: CommentDiscussionIcon,
|
|
37
|
-
'floating-small': DeviceMobileIcon,
|
|
38
|
-
'floating-draggable': GrabberIcon,
|
|
39
|
-
sidebar: SidebarExpandIcon,
|
|
40
|
-
};
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
32
|
// Component
|
|
43
33
|
// ---------------------------------------------------------------------------
|
|
44
34
|
export function ChatBaseHeader({ title, subtitle, disableReason, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, kernelIndicatorState, kernelIndicatorPlacement = 'left', runtimeStatus, kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, disabledViewModes = [], showEphemeralSurfaceControl = false, enableEphemeralNotebookOption = false, enableEphemeralDocumentOption = false, ephemeralSurfaceMode = 'none', onEphemeralSurfaceModeChange, }) {
|
|
@@ -90,47 +80,5 @@ export function ChatBaseHeader({ title, subtitle, disableReason, brandIcon, head
|
|
|
90
80
|
color: 'fg.muted',
|
|
91
81
|
minWidth: 0,
|
|
92
82
|
maxWidth: '100%',
|
|
93
|
-
}, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), disableReason && (_jsx(Label, { variant: "attention", sx: { flexShrink: 0 }, children: disableReason })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick })), kernelIndicatorPlacement === 'center' && kernelIndicatorElement] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [kernelIndicatorPlacement === 'right' && kernelIndicatorElement, headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), showEphemeralSurfaceControl && (_jsx(EphemeralSurfaceControl, { mode: ephemeralSurfaceMode, onChange: onEphemeralSurfaceModeChange, enableNotebook: enableEphemeralNotebookOption, enableDocument: enableEphemeralDocumentOption })), chatViewMode && onChatViewModeChange && (_jsx(
|
|
94
|
-
display: 'inline-flex',
|
|
95
|
-
alignItems: 'center',
|
|
96
|
-
bg: 'neutral.muted',
|
|
97
|
-
borderRadius: '6px',
|
|
98
|
-
p: '2px',
|
|
99
|
-
gap: '1px',
|
|
100
|
-
}, children: CHAT_VIEW_MODES.map(({ mode, label }) => {
|
|
101
|
-
const ModeIcon = VIEW_MODE_ICONS[mode];
|
|
102
|
-
const selected = chatViewMode === mode;
|
|
103
|
-
/* Greyed rather than left out: the person sees the mode
|
|
104
|
-
exists, and that this page has no place for it. */
|
|
105
|
-
const disabled = disabledViewModes.includes(mode);
|
|
106
|
-
return (_jsx(Tooltip, { text: disabled && mode === 'sidebar'
|
|
107
|
-
? `${label} — ${SIDEBAR_NEEDS_MOUNT_POINT}`
|
|
108
|
-
: label, direction: "n", children: _jsx(Box, { as: "button", "aria-label": label, "aria-disabled": disabled || undefined, onClick: () => {
|
|
109
|
-
if (!disabled) {
|
|
110
|
-
onChatViewModeChange(mode);
|
|
111
|
-
}
|
|
112
|
-
}, sx: {
|
|
113
|
-
display: 'inline-flex',
|
|
114
|
-
alignItems: 'center',
|
|
115
|
-
justifyContent: 'center',
|
|
116
|
-
width: 26,
|
|
117
|
-
height: 24,
|
|
118
|
-
borderRadius: '4px',
|
|
119
|
-
border: 'none',
|
|
120
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
121
|
-
opacity: disabled ? 0.45 : 1,
|
|
122
|
-
bg: selected ? 'canvas.default' : 'transparent',
|
|
123
|
-
boxShadow: selected ? 'shadow.small' : 'none',
|
|
124
|
-
color: selected ? 'fg.default' : 'fg.muted',
|
|
125
|
-
transition: 'all 0.15s ease',
|
|
126
|
-
'&:hover': disabled
|
|
127
|
-
? {}
|
|
128
|
-
: {
|
|
129
|
-
color: 'fg.default',
|
|
130
|
-
bg: selected
|
|
131
|
-
? 'canvas.default'
|
|
132
|
-
: 'neutral.subtle',
|
|
133
|
-
},
|
|
134
|
-
}, children: _jsx(ModeIcon, { size: 14 }) }) }, mode));
|
|
135
|
-
}) })), headerActions] })] }) }));
|
|
83
|
+
}, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), disableReason && (_jsx(Label, { variant: "attention", sx: { flexShrink: 0 }, children: disableReason })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick })), kernelIndicatorPlacement === 'center' && kernelIndicatorElement] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [kernelIndicatorPlacement === 'right' && kernelIndicatorElement, headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), showEphemeralSurfaceControl && (_jsx(EphemeralSurfaceControl, { mode: ephemeralSurfaceMode, onChange: onEphemeralSurfaceModeChange, enableNotebook: enableEphemeralNotebookOption, enableDocument: enableEphemeralDocumentOption })), chatViewMode && onChatViewModeChange && (_jsx(ChatViewModeToggle, { value: chatViewMode, onChange: onChatViewModeChange, disabledModes: disabledViewModes })), headerActions] })] }) }));
|
|
136
84
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segmented toggle for how a chat is shown — the four modes of
|
|
3
|
+
* `CHAT_VIEW_MODES`, drawn as icons in a pill, the current one raised.
|
|
4
|
+
*
|
|
5
|
+
* It grew up in the chat header, and lives apart from it so the same
|
|
6
|
+
* control can stand elsewhere: a composer floating over a page as a
|
|
7
|
+
* draggable card carries it in its own footer, where the person deciding
|
|
8
|
+
* how the chat should sit has the chat under their hands. The rules — the
|
|
9
|
+
* order, the words, which modes are greyed out — stay in `viewModes.ts`;
|
|
10
|
+
* this is only their drawing.
|
|
11
|
+
*
|
|
12
|
+
* @module chat/header/ChatViewModeToggle
|
|
13
|
+
*/
|
|
14
|
+
import type { JSX } from 'react';
|
|
15
|
+
import { type Icon } from '@primer/octicons-react';
|
|
16
|
+
import type { ChatViewMode } from '../../types/chat';
|
|
17
|
+
/** The drawing of each mode. */
|
|
18
|
+
export declare const VIEW_MODE_ICONS: Record<ChatViewMode, Icon>;
|
|
19
|
+
export type ChatViewModeToggleProps = {
|
|
20
|
+
/** The mode the chat is in now. */
|
|
21
|
+
value?: ChatViewMode;
|
|
22
|
+
/** The mode the person picked. */
|
|
23
|
+
onChange: (mode: ChatViewMode) => void;
|
|
24
|
+
/** Modes shown greyed out: the sidebar, when the host has no mount point. */
|
|
25
|
+
disabledModes?: readonly ChatViewMode[];
|
|
26
|
+
/** Where the tooltips open. Above by default; below for a control at the top of its window. */
|
|
27
|
+
tooltipDirection?: 'n' | 's';
|
|
28
|
+
};
|
|
29
|
+
export declare function ChatViewModeToggle({ value, onChange, disabledModes, tooltipDirection, }: ChatViewModeToggleProps): JSX.Element;
|