@datalayer/agent-runtimes 0.0.11 → 0.0.12
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 +2 -2
- package/lib/Agent.d.ts +29 -0
- package/lib/Agent.js +131 -0
- package/lib/AgentLexical.d.ts +34 -0
- package/lib/AgentLexical.js +296 -0
- package/lib/AgentNotebook.d.ts +19 -0
- package/lib/AgentNotebook.js +192 -0
- package/lib/agent-lexical-main.d.ts +1 -0
- package/lib/agent-lexical-main.js +11 -0
- package/lib/agent-main.d.ts +1 -0
- package/lib/agent-main.js +11 -0
- package/lib/agent-notebook-main.d.ts +1 -0
- package/lib/agent-notebook-main.js +12 -0
- package/lib/components/AgentConfiguration.d.ts +4 -22
- package/lib/components/AgentConfiguration.js +8 -8
- package/lib/components/chat/components/AgentDetails.d.ts +3 -1
- package/lib/components/chat/components/AgentDetails.js +6 -6
- package/lib/components/chat/components/Chat.d.ts +29 -3
- package/lib/components/chat/components/Chat.js +64 -59
- package/lib/components/chat/components/ChatFloating.d.ts +34 -12
- package/lib/components/chat/components/ChatFloating.js +54 -21
- package/lib/components/chat/components/ChatInline.d.ts +5 -1
- package/lib/components/chat/components/ChatInline.js +8 -1
- package/lib/components/chat/components/ChatSidebar.d.ts +6 -1
- package/lib/components/chat/components/ChatSidebar.js +2 -2
- package/lib/components/chat/components/ChatStandalone.d.ts +6 -1
- package/lib/components/chat/components/ChatStandalone.js +2 -2
- package/lib/components/chat/components/base/ChatBase.d.ts +49 -8
- package/lib/components/chat/components/base/ChatBase.js +544 -149
- package/lib/components/chat/components/base/InputPrompt.d.ts +42 -0
- package/lib/components/chat/components/base/InputPrompt.js +131 -0
- package/lib/components/chat/components/index.d.ts +3 -3
- package/lib/components/chat/components/index.js +1 -1
- package/lib/components/chat/components/parts/ReasoningPart.js +2 -4
- package/lib/components/chat/components/parts/TextPart.js +2 -70
- package/lib/components/chat/components/styles/streamdownStyles.d.ts +23 -0
- package/lib/components/chat/components/styles/streamdownStyles.js +319 -0
- package/lib/components/chat/index.d.ts +1 -1
- package/lib/components/chat/index.js +1 -1
- package/lib/components/chat/inference/DatalayerInferenceProvider.js +16 -12
- package/lib/components/chat/inference/SelfHostedInferenceProvider.js +16 -12
- package/lib/components/chat/protocols/AGUIAdapter.d.ts +10 -3
- package/lib/components/chat/protocols/AGUIAdapter.js +123 -44
- package/lib/components/chat/types/tool.d.ts +5 -2
- package/lib/components/index.d.ts +1 -18
- package/lib/components/index.js +0 -9
- package/lib/config/index.d.ts +0 -4
- package/lib/config/index.js +0 -4
- package/lib/examples/A2UiRestaurantExample.js +1 -1
- package/lib/examples/AgentRuntimeChatExample.d.ts +15 -0
- package/lib/examples/AgentRuntimeChatExample.js +126 -0
- package/lib/examples/{AgentSpaceFormExample.d.ts → AgentRuntimeFormExample.d.ts} +3 -3
- package/lib/examples/{AgentSpaceFormExample.js → AgentRuntimeFormExample.js} +10 -8
- package/lib/examples/AgentRuntimeLexicalExample.js +6 -3
- package/lib/examples/AgentRuntimeLexicalSidebarExample.js +8 -1
- package/lib/examples/AgentRuntimeNotebookExample.js +6 -5
- package/lib/examples/CopilotKitNotebookExample.js +2 -2
- package/lib/examples/JupyterNotebookExample.js +2 -2
- package/lib/{components → examples/components}/Header.d.ts +2 -1
- package/lib/{components → examples/components}/HeaderControls.js +1 -1
- package/lib/{components → examples/components}/LexicalEditor.d.ts +6 -1
- package/lib/{components → examples/components}/LexicalEditor.js +4 -4
- package/lib/{components → examples/components}/MainContent.d.ts +1 -1
- package/lib/{components → examples/components}/MainContent.js +7 -5
- package/lib/examples/components/index.d.ts +16 -0
- package/lib/examples/components/index.js +13 -0
- package/lib/examples/example-selector.js +2 -1
- package/lib/examples/index.d.ts +1 -1
- package/lib/examples/index.js +1 -1
- package/lib/examples/main.js +2 -2
- package/lib/examples/stores/examplesStore.d.ts +2 -23
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -0
- package/lib/lexical/ChatInlinePlugin.d.ts +13 -2
- package/lib/lexical/ChatInlinePlugin.js +41 -179
- package/lib/lexical/index.d.ts +1 -0
- package/lib/lexical/index.js +1 -0
- package/lib/lexical/useChatInlineToolbarItems.d.ts +28 -0
- package/lib/lexical/useChatInlineToolbarItems.js +163 -0
- package/lib/runtime/useAgentRuntime.d.ts +1 -1
- package/lib/runtime/useAgentRuntime.js +1 -1
- package/lib/{config/agents/code-ai → specs/agents/codeai}/agents.d.ts +5 -2
- package/lib/specs/agents/codeai/agents.js +151 -0
- package/lib/{config → specs}/agents/codemode-paper/agents.d.ts +4 -2
- package/lib/{config → specs}/agents/codemode-paper/agents.js +39 -19
- package/lib/{config → specs}/agents/datalayer-ai/agents.d.ts +4 -2
- package/lib/{config → specs}/agents/datalayer-ai/agents.js +17 -2
- package/lib/{config → specs}/agents/index.d.ts +3 -1
- package/lib/{config → specs}/agents/index.js +12 -3
- package/lib/{config → specs}/envvars.d.ts +1 -0
- package/lib/{config → specs}/envvars.js +10 -0
- package/lib/specs/index.d.ts +5 -0
- package/lib/specs/index.js +9 -0
- package/lib/{config → specs}/mcpServers.d.ts +2 -1
- package/lib/{config → specs}/mcpServers.js +23 -1
- package/lib/specs/models.d.ts +68 -0
- package/lib/specs/models.js +239 -0
- package/lib/state/substates/AIAgentState.d.ts +0 -1
- package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.d.ts +11 -22
- package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.js +5 -5
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +6 -6
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +4 -4
- package/lib/tools/adapters/agent-runtimes/notebookHooks.d.ts +6 -6
- package/lib/tools/adapters/agent-runtimes/notebookHooks.js +4 -4
- package/lib/{types.d.ts → types/Types.d.ts} +32 -6
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/package.json +11 -5
- package/scripts/codegen/generate_agents.py +53 -13
- package/scripts/codegen/generate_envvars.py +1 -1
- package/scripts/codegen/generate_mcp_servers.py +5 -5
- package/scripts/codegen/generate_models.py +486 -0
- package/scripts/codegen/generate_skills.py +2 -2
- package/style/primer-primitives.css +22 -0
- package/lib/components/chat/components/elements/ChatInputPrompt.d.ts +0 -37
- package/lib/components/chat/components/elements/ChatInputPrompt.js +0 -150
- package/lib/config/agents/code-ai/agents.js +0 -70
- /package/lib/{components → examples/components}/FooterMetrics.d.ts +0 -0
- /package/lib/{components → examples/components}/FooterMetrics.js +0 -0
- /package/lib/{components → examples/components}/Header.js +0 -0
- /package/lib/{components → examples/components}/HeaderControls.d.ts +0 -0
- /package/lib/{components → examples/components}/MockFileBrowser.d.ts +0 -0
- /package/lib/{components → examples/components}/MockFileBrowser.js +0 -0
- /package/lib/{components → examples/components}/SessionTabs.d.ts +0 -0
- /package/lib/{components → examples/components}/SessionTabs.js +0 -0
- /package/lib/{components → examples/components}/TimeTravel.d.ts +0 -0
- /package/lib/{components → examples/components}/TimeTravel.js +0 -0
- /package/lib/{config/agents/code-ai → specs/agents/codeai}/index.d.ts +0 -0
- /package/lib/{config/agents/code-ai → specs/agents/codeai}/index.js +0 -0
- /package/lib/{config → specs}/agents/codemode-paper/index.d.ts +0 -0
- /package/lib/{config → specs}/agents/codemode-paper/index.js +0 -0
- /package/lib/{config → specs}/agents/datalayer-ai/index.d.ts +0 -0
- /package/lib/{config → specs}/agents/datalayer-ai/index.js +0 -0
- /package/lib/{config → specs}/skills.d.ts +0 -0
- /package/lib/{config → specs}/skills.js +0 -0
- /package/lib/{types.js → types/Types.js} +0 -0
|
@@ -17,12 +17,14 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
17
17
|
*/
|
|
18
18
|
import { useContext } from 'react';
|
|
19
19
|
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
20
|
-
import { Heading, Text, Spinner, IconButton,
|
|
20
|
+
import { Heading, Text, Spinner, IconButton, Button, ActionMenu, ActionList, LabelGroup, Label, ToggleSwitch, } from '@primer/react';
|
|
21
21
|
import { Box } from '@datalayer/primer-addons';
|
|
22
|
-
import { AlertIcon, PlusIcon, TrashIcon, GearIcon, PersonIcon,
|
|
22
|
+
import { AlertIcon, PlusIcon, TrashIcon, GearIcon, PersonIcon, ToolsIcon, AiModelIcon, BriefcaseIcon, CircleIcon, SquareFillIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, InfoIcon, } from '@primer/octicons-react';
|
|
23
23
|
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
24
|
+
import ReactECharts from 'echarts-for-react';
|
|
24
25
|
import { useQuery, QueryClient, QueryClientProvider, QueryClientContext, } from '@tanstack/react-query';
|
|
25
26
|
import { Streamdown } from 'streamdown';
|
|
27
|
+
import { streamdownMarkdownStyles, streamdownCodeBlockStyles, } from '../styles/streamdownStyles';
|
|
26
28
|
import { PoweredByTag } from '../elements/PoweredByTag';
|
|
27
29
|
import { requestAPI } from '../../handler';
|
|
28
30
|
import { useChatStore } from '../../store/chatStore';
|
|
@@ -30,6 +32,7 @@ import { useConversationStore } from '../../store/conversationStore';
|
|
|
30
32
|
import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/message';
|
|
31
33
|
import { AGUIAdapter, A2AAdapter, VercelAIAdapter, ACPAdapter, } from '../../protocols';
|
|
32
34
|
import { ToolCallDisplay } from '../display/ToolCallDisplay';
|
|
35
|
+
import { InputPrompt } from './InputPrompt';
|
|
33
36
|
// Singleton QueryClient for ChatBase instances without external QueryClientProvider
|
|
34
37
|
const internalQueryClient = new QueryClient({
|
|
35
38
|
defaultOptions: {
|
|
@@ -94,6 +97,79 @@ function getMessageText(message) {
|
|
|
94
97
|
.map(part => part.text)
|
|
95
98
|
.join('');
|
|
96
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Convert history messages to display items.
|
|
102
|
+
*
|
|
103
|
+
* History returns:
|
|
104
|
+
* - Assistant messages with `toolCalls` array (tool invocations)
|
|
105
|
+
* - Tool messages (role='tool') with content (tool results)
|
|
106
|
+
*
|
|
107
|
+
* For display, we need to:
|
|
108
|
+
* 1. Keep user/assistant text messages as ChatMessage
|
|
109
|
+
* 2. Convert each toolCall from assistant messages into a ToolCallMessage
|
|
110
|
+
* 3. Match tool result messages (role='tool') to their ToolCallMessage and update result
|
|
111
|
+
* 4. Filter out raw tool messages (role='tool') from display — they're merged into ToolCallMessage
|
|
112
|
+
*/
|
|
113
|
+
function convertHistoryToDisplayItems(messages) {
|
|
114
|
+
const displayItems = [];
|
|
115
|
+
const toolCallMap = new Map();
|
|
116
|
+
// First pass: collect all tool calls and build the initial display list
|
|
117
|
+
for (const msg of messages) {
|
|
118
|
+
if (msg.role === 'tool') {
|
|
119
|
+
// Tool result messages — will be merged later
|
|
120
|
+
const toolCallId = msg.metadata?.toolCallId;
|
|
121
|
+
if (toolCallId && toolCallMap.has(toolCallId)) {
|
|
122
|
+
// Update the existing tool call with the result
|
|
123
|
+
const toolCall = toolCallMap.get(toolCallId);
|
|
124
|
+
toolCall.result = msg.content;
|
|
125
|
+
toolCall.status = 'complete';
|
|
126
|
+
}
|
|
127
|
+
// Don't add tool messages to display — they're represented by ToolCallMessage
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
131
|
+
// Assistant message with tool calls
|
|
132
|
+
// First add any text content as a regular message
|
|
133
|
+
const textContent = typeof msg.content === 'string' ? msg.content.trim() : '';
|
|
134
|
+
if (textContent) {
|
|
135
|
+
displayItems.push({
|
|
136
|
+
...msg,
|
|
137
|
+
toolCalls: undefined, // Remove toolCalls from the text message
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Then add each tool call as a ToolCallMessage
|
|
141
|
+
// Map from message.ts ToolCallStatus to ChatBase ToolCallStatus
|
|
142
|
+
for (const tc of msg.toolCalls) {
|
|
143
|
+
let status = 'complete';
|
|
144
|
+
if (tc.status === 'pending' || tc.status === 'awaiting-approval') {
|
|
145
|
+
status = 'inProgress';
|
|
146
|
+
}
|
|
147
|
+
else if (tc.status === 'executing') {
|
|
148
|
+
status = 'executing';
|
|
149
|
+
}
|
|
150
|
+
else if (tc.status === 'failed') {
|
|
151
|
+
status = 'error';
|
|
152
|
+
}
|
|
153
|
+
const toolCallMsg = {
|
|
154
|
+
id: `tc-${tc.toolCallId}`,
|
|
155
|
+
type: 'tool-call',
|
|
156
|
+
toolCallId: tc.toolCallId,
|
|
157
|
+
toolName: tc.toolName,
|
|
158
|
+
args: tc.args || {},
|
|
159
|
+
status,
|
|
160
|
+
result: tc.result,
|
|
161
|
+
};
|
|
162
|
+
toolCallMap.set(tc.toolCallId, toolCallMsg);
|
|
163
|
+
displayItems.push(toolCallMsg);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Regular user/assistant/system message
|
|
168
|
+
displayItems.push(msg);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return displayItems;
|
|
172
|
+
}
|
|
97
173
|
/**
|
|
98
174
|
* Create protocol adapter based on configuration
|
|
99
175
|
*/
|
|
@@ -259,19 +335,63 @@ function useContextSnapshotQuery(enabled, configEndpoint, agentId, authToken) {
|
|
|
259
335
|
});
|
|
260
336
|
return result;
|
|
261
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Hook to poll sandbox execution status from the backend.
|
|
340
|
+
* Returns whether a sandbox is available and if code is currently executing.
|
|
341
|
+
*/
|
|
342
|
+
function useSandboxStatusQuery(enabled, configEndpoint, authToken) {
|
|
343
|
+
const queryClient = useContext(QueryClientContext);
|
|
344
|
+
if (!queryClient) {
|
|
345
|
+
return {
|
|
346
|
+
data: undefined,
|
|
347
|
+
isLoading: false,
|
|
348
|
+
isError: false,
|
|
349
|
+
error: null,
|
|
350
|
+
refetch: () => Promise.resolve({}),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const statusUrl = configEndpoint
|
|
354
|
+
? `${getApiBaseFromConfig(configEndpoint)}/configure/sandbox-status`
|
|
355
|
+
: undefined;
|
|
356
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
357
|
+
const result = useQuery({
|
|
358
|
+
queryKey: ['sandbox-status', statusUrl],
|
|
359
|
+
queryFn: async () => {
|
|
360
|
+
if (!statusUrl) {
|
|
361
|
+
throw new Error('No sandbox status URL available');
|
|
362
|
+
}
|
|
363
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
364
|
+
if (authToken) {
|
|
365
|
+
headers['Authorization'] = `Bearer ${authToken}`;
|
|
366
|
+
}
|
|
367
|
+
const response = await fetch(statusUrl, { headers });
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
throw new Error(`Sandbox status fetch failed: ${response.statusText}`);
|
|
370
|
+
}
|
|
371
|
+
return response.json();
|
|
372
|
+
},
|
|
373
|
+
enabled: enabled && !!statusUrl,
|
|
374
|
+
refetchInterval: query => (query.state.status === 'error' ? false : 2_000),
|
|
375
|
+
refetchOnMount: 'always',
|
|
376
|
+
staleTime: 0,
|
|
377
|
+
retry: 1,
|
|
378
|
+
});
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
262
381
|
/**
|
|
263
382
|
* ChatBase component - Universal chat panel supporting store, protocol, and custom modes
|
|
264
383
|
*/
|
|
265
|
-
export function ChatBase({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions,
|
|
384
|
+
export function ChatBase({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions, chatViewMode, onChatViewModeChange,
|
|
266
385
|
// Mode selection
|
|
267
386
|
useStore: useStoreMode = true, protocol: protocolProp, agentRuntimeConfig, onSendMessage, enableStreaming = false,
|
|
268
387
|
// Extended props
|
|
269
|
-
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
|
|
388
|
+
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
|
|
270
389
|
// Identity/Authorization props
|
|
271
390
|
onAuthorizationRequired, connectedIdentities,
|
|
272
391
|
// Conversation persistence
|
|
273
|
-
runtimeId, historyEndpoint, historyAuthToken,
|
|
274
|
-
|
|
392
|
+
runtimeId, historyEndpoint, historyAuthToken,
|
|
393
|
+
// Pending prompt
|
|
394
|
+
pendingPrompt, }) {
|
|
275
395
|
const protocol = agentRuntimeConfig
|
|
276
396
|
? {
|
|
277
397
|
type: agentRuntimeConfig.protocol || 'ag-ui',
|
|
@@ -288,25 +408,30 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
288
408
|
const existingQueryClient = useContext(QueryClientContext);
|
|
289
409
|
// If no QueryClient is available, wrap with our internal provider
|
|
290
410
|
if (!existingQueryClient) {
|
|
291
|
-
return (_jsx(QueryClientProvider, { client: internalQueryClient, children: _jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken }) }));
|
|
411
|
+
return (_jsx(QueryClientProvider, { client: internalQueryClient, children: _jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, chatViewMode: chatViewMode, onChatViewModeChange: onChatViewModeChange, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, showInformation: showInformation, onInformationClick: onInformationClick, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt }) }));
|
|
292
412
|
}
|
|
293
413
|
// QueryClient already available, render inner component directly
|
|
294
|
-
return (_jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken }));
|
|
414
|
+
return (_jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, chatViewMode: chatViewMode, onChatViewModeChange: onChatViewModeChange, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, showInformation: showInformation, onInformationClick: onInformationClick, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt }));
|
|
295
415
|
}
|
|
296
416
|
/**
|
|
297
417
|
* Inner ChatBase component - contains all the actual logic
|
|
298
418
|
*/
|
|
299
|
-
function ChatBaseInner({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions,
|
|
419
|
+
function ChatBaseInner({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions, chatViewMode, onChatViewModeChange,
|
|
300
420
|
// Mode selection
|
|
301
421
|
useStore: useStoreMode = true, protocol, onSendMessage, enableStreaming = false,
|
|
302
422
|
// Extended props
|
|
303
|
-
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
|
|
423
|
+
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
|
|
304
424
|
// Identity/Authorization props
|
|
305
425
|
onAuthorizationRequired, connectedIdentities,
|
|
306
426
|
// Conversation persistence
|
|
307
|
-
runtimeId, historyEndpoint, historyAuthToken,
|
|
308
|
-
|
|
427
|
+
runtimeId, historyEndpoint, historyAuthToken,
|
|
428
|
+
// Pending prompt
|
|
429
|
+
pendingPrompt, }) {
|
|
309
430
|
useHighZIndexPortal();
|
|
431
|
+
// Stabilize the protocol reference so that the adapter-init effect only
|
|
432
|
+
// re-runs when the protocol *contents* actually change, not just when the
|
|
433
|
+
// parent re-renders with a new object literal that has the same values.
|
|
434
|
+
const protocolKey = protocol ? JSON.stringify(protocol) : '';
|
|
310
435
|
// Store (optional for message persistence)
|
|
311
436
|
const clearStoreMessages = useChatStore(state => state.clearMessages);
|
|
312
437
|
// Check if protocol is A2A (doesn't support per-request model override)
|
|
@@ -317,7 +442,12 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
317
442
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
318
443
|
const [error, setError] = useState(null);
|
|
319
444
|
const [input, setInput] = useState('');
|
|
320
|
-
//
|
|
445
|
+
// History-loaded flag — true immediately when there is nothing to fetch
|
|
446
|
+
const [historyLoaded, setHistoryLoaded] = useState(!runtimeId);
|
|
447
|
+
// Adapter-ready flag — flipped to true once the protocol adapter is initialised
|
|
448
|
+
const [adapterReady, setAdapterReady] = useState(false);
|
|
449
|
+
// Guard so the pending prompt is sent at most once
|
|
450
|
+
const pendingPromptSentRef = useRef(false);
|
|
321
451
|
const [selectedModel, setSelectedModel] = useState('');
|
|
322
452
|
// enabledTools tracks which MCP server tools are enabled
|
|
323
453
|
// Format: Map<serverId, Set<toolName>>
|
|
@@ -336,15 +466,26 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
336
466
|
// Gated by showTokenUsage (not showHeader), so usage is visible even without a title bar.
|
|
337
467
|
const contextSnapshotQuery = useContextSnapshotQuery(Boolean(protocol?.enableConfigQuery) && showTokenUsage, protocol?.configEndpoint, protocol?.agentId, protocol?.authToken);
|
|
338
468
|
const agentUsage = contextSnapshotQuery.data;
|
|
469
|
+
// Sandbox status query — polls sandbox execution state for the header indicator.
|
|
470
|
+
// Only active when codemode is enabled and there's a config endpoint.
|
|
471
|
+
const sandboxStatusQuery = useSandboxStatusQuery(Boolean(protocol?.enableConfigQuery) && codemodeEnabled && showHeader, protocol?.configEndpoint, protocol?.authToken);
|
|
472
|
+
const sandboxStatus = sandboxStatusQuery.data;
|
|
339
473
|
// Refs
|
|
340
474
|
const adapterRef = useRef(null);
|
|
341
475
|
const unsubscribeRef = useRef(null);
|
|
342
476
|
const toolCallsRef = useRef(new Map());
|
|
477
|
+
// Track the number of in-flight frontend tool executions.
|
|
478
|
+
// While > 0, isLoading must stay true (the agent turn is not finished).
|
|
479
|
+
const pendingToolExecutionsRef = useRef(0);
|
|
343
480
|
const currentAssistantMessageRef = useRef(null);
|
|
344
481
|
const threadIdRef = useRef(generateMessageId());
|
|
345
482
|
const messagesEndRef = useRef(null);
|
|
346
483
|
const inputRef = useRef(null);
|
|
347
484
|
const abortControllerRef = useRef(null);
|
|
485
|
+
// State for context pie chart overlay
|
|
486
|
+
const [contextOverlayOpen, setContextOverlayOpen] = useState(false);
|
|
487
|
+
const contextAnchorRef = useRef(null);
|
|
488
|
+
const hoverTimeoutRef = useRef(null);
|
|
348
489
|
// Use a ref for connectedIdentities to avoid infinite loops in useCallback
|
|
349
490
|
// (the array reference changes on every render even if contents are the same)
|
|
350
491
|
const connectedIdentitiesRef = useRef(connectedIdentities);
|
|
@@ -418,15 +559,16 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
418
559
|
if ((configQuery.data || availableModels) && !selectedModel) {
|
|
419
560
|
// Use availableModels override if provided, otherwise use config models
|
|
420
561
|
const modelsList = availableModels || configQuery.data?.models || [];
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
562
|
+
// Priority: initialModel prop > defaultModel from config > first available model
|
|
563
|
+
const preferredModel = initialModel || configQuery.data?.defaultModel;
|
|
564
|
+
if (preferredModel) {
|
|
565
|
+
// Check if the preferred model exists in the models list
|
|
566
|
+
const modelExists = modelsList.some(m => m.id === preferredModel);
|
|
425
567
|
if (modelExists) {
|
|
426
|
-
setSelectedModel(
|
|
568
|
+
setSelectedModel(preferredModel);
|
|
427
569
|
}
|
|
428
570
|
else {
|
|
429
|
-
// Fallback to first available model if
|
|
571
|
+
// Fallback to first available model if preferred model not found
|
|
430
572
|
const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
|
|
431
573
|
const firstModel = firstAvailableModel || modelsList[0];
|
|
432
574
|
if (firstModel) {
|
|
@@ -435,7 +577,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
435
577
|
}
|
|
436
578
|
}
|
|
437
579
|
else {
|
|
438
|
-
// No
|
|
580
|
+
// No preferred model, select first available model
|
|
439
581
|
const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
|
|
440
582
|
const firstModel = firstAvailableModel || modelsList[0];
|
|
441
583
|
if (firstModel) {
|
|
@@ -596,18 +738,26 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
596
738
|
if (storedMessages.length > 0) {
|
|
597
739
|
setDisplayItems(storedMessages);
|
|
598
740
|
}
|
|
741
|
+
setHistoryLoaded(true);
|
|
599
742
|
return;
|
|
600
743
|
}
|
|
601
744
|
// Mark as fetching to prevent duplicate requests
|
|
602
745
|
store.setFetching(runtimeId, true);
|
|
603
746
|
// Build the history endpoint URL
|
|
604
|
-
|
|
605
|
-
(protocol?.endpoint ? `${protocol.endpoint}/history` : null);
|
|
747
|
+
let endpoint = historyEndpoint ||
|
|
748
|
+
(protocol?.endpoint ? `${protocol.endpoint}/api/v1/history` : null);
|
|
606
749
|
if (!endpoint) {
|
|
607
750
|
console.warn('[ChatBase] No history endpoint available for runtimeId:', runtimeId);
|
|
608
751
|
store.markFetched(runtimeId);
|
|
752
|
+
setHistoryLoaded(true);
|
|
609
753
|
return;
|
|
610
754
|
}
|
|
755
|
+
// Append agent_id query param if the protocol has an agentId
|
|
756
|
+
// and the URL doesn't already include one
|
|
757
|
+
if (protocol?.agentId && !endpoint.includes('agent_id=')) {
|
|
758
|
+
const separator = endpoint.includes('?') ? '&' : '?';
|
|
759
|
+
endpoint = `${endpoint}${separator}agent_id=${encodeURIComponent(protocol.agentId)}`;
|
|
760
|
+
}
|
|
611
761
|
// Fetch conversation history from server
|
|
612
762
|
const fetchHistory = async () => {
|
|
613
763
|
try {
|
|
@@ -627,17 +777,51 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
627
777
|
throw new Error(`Failed to fetch history: ${response.status} ${response.statusText}`);
|
|
628
778
|
}
|
|
629
779
|
const data = await response.json();
|
|
630
|
-
|
|
631
|
-
//
|
|
780
|
+
// Map server history messages to ChatMessage format.
|
|
781
|
+
// The server may return toolCalls in either the legacy {id, name, arguments}
|
|
782
|
+
// shape or the correct ToolCallContentPart {toolCallId, toolName, args} shape.
|
|
783
|
+
// Normalize both to ToolCallContentPart so the AG-UI adapter can serialize them.
|
|
784
|
+
const messages = (data.messages || []).map((msg) => {
|
|
785
|
+
if (msg.toolCalls && Array.isArray(msg.toolCalls)) {
|
|
786
|
+
msg.toolCalls = msg.toolCalls.map((tc) => {
|
|
787
|
+
// If already in ToolCallContentPart format, keep as-is
|
|
788
|
+
if (tc.toolCallId && tc.toolName)
|
|
789
|
+
return tc;
|
|
790
|
+
// Legacy format: {id, name, arguments} → ToolCallContentPart
|
|
791
|
+
let parsedArgs = tc.args ?? tc.arguments ?? {};
|
|
792
|
+
if (typeof parsedArgs === 'string') {
|
|
793
|
+
try {
|
|
794
|
+
parsedArgs = JSON.parse(parsedArgs);
|
|
795
|
+
}
|
|
796
|
+
catch {
|
|
797
|
+
parsedArgs = {};
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
type: 'tool-call',
|
|
802
|
+
toolCallId: tc.toolCallId ?? tc.id ?? tc.tool_call_id ?? '',
|
|
803
|
+
toolName: tc.toolName ?? tc.name ?? tc.tool_name ?? '',
|
|
804
|
+
args: parsedArgs,
|
|
805
|
+
status: tc.status ?? 'completed',
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return msg;
|
|
810
|
+
});
|
|
811
|
+
// Store in memory and convert to display items
|
|
632
812
|
if (messages.length > 0) {
|
|
633
813
|
store.setMessages(runtimeId, messages);
|
|
634
|
-
|
|
814
|
+
// Convert to display items: expand tool calls, merge tool results
|
|
815
|
+
const items = convertHistoryToDisplayItems(messages);
|
|
816
|
+
setDisplayItems(items);
|
|
635
817
|
}
|
|
636
818
|
store.markFetched(runtimeId);
|
|
819
|
+
setHistoryLoaded(true);
|
|
637
820
|
}
|
|
638
821
|
catch (err) {
|
|
639
822
|
console.error('[ChatBase] Failed to fetch conversation history:', err);
|
|
640
823
|
store.markFetched(runtimeId);
|
|
824
|
+
setHistoryLoaded(true);
|
|
641
825
|
}
|
|
642
826
|
};
|
|
643
827
|
fetchHistory();
|
|
@@ -691,6 +875,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
691
875
|
if (!adapter)
|
|
692
876
|
return;
|
|
693
877
|
adapterRef.current = adapter;
|
|
878
|
+
setAdapterReady(true);
|
|
694
879
|
// Subscribe to protocol events
|
|
695
880
|
unsubscribeRef.current = adapter.subscribe((event) => {
|
|
696
881
|
switch (event.type) {
|
|
@@ -730,10 +915,35 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
730
915
|
const newMessage = createAssistantMessage(typeof contentStr === 'string' ? contentStr : '');
|
|
731
916
|
newMessage.id = event.message.id || newMessage.id;
|
|
732
917
|
currentAssistantMessageRef.current = newMessage;
|
|
733
|
-
|
|
734
|
-
//
|
|
918
|
+
// Guard against duplicates: if an item with the same ID
|
|
919
|
+
// already exists (e.g. continuation arriving after
|
|
920
|
+
// handleSend's finally cleared currentAssistantMessageRef),
|
|
921
|
+
// update it in place instead of appending a second copy.
|
|
922
|
+
setDisplayItems(prev => {
|
|
923
|
+
const existingIdx = prev.findIndex(item => !isToolCallMessage(item) && item.id === newMessage.id);
|
|
924
|
+
if (existingIdx >= 0) {
|
|
925
|
+
const newItems = [...prev];
|
|
926
|
+
newItems[existingIdx] = {
|
|
927
|
+
...newItems[existingIdx],
|
|
928
|
+
content: event.message?.content ?? '',
|
|
929
|
+
};
|
|
930
|
+
return newItems;
|
|
931
|
+
}
|
|
932
|
+
return [...prev, newMessage];
|
|
933
|
+
});
|
|
934
|
+
// Add message to store (only if truly new)
|
|
735
935
|
if (useStoreMode) {
|
|
736
|
-
useChatStore
|
|
936
|
+
const existingInStore = useChatStore
|
|
937
|
+
.getState()
|
|
938
|
+
.messages.find(m => m.id === newMessage.id);
|
|
939
|
+
if (existingInStore) {
|
|
940
|
+
useChatStore.getState().updateMessage(newMessage.id, {
|
|
941
|
+
content: event.message?.content ?? '',
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
useChatStore.getState().addMessage(newMessage);
|
|
946
|
+
}
|
|
737
947
|
}
|
|
738
948
|
}
|
|
739
949
|
}
|
|
@@ -767,6 +977,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
767
977
|
const toolHandler = frontendTool?.handler;
|
|
768
978
|
if (toolHandler && Object.keys(args).length > 0) {
|
|
769
979
|
// Execute frontend tool
|
|
980
|
+
pendingToolExecutionsRef.current++;
|
|
770
981
|
(async () => {
|
|
771
982
|
try {
|
|
772
983
|
const result = await toolHandler(updatedToolCall.args);
|
|
@@ -803,6 +1014,14 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
803
1014
|
? errorToolCall
|
|
804
1015
|
: item));
|
|
805
1016
|
}
|
|
1017
|
+
finally {
|
|
1018
|
+
pendingToolExecutionsRef.current--;
|
|
1019
|
+
if (pendingToolExecutionsRef.current <= 0) {
|
|
1020
|
+
pendingToolExecutionsRef.current = 0;
|
|
1021
|
+
setIsLoading(false);
|
|
1022
|
+
setIsStreaming(false);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
806
1025
|
})();
|
|
807
1026
|
}
|
|
808
1027
|
}
|
|
@@ -824,6 +1043,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
824
1043
|
const frontendTool = frontendTools?.find(t => t.name === toolName);
|
|
825
1044
|
const toolHandler = frontendTool?.handler;
|
|
826
1045
|
if (toolHandler && Object.keys(args).length > 0) {
|
|
1046
|
+
pendingToolExecutionsRef.current++;
|
|
827
1047
|
(async () => {
|
|
828
1048
|
try {
|
|
829
1049
|
const result = await toolHandler(args);
|
|
@@ -860,6 +1080,14 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
860
1080
|
? errorToolCall
|
|
861
1081
|
: item));
|
|
862
1082
|
}
|
|
1083
|
+
finally {
|
|
1084
|
+
pendingToolExecutionsRef.current--;
|
|
1085
|
+
if (pendingToolExecutionsRef.current <= 0) {
|
|
1086
|
+
pendingToolExecutionsRef.current = 0;
|
|
1087
|
+
setIsLoading(false);
|
|
1088
|
+
setIsStreaming(false);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
863
1091
|
})();
|
|
864
1092
|
}
|
|
865
1093
|
}
|
|
@@ -987,21 +1215,25 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
987
1215
|
unsubscribeRef.current?.();
|
|
988
1216
|
adapterRef.current?.disconnect();
|
|
989
1217
|
};
|
|
990
|
-
//
|
|
1218
|
+
// protocolKey (JSON-serialised) replaces the protocol object reference so
|
|
1219
|
+
// that parent re-renders producing a new-but-identical protocol object
|
|
1220
|
+
// don't tear down and re-create the adapter mid-request.
|
|
1221
|
+
// frontendTools is accessed via ref-like closure, not as reactive dependency
|
|
991
1222
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
992
|
-
}, [
|
|
1223
|
+
}, [protocolKey, renderToolResult, onStateUpdate, useStoreMode]);
|
|
993
1224
|
// Auto-scroll to bottom
|
|
994
1225
|
useEffect(() => {
|
|
995
1226
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
996
1227
|
}, [displayItems]);
|
|
997
|
-
// Handle sending message in protocol mode or custom mode
|
|
998
|
-
|
|
999
|
-
|
|
1228
|
+
// Handle sending message in protocol mode or custom mode.
|
|
1229
|
+
// An optional messageOverride bypasses the input state (used by pendingPrompt).
|
|
1230
|
+
const handleSend = useCallback(async (messageOverride) => {
|
|
1231
|
+
const messageContent = (messageOverride ?? input).trim();
|
|
1232
|
+
if (!messageContent || isLoading)
|
|
1000
1233
|
return;
|
|
1001
1234
|
// Need either an adapter (protocol mode) or onSendMessage handler (custom mode)
|
|
1002
1235
|
if (!adapterRef.current && !onSendMessage)
|
|
1003
1236
|
return;
|
|
1004
|
-
const messageContent = input.trim();
|
|
1005
1237
|
const userMessage = createUserMessage(messageContent);
|
|
1006
1238
|
const currentMessages = displayItems.filter((item) => !isToolCallMessage(item));
|
|
1007
1239
|
const allMessages = [...currentMessages, userMessage];
|
|
@@ -1052,9 +1284,9 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1052
1284
|
? { ...item, content: fullResponse }
|
|
1053
1285
|
: item));
|
|
1054
1286
|
if (useStoreMode) {
|
|
1055
|
-
useChatStore
|
|
1056
|
-
|
|
1057
|
-
|
|
1287
|
+
useChatStore.getState().updateMessage(assistantMessageId, {
|
|
1288
|
+
content: fullResponse,
|
|
1289
|
+
});
|
|
1058
1290
|
useChatStore.getState().stopStreaming();
|
|
1059
1291
|
}
|
|
1060
1292
|
},
|
|
@@ -1065,9 +1297,9 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1065
1297
|
? { ...item, content: errorContent }
|
|
1066
1298
|
: item));
|
|
1067
1299
|
if (useStoreMode) {
|
|
1068
|
-
useChatStore
|
|
1069
|
-
|
|
1070
|
-
|
|
1300
|
+
useChatStore.getState().updateMessage(assistantMessageId, {
|
|
1301
|
+
content: errorContent,
|
|
1302
|
+
});
|
|
1071
1303
|
useChatStore.getState().stopStreaming();
|
|
1072
1304
|
}
|
|
1073
1305
|
setError(error);
|
|
@@ -1121,8 +1353,13 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1121
1353
|
}
|
|
1122
1354
|
}
|
|
1123
1355
|
finally {
|
|
1124
|
-
|
|
1125
|
-
|
|
1356
|
+
// Only clear loading state if no frontend tool executions are
|
|
1357
|
+
// still in flight. When tools are pending, sendToolResult will
|
|
1358
|
+
// trigger a continuation that eventually clears isLoading.
|
|
1359
|
+
if (pendingToolExecutionsRef.current <= 0) {
|
|
1360
|
+
setIsLoading(false);
|
|
1361
|
+
setIsStreaming(false);
|
|
1362
|
+
}
|
|
1126
1363
|
currentAssistantMessageRef.current = null;
|
|
1127
1364
|
abortControllerRef.current = null;
|
|
1128
1365
|
}
|
|
@@ -1138,6 +1375,19 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1138
1375
|
getEnabledMcpToolNames,
|
|
1139
1376
|
getEnabledSkillIds,
|
|
1140
1377
|
]);
|
|
1378
|
+
// Send the pending prompt once history is loaded and the
|
|
1379
|
+
// adapter (or custom handler) is available.
|
|
1380
|
+
useEffect(() => {
|
|
1381
|
+
if (!pendingPrompt || pendingPromptSentRef.current)
|
|
1382
|
+
return;
|
|
1383
|
+
if (!historyLoaded)
|
|
1384
|
+
return;
|
|
1385
|
+
if (!adapterReady && !onSendMessage)
|
|
1386
|
+
return;
|
|
1387
|
+
pendingPromptSentRef.current = true;
|
|
1388
|
+
// Use a microtask to ensure the adapter subscription is fully wired.
|
|
1389
|
+
queueMicrotask(() => handleSend(pendingPrompt));
|
|
1390
|
+
}, [pendingPrompt, historyLoaded, adapterReady, handleSend, onSendMessage]);
|
|
1141
1391
|
// Handle stop
|
|
1142
1392
|
const handleStop = useCallback(() => {
|
|
1143
1393
|
// Abort custom mode request
|
|
@@ -1148,20 +1398,16 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1148
1398
|
if (useStoreMode) {
|
|
1149
1399
|
useChatStore.getState().stopStreaming();
|
|
1150
1400
|
}
|
|
1401
|
+
// Reset pending tool counter so loading can clear
|
|
1402
|
+
pendingToolExecutionsRef.current = 0;
|
|
1151
1403
|
setIsLoading(false);
|
|
1152
1404
|
setIsStreaming(false);
|
|
1153
1405
|
}, [useStoreMode]);
|
|
1154
|
-
// Handle key press
|
|
1155
|
-
const handleKeyDown = useCallback((e) => {
|
|
1156
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1157
|
-
e.preventDefault();
|
|
1158
|
-
handleSend();
|
|
1159
|
-
}
|
|
1160
|
-
}, [handleSend]);
|
|
1161
1406
|
// Handle new chat
|
|
1162
1407
|
const handleNewChat = useCallback(() => {
|
|
1163
1408
|
setDisplayItems([]);
|
|
1164
1409
|
toolCallsRef.current.clear();
|
|
1410
|
+
pendingToolExecutionsRef.current = 0;
|
|
1165
1411
|
setInput('');
|
|
1166
1412
|
threadIdRef.current = generateMessageId();
|
|
1167
1413
|
if (useStoreMode) {
|
|
@@ -1190,6 +1436,24 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1190
1436
|
headerButtons?.onClear?.();
|
|
1191
1437
|
}
|
|
1192
1438
|
}, [clearStoreMessages, onClear, headerButtons, useStoreMode, runtimeId]);
|
|
1439
|
+
// Handle sandbox interrupt
|
|
1440
|
+
const handleSandboxInterrupt = useCallback(async () => {
|
|
1441
|
+
if (!protocol?.configEndpoint)
|
|
1442
|
+
return;
|
|
1443
|
+
const interruptUrl = `${getApiBaseFromConfig(protocol.configEndpoint)}/configure/sandbox/interrupt`;
|
|
1444
|
+
try {
|
|
1445
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
1446
|
+
if (protocol.authToken) {
|
|
1447
|
+
headers['Authorization'] = `Bearer ${protocol.authToken}`;
|
|
1448
|
+
}
|
|
1449
|
+
await fetch(interruptUrl, { method: 'POST', headers });
|
|
1450
|
+
// Refetch status immediately so the icon updates
|
|
1451
|
+
sandboxStatusQuery.refetch();
|
|
1452
|
+
}
|
|
1453
|
+
catch (e) {
|
|
1454
|
+
// Interrupt is best-effort
|
|
1455
|
+
}
|
|
1456
|
+
}, [protocol?.configEndpoint, protocol?.authToken, sandboxStatusQuery]);
|
|
1193
1457
|
// Not ready yet (store mode only)
|
|
1194
1458
|
if (!ready) {
|
|
1195
1459
|
return (_jsx(Box, { className: className, sx: {
|
|
@@ -1219,7 +1483,60 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1219
1483
|
alignItems: 'center',
|
|
1220
1484
|
justifyContent: 'space-between',
|
|
1221
1485
|
p: padding,
|
|
1222
|
-
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), title && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, fontWeight: 'semibold' }, children: title })), headerContent
|
|
1486
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), title && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, fontWeight: 'semibold' }, children: title })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [sandboxStatus?.available &&
|
|
1487
|
+
sandboxStatus?.sandbox_running &&
|
|
1488
|
+
(sandboxStatus.is_executing ? (_jsx(IconButton, { icon: SquareFillIcon, "aria-label": "Interrupt code execution", variant: "invisible", size: "small", sx: { color: 'danger.fg' }, onClick: handleSandboxInterrupt })) : (_jsx(Box, { sx: {
|
|
1489
|
+
display: 'flex',
|
|
1490
|
+
alignItems: 'center',
|
|
1491
|
+
justifyContent: 'center',
|
|
1492
|
+
width: 28,
|
|
1493
|
+
height: 28,
|
|
1494
|
+
color: 'fg.subtle',
|
|
1495
|
+
}, title: "Code sandbox ready", children: _jsx(CircleIcon, { size: 12 }) }))), headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: handleNewChat })), headerButtons?.showClear && messages.length > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: handleClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
|
|
1496
|
+
display: 'inline-flex',
|
|
1497
|
+
alignItems: 'center',
|
|
1498
|
+
bg: 'neutral.muted',
|
|
1499
|
+
borderRadius: '6px',
|
|
1500
|
+
p: '2px',
|
|
1501
|
+
gap: '1px',
|
|
1502
|
+
}, children: [
|
|
1503
|
+
{
|
|
1504
|
+
mode: 'floating',
|
|
1505
|
+
icon: CommentDiscussionIcon,
|
|
1506
|
+
label: 'Full-height popup',
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
mode: 'floating-small',
|
|
1510
|
+
icon: DeviceMobileIcon,
|
|
1511
|
+
label: 'Floating popup',
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
mode: 'sidebar',
|
|
1515
|
+
icon: SidebarExpandIcon,
|
|
1516
|
+
label: 'Sidebar panel',
|
|
1517
|
+
},
|
|
1518
|
+
].map(({ mode, icon: ModeIcon, label }) => (_jsx(Box, { as: "button", "aria-label": label, title: label, onClick: () => onChatViewModeChange(mode), sx: {
|
|
1519
|
+
display: 'inline-flex',
|
|
1520
|
+
alignItems: 'center',
|
|
1521
|
+
justifyContent: 'center',
|
|
1522
|
+
width: 26,
|
|
1523
|
+
height: 24,
|
|
1524
|
+
borderRadius: '4px',
|
|
1525
|
+
border: 'none',
|
|
1526
|
+
cursor: 'pointer',
|
|
1527
|
+
bg: chatViewMode === mode
|
|
1528
|
+
? 'canvas.default'
|
|
1529
|
+
: 'transparent',
|
|
1530
|
+
boxShadow: chatViewMode === mode ? 'shadow.small' : 'none',
|
|
1531
|
+
color: chatViewMode === mode ? 'fg.default' : 'fg.muted',
|
|
1532
|
+
transition: 'all 0.15s ease',
|
|
1533
|
+
'&:hover': {
|
|
1534
|
+
color: 'fg.default',
|
|
1535
|
+
bg: chatViewMode === mode
|
|
1536
|
+
? 'canvas.default'
|
|
1537
|
+
: 'neutral.subtle',
|
|
1538
|
+
},
|
|
1539
|
+
}, children: _jsx(ModeIcon, { size: 14 }) }, mode))) })), headerActions] })] }) }));
|
|
1223
1540
|
};
|
|
1224
1541
|
// Render token usage bar between input and selectors
|
|
1225
1542
|
const renderTokenUsage = () => {
|
|
@@ -1235,6 +1552,109 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1235
1552
|
agentUsage.sessionUsage.outputTokens > 0);
|
|
1236
1553
|
if (!hasContext)
|
|
1237
1554
|
return null;
|
|
1555
|
+
// Build pie chart data from distribution or fallback to fields
|
|
1556
|
+
const usedTokens = agentUsage.totalTokens;
|
|
1557
|
+
const windowTokens = agentUsage.contextWindow;
|
|
1558
|
+
const freeTokens = Math.max(0, windowTokens - usedTokens);
|
|
1559
|
+
const pct = windowTokens > 0 ? (usedTokens / windowTokens) * 100 : 0;
|
|
1560
|
+
// Build category breakdown from distribution or individual fields
|
|
1561
|
+
const categories = [];
|
|
1562
|
+
if (agentUsage.distribution?.children?.length) {
|
|
1563
|
+
const colorMap = {
|
|
1564
|
+
'System Prompts': '#8250df',
|
|
1565
|
+
'Tool Definitions': '#bf8700',
|
|
1566
|
+
'User Messages': '#0969da',
|
|
1567
|
+
'Assistant Messages': '#1a7f37',
|
|
1568
|
+
'Tool Usage': '#cf222e',
|
|
1569
|
+
};
|
|
1570
|
+
for (const child of agentUsage.distribution.children) {
|
|
1571
|
+
categories.push({
|
|
1572
|
+
name: child.name,
|
|
1573
|
+
value: child.value,
|
|
1574
|
+
color: colorMap[child.name] || '#6e7781',
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
// Fallback: build from individual fields
|
|
1580
|
+
if (agentUsage.systemPromptTokens > 0) {
|
|
1581
|
+
categories.push({
|
|
1582
|
+
name: 'System Prompts',
|
|
1583
|
+
value: agentUsage.systemPromptTokens,
|
|
1584
|
+
color: '#8250df',
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
if (agentUsage.toolTokens > 0) {
|
|
1588
|
+
categories.push({
|
|
1589
|
+
name: 'Tool Definitions',
|
|
1590
|
+
value: agentUsage.toolTokens,
|
|
1591
|
+
color: '#bf8700',
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
const messageTokens = (agentUsage.userMessageTokens || 0) +
|
|
1595
|
+
(agentUsage.assistantMessageTokens || 0);
|
|
1596
|
+
if (messageTokens > 0) {
|
|
1597
|
+
categories.push({
|
|
1598
|
+
name: 'Messages',
|
|
1599
|
+
value: messageTokens,
|
|
1600
|
+
color: '#0969da',
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
const toolResultTokens = (agentUsage.toolCallTokens || 0) + (agentUsage.toolReturnTokens || 0);
|
|
1604
|
+
if (toolResultTokens > 0) {
|
|
1605
|
+
categories.push({
|
|
1606
|
+
name: 'Tool Results',
|
|
1607
|
+
value: toolResultTokens,
|
|
1608
|
+
color: '#cf222e',
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
// Tiny filled pie chart options
|
|
1613
|
+
const pieColor = pct > 90 ? '#cf222e' : pct > 70 ? '#bf8700' : '#0969da';
|
|
1614
|
+
const freeSliceColor = 'var(--bgColor-muted, #f6f8fa)';
|
|
1615
|
+
const freeSliceOverlayColor = 'var(--borderColor-default, #d1d9e0)';
|
|
1616
|
+
const miniPieOption = {
|
|
1617
|
+
animation: false,
|
|
1618
|
+
series: [
|
|
1619
|
+
{
|
|
1620
|
+
type: 'pie',
|
|
1621
|
+
radius: [0, '90%'],
|
|
1622
|
+
center: ['50%', '50%'],
|
|
1623
|
+
silent: true,
|
|
1624
|
+
label: { show: false },
|
|
1625
|
+
labelLine: { show: false },
|
|
1626
|
+
data: [
|
|
1627
|
+
{ value: usedTokens, itemStyle: { color: pieColor } },
|
|
1628
|
+
{ value: freeTokens, itemStyle: { color: freeSliceColor } },
|
|
1629
|
+
],
|
|
1630
|
+
},
|
|
1631
|
+
],
|
|
1632
|
+
};
|
|
1633
|
+
// Overlay detail pie options (donut for category breakdown)
|
|
1634
|
+
const overlayPieOption = {
|
|
1635
|
+
animation: false,
|
|
1636
|
+
series: [
|
|
1637
|
+
{
|
|
1638
|
+
type: 'pie',
|
|
1639
|
+
radius: ['45%', '80%'],
|
|
1640
|
+
center: ['50%', '50%'],
|
|
1641
|
+
silent: true,
|
|
1642
|
+
label: { show: false },
|
|
1643
|
+
labelLine: { show: false },
|
|
1644
|
+
itemStyle: {
|
|
1645
|
+
borderColor: 'var(--bgColor-default, #ffffff)',
|
|
1646
|
+
borderWidth: 1,
|
|
1647
|
+
},
|
|
1648
|
+
data: [
|
|
1649
|
+
...categories.map(c => ({
|
|
1650
|
+
value: c.value,
|
|
1651
|
+
itemStyle: { color: c.color },
|
|
1652
|
+
})),
|
|
1653
|
+
{ value: freeTokens, itemStyle: { color: freeSliceOverlayColor } },
|
|
1654
|
+
],
|
|
1655
|
+
},
|
|
1656
|
+
],
|
|
1657
|
+
};
|
|
1238
1658
|
return (_jsxs(Box, { sx: {
|
|
1239
1659
|
display: 'flex',
|
|
1240
1660
|
alignItems: 'center',
|
|
@@ -1244,10 +1664,76 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1244
1664
|
px: padding,
|
|
1245
1665
|
bg: 'canvas.subtle',
|
|
1246
1666
|
flexWrap: 'nowrap',
|
|
1247
|
-
overflow: '
|
|
1667
|
+
overflow: 'visible',
|
|
1248
1668
|
whiteSpace: 'nowrap',
|
|
1249
1669
|
minWidth: 0,
|
|
1250
|
-
}, children: [_jsxs(
|
|
1670
|
+
}, children: [_jsxs(Box, { sx: { position: 'relative', flexShrink: 0 }, onMouseEnter: () => {
|
|
1671
|
+
if (hoverTimeoutRef.current)
|
|
1672
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
1673
|
+
hoverTimeoutRef.current = setTimeout(() => setContextOverlayOpen(true), 150);
|
|
1674
|
+
}, onMouseLeave: () => {
|
|
1675
|
+
if (hoverTimeoutRef.current)
|
|
1676
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
1677
|
+
hoverTimeoutRef.current = setTimeout(() => setContextOverlayOpen(false), 250);
|
|
1678
|
+
}, children: [_jsx(Box, { ref: contextAnchorRef, sx: {
|
|
1679
|
+
cursor: 'pointer',
|
|
1680
|
+
width: 20,
|
|
1681
|
+
height: 20,
|
|
1682
|
+
display: 'flex',
|
|
1683
|
+
alignItems: 'center',
|
|
1684
|
+
justifyContent: 'center',
|
|
1685
|
+
border: '1px solid',
|
|
1686
|
+
borderColor: 'border.default',
|
|
1687
|
+
borderRadius: '50%',
|
|
1688
|
+
}, children: _jsx(ReactECharts, { option: miniPieOption, style: { width: 18, height: 18 }, opts: { renderer: 'svg' } }) }), contextOverlayOpen && (_jsxs(Box, { sx: {
|
|
1689
|
+
position: 'absolute',
|
|
1690
|
+
bottom: '100%',
|
|
1691
|
+
left: 0,
|
|
1692
|
+
mb: 1,
|
|
1693
|
+
p: 3,
|
|
1694
|
+
width: 260,
|
|
1695
|
+
bg: 'canvas.overlay',
|
|
1696
|
+
borderRadius: 2,
|
|
1697
|
+
boxShadow: 'shadow.large',
|
|
1698
|
+
border: '1px solid',
|
|
1699
|
+
borderColor: 'border.default',
|
|
1700
|
+
zIndex: 100,
|
|
1701
|
+
}, children: [_jsx(Text, { sx: {
|
|
1702
|
+
fontWeight: 'bold',
|
|
1703
|
+
fontSize: 1,
|
|
1704
|
+
color: 'fg.default',
|
|
1705
|
+
display: 'block',
|
|
1706
|
+
mb: 2,
|
|
1707
|
+
}, children: "Context Window" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block', mb: 1 }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'semibold', color: 'fg.default' }, children: formatTokenCount(usedTokens) }), ' / ', formatTokenCount(windowTokens), ' tokens'] }), _jsxs(Text, { sx: {
|
|
1708
|
+
fontSize: 0,
|
|
1709
|
+
color: pct > 90
|
|
1710
|
+
? 'danger.fg'
|
|
1711
|
+
: pct > 70
|
|
1712
|
+
? 'attention.fg'
|
|
1713
|
+
: 'fg.muted',
|
|
1714
|
+
fontWeight: 'semibold',
|
|
1715
|
+
display: 'block',
|
|
1716
|
+
mb: 2,
|
|
1717
|
+
}, children: ['• ', pct.toFixed(0), "%"] }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'center', mb: 2 }, children: _jsx(ReactECharts, { option: overlayPieOption, style: { width: 80, height: 80 }, opts: { renderer: 'svg' } }) }), _jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 1 }, children: categories.map(cat => {
|
|
1718
|
+
const catPct = usedTokens > 0 ? (cat.value / usedTokens) * 100 : 0;
|
|
1719
|
+
return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Box, { sx: {
|
|
1720
|
+
width: 8,
|
|
1721
|
+
height: 8,
|
|
1722
|
+
borderRadius: '50%',
|
|
1723
|
+
bg: cat.color,
|
|
1724
|
+
flexShrink: 0,
|
|
1725
|
+
} }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', flex: 1 }, children: cat.name }), _jsxs(Text, { sx: {
|
|
1726
|
+
fontSize: 0,
|
|
1727
|
+
color: 'fg.default',
|
|
1728
|
+
fontWeight: 'semibold',
|
|
1729
|
+
}, children: [catPct.toFixed(1), "%"] })] }, cat.name));
|
|
1730
|
+
}) }), pct > 70 && (_jsx(Text, { sx: {
|
|
1731
|
+
fontSize: 0,
|
|
1732
|
+
color: 'attention.fg',
|
|
1733
|
+
display: 'block',
|
|
1734
|
+
mt: 2,
|
|
1735
|
+
fontStyle: 'italic',
|
|
1736
|
+
}, children: "Quality may decline as limit nears." }))] }))] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'semibold', color: 'fg.default', fontSize: 0 }, children: formatTokenCount(agentUsage.totalTokens) }), ' / ', formatTokenCount(agentUsage.contextWindow), ' ctx'] }), hasSession && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: ['· ', formatTokenCount(agentUsage.sessionUsage.inputTokens), _jsx(Text, { as: "span", sx: { color: 'success.fg', fontSize: 0 }, children: '▲' }), ' ', formatTokenCount(agentUsage.sessionUsage.outputTokens), _jsx(Text, { as: "span", sx: { color: 'attention.fg', fontSize: 0 }, children: '▼' })] })), hasTurn && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: ['· turn ', formatTokenCount(agentUsage.turnUsage.inputTokens), _jsx(Text, { as: "span", sx: { color: 'success.fg', fontSize: 0 }, children: '▲' }), ' ', formatTokenCount(agentUsage.turnUsage.outputTokens), _jsx(Text, { as: "span", sx: { color: 'attention.fg', fontSize: 0 }, children: '▼' })] }))] }));
|
|
1251
1737
|
};
|
|
1252
1738
|
// Render empty state
|
|
1253
1739
|
const renderEmptyState = () => {
|
|
@@ -1516,88 +2002,12 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1516
2002
|
? 'accent.emphasis'
|
|
1517
2003
|
: 'canvas.subtle',
|
|
1518
2004
|
color: isUser ? 'fg.onEmphasis' : 'fg.default',
|
|
1519
|
-
|
|
1520
|
-
// Code block container
|
|
1521
|
-
'& [data-streamdown="code-block"]': {
|
|
1522
|
-
borderRadius: '8px',
|
|
1523
|
-
border: '1px solid',
|
|
1524
|
-
borderColor: 'border.default',
|
|
1525
|
-
overflow: 'hidden',
|
|
1526
|
-
my: 2,
|
|
1527
|
-
},
|
|
1528
|
-
// Code block header with language label and buttons
|
|
1529
|
-
'& [data-streamdown="code-block-header"]': {
|
|
1530
|
-
display: 'flex',
|
|
1531
|
-
alignItems: 'center',
|
|
1532
|
-
justifyContent: 'space-between',
|
|
1533
|
-
backgroundColor: 'canvas.subtle',
|
|
1534
|
-
padding: '8px 12px',
|
|
1535
|
-
fontSize: '12px',
|
|
1536
|
-
color: 'fg.muted',
|
|
1537
|
-
},
|
|
1538
|
-
// Style the buttons in the header
|
|
1539
|
-
'& [data-streamdown="code-block-header"] button': {
|
|
1540
|
-
background: 'none',
|
|
1541
|
-
border: 'none',
|
|
1542
|
-
cursor: 'pointer',
|
|
1543
|
-
padding: '4px',
|
|
1544
|
-
color: 'fg.muted',
|
|
1545
|
-
borderRadius: '4px',
|
|
1546
|
-
'&:hover': {
|
|
1547
|
-
backgroundColor: 'neutral.muted',
|
|
1548
|
-
color: 'fg.default',
|
|
1549
|
-
},
|
|
1550
|
-
},
|
|
1551
|
-
// Code block body
|
|
1552
|
-
'& [data-streamdown="code-block-body"]': {
|
|
1553
|
-
backgroundColor: 'canvas.subtle',
|
|
1554
|
-
padding: '12px',
|
|
1555
|
-
margin: 0,
|
|
1556
|
-
overflow: 'auto',
|
|
1557
|
-
fontSize: '13px',
|
|
1558
|
-
lineHeight: 1.5,
|
|
1559
|
-
},
|
|
1560
|
-
// Make each line display as a block for line breaks
|
|
1561
|
-
'& [data-streamdown="code-block-body"] code': {
|
|
1562
|
-
display: 'block',
|
|
1563
|
-
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
1564
|
-
},
|
|
1565
|
-
'& [data-streamdown="code-block-body"] code > span.block': {
|
|
1566
|
-
display: 'block',
|
|
1567
|
-
},
|
|
1568
|
-
'& [data-streamdown="code-block-body"] code > span': {
|
|
1569
|
-
display: 'block',
|
|
1570
|
-
},
|
|
1571
|
-
// General pre/code styling fallback
|
|
1572
|
-
'& pre': {
|
|
1573
|
-
whiteSpace: 'pre-wrap',
|
|
1574
|
-
wordBreak: 'break-word',
|
|
1575
|
-
overflowX: 'auto',
|
|
1576
|
-
margin: 0,
|
|
1577
|
-
},
|
|
1578
|
-
'& pre code': {
|
|
1579
|
-
whiteSpace: 'pre-wrap',
|
|
1580
|
-
},
|
|
1581
|
-
'& code': {
|
|
1582
|
-
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
1583
|
-
},
|
|
2005
|
+
...streamdownCodeBlockStyles,
|
|
1584
2006
|
}, children: isUser ? (_jsx(Text, { sx: {
|
|
1585
2007
|
fontSize: 1,
|
|
1586
2008
|
whiteSpace: 'pre-wrap',
|
|
1587
2009
|
wordBreak: 'break-word',
|
|
1588
|
-
}, children: getMessageText(message) })) : (_jsx(Box, { sx: {
|
|
1589
|
-
fontSize: 1,
|
|
1590
|
-
lineHeight: 1.5,
|
|
1591
|
-
'& ul, & ol': {
|
|
1592
|
-
marginTop: '0.5em',
|
|
1593
|
-
marginBottom: '0.5em',
|
|
1594
|
-
paddingInlineStart: '1.25em',
|
|
1595
|
-
listStylePosition: 'inside',
|
|
1596
|
-
},
|
|
1597
|
-
'& li': {
|
|
1598
|
-
paddingInlineStart: '0.25em',
|
|
1599
|
-
},
|
|
1600
|
-
}, children: _jsx(Streamdown, { children: getMessageText(message) || (isStreaming ? '...' : '') }) })) })] }) }, message.id));
|
|
2010
|
+
}, children: getMessageText(message) })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: getMessageText(message) || (isStreaming ? '...' : '') }) })) })] }) }, message.id));
|
|
1601
2011
|
}), showLoadingIndicator && (isLoading || isStreaming) && (_jsx(Box, { sx: {
|
|
1602
2012
|
display: 'flex',
|
|
1603
2013
|
alignItems: 'flex-start',
|
|
@@ -1677,26 +2087,11 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1677
2087
|
},
|
|
1678
2088
|
} })] })] }) })), _jsx("div", { ref: messagesEndRef })] }));
|
|
1679
2089
|
};
|
|
1680
|
-
// Render
|
|
1681
|
-
const
|
|
2090
|
+
// Render input prompt
|
|
2091
|
+
const renderInputPrompt = () => {
|
|
1682
2092
|
const availableTools = configQuery.data?.builtinTools || [];
|
|
1683
2093
|
const models = availableModels || configQuery.data?.models || [];
|
|
1684
|
-
return (_jsxs(Box, { children: [_jsx(
|
|
1685
|
-
p: padding,
|
|
1686
|
-
borderTop: '1px solid',
|
|
1687
|
-
borderColor: 'border.default',
|
|
1688
|
-
bg: 'canvas.subtle',
|
|
1689
|
-
}, children: _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'flex-end' }, children: [_jsx(Textarea, { ref: inputRef, value: input, onChange: e => {
|
|
1690
|
-
setInput(e.target.value);
|
|
1691
|
-
// Height adjustment happens via useEffect watching input
|
|
1692
|
-
}, onKeyDown: handleKeyDown, placeholder: placeholder || 'Type a message...', disabled: isLoading, sx: {
|
|
1693
|
-
flex: 1,
|
|
1694
|
-
resize: 'none',
|
|
1695
|
-
minHeight: '40px',
|
|
1696
|
-
maxHeight: '120px',
|
|
1697
|
-
overflow: 'hidden',
|
|
1698
|
-
transition: 'height 0.1s ease-out',
|
|
1699
|
-
}, rows: 1 }), isLoading ? (_jsx(IconButton, { icon: SquareCircleIcon, "aria-label": "Stop", onClick: handleStop, sx: { alignSelf: 'flex-end' } })) : (_jsx(IconButton, { icon: PaperAirplaneIcon, "aria-label": "Send", onClick: handleSend, disabled: !input.trim(), sx: { alignSelf: 'flex-end' } }))] }) }), renderTokenUsage(), (showModelSelector || showToolsMenu || showSkillsMenu) &&
|
|
2094
|
+
return (_jsxs(Box, { children: [_jsx(InputPrompt, { placeholder: placeholder || 'Type a message...', isLoading: isLoading, onSend: () => handleSend(), onStop: handleStop, autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, value: input, onChange: setInput }), renderTokenUsage(), (showModelSelector || showToolsMenu || showSkillsMenu) &&
|
|
1700
2095
|
(configQuery.data || skillsQuery.data) && (_jsxs(Box, { sx: {
|
|
1701
2096
|
display: 'flex',
|
|
1702
2097
|
gap: 2,
|
|
@@ -1840,6 +2235,6 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
|
|
|
1840
2235
|
flexDirection: 'column',
|
|
1841
2236
|
minHeight: '100%',
|
|
1842
2237
|
bg: 'canvas.default',
|
|
1843
|
-
}, children: renderProtocolMessages() })) }), footerContent, showInput &&
|
|
2238
|
+
}, children: renderProtocolMessages() })) }), footerContent, showInput && renderInputPrompt(), showPoweredBy && _jsx(PoweredByTag, { ...poweredByProps })] }));
|
|
1844
2239
|
}
|
|
1845
2240
|
export default ChatBase;
|