@datalayer/agent-runtimes 1.0.1 → 1.0.3
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 -1
- package/lib/Agent.js +2 -2
- package/lib/AgentLexical.d.ts +1 -12
- package/lib/AgentLexical.js +3 -14
- package/lib/AgentNotebook.js +3 -3
- package/lib/App.js +2 -2
- package/lib/{components/chat/components → agents}/AgentDetails.d.ts +13 -2
- package/lib/{components/chat/components → agents}/AgentDetails.js +6 -12
- package/lib/api/agents.d.ts +47 -0
- package/lib/api/agents.js +106 -0
- package/lib/api/context.d.ts +17 -0
- package/lib/api/context.js +45 -0
- package/lib/api/evals.d.ts +27 -0
- package/lib/api/evals.js +63 -0
- package/lib/api/events.d.ts +17 -0
- package/lib/api/events.js +93 -0
- package/lib/{components/chat → api}/handler.js +1 -1
- package/lib/api/index.d.ts +15 -0
- package/lib/api/index.js +20 -0
- package/lib/api/notifications.d.ts +39 -0
- package/lib/api/notifications.js +103 -0
- package/lib/api/output.d.ts +28 -0
- package/lib/api/output.js +64 -0
- package/lib/api/tool-approvals.d.ts +62 -0
- package/lib/api/tool-approvals.js +145 -0
- package/lib/{components/chat/components → chat}/Chat.d.ts +25 -16
- package/lib/{components/chat/components → chat}/Chat.js +16 -16
- package/lib/{components/chat/components → chat}/ChatFloating.d.ts +17 -9
- package/lib/{components/chat/components → chat}/ChatFloating.js +17 -9
- package/lib/{components/chat/components → chat}/ChatInline.d.ts +2 -2
- package/lib/{components/chat/components → chat}/ChatInline.js +8 -6
- package/lib/{components/chat/components → chat}/ChatPopupStandalone.d.ts +3 -4
- package/lib/{components/chat/components → chat}/ChatPopupStandalone.js +6 -6
- package/lib/{components/chat/components → chat}/ChatSidebar.d.ts +14 -5
- package/lib/{components/chat/components → chat}/ChatSidebar.js +48 -26
- package/lib/{components/chat/components → chat}/ChatStandalone.d.ts +3 -4
- package/lib/{components/chat/components → chat}/ChatStandalone.js +5 -5
- package/lib/chat/base/ChatBase.d.ts +6 -0
- package/lib/chat/base/ChatBase.js +1287 -0
- package/lib/chat/display/EmptyState.d.ts +27 -0
- package/lib/chat/display/EmptyState.js +41 -0
- package/lib/{components/chat/components/elements → chat/display}/FloatingBrandButton.d.ts +1 -1
- package/lib/{components/chat/components/elements → chat/display}/FloatingBrandButton.js +1 -1
- package/lib/{components/chat/components/elements → chat/display}/PoweredByTag.d.ts +1 -1
- package/lib/{components/chat/components/elements → chat/header}/ChatHeader.d.ts +1 -4
- package/lib/{components/chat/components/elements → chat/header}/ChatHeader.js +1 -1
- package/lib/chat/header/ChatHeaderBase.d.ts +38 -0
- package/lib/chat/header/ChatHeaderBase.js +83 -0
- package/lib/chat/index.d.ts +66 -0
- package/lib/chat/index.js +74 -0
- package/lib/chat/indicators/McpStatusIndicator.d.ts +9 -0
- package/lib/chat/indicators/McpStatusIndicator.js +128 -0
- package/lib/chat/indicators/SandboxStatusIndicator.d.ts +10 -0
- package/lib/chat/indicators/SandboxStatusIndicator.js +175 -0
- package/lib/chat/indicators/index.d.ts +17 -0
- package/lib/chat/indicators/index.js +19 -0
- package/lib/chat/messages/ChatMessageList.d.ts +49 -0
- package/lib/chat/messages/ChatMessageList.js +319 -0
- package/lib/{components/chat/components/elements → chat/messages}/ChatMessages.d.ts +2 -2
- package/lib/{components/chat/components/elements → chat/messages}/ChatMessages.js +6 -4
- package/lib/{components/chat/components → chat}/parts/DynamicToolPart.d.ts +1 -1
- package/lib/{components/chat/components/elements → chat/parts}/MessagePart.d.ts +1 -1
- package/lib/{components/chat/components/elements → chat/parts}/MessagePart.js +4 -4
- package/lib/{components/chat/components → chat}/parts/ReasoningPart.js +1 -1
- package/lib/{components/chat/components → chat}/parts/TextPart.d.ts +1 -1
- package/lib/{components/chat/components → chat}/parts/ToolPart.js +1 -1
- package/lib/{components/chat/components → chat}/parts/index.d.ts +2 -1
- package/lib/{components/chat/components → chat}/parts/index.js +2 -1
- package/lib/chat/prompt/InputFooter.d.ts +43 -0
- package/lib/chat/prompt/InputFooter.js +135 -0
- package/lib/chat/prompt/InputPrompt.d.ts +60 -0
- package/lib/chat/prompt/InputPrompt.js +83 -0
- package/lib/chat/prompt/InputPromptFooter.d.ts +25 -0
- package/lib/chat/prompt/InputPromptFooter.js +15 -0
- package/lib/chat/prompt/InputPromptHeader.d.ts +15 -0
- package/lib/chat/prompt/InputPromptHeader.js +15 -0
- package/lib/chat/prompt/InputPromptLexical.d.ts +16 -0
- package/lib/chat/prompt/InputPromptLexical.js +122 -0
- package/lib/chat/prompt/InputPromptText.d.ts +24 -0
- package/lib/chat/prompt/InputPromptText.js +66 -0
- package/lib/chat/prompt/index.d.ts +11 -0
- package/lib/chat/prompt/index.js +15 -0
- package/lib/{components/chat/components → chat}/styles/streamdownStyles.d.ts +1 -1
- package/lib/chat/tools/ToolApprovalBanner.d.ts +31 -0
- package/lib/chat/tools/ToolApprovalBanner.js +62 -0
- package/lib/{components/chat/components/elements → chat/tools}/ToolApprovalDialog.js +1 -1
- package/lib/{components/chat/components/display → chat/tools}/ToolCallDisplay.d.ts +13 -4
- package/lib/{components/chat/components/display → chat/tools}/ToolCallDisplay.js +18 -8
- package/lib/chat/tools/index.d.ts +8 -0
- package/lib/{components/chat/components/display → chat/tools}/index.js +3 -1
- package/lib/chat/usage/TokenUsageBar.d.ts +8 -0
- package/lib/chat/usage/TokenUsageBar.js +213 -0
- package/lib/client/AgentsMixin.d.ts +169 -0
- package/lib/client/AgentsMixin.js +279 -0
- package/lib/client/index.d.ts +6 -0
- package/lib/client/index.js +10 -0
- package/lib/components/NotificationEventCard.d.ts +8 -0
- package/lib/components/NotificationEventCard.js +152 -0
- package/lib/components/OutputCard.d.ts +8 -0
- package/lib/components/OutputCard.js +80 -0
- package/lib/components/ToolApprovalCard.d.ts +33 -0
- package/lib/components/ToolApprovalCard.js +60 -0
- package/lib/components/index.d.ts +3 -7
- package/lib/components/index.js +3 -4
- package/lib/{components → config}/AgentConfiguration.d.ts +43 -21
- package/lib/{components → config}/AgentConfiguration.js +48 -28
- package/lib/config/index.d.ts +2 -0
- package/lib/config/index.js +1 -0
- package/lib/{components/chat/components → context}/ContextInspector.js +38 -8
- package/lib/{components/chat/components → context}/ContextPanel.js +27 -6
- package/lib/context/CostTracker.d.ts +37 -0
- package/lib/context/CostTracker.js +124 -0
- package/lib/context/TokenUsageChart.d.ts +10 -0
- package/lib/context/TokenUsageChart.js +288 -0
- package/lib/examples/A2UiComponentGalleryExample.d.ts +20 -0
- package/lib/examples/A2UiComponentGalleryExample.js +568 -0
- package/lib/examples/A2UiContactCardExample.d.ts +21 -0
- package/lib/examples/A2UiContactCardExample.js +432 -0
- package/lib/examples/A2UiRestaurantExample.d.ts +11 -3
- package/lib/examples/A2UiRestaurantExample.js +63 -98
- package/lib/examples/A2UiViewerExample.d.ts +21 -0
- package/lib/examples/A2UiViewerExample.js +563 -0
- package/lib/examples/AgUiAgenticExample.js +3 -3
- package/lib/examples/AgUiBackendToolRenderingExample.js +3 -3
- package/lib/examples/{AgUiHaikuGenUIExample.d.ts → AgUiHaikuGenUiExample.d.ts} +4 -4
- package/lib/examples/{AgUiHaikuGenUIExample.js → AgUiHaikuGenUiExample.js} +7 -7
- package/lib/examples/AgUiHumanInTheLoopExample.js +3 -3
- package/lib/examples/AgUiSharedStateExample.js +3 -3
- package/lib/examples/{AgUiToolsBasedGenUIExample.d.ts → AgUiToolsBasedGenUiExample.d.ts} +4 -4
- package/lib/examples/{AgUiToolsBasedGenUIExample.js → AgUiToolsBasedGenUiExample.js} +7 -7
- package/lib/examples/AgentCheckpointsExample.d.ts +19 -0
- package/lib/examples/AgentCheckpointsExample.js +506 -0
- package/lib/examples/AgentCodemodeExample.d.ts +14 -0
- package/lib/examples/AgentCodemodeExample.js +262 -0
- package/lib/examples/AgentEvalsExample.d.ts +14 -0
- package/lib/examples/AgentEvalsExample.js +216 -0
- package/lib/examples/AgentGuardrailsExample.d.ts +14 -0
- package/lib/examples/AgentGuardrailsExample.js +218 -0
- package/lib/examples/AgentMemoryExample.d.ts +14 -0
- package/lib/examples/AgentMemoryExample.js +234 -0
- package/lib/examples/AgentMonitoringExample.d.ts +13 -0
- package/lib/examples/AgentMonitoringExample.js +311 -0
- package/lib/examples/AgentNotificationsExample.d.ts +14 -0
- package/lib/examples/AgentNotificationsExample.js +273 -0
- package/lib/examples/AgentOtelExample.d.ts +25 -0
- package/lib/examples/AgentOtelExample.js +281 -0
- package/lib/examples/AgentOutputsExample.d.ts +14 -0
- package/lib/examples/AgentOutputsExample.js +211 -0
- package/lib/examples/AgentSandboxExample.d.ts +17 -0
- package/lib/examples/AgentSandboxExample.js +496 -0
- package/lib/examples/AgentSkillsExample.d.ts +3 -0
- package/lib/examples/AgentSkillsExample.js +290 -0
- package/lib/examples/AgentToolApprovalsExample.d.ts +3 -0
- package/lib/examples/AgentToolApprovalsExample.js +672 -0
- package/lib/examples/AgentTriggersExample.d.ts +14 -0
- package/lib/examples/AgentTriggersExample.js +523 -0
- package/lib/examples/{AgentRuntimeFormExample.d.ts → AgentspecExample.d.ts} +3 -4
- package/lib/examples/{AgentRuntimeFormExample.js → AgentspecExample.js} +92 -34
- package/lib/examples/{JupyterCellExample.js → CellSimpleExample.js} +1 -1
- package/lib/examples/{AgentRuntimeCustomExample.js → ChatCustomExample.js} +3 -3
- package/lib/examples/{AgentRuntimeChatExample.js → ChatExample.js} +4 -4
- package/lib/examples/{AgentRuntimeStandaloneExample.js → ChatStandaloneExample.js} +2 -2
- package/lib/examples/CopilotKitLexicalExample.d.ts +3 -14
- package/lib/examples/CopilotKitLexicalExample.js +4 -15
- package/lib/examples/CopilotKitNotebookExample.js +4 -6
- package/lib/examples/DatalayerNotebookExample.js +2 -2
- package/lib/examples/{AgentRuntimeLexical2Example.d.ts → Lexical2Example.d.ts} +2 -13
- package/lib/examples/{AgentRuntimeLexical2Example.js → Lexical2Example.js} +8 -17
- package/lib/examples/{AgentRuntimeLexicalExample.d.ts → LexicalExample.d.ts} +1 -12
- package/lib/examples/{AgentRuntimeLexicalExample.js → LexicalExample.js} +28 -27
- package/lib/examples/{AgentRuntimeLexicalSidebarExample.d.ts → LexicalSidebarExample.d.ts} +2 -13
- package/lib/examples/{AgentRuntimeLexicalSidebarExample.js → LexicalSidebarExample.js} +6 -18
- package/lib/examples/{AgentRuntimeNotebookExample.js → NotebookExample.js} +14 -10
- package/lib/examples/{AgentRuntimeNotebookSidebarExample.js → NotebookSidebarExample.js} +5 -8
- package/lib/examples/{JupyterNotebookExample.js → NotebookSimpleExample.js} +2 -2
- package/lib/examples/ag-ui/weather/InlineWeatherCard.js +1 -1
- package/lib/examples/components/ExampleWrapper.d.ts +12 -0
- package/lib/examples/components/ExampleWrapper.js +16 -0
- package/lib/examples/components/Header.d.ts +2 -2
- package/lib/examples/components/HeaderControls.js +1 -1
- package/lib/examples/components/LexicalEditor.d.ts +1 -12
- package/lib/examples/components/LexicalEditor.js +1 -12
- package/lib/examples/components/MainContent.d.ts +4 -11
- package/lib/examples/components/MainContent.js +6 -60
- package/lib/examples/components/index.d.ts +1 -0
- package/lib/examples/components/index.js +1 -0
- package/lib/examples/example-selector.d.ts +1 -1
- package/lib/examples/example-selector.js +35 -22
- package/lib/examples/index.d.ts +26 -13
- package/lib/examples/index.js +26 -12
- package/lib/examples/main.d.ts +6 -0
- package/lib/examples/main.js +20 -43
- package/lib/examples/utils/examplesStore.d.ts +4 -0
- package/lib/examples/{stores → utils}/examplesStore.js +1 -1
- package/lib/examples/utils/notebooks/Empty.ipynb.json +33 -0
- package/lib/examples/utils/notebooks/NotebookExample2.ipynb.json +48 -0
- package/lib/examples/utils/themeStore.d.ts +8 -0
- package/lib/examples/utils/themeStore.js +14 -0
- package/lib/extensions/A2UIExtension.d.ts +65 -0
- package/lib/extensions/A2UIExtension.js +202 -0
- package/lib/{components/chat/extensions → extensions}/ExtensionRegistry.d.ts +2 -3
- package/lib/{components/chat/extensions → extensions}/ExtensionRegistry.js +0 -2
- package/lib/{components/chat/extensions → extensions}/MCPUIExtension.d.ts +2 -2
- package/lib/{components/chat/extensions → extensions}/MCPUIExtension.js +1 -1
- package/lib/extensions/index.d.ts +9 -0
- package/lib/{components/chat/extensions → extensions}/index.js +2 -2
- package/lib/hooks/index.d.ts +37 -12
- package/lib/hooks/index.js +37 -12
- package/lib/hooks/useAIAgentsWebSocket.d.ts +29 -0
- package/lib/hooks/useAIAgentsWebSocket.js +136 -0
- package/lib/hooks/{useAGUI.d.ts → useAgUi.d.ts} +2 -2
- package/lib/hooks/{useAGUI.js → useAgUi.js} +2 -2
- package/lib/hooks/useAgents.d.ts +150 -11
- package/lib/hooks/useAgents.js +623 -61
- package/lib/hooks/useAgentsCatalog.d.ts +25 -0
- package/lib/hooks/useAgentsCatalog.js +28 -0
- package/lib/hooks/useAgentsRegistry.d.ts +10 -0
- package/lib/hooks/useAgentsRegistry.js +20 -0
- package/lib/hooks/useAgentsService.d.ts +22 -0
- package/lib/hooks/useAgentsService.js +146 -0
- package/lib/hooks/useChat.d.ts +2 -2
- package/lib/hooks/useChat.js +14 -8
- package/lib/hooks/useCheckpoints.d.ts +176 -0
- package/lib/hooks/useCheckpoints.js +466 -0
- package/lib/hooks/useConfig.d.ts +11 -0
- package/lib/hooks/useConfig.js +46 -0
- package/lib/hooks/useContextSnapshot.d.ts +11 -0
- package/lib/hooks/useContextSnapshot.js +44 -0
- package/lib/hooks/useMonitoring.d.ts +24 -0
- package/lib/hooks/useMonitoring.js +111 -0
- package/lib/hooks/useNotifications.d.ts +67 -0
- package/lib/hooks/useNotifications.js +208 -0
- package/lib/hooks/useSandbox.d.ts +12 -0
- package/lib/hooks/useSandbox.js +49 -0
- package/lib/hooks/useSkills.d.ts +13 -0
- package/lib/hooks/useSkills.js +46 -0
- package/lib/hooks/useToolApprovals.d.ts +45 -0
- package/lib/hooks/useToolApprovals.js +126 -0
- package/lib/hooks/useTools.d.ts +4 -4
- package/lib/hooks/useTools.js +2 -2
- package/lib/hooks/{useVercelChat.d.ts → useVercelAI.d.ts} +3 -3
- package/lib/hooks/{useVercelChat.js → useVercelAI.js} +2 -2
- package/lib/{components/chat/components → identity}/AgentIdentity.d.ts +1 -1
- package/lib/{components/chat/components → identity}/AgentIdentity.js +4 -3
- package/lib/identity/index.d.ts +1 -0
- package/lib/identity/index.js +2 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.js +3 -2
- package/lib/{components/chat/inference → inference}/BaseInferenceProvider.d.ts +3 -3
- package/lib/{components/chat/inference → inference}/DatalayerInferenceProvider.d.ts +3 -3
- package/lib/{components/chat/inference → inference}/DatalayerInferenceProvider.js +1 -1
- package/lib/{components/chat/inference → inference}/SelfHostedInferenceProvider.d.ts +2 -2
- package/lib/{components/chat/inference → inference}/SelfHostedInferenceProvider.js +1 -1
- package/lib/{components/chat/inference → inference}/index.d.ts +1 -1
- package/lib/{components/chat/inference → inference}/index.js +1 -1
- package/lib/lexical/ChatInlinePlugin.d.ts +1 -1
- package/lib/lexical/ChatInlinePlugin.js +1 -1
- package/lib/{components → mcp}/McpServerManager.d.ts +2 -3
- package/lib/{components → mcp}/McpServerManager.js +1 -1
- package/lib/mcp/index.d.ts +1 -0
- package/lib/{specs/agents/codeai → mcp}/index.js +1 -1
- package/lib/{components/chat/middleware → middleware}/MiddlewarePipeline.d.ts +3 -3
- package/lib/{components/chat/middleware → middleware}/index.d.ts +1 -1
- package/lib/{components/chat/middleware → middleware}/index.js +1 -1
- package/lib/{components/chat/protocols → protocols}/A2AAdapter.d.ts +6 -6
- package/lib/{components/chat/protocols → protocols}/A2AAdapter.js +3 -3
- package/lib/{components/chat/protocols → protocols}/ACPAdapter.d.ts +6 -6
- package/lib/{components/chat/protocols → protocols}/ACPAdapter.js +4 -4
- package/lib/{components/chat/protocols → protocols}/AGUIAdapter.d.ts +14 -6
- package/lib/{components/chat/protocols → protocols}/AGUIAdapter.js +72 -10
- package/lib/{components/chat/protocols → protocols}/BaseProtocolAdapter.d.ts +6 -6
- package/lib/{components/chat/protocols → protocols}/BaseProtocolAdapter.js +1 -1
- package/lib/{components/chat/protocols → protocols}/VercelAIAdapter.d.ts +31 -7
- package/lib/protocols/VercelAIAdapter.js +682 -0
- package/lib/{components/chat/protocols → protocols}/index.d.ts +1 -2
- package/lib/{components/chat/protocols → protocols}/index.js +1 -1
- package/lib/specs/agents/agents.d.ts +54 -0
- package/lib/specs/agents/agents.js +2638 -0
- package/lib/specs/agents/index.js +14 -7
- package/lib/specs/envvars.d.ts +11 -19
- package/lib/specs/envvars.js +42 -21
- package/lib/specs/evals.d.ts +20 -0
- package/lib/specs/evals.js +133 -0
- package/lib/specs/events.d.ts +18 -0
- package/lib/specs/events.js +182 -0
- package/lib/specs/frontendTools.d.ts +14 -0
- package/lib/specs/frontendTools.js +53 -0
- package/lib/specs/guardrails.d.ts +22 -0
- package/lib/specs/guardrails.js +391 -0
- package/lib/specs/index.d.ts +15 -2
- package/lib/specs/index.js +15 -2
- package/lib/specs/mcpServers.d.ts +13 -10
- package/lib/specs/mcpServers.js +95 -25
- package/lib/specs/memory.d.ts +34 -0
- package/lib/specs/memory.js +99 -0
- package/lib/specs/models.d.ts +22 -35
- package/lib/specs/models.js +64 -44
- package/lib/specs/notifications.d.ts +17 -0
- package/lib/specs/notifications.js +187 -0
- package/lib/specs/outputs.d.ts +19 -0
- package/lib/specs/outputs.js +112 -0
- package/lib/specs/skills.d.ts +7 -16
- package/lib/specs/skills.js +89 -12
- package/lib/specs/teams/index.d.ts +17 -0
- package/lib/specs/teams/index.js +37 -0
- package/lib/specs/teams/teams.d.ts +27 -0
- package/lib/specs/teams/teams.js +1120 -0
- package/lib/specs/tools.d.ts +15 -0
- package/lib/specs/tools.js +83 -0
- package/lib/specs/triggers.d.ts +15 -0
- package/lib/specs/triggers.js +117 -0
- package/lib/stores/agentsStore.d.ts +123 -0
- package/lib/stores/agentsStore.js +270 -0
- package/lib/{components/chat/store → stores}/chatStore.d.ts +3 -2
- package/lib/{components/chat/store → stores}/chatStore.js +2 -2
- package/lib/{components/chat/store → stores}/conversationStore.d.ts +2 -2
- package/lib/{components/chat/store → stores}/conversationStore.js +3 -3
- package/lib/{components/chat/store → stores}/index.d.ts +3 -2
- package/lib/{components/chat/store → stores}/index.js +3 -2
- package/lib/{components/chat/tools → tools}/ToolExecutor.d.ts +2 -2
- package/lib/{components/chat/tools → tools}/ToolExecutor.js +1 -1
- package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.d.ts +1 -1
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +14 -10
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +31 -21
- package/lib/tools/adapters/agent-runtimes/notebookHooks.d.ts +1 -1
- package/lib/tools/index.d.ts +3 -0
- package/lib/tools/index.js +3 -7
- package/lib/types/a2a.d.ts +39 -0
- package/lib/types/acp.d.ts +21 -0
- package/lib/types/ag-ui.d.ts +25 -0
- package/lib/types/agents-lifecycle.d.ts +36 -0
- package/lib/types/agents.d.ts +80 -0
- package/lib/types/agents.js +22 -0
- package/lib/types/agentspecs.d.ts +90 -0
- package/lib/{components/chat/components/base/ChatBase.d.ts → types/chat.d.ts} +60 -100
- package/lib/types/checkpoints.d.ts +32 -0
- package/lib/types/checkpoints.js +5 -0
- package/lib/types/config.d.ts +67 -0
- package/lib/{runtime/types.js → types/config.js} +2 -2
- package/lib/types/connection.d.ts +31 -0
- package/lib/types/connection.js +5 -0
- package/lib/types/context.d.ts +67 -0
- package/lib/types/context.js +5 -0
- package/lib/types/cost.d.ts +42 -0
- package/lib/types/cost.js +5 -0
- package/lib/types/envvars.d.ts +21 -0
- package/lib/types/envvars.js +5 -0
- package/lib/types/evals.d.ts +66 -0
- package/lib/types/evals.js +5 -0
- package/lib/types/events.d.ts +49 -0
- package/lib/types/events.js +5 -0
- package/lib/types/eventspecs.d.ts +39 -0
- package/lib/types/eventspecs.js +5 -0
- package/lib/types/examples.d.ts +31 -0
- package/lib/types/examples.js +5 -0
- package/lib/{components/chat/types → types}/execution.d.ts +10 -1
- package/lib/{components/chat/types/extension.d.ts → types/extensions.d.ts} +3 -3
- package/lib/types/guardrails.d.ts +106 -0
- package/lib/types/guardrails.js +5 -0
- package/lib/types/index.d.ts +36 -2
- package/lib/types/index.js +35 -2
- package/lib/{components/chat/types → types}/inference.d.ts +3 -3
- package/lib/types/inference.js +5 -0
- package/lib/types/mcp.d.ts +117 -0
- package/lib/types/mcp.js +27 -0
- package/lib/types/memory.d.ts +23 -0
- package/lib/types/memory.js +5 -0
- package/lib/{components/chat/types/message.d.ts → types/messages.d.ts} +20 -1
- package/lib/{components/chat/types → types}/middleware.d.ts +3 -3
- package/lib/types/models.d.ts +63 -0
- package/lib/types/models.js +5 -0
- package/lib/types/notifications.d.ts +85 -0
- package/lib/types/notifications.js +5 -0
- package/lib/types/outputs.d.ts +51 -0
- package/lib/types/outputs.js +5 -0
- package/lib/{components/chat/types → types}/protocol.d.ts +37 -99
- package/lib/types/protocol.js +5 -0
- package/lib/types/sandbox.d.ts +27 -0
- package/lib/types/sandbox.js +24 -0
- package/lib/types/skills.d.ts +74 -0
- package/lib/types/skills.js +5 -0
- package/lib/types/teams.d.ts +133 -0
- package/lib/types/teams.js +5 -0
- package/lib/types/tool-approvals.d.ts +39 -0
- package/lib/types/tool-approvals.js +5 -0
- package/lib/{components/chat/types/tool.d.ts → types/tools.d.ts} +59 -4
- package/lib/types/triggers.d.ts +48 -0
- package/lib/types/triggers.js +5 -0
- package/lib/types/usage.d.ts +36 -0
- package/lib/types/usage.js +5 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +5 -0
- package/lib/utils/utils.d.ts +60 -0
- package/lib/utils/utils.js +205 -0
- package/package.json +13 -14
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_envvars.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_guardrails.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_mcp_servers.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_memory.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_models.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_notifications.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_outputs.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_skills.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_teams.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_tools.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_triggers.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +464 -58
- package/scripts/codegen/generate_envvars.py +36 -35
- package/scripts/codegen/generate_evals.py +279 -0
- package/scripts/codegen/generate_events.py +312 -0
- package/scripts/codegen/generate_frontend_tools.py +266 -0
- package/scripts/codegen/generate_guardrails.py +475 -0
- package/scripts/codegen/generate_mcp_servers.py +36 -9
- package/scripts/codegen/generate_memory.py +468 -0
- package/scripts/codegen/generate_models.py +22 -46
- package/scripts/codegen/generate_notifications.py +309 -0
- package/scripts/codegen/generate_outputs.py +267 -0
- package/scripts/codegen/generate_skills.py +108 -51
- package/scripts/codegen/generate_teams.py +922 -0
- package/scripts/codegen/generate_tools.py +326 -0
- package/scripts/codegen/generate_triggers.py +295 -0
- package/scripts/codegen/versioning.py +53 -0
- package/lib/components/chat/components/base/ChatBase.js +0 -2240
- package/lib/components/chat/components/base/InputPrompt.d.ts +0 -42
- package/lib/components/chat/components/base/InputPrompt.js +0 -131
- package/lib/components/chat/components/display/index.d.ts +0 -6
- package/lib/components/chat/components/index.d.ts +0 -26
- package/lib/components/chat/components/index.js +0 -39
- package/lib/components/chat/extensions/A2UIExtension.d.ts +0 -87
- package/lib/components/chat/extensions/A2UIExtension.js +0 -312
- package/lib/components/chat/extensions/index.d.ts +0 -10
- package/lib/components/chat/index.d.ts +0 -61
- package/lib/components/chat/index.js +0 -76
- package/lib/components/chat/protocols/VercelAIAdapter.js +0 -315
- package/lib/components/chat/tools/index.d.ts +0 -8
- package/lib/components/chat/tools/index.js +0 -11
- package/lib/components/chat/types/index.d.ts +0 -12
- package/lib/components/chat/types/index.js +0 -17
- package/lib/components/sparklines/Sparklines.d.ts +0 -16
- package/lib/components/sparklines/Sparklines.js +0 -65
- package/lib/components/sparklines/SparklinesLine.d.ts +0 -8
- package/lib/components/sparklines/SparklinesLine.js +0 -37
- package/lib/components/sparklines/dataProcessing.d.ts +0 -25
- package/lib/components/sparklines/dataProcessing.js +0 -35
- package/lib/components/sparklines/index.d.ts +0 -4
- package/lib/components/sparklines/index.js +0 -7
- package/lib/components/sparklines/types.d.ts +0 -36
- package/lib/examples/stores/examplesStore.d.ts +0 -5
- package/lib/examples/stores/notebooks/Empty.ipynb.json +0 -33
- package/lib/examples/stores/notebooks/NotebookExample2.ipynb.json +0 -48
- package/lib/examples/stores/themeStore.d.ts +0 -33
- package/lib/examples/stores/themeStore.js +0 -38
- package/lib/hooks/useKeyboardShortcuts.d.ts +0 -47
- package/lib/hooks/useKeyboardShortcuts.js +0 -153
- package/lib/hooks/useMobile.d.ts +0 -1
- package/lib/hooks/useMobile.js +0 -19
- package/lib/hooks/useNotebookAIAgent.d.ts +0 -8
- package/lib/hooks/useNotebookAIAgent.js +0 -73
- package/lib/renderers/a2ui/components/A2UIRenderer.d.ts +0 -7
- package/lib/renderers/a2ui/components/A2UIRenderer.js +0 -102
- package/lib/renderers/a2ui/components/SurfaceRenderer.d.ts +0 -7
- package/lib/renderers/a2ui/components/SurfaceRenderer.js +0 -101
- package/lib/renderers/a2ui/components/content/AudioPlayer.d.ts +0 -9
- package/lib/renderers/a2ui/components/content/AudioPlayer.js +0 -38
- package/lib/renderers/a2ui/components/content/Divider.d.ts +0 -9
- package/lib/renderers/a2ui/components/content/Divider.js +0 -35
- package/lib/renderers/a2ui/components/content/Icon.d.ts +0 -9
- package/lib/renderers/a2ui/components/content/Icon.js +0 -110
- package/lib/renderers/a2ui/components/content/Image.d.ts +0 -9
- package/lib/renderers/a2ui/components/content/Image.js +0 -61
- package/lib/renderers/a2ui/components/content/Text.d.ts +0 -9
- package/lib/renderers/a2ui/components/content/Text.js +0 -64
- package/lib/renderers/a2ui/components/content/Video.d.ts +0 -9
- package/lib/renderers/a2ui/components/content/Video.js +0 -37
- package/lib/renderers/a2ui/components/content/index.d.ts +0 -6
- package/lib/renderers/a2ui/components/content/index.js +0 -25
- package/lib/renderers/a2ui/components/index.d.ts +0 -5
- package/lib/renderers/a2ui/components/index.js +0 -24
- package/lib/renderers/a2ui/components/interactive/Button.d.ts +0 -11
- package/lib/renderers/a2ui/components/interactive/Button.js +0 -71
- package/lib/renderers/a2ui/components/interactive/CheckBox.d.ts +0 -9
- package/lib/renderers/a2ui/components/interactive/CheckBox.js +0 -48
- package/lib/renderers/a2ui/components/interactive/DateTimeInput.d.ts +0 -9
- package/lib/renderers/a2ui/components/interactive/DateTimeInput.js +0 -62
- package/lib/renderers/a2ui/components/interactive/MultipleChoice.d.ts +0 -9
- package/lib/renderers/a2ui/components/interactive/MultipleChoice.js +0 -73
- package/lib/renderers/a2ui/components/interactive/Slider.d.ts +0 -9
- package/lib/renderers/a2ui/components/interactive/Slider.js +0 -53
- package/lib/renderers/a2ui/components/interactive/TextField.d.ts +0 -9
- package/lib/renderers/a2ui/components/interactive/TextField.js +0 -72
- package/lib/renderers/a2ui/components/interactive/index.d.ts +0 -6
- package/lib/renderers/a2ui/components/interactive/index.js +0 -25
- package/lib/renderers/a2ui/components/layout/Card.d.ts +0 -11
- package/lib/renderers/a2ui/components/layout/Card.js +0 -30
- package/lib/renderers/a2ui/components/layout/Column.d.ts +0 -11
- package/lib/renderers/a2ui/components/layout/Column.js +0 -65
- package/lib/renderers/a2ui/components/layout/List.d.ts +0 -11
- package/lib/renderers/a2ui/components/layout/List.js +0 -55
- package/lib/renderers/a2ui/components/layout/Modal.d.ts +0 -11
- package/lib/renderers/a2ui/components/layout/Modal.js +0 -58
- package/lib/renderers/a2ui/components/layout/Row.d.ts +0 -11
- package/lib/renderers/a2ui/components/layout/Row.js +0 -65
- package/lib/renderers/a2ui/components/layout/Tabs.d.ts +0 -11
- package/lib/renderers/a2ui/components/layout/Tabs.js +0 -48
- package/lib/renderers/a2ui/components/layout/index.d.ts +0 -6
- package/lib/renderers/a2ui/components/layout/index.js +0 -25
- package/lib/renderers/a2ui/context/A2UIContext.d.ts +0 -17
- package/lib/renderers/a2ui/context/A2UIContext.js +0 -54
- package/lib/renderers/a2ui/context/ThemeContext.d.ts +0 -20
- package/lib/renderers/a2ui/context/ThemeContext.js +0 -333
- package/lib/renderers/a2ui/hooks/useA2UI.d.ts +0 -36
- package/lib/renderers/a2ui/hooks/useA2UI.js +0 -62
- package/lib/renderers/a2ui/hooks/useDataBinding.d.ts +0 -8
- package/lib/renderers/a2ui/hooks/useDataBinding.js +0 -83
- package/lib/renderers/a2ui/index.d.ts +0 -9
- package/lib/renderers/a2ui/index.js +0 -28
- package/lib/renderers/a2ui/lib/utils.d.ts +0 -11
- package/lib/renderers/a2ui/lib/utils.js +0 -38
- package/lib/renderers/a2ui/types/index.d.ts +0 -17
- package/lib/runtime/index.d.ts +0 -38
- package/lib/runtime/index.js +0 -40
- package/lib/runtime/runtimeStore.d.ts +0 -76
- package/lib/runtime/runtimeStore.js +0 -184
- package/lib/runtime/types.d.ts +0 -84
- package/lib/runtime/useAgentConnection.d.ts +0 -45
- package/lib/runtime/useAgentConnection.js +0 -112
- package/lib/runtime/useAgentRuntime.d.ts +0 -93
- package/lib/runtime/useAgentRuntime.js +0 -125
- package/lib/specs/agents/codeai/agents.d.ts +0 -28
- package/lib/specs/agents/codeai/agents.js +0 -151
- package/lib/specs/agents/codeai/index.d.ts +0 -1
- package/lib/specs/agents/codemode-paper/agents.d.ts +0 -31
- package/lib/specs/agents/codemode-paper/agents.js +0 -308
- package/lib/specs/agents/codemode-paper/index.d.ts +0 -1
- package/lib/specs/agents/codemode-paper/index.js +0 -5
- package/lib/specs/agents/datalayer-ai/agents.d.ts +0 -31
- package/lib/specs/agents/datalayer-ai/agents.js +0 -282
- package/lib/specs/agents/datalayer-ai/index.d.ts +0 -1
- package/lib/specs/agents/datalayer-ai/index.js +0 -5
- package/lib/state/index.d.ts +0 -1
- package/lib/state/index.js +0 -5
- package/lib/state/substates/AIAgentState.d.ts +0 -80
- package/lib/state/substates/AIAgentState.js +0 -108
- package/lib/state/substates/index.d.ts +0 -1
- package/lib/state/substates/index.js +0 -5
- package/lib/types/AIAgent.d.ts +0 -17
- package/lib/types/Types.d.ts +0 -189
- /package/lib/{components/chat → api}/handler.d.ts +0 -0
- /package/lib/{renderers/index.d.ts → api/utils.d.ts} +0 -0
- /package/lib/{renderers/index.js → api/utils.js} +0 -0
- /package/lib/{components/chat/components/elements → chat/display}/PoweredByTag.js +0 -0
- /package/lib/{components/chat/components → chat}/parts/DynamicToolPart.js +0 -0
- /package/lib/{components/chat/components → chat}/parts/ReasoningPart.d.ts +0 -0
- /package/lib/{components/chat/components → chat}/parts/TextPart.js +0 -0
- /package/lib/{components/chat/components → chat}/parts/ToolPart.d.ts +0 -0
- /package/lib/{components/chat/components → chat}/styles/streamdownStyles.js +0 -0
- /package/lib/{components/chat/components/elements → chat/tools}/ToolApprovalDialog.d.ts +0 -0
- /package/lib/{components/chat/components → context}/ContextDistribution.d.ts +0 -0
- /package/lib/{components/chat/components → context}/ContextDistribution.js +0 -0
- /package/lib/{components/chat/components → context}/ContextInspector.d.ts +0 -0
- /package/lib/{components/chat/components → context}/ContextPanel.d.ts +0 -0
- /package/lib/{components/chat/components → context}/ContextUsage.d.ts +0 -0
- /package/lib/{components/chat/components → context}/ContextUsage.js +0 -0
- /package/lib/examples/{JupyterCellExample.d.ts → CellSimpleExample.d.ts} +0 -0
- /package/lib/examples/{AgentRuntimeCustomExample.d.ts → ChatCustomExample.d.ts} +0 -0
- /package/lib/examples/{AgentRuntimeChatExample.d.ts → ChatExample.d.ts} +0 -0
- /package/lib/examples/{AgentRuntimeStandaloneExample.d.ts → ChatStandaloneExample.d.ts} +0 -0
- /package/lib/examples/{AgentRuntimeNotebookExample.d.ts → NotebookExample.d.ts} +0 -0
- /package/lib/examples/{AgentRuntimeNotebookSidebarExample.d.ts → NotebookSidebarExample.d.ts} +0 -0
- /package/lib/examples/{JupyterNotebookExample.d.ts → NotebookSimpleExample.d.ts} +0 -0
- /package/lib/examples/{stores → utils}/agents/earthquake-detector.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/agents/earthquake-detector.json +0 -0
- /package/lib/examples/{stores → utils}/agents/earthquake-detector.lexical.json +0 -0
- /package/lib/examples/{stores → utils}/agents/sales-forecaster.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/agents/sales-forecaster.json +0 -0
- /package/lib/examples/{stores → utils}/agents/sales-forecaster.lexical.json +0 -0
- /package/lib/examples/{stores → utils}/agents/social-post-generator.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/agents/social-post-generator.json +0 -0
- /package/lib/examples/{stores → utils}/agents/social-post-generator.lexical.json +0 -0
- /package/lib/examples/{stores → utils}/agents/stock-market.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/agents/stock-market.json +0 -0
- /package/lib/examples/{stores → utils}/agents/stock-market.lexical.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/IPyWidgetsExample.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/IPyWidgetsExampleWithState.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/Lite.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/Matplotlib.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/NotebookExample1.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/NotebookOutputs.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/NotebookToCExample.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/notebooks/OutputIPyWidgetsExample.d.ts +0 -0
- /package/lib/examples/{stores → utils}/notebooks/OutputIPyWidgetsExample.js +0 -0
- /package/lib/examples/{stores → utils}/notebooks/PyGWalker.ipynb.json +0 -0
- /package/lib/examples/{stores → utils}/themedProvider.d.ts +0 -0
- /package/lib/examples/{stores → utils}/themedProvider.js +0 -0
- /package/lib/{components/chat/inference → inference}/BaseInferenceProvider.js +0 -0
- /package/lib/{components/chat/middleware → middleware}/MiddlewarePipeline.js +0 -0
- /package/lib/{components/chat/types/inference.js → types/a2a.js} +0 -0
- /package/lib/{components/chat/types/protocol.js → types/acp.js} +0 -0
- /package/lib/{components/sparklines/types.js → types/ag-ui.js} +0 -0
- /package/lib/{renderers/a2ui/types/index.js → types/agents-lifecycle.js} +0 -0
- /package/lib/types/{AIAgent.js → agentspecs.js} +0 -0
- /package/lib/types/{Types.js → chat.js} +0 -0
- /package/lib/{components/chat/types → types}/execution.js +0 -0
- /package/lib/{components/chat/types/extension.js → types/extensions.js} +0 -0
- /package/lib/{components/chat/types/message.js → types/messages.js} +0 -0
- /package/lib/{components/chat/types → types}/middleware.js +0 -0
- /package/lib/{components/chat/types/tool.js → types/tools.js} +0 -0
|
@@ -1,2240 +0,0 @@
|
|
|
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
|
-
* Main ChatBase component.
|
|
8
|
-
* Provides a full chat interface with messages and input.
|
|
9
|
-
* This is the base component used by all other chat container components.
|
|
10
|
-
*
|
|
11
|
-
* Supports multiple modes:
|
|
12
|
-
* 1. Store mode: Uses Zustand store for state management (default)
|
|
13
|
-
* 2. Protocol mode: Connects to backend via AG-UI, A2A, ACP, or Vercel AI protocols
|
|
14
|
-
* 3. Custom mode: Uses onSendMessage prop for custom message handling
|
|
15
|
-
*
|
|
16
|
-
* @module components/chat/components/ChatBase
|
|
17
|
-
*/
|
|
18
|
-
import { useContext } from 'react';
|
|
19
|
-
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
20
|
-
import { Heading, Text, Spinner, IconButton, Button, ActionMenu, ActionList, LabelGroup, Label, ToggleSwitch, } from '@primer/react';
|
|
21
|
-
import { Box } from '@datalayer/primer-addons';
|
|
22
|
-
import { AlertIcon, PlusIcon, TrashIcon, GearIcon, PersonIcon, ToolsIcon, AiModelIcon, BriefcaseIcon, CircleIcon, SquareFillIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, InfoIcon, } from '@primer/octicons-react';
|
|
23
|
-
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
24
|
-
import ReactECharts from 'echarts-for-react';
|
|
25
|
-
import { useQuery, QueryClient, QueryClientProvider, QueryClientContext, } from '@tanstack/react-query';
|
|
26
|
-
import { Streamdown } from 'streamdown';
|
|
27
|
-
import { streamdownMarkdownStyles, streamdownCodeBlockStyles, } from '../styles/streamdownStyles';
|
|
28
|
-
import { PoweredByTag } from '../elements/PoweredByTag';
|
|
29
|
-
import { requestAPI } from '../../handler';
|
|
30
|
-
import { useChatStore } from '../../store/chatStore';
|
|
31
|
-
import { useConversationStore } from '../../store/conversationStore';
|
|
32
|
-
import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/message';
|
|
33
|
-
import { AGUIAdapter, A2AAdapter, VercelAIAdapter, ACPAdapter, } from '../../protocols';
|
|
34
|
-
import { ToolCallDisplay } from '../display/ToolCallDisplay';
|
|
35
|
-
import { InputPrompt } from './InputPrompt';
|
|
36
|
-
// Singleton QueryClient for ChatBase instances without external QueryClientProvider
|
|
37
|
-
const internalQueryClient = new QueryClient({
|
|
38
|
-
defaultOptions: {
|
|
39
|
-
queries: {
|
|
40
|
-
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
41
|
-
refetchOnWindowFocus: false,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
// Primer's default portal root ID
|
|
46
|
-
const PRIMER_PORTAL_ROOT_ID = '__primerPortalRoot__';
|
|
47
|
-
/**
|
|
48
|
-
* Hook to ensure Primer's default portal root has a high z-index.
|
|
49
|
-
* This ensures dropdown menus appear above floating chat panels.
|
|
50
|
-
*/
|
|
51
|
-
function useHighZIndexPortal() {
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
// Set up a MutationObserver to watch for the portal root being added
|
|
54
|
-
const setPortalZIndex = () => {
|
|
55
|
-
const portalRoot = document.getElementById(PRIMER_PORTAL_ROOT_ID);
|
|
56
|
-
if (portalRoot) {
|
|
57
|
-
portalRoot.style.zIndex = '9999';
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
};
|
|
62
|
-
// Try immediately
|
|
63
|
-
if (setPortalZIndex()) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
// If not found yet, observe for it
|
|
67
|
-
const observer = new MutationObserver(() => {
|
|
68
|
-
if (setPortalZIndex()) {
|
|
69
|
-
observer.disconnect();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
observer.observe(document.body, {
|
|
73
|
-
childList: true,
|
|
74
|
-
subtree: true,
|
|
75
|
-
});
|
|
76
|
-
return () => {
|
|
77
|
-
observer.disconnect();
|
|
78
|
-
};
|
|
79
|
-
}, []);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Check if an item is a tool call message
|
|
83
|
-
*/
|
|
84
|
-
function isToolCallMessage(item) {
|
|
85
|
-
return 'type' in item && item.type === 'tool-call';
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Extract text content from a ChatMessage
|
|
89
|
-
*/
|
|
90
|
-
function getMessageText(message) {
|
|
91
|
-
if (typeof message.content === 'string') {
|
|
92
|
-
return message.content;
|
|
93
|
-
}
|
|
94
|
-
// Array of ContentPart - extract text parts
|
|
95
|
-
return message.content
|
|
96
|
-
.filter((part) => part.type === 'text')
|
|
97
|
-
.map(part => part.text)
|
|
98
|
-
.join('');
|
|
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
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Create protocol adapter based on configuration
|
|
175
|
-
*/
|
|
176
|
-
function createProtocolAdapter(config) {
|
|
177
|
-
const adapterConfig = {
|
|
178
|
-
type: config.type,
|
|
179
|
-
baseUrl: config.endpoint,
|
|
180
|
-
authToken: config.authToken,
|
|
181
|
-
agentId: config.agentId,
|
|
182
|
-
...config.options,
|
|
183
|
-
};
|
|
184
|
-
switch (config.type) {
|
|
185
|
-
case 'ag-ui':
|
|
186
|
-
return new AGUIAdapter(adapterConfig);
|
|
187
|
-
case 'a2a':
|
|
188
|
-
return new A2AAdapter(adapterConfig);
|
|
189
|
-
case 'vercel-ai':
|
|
190
|
-
return new VercelAIAdapter(adapterConfig);
|
|
191
|
-
case 'acp':
|
|
192
|
-
return new ACPAdapter(adapterConfig);
|
|
193
|
-
default:
|
|
194
|
-
console.warn(`[ChatBase] Unknown protocol type: ${config.type}`);
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Hook to safely use query when QueryClient is available
|
|
200
|
-
* Returns null if no QueryClientProvider is present
|
|
201
|
-
*/
|
|
202
|
-
function useConfigQuery(enabled, configEndpoint, authToken) {
|
|
203
|
-
const queryClient = useContext(QueryClientContext);
|
|
204
|
-
// If no QueryClient is available, return a mock result
|
|
205
|
-
if (!queryClient) {
|
|
206
|
-
return {
|
|
207
|
-
data: undefined,
|
|
208
|
-
isLoading: false,
|
|
209
|
-
isError: false,
|
|
210
|
-
error: null,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
214
|
-
return useQuery({
|
|
215
|
-
queryFn: async () => {
|
|
216
|
-
// If configEndpoint is provided, use direct fetch (for FastAPI)
|
|
217
|
-
if (configEndpoint) {
|
|
218
|
-
const headers = {
|
|
219
|
-
'Content-Type': 'application/json',
|
|
220
|
-
};
|
|
221
|
-
if (authToken) {
|
|
222
|
-
headers['Authorization'] = `Bearer ${authToken}`;
|
|
223
|
-
}
|
|
224
|
-
const response = await fetch(configEndpoint, { headers });
|
|
225
|
-
if (!response.ok) {
|
|
226
|
-
throw new Error(`Config fetch failed: ${response.statusText}`);
|
|
227
|
-
}
|
|
228
|
-
return response.json();
|
|
229
|
-
}
|
|
230
|
-
// Otherwise use Jupyter requestAPI
|
|
231
|
-
return requestAPI('configure');
|
|
232
|
-
},
|
|
233
|
-
queryKey: ['models', configEndpoint || 'jupyter'],
|
|
234
|
-
enabled,
|
|
235
|
-
retry: 1,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Hook to fetch available skills from backend
|
|
240
|
-
*/
|
|
241
|
-
function useSkillsQuery(enabled, baseEndpoint, authToken) {
|
|
242
|
-
const queryClient = useContext(QueryClientContext);
|
|
243
|
-
// If no QueryClient is available, return a mock result
|
|
244
|
-
if (!queryClient) {
|
|
245
|
-
return {
|
|
246
|
-
data: undefined,
|
|
247
|
-
isLoading: false,
|
|
248
|
-
isError: false,
|
|
249
|
-
error: null,
|
|
250
|
-
refetch: () => Promise.resolve({ data: undefined }),
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
254
|
-
return useQuery({
|
|
255
|
-
queryFn: async () => {
|
|
256
|
-
if (!baseEndpoint) {
|
|
257
|
-
return { skills: [], total: 0 };
|
|
258
|
-
}
|
|
259
|
-
// Derive skills endpoint from config endpoint
|
|
260
|
-
const skillsEndpoint = baseEndpoint.replace('/configure', '/skills');
|
|
261
|
-
const headers = {
|
|
262
|
-
'Content-Type': 'application/json',
|
|
263
|
-
};
|
|
264
|
-
if (authToken) {
|
|
265
|
-
headers['Authorization'] = `Bearer ${authToken}`;
|
|
266
|
-
}
|
|
267
|
-
const response = await fetch(skillsEndpoint, { headers });
|
|
268
|
-
if (!response.ok) {
|
|
269
|
-
throw new Error(`Skills fetch failed: ${response.statusText}`);
|
|
270
|
-
}
|
|
271
|
-
return response.json();
|
|
272
|
-
},
|
|
273
|
-
queryKey: ['skills', baseEndpoint || 'jupyter'],
|
|
274
|
-
enabled,
|
|
275
|
-
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
276
|
-
retry: 1,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Format token count for compact display
|
|
281
|
-
*/
|
|
282
|
-
function formatTokenCount(tokens) {
|
|
283
|
-
if (tokens >= 1_000_000)
|
|
284
|
-
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
285
|
-
if (tokens >= 1_000)
|
|
286
|
-
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
287
|
-
return tokens.toString();
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Derive the API base URL from a configEndpoint.
|
|
291
|
-
* configEndpoint may look like:
|
|
292
|
-
* "http://host:port/api/v1/config" (from agentRuntimeConfig)
|
|
293
|
-
* "http://host:port/api/v1/configure" (from Chat component)
|
|
294
|
-
* We strip the trailing path segment to get "http://host:port/api/v1".
|
|
295
|
-
*/
|
|
296
|
-
function getApiBaseFromConfig(configEndpoint) {
|
|
297
|
-
return configEndpoint.replace(/\/(config|configure)\/?$/, '');
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Hook to poll agent context-snapshot from the backend.
|
|
301
|
-
* Returns cumulative token usage (input/output breakdown) tracked by the agent server.
|
|
302
|
-
* Uses the same endpoint as codeai: GET /api/v1/configure/agents/{agentId}/context-snapshot
|
|
303
|
-
*/
|
|
304
|
-
function useContextSnapshotQuery(enabled, configEndpoint, agentId, authToken) {
|
|
305
|
-
const queryClient = useContext(QueryClientContext);
|
|
306
|
-
if (!queryClient) {
|
|
307
|
-
return { data: undefined, isLoading: false, isError: false, error: null };
|
|
308
|
-
}
|
|
309
|
-
const snapshotUrl = configEndpoint && agentId
|
|
310
|
-
? `${getApiBaseFromConfig(configEndpoint)}/configure/agents/${encodeURIComponent(agentId)}/context-snapshot`
|
|
311
|
-
: undefined;
|
|
312
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
313
|
-
const result = useQuery({
|
|
314
|
-
queryKey: ['context-snapshot-header', agentId, snapshotUrl],
|
|
315
|
-
queryFn: async () => {
|
|
316
|
-
if (!snapshotUrl) {
|
|
317
|
-
throw new Error('No context-snapshot URL available');
|
|
318
|
-
}
|
|
319
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
320
|
-
if (authToken) {
|
|
321
|
-
headers['Authorization'] = `Bearer ${authToken}`;
|
|
322
|
-
}
|
|
323
|
-
const response = await fetch(snapshotUrl, { headers });
|
|
324
|
-
if (!response.ok) {
|
|
325
|
-
throw new Error(`Context snapshot fetch failed: ${response.statusText}`);
|
|
326
|
-
}
|
|
327
|
-
return response.json();
|
|
328
|
-
},
|
|
329
|
-
enabled: enabled && !!snapshotUrl,
|
|
330
|
-
// Poll every 10 seconds, but stop polling once the query has errored (e.g. runtime terminated)
|
|
331
|
-
refetchInterval: query => (query.state.status === 'error' ? false : 10_000),
|
|
332
|
-
refetchOnMount: 'always',
|
|
333
|
-
staleTime: 0,
|
|
334
|
-
retry: 1,
|
|
335
|
-
});
|
|
336
|
-
return result;
|
|
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
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* ChatBase component - Universal chat panel supporting store, protocol, and custom modes
|
|
383
|
-
*/
|
|
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,
|
|
385
|
-
// Mode selection
|
|
386
|
-
useStore: useStoreMode = true, protocol: protocolProp, agentRuntimeConfig, onSendMessage, enableStreaming = false,
|
|
387
|
-
// Extended props
|
|
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,
|
|
389
|
-
// Identity/Authorization props
|
|
390
|
-
onAuthorizationRequired, connectedIdentities,
|
|
391
|
-
// Conversation persistence
|
|
392
|
-
runtimeId, historyEndpoint, historyAuthToken,
|
|
393
|
-
// Pending prompt
|
|
394
|
-
pendingPrompt, }) {
|
|
395
|
-
const protocol = agentRuntimeConfig
|
|
396
|
-
? {
|
|
397
|
-
type: agentRuntimeConfig.protocol || 'ag-ui',
|
|
398
|
-
endpoint: agentRuntimeConfig.url,
|
|
399
|
-
authToken: agentRuntimeConfig.authToken,
|
|
400
|
-
agentId: agentRuntimeConfig.agentId,
|
|
401
|
-
enableConfigQuery: true,
|
|
402
|
-
configEndpoint: `${agentRuntimeConfig.url}/api/v1/config`,
|
|
403
|
-
}
|
|
404
|
-
: protocolProp;
|
|
405
|
-
// If agentRuntimeConfig is provided, force protocol mode
|
|
406
|
-
const effectiveUseStoreMode = agentRuntimeConfig ? false : useStoreMode;
|
|
407
|
-
// Check if QueryClientProvider is already available
|
|
408
|
-
const existingQueryClient = useContext(QueryClientContext);
|
|
409
|
-
// If no QueryClient is available, wrap with our internal provider
|
|
410
|
-
if (!existingQueryClient) {
|
|
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 }) }));
|
|
412
|
-
}
|
|
413
|
-
// QueryClient already available, render inner component directly
|
|
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 }));
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Inner ChatBase component - contains all the actual logic
|
|
418
|
-
*/
|
|
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,
|
|
420
|
-
// Mode selection
|
|
421
|
-
useStore: useStoreMode = true, protocol, onSendMessage, enableStreaming = false,
|
|
422
|
-
// Extended props
|
|
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,
|
|
424
|
-
// Identity/Authorization props
|
|
425
|
-
onAuthorizationRequired, connectedIdentities,
|
|
426
|
-
// Conversation persistence
|
|
427
|
-
runtimeId, historyEndpoint, historyAuthToken,
|
|
428
|
-
// Pending prompt
|
|
429
|
-
pendingPrompt, }) {
|
|
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) : '';
|
|
435
|
-
// Store (optional for message persistence)
|
|
436
|
-
const clearStoreMessages = useChatStore(state => state.clearMessages);
|
|
437
|
-
// Check if protocol is A2A (doesn't support per-request model override)
|
|
438
|
-
const isA2AProtocol = protocol?.type === 'a2a';
|
|
439
|
-
// Component state
|
|
440
|
-
const [displayItems, setDisplayItems] = useState([]);
|
|
441
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
442
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
443
|
-
const [error, setError] = useState(null);
|
|
444
|
-
const [input, setInput] = useState('');
|
|
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);
|
|
451
|
-
const [selectedModel, setSelectedModel] = useState('');
|
|
452
|
-
// enabledTools tracks which MCP server tools are enabled
|
|
453
|
-
// Format: Map<serverId, Set<toolName>>
|
|
454
|
-
const [enabledMcpTools, setEnabledMcpTools] = useState(new Map());
|
|
455
|
-
// Note: legacy _enabledTools for backend-defined tools from config query
|
|
456
|
-
// Frontend tools are passed via frontendTools prop
|
|
457
|
-
const [_enabledTools, setEnabledTools] = useState([]);
|
|
458
|
-
// Skills state - tracks which skills are enabled
|
|
459
|
-
const [enabledSkills, setEnabledSkills] = useState(new Set());
|
|
460
|
-
// Config query (for protocols that support it)
|
|
461
|
-
// Safely handles missing QueryClientProvider
|
|
462
|
-
const configQuery = useConfigQuery(Boolean(protocol?.enableConfigQuery), protocol?.configEndpoint, protocol?.authToken);
|
|
463
|
-
// Skills query (for protocols that support it)
|
|
464
|
-
const skillsQuery = useSkillsQuery(Boolean(protocol?.enableConfigQuery) && showSkillsMenu, protocol?.configEndpoint, protocol?.authToken);
|
|
465
|
-
// Context snapshot query — polls agent-level cumulative token usage (input/output breakdown)
|
|
466
|
-
// Gated by showTokenUsage (not showHeader), so usage is visible even without a title bar.
|
|
467
|
-
const contextSnapshotQuery = useContextSnapshotQuery(Boolean(protocol?.enableConfigQuery) && showTokenUsage, protocol?.configEndpoint, protocol?.agentId, protocol?.authToken);
|
|
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;
|
|
473
|
-
// Refs
|
|
474
|
-
const adapterRef = useRef(null);
|
|
475
|
-
const unsubscribeRef = useRef(null);
|
|
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);
|
|
480
|
-
const currentAssistantMessageRef = useRef(null);
|
|
481
|
-
const threadIdRef = useRef(generateMessageId());
|
|
482
|
-
const messagesEndRef = useRef(null);
|
|
483
|
-
const inputRef = useRef(null);
|
|
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);
|
|
489
|
-
// Use a ref for connectedIdentities to avoid infinite loops in useCallback
|
|
490
|
-
// (the array reference changes on every render even if contents are the same)
|
|
491
|
-
const connectedIdentitiesRef = useRef(connectedIdentities);
|
|
492
|
-
connectedIdentitiesRef.current = connectedIdentities;
|
|
493
|
-
const isServerSelected = useCallback((server) => {
|
|
494
|
-
if (!mcpServers)
|
|
495
|
-
return true;
|
|
496
|
-
const origin = server.isConfig === false ? 'catalog' : 'config';
|
|
497
|
-
return mcpServers.some(s => s.id === server.id && s.origin === origin);
|
|
498
|
-
}, [mcpServers]);
|
|
499
|
-
// Auto-focus input on mount
|
|
500
|
-
useEffect(() => {
|
|
501
|
-
if (autoFocus && inputRef.current) {
|
|
502
|
-
// Small delay to ensure the component is fully rendered
|
|
503
|
-
const timeoutId = setTimeout(() => {
|
|
504
|
-
inputRef.current?.focus();
|
|
505
|
-
}, 100);
|
|
506
|
-
return () => clearTimeout(timeoutId);
|
|
507
|
-
}
|
|
508
|
-
}, [autoFocus]);
|
|
509
|
-
// Refocus input when focusTrigger changes
|
|
510
|
-
useEffect(() => {
|
|
511
|
-
if (focusTrigger !== undefined && focusTrigger > 0 && inputRef.current) {
|
|
512
|
-
// Small delay to ensure any layout changes have completed
|
|
513
|
-
const timeoutId = setTimeout(() => {
|
|
514
|
-
inputRef.current?.focus();
|
|
515
|
-
}, 150);
|
|
516
|
-
return () => clearTimeout(timeoutId);
|
|
517
|
-
}
|
|
518
|
-
}, [focusTrigger]);
|
|
519
|
-
// Track previous loading state to detect when loading completes
|
|
520
|
-
const wasLoadingRef = useRef(false);
|
|
521
|
-
// Refocus input when loading completes
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
if (wasLoadingRef.current && !isLoading && inputRef.current) {
|
|
524
|
-
// Small delay to ensure the input is fully enabled
|
|
525
|
-
const timeoutId = setTimeout(() => {
|
|
526
|
-
inputRef.current?.focus();
|
|
527
|
-
}, 50);
|
|
528
|
-
return () => clearTimeout(timeoutId);
|
|
529
|
-
}
|
|
530
|
-
wasLoadingRef.current = isLoading;
|
|
531
|
-
}, [isLoading]);
|
|
532
|
-
// Auto-resize textarea based on content
|
|
533
|
-
const adjustTextareaHeight = useCallback(() => {
|
|
534
|
-
const textarea = inputRef.current;
|
|
535
|
-
if (textarea) {
|
|
536
|
-
// Reset height to auto to get proper scrollHeight
|
|
537
|
-
textarea.style.height = 'auto';
|
|
538
|
-
// Set height to scrollHeight, capped at maxHeight (120px)
|
|
539
|
-
const maxHeight = 120;
|
|
540
|
-
const minHeight = 40;
|
|
541
|
-
const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
|
|
542
|
-
textarea.style.height = `${newHeight}px`;
|
|
543
|
-
// Add overflow if content exceeds maxHeight
|
|
544
|
-
textarea.style.overflowY =
|
|
545
|
-
textarea.scrollHeight > maxHeight ? 'auto' : 'hidden';
|
|
546
|
-
}
|
|
547
|
-
}, []);
|
|
548
|
-
// Adjust textarea height when input changes
|
|
549
|
-
useEffect(() => {
|
|
550
|
-
adjustTextareaHeight();
|
|
551
|
-
}, [input, adjustTextareaHeight]);
|
|
552
|
-
// Ensure textarea has a minimum height on mount
|
|
553
|
-
useEffect(() => {
|
|
554
|
-
const timer = setTimeout(adjustTextareaHeight, 0);
|
|
555
|
-
return () => clearTimeout(timer);
|
|
556
|
-
}, [adjustTextareaHeight]);
|
|
557
|
-
// Initialize model and tools when config is available
|
|
558
|
-
useEffect(() => {
|
|
559
|
-
if ((configQuery.data || availableModels) && !selectedModel) {
|
|
560
|
-
// Use availableModels override if provided, otherwise use config models
|
|
561
|
-
const modelsList = availableModels || configQuery.data?.models || [];
|
|
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);
|
|
567
|
-
if (modelExists) {
|
|
568
|
-
setSelectedModel(preferredModel);
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
// Fallback to first available model if preferred model not found
|
|
572
|
-
const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
|
|
573
|
-
const firstModel = firstAvailableModel || modelsList[0];
|
|
574
|
-
if (firstModel) {
|
|
575
|
-
setSelectedModel(firstModel.id);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
// No preferred model, select first available model
|
|
581
|
-
const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
|
|
582
|
-
const firstModel = firstAvailableModel || modelsList[0];
|
|
583
|
-
if (firstModel) {
|
|
584
|
-
setSelectedModel(firstModel.id);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
const allToolIds = configQuery.data?.builtinTools?.map(tool => tool.id) || [];
|
|
588
|
-
setEnabledTools(allToolIds);
|
|
589
|
-
// Initialize MCP server tools
|
|
590
|
-
if (configQuery.data?.mcpServers) {
|
|
591
|
-
const newEnabledMcpTools = new Map();
|
|
592
|
-
for (const server of configQuery.data.mcpServers) {
|
|
593
|
-
if (server.isAvailable && server.enabled) {
|
|
594
|
-
// If mcpServers is provided, only enable those servers
|
|
595
|
-
// If not provided, enable all available servers
|
|
596
|
-
const shouldEnableServer = isServerSelected(server);
|
|
597
|
-
if (shouldEnableServer) {
|
|
598
|
-
const enabledToolNames = new Set(server.tools.filter(t => t.enabled).map(t => t.name));
|
|
599
|
-
newEnabledMcpTools.set(server.id, enabledToolNames);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
setEnabledMcpTools(newEnabledMcpTools);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}, [
|
|
607
|
-
configQuery.data,
|
|
608
|
-
selectedModel,
|
|
609
|
-
initialModel,
|
|
610
|
-
availableModels,
|
|
611
|
-
mcpServers,
|
|
612
|
-
isServerSelected,
|
|
613
|
-
]);
|
|
614
|
-
// Update enabled MCP servers when mcpServers prop changes (e.g., server removed)
|
|
615
|
-
useEffect(() => {
|
|
616
|
-
if (!configQuery.data?.mcpServers || !mcpServers)
|
|
617
|
-
return;
|
|
618
|
-
setEnabledMcpTools(prev => {
|
|
619
|
-
const newMap = new Map();
|
|
620
|
-
// Only keep servers that are in mcpServers
|
|
621
|
-
for (const server of configQuery.data?.mcpServers ?? []) {
|
|
622
|
-
if (isServerSelected(server) && prev.has(server.id)) {
|
|
623
|
-
// Keep existing tool selection for this server
|
|
624
|
-
newMap.set(server.id, prev.get(server.id));
|
|
625
|
-
}
|
|
626
|
-
else if (isServerSelected(server) &&
|
|
627
|
-
server.isAvailable &&
|
|
628
|
-
server.enabled) {
|
|
629
|
-
// Newly added server - enable all tools
|
|
630
|
-
const enabledToolNames = new Set(server.tools.filter(t => t.enabled).map(t => t.name));
|
|
631
|
-
newMap.set(server.id, enabledToolNames);
|
|
632
|
-
}
|
|
633
|
-
// Servers not in mcpServers are excluded
|
|
634
|
-
}
|
|
635
|
-
return newMap;
|
|
636
|
-
});
|
|
637
|
-
}, [mcpServers, configQuery.data?.mcpServers, isServerSelected]);
|
|
638
|
-
// Initialize enabled skills from initialSkills prop
|
|
639
|
-
useEffect(() => {
|
|
640
|
-
if (initialSkills && initialSkills.length > 0) {
|
|
641
|
-
setEnabledSkills(new Set(initialSkills));
|
|
642
|
-
}
|
|
643
|
-
}, [initialSkills]);
|
|
644
|
-
// Helper to toggle MCP tool enabled state
|
|
645
|
-
const toggleMcpTool = useCallback((serverId, toolName) => {
|
|
646
|
-
setEnabledMcpTools(prev => {
|
|
647
|
-
const newMap = new Map(prev);
|
|
648
|
-
const serverTools = new Set(prev.get(serverId) || []);
|
|
649
|
-
if (serverTools.has(toolName)) {
|
|
650
|
-
serverTools.delete(toolName);
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
serverTools.add(toolName);
|
|
654
|
-
}
|
|
655
|
-
newMap.set(serverId, serverTools);
|
|
656
|
-
return newMap;
|
|
657
|
-
});
|
|
658
|
-
}, []);
|
|
659
|
-
// Helper to toggle all tools for a MCP server
|
|
660
|
-
const toggleAllMcpServerTools = useCallback((serverId, allToolNames, enable) => {
|
|
661
|
-
setEnabledMcpTools(prev => {
|
|
662
|
-
const newMap = new Map(prev);
|
|
663
|
-
if (enable) {
|
|
664
|
-
newMap.set(serverId, new Set(allToolNames));
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
newMap.set(serverId, new Set());
|
|
668
|
-
}
|
|
669
|
-
return newMap;
|
|
670
|
-
});
|
|
671
|
-
}, []);
|
|
672
|
-
// Helper to toggle skill enabled state
|
|
673
|
-
const toggleSkill = useCallback((skillId) => {
|
|
674
|
-
setEnabledSkills(prev => {
|
|
675
|
-
const newSet = new Set(prev);
|
|
676
|
-
if (newSet.has(skillId)) {
|
|
677
|
-
newSet.delete(skillId);
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
newSet.add(skillId);
|
|
681
|
-
}
|
|
682
|
-
return newSet;
|
|
683
|
-
});
|
|
684
|
-
}, []);
|
|
685
|
-
// Helper to toggle all skills
|
|
686
|
-
const toggleAllSkills = useCallback((allSkillIds, enable) => {
|
|
687
|
-
setEnabledSkills(enable ? new Set(allSkillIds) : new Set());
|
|
688
|
-
}, []);
|
|
689
|
-
// Get all enabled MCP tool names (for sending with requests)
|
|
690
|
-
// Only counts tools from servers that are in mcpServers (if provided)
|
|
691
|
-
const getEnabledMcpToolNames = useCallback(() => {
|
|
692
|
-
const toolNames = [];
|
|
693
|
-
enabledMcpTools.forEach((tools, serverId) => {
|
|
694
|
-
// Filter by mcpServers if provided
|
|
695
|
-
if (!mcpServers || mcpServers.some(s => s.id === serverId)) {
|
|
696
|
-
tools.forEach(toolName => toolNames.push(toolName));
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
return toolNames;
|
|
700
|
-
}, [enabledMcpTools, mcpServers]);
|
|
701
|
-
// Get all enabled skill IDs (for sending with requests)
|
|
702
|
-
const getEnabledSkillIds = useCallback(() => {
|
|
703
|
-
return Array.from(enabledSkills);
|
|
704
|
-
}, [enabledSkills]);
|
|
705
|
-
// Load messages from store on mount when useStoreMode is enabled
|
|
706
|
-
useEffect(() => {
|
|
707
|
-
if (useStoreMode) {
|
|
708
|
-
const storeMessages = useChatStore.getState().messages;
|
|
709
|
-
if (storeMessages.length > 0) {
|
|
710
|
-
setDisplayItems(storeMessages);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
}, [useStoreMode]);
|
|
714
|
-
// Track previous runtimeId to detect changes
|
|
715
|
-
const prevRuntimeIdRef = useRef(undefined);
|
|
716
|
-
// Clear displayItems and load messages when runtimeId changes
|
|
717
|
-
// This ensures each agent space has isolated conversation history
|
|
718
|
-
useEffect(() => {
|
|
719
|
-
// If runtimeId changed, clear displayItems first
|
|
720
|
-
if (runtimeId !== prevRuntimeIdRef.current) {
|
|
721
|
-
prevRuntimeIdRef.current = runtimeId;
|
|
722
|
-
// Clear current display items when switching runtime
|
|
723
|
-
setDisplayItems([]);
|
|
724
|
-
toolCallsRef.current.clear();
|
|
725
|
-
// If no runtimeId, nothing more to do
|
|
726
|
-
if (!runtimeId)
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
// runtimeId didn't change, skip
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
const store = useConversationStore.getState();
|
|
734
|
-
// Check if we need to fetch (not already fetched and not currently fetching)
|
|
735
|
-
if (!store.needsFetch(runtimeId)) {
|
|
736
|
-
// Already fetched - load from in-memory store
|
|
737
|
-
const storedMessages = store.getMessages(runtimeId);
|
|
738
|
-
if (storedMessages.length > 0) {
|
|
739
|
-
setDisplayItems(storedMessages);
|
|
740
|
-
}
|
|
741
|
-
setHistoryLoaded(true);
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
// Mark as fetching to prevent duplicate requests
|
|
745
|
-
store.setFetching(runtimeId, true);
|
|
746
|
-
// Build the history endpoint URL
|
|
747
|
-
let endpoint = historyEndpoint ||
|
|
748
|
-
(protocol?.endpoint ? `${protocol.endpoint}/api/v1/history` : null);
|
|
749
|
-
if (!endpoint) {
|
|
750
|
-
console.warn('[ChatBase] No history endpoint available for runtimeId:', runtimeId);
|
|
751
|
-
store.markFetched(runtimeId);
|
|
752
|
-
setHistoryLoaded(true);
|
|
753
|
-
return;
|
|
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
|
-
}
|
|
761
|
-
// Fetch conversation history from server
|
|
762
|
-
const fetchHistory = async () => {
|
|
763
|
-
try {
|
|
764
|
-
const authToken = historyAuthToken || protocol?.authToken;
|
|
765
|
-
const headers = {
|
|
766
|
-
'Content-Type': 'application/json',
|
|
767
|
-
};
|
|
768
|
-
if (authToken) {
|
|
769
|
-
headers['Authorization'] = `Bearer ${authToken}`;
|
|
770
|
-
}
|
|
771
|
-
const response = await fetch(endpoint, {
|
|
772
|
-
method: 'GET',
|
|
773
|
-
headers,
|
|
774
|
-
credentials: 'include',
|
|
775
|
-
});
|
|
776
|
-
if (!response.ok) {
|
|
777
|
-
throw new Error(`Failed to fetch history: ${response.status} ${response.statusText}`);
|
|
778
|
-
}
|
|
779
|
-
const data = await response.json();
|
|
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
|
|
812
|
-
if (messages.length > 0) {
|
|
813
|
-
store.setMessages(runtimeId, messages);
|
|
814
|
-
// Convert to display items: expand tool calls, merge tool results
|
|
815
|
-
const items = convertHistoryToDisplayItems(messages);
|
|
816
|
-
setDisplayItems(items);
|
|
817
|
-
}
|
|
818
|
-
store.markFetched(runtimeId);
|
|
819
|
-
setHistoryLoaded(true);
|
|
820
|
-
}
|
|
821
|
-
catch (err) {
|
|
822
|
-
console.error('[ChatBase] Failed to fetch conversation history:', err);
|
|
823
|
-
store.markFetched(runtimeId);
|
|
824
|
-
setHistoryLoaded(true);
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
fetchHistory();
|
|
828
|
-
}, [
|
|
829
|
-
runtimeId,
|
|
830
|
-
historyEndpoint,
|
|
831
|
-
historyAuthToken,
|
|
832
|
-
protocol?.endpoint,
|
|
833
|
-
protocol?.authToken,
|
|
834
|
-
]);
|
|
835
|
-
// Keep in-memory store in sync with displayItems (for session persistence)
|
|
836
|
-
useEffect(() => {
|
|
837
|
-
if (runtimeId && displayItems.length > 0) {
|
|
838
|
-
// Filter to only save ChatMessage items (not tool call items)
|
|
839
|
-
const messagesToSave = displayItems.filter((item) => !isToolCallMessage(item));
|
|
840
|
-
if (messagesToSave.length > 0) {
|
|
841
|
-
useConversationStore.getState().setMessages(runtimeId, messagesToSave);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}, [runtimeId, displayItems]);
|
|
845
|
-
// Derived state
|
|
846
|
-
const messages = displayItems.filter((item) => !isToolCallMessage(item));
|
|
847
|
-
const ready = true;
|
|
848
|
-
// Track previous message count to avoid unnecessary callbacks
|
|
849
|
-
const prevMessageCountRef = useRef(0);
|
|
850
|
-
// Notify parent when messages change (only when count actually changes)
|
|
851
|
-
useEffect(() => {
|
|
852
|
-
const currentCount = messages.length;
|
|
853
|
-
if (currentCount !== prevMessageCountRef.current) {
|
|
854
|
-
prevMessageCountRef.current = currentCount;
|
|
855
|
-
onMessagesChange?.(messages);
|
|
856
|
-
}
|
|
857
|
-
}, [displayItems, onMessagesChange]); // Use displayItems instead of messages to avoid infinite loop
|
|
858
|
-
// Padding based on compact mode
|
|
859
|
-
const padding = compact ? 2 : 3;
|
|
860
|
-
// Default avatar config
|
|
861
|
-
const defaultAvatarConfig = {
|
|
862
|
-
userAvatar: _jsx(PersonIcon, { size: 16 }),
|
|
863
|
-
assistantAvatar: _jsx(AiAgentIcon, { colored: true, size: 16 }),
|
|
864
|
-
showAvatars: true,
|
|
865
|
-
avatarSize: 32,
|
|
866
|
-
userAvatarBg: 'neutral.muted',
|
|
867
|
-
assistantAvatarBg: 'accent.emphasis',
|
|
868
|
-
...avatarConfig,
|
|
869
|
-
};
|
|
870
|
-
// Initialize protocol adapter
|
|
871
|
-
useEffect(() => {
|
|
872
|
-
if (!protocol)
|
|
873
|
-
return;
|
|
874
|
-
const adapter = createProtocolAdapter(protocol);
|
|
875
|
-
if (!adapter)
|
|
876
|
-
return;
|
|
877
|
-
adapterRef.current = adapter;
|
|
878
|
-
setAdapterReady(true);
|
|
879
|
-
// Subscribe to protocol events
|
|
880
|
-
unsubscribeRef.current = adapter.subscribe((event) => {
|
|
881
|
-
switch (event.type) {
|
|
882
|
-
case 'message':
|
|
883
|
-
// Update or create assistant message
|
|
884
|
-
if (event.message) {
|
|
885
|
-
const incomingId = event.message.id;
|
|
886
|
-
const currentId = currentAssistantMessageRef.current?.id;
|
|
887
|
-
const isNewMessage = !currentId || (incomingId && incomingId !== currentId);
|
|
888
|
-
if (currentAssistantMessageRef.current && !isNewMessage) {
|
|
889
|
-
// Update existing message (same ID)
|
|
890
|
-
setDisplayItems(prev => {
|
|
891
|
-
const newItems = [...prev];
|
|
892
|
-
const idx = newItems.findIndex(item => !isToolCallMessage(item) &&
|
|
893
|
-
item.id === currentAssistantMessageRef.current?.id);
|
|
894
|
-
if (idx >= 0 && !isToolCallMessage(newItems[idx])) {
|
|
895
|
-
newItems[idx] = {
|
|
896
|
-
...newItems[idx],
|
|
897
|
-
content: event.message?.content ?? '',
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
return newItems;
|
|
901
|
-
});
|
|
902
|
-
// Update message in store
|
|
903
|
-
if (useStoreMode && currentAssistantMessageRef.current) {
|
|
904
|
-
useChatStore
|
|
905
|
-
.getState()
|
|
906
|
-
.updateMessage(currentAssistantMessageRef.current.id, {
|
|
907
|
-
content: event.message?.content ?? '',
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
// Create new assistant message (new ID or first message)
|
|
913
|
-
const content = event.message.content;
|
|
914
|
-
const contentStr = typeof content === 'string' ? content : (content ?? '');
|
|
915
|
-
const newMessage = createAssistantMessage(typeof contentStr === 'string' ? contentStr : '');
|
|
916
|
-
newMessage.id = event.message.id || newMessage.id;
|
|
917
|
-
currentAssistantMessageRef.current = newMessage;
|
|
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)
|
|
935
|
-
if (useStoreMode) {
|
|
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
|
-
}
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
break;
|
|
951
|
-
case 'tool-call':
|
|
952
|
-
// Handle tool call start or update
|
|
953
|
-
if (event.toolCall) {
|
|
954
|
-
const toolCallId = event.toolCall.toolCallId || generateMessageId();
|
|
955
|
-
const toolName = event.toolCall.toolName;
|
|
956
|
-
const args = event.toolCall.args || {};
|
|
957
|
-
// Check if this tool call already exists (update args)
|
|
958
|
-
if (toolCallsRef.current.has(toolCallId)) {
|
|
959
|
-
const existingToolCall = toolCallsRef.current.get(toolCallId);
|
|
960
|
-
if (existingToolCall) {
|
|
961
|
-
// Merge new args with existing (update from TOOL_CALL_END)
|
|
962
|
-
const updatedToolCall = {
|
|
963
|
-
...existingToolCall,
|
|
964
|
-
args: {
|
|
965
|
-
...existingToolCall.args,
|
|
966
|
-
...args,
|
|
967
|
-
},
|
|
968
|
-
};
|
|
969
|
-
toolCallsRef.current.set(toolCallId, updatedToolCall);
|
|
970
|
-
// Always update displayItems (default ToolCallDisplay will be used if no custom renderer)
|
|
971
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) && item.toolCallId === toolCallId
|
|
972
|
-
? updatedToolCall
|
|
973
|
-
: item));
|
|
974
|
-
// Check if this is a frontend tool and we have complete args
|
|
975
|
-
// Execute the tool handler if available
|
|
976
|
-
const frontendTool = frontendTools?.find(t => t.name === toolName);
|
|
977
|
-
const toolHandler = frontendTool?.handler;
|
|
978
|
-
if (toolHandler && Object.keys(args).length > 0) {
|
|
979
|
-
// Execute frontend tool
|
|
980
|
-
pendingToolExecutionsRef.current++;
|
|
981
|
-
(async () => {
|
|
982
|
-
try {
|
|
983
|
-
const result = await toolHandler(updatedToolCall.args);
|
|
984
|
-
// Send result back to adapter
|
|
985
|
-
if (adapterRef.current) {
|
|
986
|
-
await adapterRef.current.sendToolResult(toolCallId, {
|
|
987
|
-
toolCallId,
|
|
988
|
-
success: true,
|
|
989
|
-
result,
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
// Update tool call with result
|
|
993
|
-
const completedToolCall = {
|
|
994
|
-
...updatedToolCall,
|
|
995
|
-
result,
|
|
996
|
-
status: 'complete',
|
|
997
|
-
};
|
|
998
|
-
toolCallsRef.current.set(toolCallId, completedToolCall);
|
|
999
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) &&
|
|
1000
|
-
item.toolCallId === toolCallId
|
|
1001
|
-
? completedToolCall
|
|
1002
|
-
: item));
|
|
1003
|
-
}
|
|
1004
|
-
catch (err) {
|
|
1005
|
-
console.error('[ChatBase] Frontend tool execution error:', err);
|
|
1006
|
-
const errorToolCall = {
|
|
1007
|
-
...updatedToolCall,
|
|
1008
|
-
status: 'error',
|
|
1009
|
-
error: err.message,
|
|
1010
|
-
};
|
|
1011
|
-
toolCallsRef.current.set(toolCallId, errorToolCall);
|
|
1012
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) &&
|
|
1013
|
-
item.toolCallId === toolCallId
|
|
1014
|
-
? errorToolCall
|
|
1015
|
-
: item));
|
|
1016
|
-
}
|
|
1017
|
-
finally {
|
|
1018
|
-
pendingToolExecutionsRef.current--;
|
|
1019
|
-
if (pendingToolExecutionsRef.current <= 0) {
|
|
1020
|
-
pendingToolExecutionsRef.current = 0;
|
|
1021
|
-
setIsLoading(false);
|
|
1022
|
-
setIsStreaming(false);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
})();
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
else {
|
|
1030
|
-
// New tool call - add it
|
|
1031
|
-
const toolCallMsg = {
|
|
1032
|
-
id: `tool-${toolCallId}`,
|
|
1033
|
-
type: 'tool-call',
|
|
1034
|
-
toolCallId,
|
|
1035
|
-
toolName,
|
|
1036
|
-
args,
|
|
1037
|
-
status: 'executing',
|
|
1038
|
-
};
|
|
1039
|
-
toolCallsRef.current.set(toolCallId, toolCallMsg);
|
|
1040
|
-
// Always add to displayItems (default ToolCallDisplay will be used if no custom renderer)
|
|
1041
|
-
setDisplayItems(prev => [...prev, toolCallMsg]);
|
|
1042
|
-
// Execute frontend tool if available and args are present
|
|
1043
|
-
const frontendTool = frontendTools?.find(t => t.name === toolName);
|
|
1044
|
-
const toolHandler = frontendTool?.handler;
|
|
1045
|
-
if (toolHandler && Object.keys(args).length > 0) {
|
|
1046
|
-
pendingToolExecutionsRef.current++;
|
|
1047
|
-
(async () => {
|
|
1048
|
-
try {
|
|
1049
|
-
const result = await toolHandler(args);
|
|
1050
|
-
// Send result back to adapter
|
|
1051
|
-
if (adapterRef.current) {
|
|
1052
|
-
await adapterRef.current.sendToolResult(toolCallId, {
|
|
1053
|
-
toolCallId,
|
|
1054
|
-
success: true,
|
|
1055
|
-
result,
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
// Update tool call with result
|
|
1059
|
-
const completedToolCall = {
|
|
1060
|
-
...toolCallMsg,
|
|
1061
|
-
result,
|
|
1062
|
-
status: 'complete',
|
|
1063
|
-
};
|
|
1064
|
-
toolCallsRef.current.set(toolCallId, completedToolCall);
|
|
1065
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) &&
|
|
1066
|
-
item.toolCallId === toolCallId
|
|
1067
|
-
? completedToolCall
|
|
1068
|
-
: item));
|
|
1069
|
-
}
|
|
1070
|
-
catch (err) {
|
|
1071
|
-
console.error('[ChatBase] Frontend tool execution error:', err);
|
|
1072
|
-
const errorToolCall = {
|
|
1073
|
-
...toolCallMsg,
|
|
1074
|
-
status: 'error',
|
|
1075
|
-
error: err.message,
|
|
1076
|
-
};
|
|
1077
|
-
toolCallsRef.current.set(toolCallId, errorToolCall);
|
|
1078
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) &&
|
|
1079
|
-
item.toolCallId === toolCallId
|
|
1080
|
-
? errorToolCall
|
|
1081
|
-
: item));
|
|
1082
|
-
}
|
|
1083
|
-
finally {
|
|
1084
|
-
pendingToolExecutionsRef.current--;
|
|
1085
|
-
if (pendingToolExecutionsRef.current <= 0) {
|
|
1086
|
-
pendingToolExecutionsRef.current = 0;
|
|
1087
|
-
setIsLoading(false);
|
|
1088
|
-
setIsStreaming(false);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
})();
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
break;
|
|
1096
|
-
case 'tool-result':
|
|
1097
|
-
// Handle tool result - always update status even without custom renderToolResult
|
|
1098
|
-
if (event.toolResult) {
|
|
1099
|
-
const toolCallId = event.toolResult.toolCallId;
|
|
1100
|
-
if (toolCallId && toolCallsRef.current.has(toolCallId)) {
|
|
1101
|
-
const existingToolCall = toolCallsRef.current.get(toolCallId);
|
|
1102
|
-
if (existingToolCall) {
|
|
1103
|
-
// Check if this is a human-in-the-loop tool (has steps in args)
|
|
1104
|
-
// If so, keep status as 'executing' until user responds
|
|
1105
|
-
const isHumanInTheLoop = existingToolCall.args &&
|
|
1106
|
-
'steps' in existingToolCall.args &&
|
|
1107
|
-
Array.isArray(existingToolCall.args.steps);
|
|
1108
|
-
// Extract rich error information from result if available
|
|
1109
|
-
const resultData = event.toolResult.result;
|
|
1110
|
-
let executionError;
|
|
1111
|
-
let codeError;
|
|
1112
|
-
let exitCode;
|
|
1113
|
-
let hasError = !!event.toolResult.error;
|
|
1114
|
-
if (resultData && typeof resultData === 'object') {
|
|
1115
|
-
// Check for execution_error (infrastructure/sandbox errors)
|
|
1116
|
-
if (resultData.execution_error &&
|
|
1117
|
-
typeof resultData.execution_error === 'string') {
|
|
1118
|
-
executionError = resultData.execution_error;
|
|
1119
|
-
hasError = true;
|
|
1120
|
-
}
|
|
1121
|
-
// Check for code_error (Python exceptions)
|
|
1122
|
-
if (resultData.code_error &&
|
|
1123
|
-
typeof resultData.code_error === 'object') {
|
|
1124
|
-
const ce = resultData.code_error;
|
|
1125
|
-
codeError = {
|
|
1126
|
-
name: ce.name || 'Error',
|
|
1127
|
-
value: ce.value || 'Unknown error',
|
|
1128
|
-
traceback: ce.traceback,
|
|
1129
|
-
};
|
|
1130
|
-
hasError = true;
|
|
1131
|
-
}
|
|
1132
|
-
// Check for exit_code (non-zero exit from sys.exit())
|
|
1133
|
-
if ('exit_code' in resultData) {
|
|
1134
|
-
const ec = resultData.exit_code;
|
|
1135
|
-
exitCode = typeof ec === 'number' ? ec : null;
|
|
1136
|
-
// Non-zero exit code counts as an error condition
|
|
1137
|
-
if (exitCode != null && exitCode !== 0) {
|
|
1138
|
-
hasError = true;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
// Check for execution_ok flag
|
|
1142
|
-
if ('execution_ok' in resultData &&
|
|
1143
|
-
resultData.execution_ok === false) {
|
|
1144
|
-
hasError = true;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
const updatedToolCall = {
|
|
1148
|
-
...existingToolCall,
|
|
1149
|
-
result: event.toolResult.result,
|
|
1150
|
-
// Keep executing for HITL, otherwise mark complete/error
|
|
1151
|
-
status: hasError
|
|
1152
|
-
? 'error'
|
|
1153
|
-
: isHumanInTheLoop
|
|
1154
|
-
? 'executing'
|
|
1155
|
-
: 'complete',
|
|
1156
|
-
error: event.toolResult.error,
|
|
1157
|
-
executionError,
|
|
1158
|
-
codeError,
|
|
1159
|
-
exitCode,
|
|
1160
|
-
};
|
|
1161
|
-
toolCallsRef.current.set(toolCallId, updatedToolCall);
|
|
1162
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) && item.toolCallId === toolCallId
|
|
1163
|
-
? updatedToolCall
|
|
1164
|
-
: item));
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
break;
|
|
1169
|
-
case 'state-update':
|
|
1170
|
-
onStateUpdate?.(event.data);
|
|
1171
|
-
// When we receive a state update, mark the last executing tool as complete
|
|
1172
|
-
// This handles tools that return state events (STATE_SNAPSHOT/STATE_DELTA) instead of TOOL_CALL_RESULT
|
|
1173
|
-
if (event.data) {
|
|
1174
|
-
// Find any tool calls that are still in 'executing' status
|
|
1175
|
-
const executingToolCalls = Array.from(toolCallsRef.current.entries()).filter(([_, tc]) => tc.status === 'executing');
|
|
1176
|
-
// Mark the most recent executing tool as complete
|
|
1177
|
-
if (executingToolCalls.length > 0) {
|
|
1178
|
-
const [lastToolCallId, existingToolCall] = executingToolCalls[executingToolCalls.length - 1];
|
|
1179
|
-
// Check if this is NOT a human-in-the-loop tool
|
|
1180
|
-
const isHumanInTheLoop = existingToolCall.args &&
|
|
1181
|
-
'steps' in existingToolCall.args &&
|
|
1182
|
-
Array.isArray(existingToolCall.args.steps);
|
|
1183
|
-
if (!isHumanInTheLoop) {
|
|
1184
|
-
// Extract result from state data if available
|
|
1185
|
-
const stateData = event.data;
|
|
1186
|
-
const result = stateData.weather ??
|
|
1187
|
-
stateData.result ??
|
|
1188
|
-
stateData.toolResult ??
|
|
1189
|
-
stateData;
|
|
1190
|
-
const updatedToolCall = {
|
|
1191
|
-
...existingToolCall,
|
|
1192
|
-
result,
|
|
1193
|
-
status: 'complete',
|
|
1194
|
-
};
|
|
1195
|
-
toolCallsRef.current.set(lastToolCallId, updatedToolCall);
|
|
1196
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) &&
|
|
1197
|
-
item.toolCallId === lastToolCallId
|
|
1198
|
-
? updatedToolCall
|
|
1199
|
-
: item));
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
break;
|
|
1204
|
-
case 'error':
|
|
1205
|
-
console.error('[ChatBase] Protocol error:', event.error);
|
|
1206
|
-
setError(event.error || new Error('Unknown error'));
|
|
1207
|
-
setIsLoading(false);
|
|
1208
|
-
setIsStreaming(false);
|
|
1209
|
-
break;
|
|
1210
|
-
}
|
|
1211
|
-
});
|
|
1212
|
-
// Connect to protocol
|
|
1213
|
-
adapter.connect().catch(console.error);
|
|
1214
|
-
return () => {
|
|
1215
|
-
unsubscribeRef.current?.();
|
|
1216
|
-
adapterRef.current?.disconnect();
|
|
1217
|
-
};
|
|
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
|
|
1222
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1223
|
-
}, [protocolKey, renderToolResult, onStateUpdate, useStoreMode]);
|
|
1224
|
-
// Auto-scroll to bottom
|
|
1225
|
-
useEffect(() => {
|
|
1226
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
1227
|
-
}, [displayItems]);
|
|
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)
|
|
1233
|
-
return;
|
|
1234
|
-
// Need either an adapter (protocol mode) or onSendMessage handler (custom mode)
|
|
1235
|
-
if (!adapterRef.current && !onSendMessage)
|
|
1236
|
-
return;
|
|
1237
|
-
const userMessage = createUserMessage(messageContent);
|
|
1238
|
-
const currentMessages = displayItems.filter((item) => !isToolCallMessage(item));
|
|
1239
|
-
const allMessages = [...currentMessages, userMessage];
|
|
1240
|
-
setDisplayItems(prev => [...prev, userMessage]);
|
|
1241
|
-
setInput('');
|
|
1242
|
-
setIsLoading(true);
|
|
1243
|
-
setIsStreaming(true);
|
|
1244
|
-
setError(null);
|
|
1245
|
-
currentAssistantMessageRef.current = null;
|
|
1246
|
-
// Persist user message to store if enabled
|
|
1247
|
-
if (useStoreMode) {
|
|
1248
|
-
useChatStore.getState().addMessage(userMessage);
|
|
1249
|
-
}
|
|
1250
|
-
try {
|
|
1251
|
-
if (onSendMessage) {
|
|
1252
|
-
// Custom mode: use the provided message handler
|
|
1253
|
-
if (enableStreaming) {
|
|
1254
|
-
// Streaming mode: create assistant message placeholder and stream updates
|
|
1255
|
-
const assistantMessageId = generateMessageId();
|
|
1256
|
-
const assistantMessage = createAssistantMessage('');
|
|
1257
|
-
assistantMessage.id = assistantMessageId;
|
|
1258
|
-
setDisplayItems(prev => [...prev, assistantMessage]);
|
|
1259
|
-
currentAssistantMessageRef.current = assistantMessage;
|
|
1260
|
-
if (useStoreMode) {
|
|
1261
|
-
useChatStore.getState().addMessage(assistantMessage);
|
|
1262
|
-
useChatStore.getState().startStreaming(assistantMessageId);
|
|
1263
|
-
}
|
|
1264
|
-
// Create abort controller for cancellation
|
|
1265
|
-
abortControllerRef.current = new AbortController();
|
|
1266
|
-
await onSendMessage(messageContent, allMessages, {
|
|
1267
|
-
onChunk: (chunk) => {
|
|
1268
|
-
// Append chunk to the assistant message
|
|
1269
|
-
setDisplayItems(prev => prev.map(item => item.id === assistantMessageId
|
|
1270
|
-
? {
|
|
1271
|
-
...item,
|
|
1272
|
-
content: item.content + chunk,
|
|
1273
|
-
}
|
|
1274
|
-
: item));
|
|
1275
|
-
if (useStoreMode) {
|
|
1276
|
-
useChatStore
|
|
1277
|
-
.getState()
|
|
1278
|
-
.appendToStream(assistantMessageId, chunk);
|
|
1279
|
-
}
|
|
1280
|
-
},
|
|
1281
|
-
onComplete: (fullResponse) => {
|
|
1282
|
-
// Update assistant message with final content
|
|
1283
|
-
setDisplayItems(prev => prev.map(item => item.id === assistantMessageId
|
|
1284
|
-
? { ...item, content: fullResponse }
|
|
1285
|
-
: item));
|
|
1286
|
-
if (useStoreMode) {
|
|
1287
|
-
useChatStore.getState().updateMessage(assistantMessageId, {
|
|
1288
|
-
content: fullResponse,
|
|
1289
|
-
});
|
|
1290
|
-
useChatStore.getState().stopStreaming();
|
|
1291
|
-
}
|
|
1292
|
-
},
|
|
1293
|
-
onError: (error) => {
|
|
1294
|
-
// Update assistant message with error
|
|
1295
|
-
const errorContent = `Error: ${error.message}`;
|
|
1296
|
-
setDisplayItems(prev => prev.map(item => item.id === assistantMessageId
|
|
1297
|
-
? { ...item, content: errorContent }
|
|
1298
|
-
: item));
|
|
1299
|
-
if (useStoreMode) {
|
|
1300
|
-
useChatStore.getState().updateMessage(assistantMessageId, {
|
|
1301
|
-
content: errorContent,
|
|
1302
|
-
});
|
|
1303
|
-
useChatStore.getState().stopStreaming();
|
|
1304
|
-
}
|
|
1305
|
-
setError(error);
|
|
1306
|
-
},
|
|
1307
|
-
signal: abortControllerRef.current.signal,
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
// Non-streaming mode: wait for full response
|
|
1312
|
-
const response = await onSendMessage(messageContent, allMessages);
|
|
1313
|
-
if (response) {
|
|
1314
|
-
const assistantMessage = createAssistantMessage(response);
|
|
1315
|
-
setDisplayItems(prev => [...prev, assistantMessage]);
|
|
1316
|
-
if (useStoreMode) {
|
|
1317
|
-
useChatStore.getState().addMessage(assistantMessage);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
else if (adapterRef.current) {
|
|
1323
|
-
// Protocol mode: use the adapter
|
|
1324
|
-
// Convert frontend tools to AG-UI format (only serializable properties)
|
|
1325
|
-
const toolsForRequest = (frontendTools || []).map(tool => ({
|
|
1326
|
-
name: tool.name,
|
|
1327
|
-
description: tool.description,
|
|
1328
|
-
parameters: tool.parameters || { type: 'object', properties: {} },
|
|
1329
|
-
}));
|
|
1330
|
-
// Get enabled MCP tool names and skill IDs
|
|
1331
|
-
const enabledMcpToolNames = getEnabledMcpToolNames();
|
|
1332
|
-
const enabledSkillIds = getEnabledSkillIds();
|
|
1333
|
-
await adapterRef.current.sendMessage(userMessage, {
|
|
1334
|
-
threadId: threadIdRef.current,
|
|
1335
|
-
messages: allMessages,
|
|
1336
|
-
...(selectedModel && { model: selectedModel }),
|
|
1337
|
-
tools: toolsForRequest,
|
|
1338
|
-
// Include enabled MCP tools as builtin_tools for backend
|
|
1339
|
-
builtinTools: enabledMcpToolNames,
|
|
1340
|
-
// Include enabled skills for backend
|
|
1341
|
-
skills: enabledSkillIds,
|
|
1342
|
-
// Include connected identities with access tokens (use ref to avoid infinite loops)
|
|
1343
|
-
identities: connectedIdentitiesRef.current,
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
catch (err) {
|
|
1348
|
-
if (err.name !== 'AbortError') {
|
|
1349
|
-
console.error('[ChatBase] Send error:', err);
|
|
1350
|
-
const errorMessage = createAssistantMessage(`Error: ${err.message}`);
|
|
1351
|
-
setDisplayItems(prev => [...prev, errorMessage]);
|
|
1352
|
-
setError(err);
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
finally {
|
|
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
|
-
}
|
|
1363
|
-
currentAssistantMessageRef.current = null;
|
|
1364
|
-
abortControllerRef.current = null;
|
|
1365
|
-
}
|
|
1366
|
-
}, [
|
|
1367
|
-
input,
|
|
1368
|
-
isLoading,
|
|
1369
|
-
displayItems,
|
|
1370
|
-
selectedModel,
|
|
1371
|
-
frontendTools,
|
|
1372
|
-
useStoreMode,
|
|
1373
|
-
onSendMessage,
|
|
1374
|
-
enableStreaming,
|
|
1375
|
-
getEnabledMcpToolNames,
|
|
1376
|
-
getEnabledSkillIds,
|
|
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]);
|
|
1391
|
-
// Handle stop
|
|
1392
|
-
const handleStop = useCallback(() => {
|
|
1393
|
-
// Abort custom mode request
|
|
1394
|
-
abortControllerRef.current?.abort();
|
|
1395
|
-
// Disconnect protocol adapter
|
|
1396
|
-
adapterRef.current?.disconnect();
|
|
1397
|
-
// Stop streaming in store
|
|
1398
|
-
if (useStoreMode) {
|
|
1399
|
-
useChatStore.getState().stopStreaming();
|
|
1400
|
-
}
|
|
1401
|
-
// Reset pending tool counter so loading can clear
|
|
1402
|
-
pendingToolExecutionsRef.current = 0;
|
|
1403
|
-
setIsLoading(false);
|
|
1404
|
-
setIsStreaming(false);
|
|
1405
|
-
}, [useStoreMode]);
|
|
1406
|
-
// Handle new chat
|
|
1407
|
-
const handleNewChat = useCallback(() => {
|
|
1408
|
-
setDisplayItems([]);
|
|
1409
|
-
toolCallsRef.current.clear();
|
|
1410
|
-
pendingToolExecutionsRef.current = 0;
|
|
1411
|
-
setInput('');
|
|
1412
|
-
threadIdRef.current = generateMessageId();
|
|
1413
|
-
if (useStoreMode) {
|
|
1414
|
-
clearStoreMessages();
|
|
1415
|
-
}
|
|
1416
|
-
// Clear from conversation store if runtimeId is provided
|
|
1417
|
-
if (runtimeId) {
|
|
1418
|
-
useConversationStore.getState().clearMessages(runtimeId);
|
|
1419
|
-
}
|
|
1420
|
-
onNewChat?.();
|
|
1421
|
-
headerButtons?.onNewChat?.();
|
|
1422
|
-
}, [clearStoreMessages, onNewChat, headerButtons, useStoreMode, runtimeId]);
|
|
1423
|
-
// Handle clear
|
|
1424
|
-
const handleClear = useCallback(() => {
|
|
1425
|
-
if (window.confirm('Clear all messages?')) {
|
|
1426
|
-
setDisplayItems([]);
|
|
1427
|
-
toolCallsRef.current.clear();
|
|
1428
|
-
if (useStoreMode) {
|
|
1429
|
-
clearStoreMessages();
|
|
1430
|
-
}
|
|
1431
|
-
// Clear from conversation store if runtimeId is provided
|
|
1432
|
-
if (runtimeId) {
|
|
1433
|
-
useConversationStore.getState().clearMessages(runtimeId);
|
|
1434
|
-
}
|
|
1435
|
-
onClear?.();
|
|
1436
|
-
headerButtons?.onClear?.();
|
|
1437
|
-
}
|
|
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]);
|
|
1457
|
-
// Not ready yet (store mode only)
|
|
1458
|
-
if (!ready) {
|
|
1459
|
-
return (_jsx(Box, { className: className, sx: {
|
|
1460
|
-
display: 'flex',
|
|
1461
|
-
flexDirection: 'column',
|
|
1462
|
-
alignItems: 'center',
|
|
1463
|
-
justifyContent: 'center',
|
|
1464
|
-
height: '100%',
|
|
1465
|
-
p: 4,
|
|
1466
|
-
borderRadius: borderRadius,
|
|
1467
|
-
bg: backgroundColor || 'canvas.default',
|
|
1468
|
-
border: border,
|
|
1469
|
-
boxShadow: boxShadow,
|
|
1470
|
-
}, children: loadingState || (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { mt: 3, color: 'fg.muted' }, children: "Initializing chat..." })] })) }));
|
|
1471
|
-
}
|
|
1472
|
-
// Render header with buttons
|
|
1473
|
-
const renderHeader = () => {
|
|
1474
|
-
if (!showHeader)
|
|
1475
|
-
return null;
|
|
1476
|
-
return (_jsx(Box, { sx: {
|
|
1477
|
-
display: 'flex',
|
|
1478
|
-
flexDirection: 'column',
|
|
1479
|
-
borderBottom: '1px solid',
|
|
1480
|
-
borderColor: 'border.default',
|
|
1481
|
-
}, children: _jsxs(Box, { sx: {
|
|
1482
|
-
display: 'flex',
|
|
1483
|
-
alignItems: 'center',
|
|
1484
|
-
justifyContent: 'space-between',
|
|
1485
|
-
p: padding,
|
|
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] })] }) }));
|
|
1540
|
-
};
|
|
1541
|
-
// Render token usage bar between input and selectors
|
|
1542
|
-
const renderTokenUsage = () => {
|
|
1543
|
-
if (!showTokenUsage)
|
|
1544
|
-
return null;
|
|
1545
|
-
// Show bar when we have any context data (totalTokens > 0 means agent is active)
|
|
1546
|
-
const hasContext = agentUsage && !agentUsage.error && agentUsage.totalTokens > 0;
|
|
1547
|
-
const hasTurn = agentUsage?.turnUsage &&
|
|
1548
|
-
(agentUsage.turnUsage.inputTokens > 0 ||
|
|
1549
|
-
agentUsage.turnUsage.outputTokens > 0);
|
|
1550
|
-
const hasSession = agentUsage?.sessionUsage &&
|
|
1551
|
-
(agentUsage.sessionUsage.inputTokens > 0 ||
|
|
1552
|
-
agentUsage.sessionUsage.outputTokens > 0);
|
|
1553
|
-
if (!hasContext)
|
|
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
|
-
};
|
|
1658
|
-
return (_jsxs(Box, { sx: {
|
|
1659
|
-
display: 'flex',
|
|
1660
|
-
alignItems: 'center',
|
|
1661
|
-
justifyContent: 'flex-start',
|
|
1662
|
-
gap: 2,
|
|
1663
|
-
py: 1,
|
|
1664
|
-
px: padding,
|
|
1665
|
-
bg: 'canvas.subtle',
|
|
1666
|
-
flexWrap: 'nowrap',
|
|
1667
|
-
overflow: 'visible',
|
|
1668
|
-
whiteSpace: 'nowrap',
|
|
1669
|
-
minWidth: 0,
|
|
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: '▼' })] }))] }));
|
|
1737
|
-
};
|
|
1738
|
-
// Render empty state
|
|
1739
|
-
const renderEmptyState = () => {
|
|
1740
|
-
if (emptyState?.render) {
|
|
1741
|
-
return emptyState.render();
|
|
1742
|
-
}
|
|
1743
|
-
// Handler for suggestion clicks
|
|
1744
|
-
const handleSuggestionClick = (suggestion) => {
|
|
1745
|
-
if (submitOnSuggestionClick) {
|
|
1746
|
-
// Auto-submit the suggestion message
|
|
1747
|
-
const userMessage = {
|
|
1748
|
-
id: generateMessageId(),
|
|
1749
|
-
role: 'user',
|
|
1750
|
-
content: suggestion.message,
|
|
1751
|
-
createdAt: new Date(),
|
|
1752
|
-
};
|
|
1753
|
-
setDisplayItems(prev => [...prev, userMessage]);
|
|
1754
|
-
setIsLoading(true);
|
|
1755
|
-
setIsStreaming(true);
|
|
1756
|
-
// Convert frontend tools to AG-UI format (same as regular message send)
|
|
1757
|
-
const toolsForSuggestion = (frontendTools || []).map(tool => ({
|
|
1758
|
-
name: tool.name,
|
|
1759
|
-
description: tool.description,
|
|
1760
|
-
parameters: tool.parameters || { type: 'object', properties: {} },
|
|
1761
|
-
}));
|
|
1762
|
-
adapterRef.current
|
|
1763
|
-
?.sendMessage(userMessage, {
|
|
1764
|
-
threadId: threadIdRef.current,
|
|
1765
|
-
messages: [userMessage],
|
|
1766
|
-
tools: toolsForSuggestion,
|
|
1767
|
-
})
|
|
1768
|
-
.catch(err => {
|
|
1769
|
-
console.error('[ChatBase] Suggestion send error:', err);
|
|
1770
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1771
|
-
})
|
|
1772
|
-
.finally(() => {
|
|
1773
|
-
setIsLoading(false);
|
|
1774
|
-
setIsStreaming(false);
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
else {
|
|
1778
|
-
// Just fill the input without submitting
|
|
1779
|
-
setInput(suggestion.message);
|
|
1780
|
-
setTimeout(() => {
|
|
1781
|
-
inputRef.current?.focus();
|
|
1782
|
-
}, 0);
|
|
1783
|
-
}
|
|
1784
|
-
};
|
|
1785
|
-
return (_jsxs(Box, { sx: {
|
|
1786
|
-
display: 'flex',
|
|
1787
|
-
flexDirection: 'column',
|
|
1788
|
-
alignItems: 'center',
|
|
1789
|
-
justifyContent: 'center',
|
|
1790
|
-
height: '100%',
|
|
1791
|
-
p: 4,
|
|
1792
|
-
color: 'fg.muted',
|
|
1793
|
-
textAlign: 'center',
|
|
1794
|
-
gap: 2,
|
|
1795
|
-
}, children: [emptyState?.icon || brandIcon || _jsx(AiAgentIcon, { colored: true, size: 48 }), _jsx(Text, { sx: { fontSize: 2 }, children: emptyState?.title || 'Start a conversation' }), (emptyState?.subtitle || description) && (_jsx(Text, { sx: { fontSize: 1 }, children: emptyState?.subtitle || description })), suggestions && suggestions.length > 0 && (_jsx(LabelGroup, { sx: { mt: 2, justifyContent: 'center' }, children: suggestions.map((suggestion, index) => (_jsx(Label, { variant: "accent", sx: {
|
|
1796
|
-
cursor: 'pointer',
|
|
1797
|
-
'&:hover': {
|
|
1798
|
-
bg: 'accent.muted',
|
|
1799
|
-
},
|
|
1800
|
-
}, onClick: () => handleSuggestionClick(suggestion), children: suggestion.title }, index))) }))] }));
|
|
1801
|
-
};
|
|
1802
|
-
// Render protocol mode messages with tool call support
|
|
1803
|
-
const renderProtocolMessages = () => {
|
|
1804
|
-
if (displayItems.length === 0) {
|
|
1805
|
-
return renderEmptyState();
|
|
1806
|
-
}
|
|
1807
|
-
// Create respond callback for a tool call (human-in-the-loop)
|
|
1808
|
-
const createRespondCallback = (toolCallId) => {
|
|
1809
|
-
return async (result) => {
|
|
1810
|
-
// Update tool call status to complete with the user's response
|
|
1811
|
-
const existingToolCall = toolCallsRef.current.get(toolCallId);
|
|
1812
|
-
if (existingToolCall && existingToolCall.status === 'executing') {
|
|
1813
|
-
const updatedToolCall = {
|
|
1814
|
-
...existingToolCall,
|
|
1815
|
-
result,
|
|
1816
|
-
status: 'complete',
|
|
1817
|
-
};
|
|
1818
|
-
toolCallsRef.current.set(toolCallId, updatedToolCall);
|
|
1819
|
-
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) && item.toolCallId === toolCallId
|
|
1820
|
-
? updatedToolCall
|
|
1821
|
-
: item));
|
|
1822
|
-
// Send the user's response back to the agent as a new message
|
|
1823
|
-
// This continues the conversation with the HITL response
|
|
1824
|
-
if (adapterRef.current) {
|
|
1825
|
-
// Format the response as a clear text message the agent can understand
|
|
1826
|
-
let responseText;
|
|
1827
|
-
if (typeof result === 'string') {
|
|
1828
|
-
responseText = result;
|
|
1829
|
-
}
|
|
1830
|
-
else if (result &&
|
|
1831
|
-
typeof result === 'object' &&
|
|
1832
|
-
'accepted' in result) {
|
|
1833
|
-
const hitlResult = result;
|
|
1834
|
-
if (hitlResult.accepted) {
|
|
1835
|
-
const stepDescriptions = hitlResult.steps?.map(s => s.description).join(', ') || '';
|
|
1836
|
-
responseText = stepDescriptions
|
|
1837
|
-
? `I confirm and approve the following steps: ${stepDescriptions}`
|
|
1838
|
-
: 'I confirm and approve the plan.';
|
|
1839
|
-
}
|
|
1840
|
-
else {
|
|
1841
|
-
responseText =
|
|
1842
|
-
'I reject this plan. Please suggest something else.';
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
else {
|
|
1846
|
-
responseText = JSON.stringify(result, null, 2);
|
|
1847
|
-
}
|
|
1848
|
-
// Create a user message with the HITL response
|
|
1849
|
-
const userMessage = {
|
|
1850
|
-
id: generateMessageId(),
|
|
1851
|
-
role: 'user',
|
|
1852
|
-
content: responseText,
|
|
1853
|
-
createdAt: new Date(),
|
|
1854
|
-
};
|
|
1855
|
-
setIsLoading(true);
|
|
1856
|
-
setIsStreaming(true);
|
|
1857
|
-
try {
|
|
1858
|
-
// Get all chat messages for context
|
|
1859
|
-
const allMessages = displayItems.filter((item) => !isToolCallMessage(item));
|
|
1860
|
-
await adapterRef.current.sendMessage(userMessage, {
|
|
1861
|
-
threadId: threadIdRef.current,
|
|
1862
|
-
messages: [...allMessages, userMessage],
|
|
1863
|
-
});
|
|
1864
|
-
}
|
|
1865
|
-
catch (err) {
|
|
1866
|
-
console.error('[ChatBase] HITL respond error:', err);
|
|
1867
|
-
}
|
|
1868
|
-
finally {
|
|
1869
|
-
setIsLoading(false);
|
|
1870
|
-
setIsStreaming(false);
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
};
|
|
1875
|
-
};
|
|
1876
|
-
// Check if there are tool calls being rendered
|
|
1877
|
-
// This is used to hide duplicate assistant text when UI is shown
|
|
1878
|
-
const renderedToolCallIds = new Set();
|
|
1879
|
-
displayItems.forEach(item => {
|
|
1880
|
-
if (isToolCallMessage(item)) {
|
|
1881
|
-
if (renderToolResult) {
|
|
1882
|
-
// Check if custom renderer produces a rendered UI
|
|
1883
|
-
const rendered = renderToolResult({
|
|
1884
|
-
toolCallId: item.toolCallId,
|
|
1885
|
-
toolName: item.toolName,
|
|
1886
|
-
name: item.toolName,
|
|
1887
|
-
args: item.args,
|
|
1888
|
-
result: item.result,
|
|
1889
|
-
status: item.status,
|
|
1890
|
-
error: item.error,
|
|
1891
|
-
});
|
|
1892
|
-
if (rendered !== null && rendered !== undefined) {
|
|
1893
|
-
renderedToolCallIds.add(item.toolCallId);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
else {
|
|
1897
|
-
// Default display always renders tool calls
|
|
1898
|
-
renderedToolCallIds.add(item.toolCallId);
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
const hasRenderedToolCall = renderedToolCallIds.size > 0;
|
|
1903
|
-
return (_jsxs(_Fragment, { children: [displayItems.map((item, index) => {
|
|
1904
|
-
// Render tool call
|
|
1905
|
-
if (isToolCallMessage(item)) {
|
|
1906
|
-
// Only provide respond callback when status is 'executing'
|
|
1907
|
-
const respond = item.status === 'executing'
|
|
1908
|
-
? createRespondCallback(item.toolCallId)
|
|
1909
|
-
: undefined;
|
|
1910
|
-
// Use custom renderToolResult if provided, otherwise use default display
|
|
1911
|
-
const toolUI = renderToolResult ? (renderToolResult({
|
|
1912
|
-
toolCallId: item.toolCallId,
|
|
1913
|
-
toolName: item.toolName,
|
|
1914
|
-
name: item.toolName,
|
|
1915
|
-
args: item.args,
|
|
1916
|
-
result: item.result,
|
|
1917
|
-
status: item.status,
|
|
1918
|
-
error: item.error,
|
|
1919
|
-
respond,
|
|
1920
|
-
})) : (_jsx(ToolCallDisplay, { toolCallId: item.toolCallId, toolName: item.toolName, args: item.args, result: item.result, status: item.status, error: item.error, executionError: item.executionError, codeError: item.codeError, exitCode: item.exitCode }));
|
|
1921
|
-
// Skip if custom render returns null/undefined
|
|
1922
|
-
if (toolUI === null || toolUI === undefined)
|
|
1923
|
-
return null;
|
|
1924
|
-
return (_jsx(Box, { sx: {
|
|
1925
|
-
display: 'flex',
|
|
1926
|
-
flexDirection: 'column',
|
|
1927
|
-
alignItems: 'flex-start',
|
|
1928
|
-
maxWidth: '95%',
|
|
1929
|
-
px: padding,
|
|
1930
|
-
py: 1,
|
|
1931
|
-
}, children: toolUI }, item.id));
|
|
1932
|
-
}
|
|
1933
|
-
// Render regular chat message
|
|
1934
|
-
const message = item;
|
|
1935
|
-
const isUser = message.role === 'user';
|
|
1936
|
-
// Skip assistant messages when hideMessagesAfterToolUI is enabled and there's a rendered tool call
|
|
1937
|
-
if (!isUser && hideMessagesAfterToolUI && hasRenderedToolCall) {
|
|
1938
|
-
// Get message text safely
|
|
1939
|
-
const messageText = getMessageText(message);
|
|
1940
|
-
// Check if this assistant message follows a rendered tool call
|
|
1941
|
-
const prevIndex = index - 1;
|
|
1942
|
-
if (prevIndex >= 0) {
|
|
1943
|
-
const prevItem = displayItems[prevIndex];
|
|
1944
|
-
if (isToolCallMessage(prevItem) &&
|
|
1945
|
-
renderedToolCallIds.has(prevItem.toolCallId)) {
|
|
1946
|
-
// Skip this assistant message as it describes the tool result
|
|
1947
|
-
return null;
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
// Also check for HITL-specific patterns (step descriptions)
|
|
1951
|
-
const hitlToolCall = displayItems.find(item => isToolCallMessage(item) &&
|
|
1952
|
-
renderedToolCallIds.has(item.toolCallId) &&
|
|
1953
|
-
item.args &&
|
|
1954
|
-
'steps' in item.args &&
|
|
1955
|
-
Array.isArray(item.args.steps));
|
|
1956
|
-
if (hitlToolCall && messageText) {
|
|
1957
|
-
const steps = hitlToolCall.args.steps;
|
|
1958
|
-
// Check if message contains step descriptions or step-like patterns
|
|
1959
|
-
const hasStepContent = steps.some(step => step.description &&
|
|
1960
|
-
messageText
|
|
1961
|
-
.toLowerCase()
|
|
1962
|
-
.includes(step.description.toLowerCase().slice(0, 20))) ||
|
|
1963
|
-
// Also hide if message contains numbered list patterns
|
|
1964
|
-
/^\s*(\d+\.\s|[-*]\s|\*\*)/.test(messageText) ||
|
|
1965
|
-
messageText.includes('**Enabled**') ||
|
|
1966
|
-
messageText.includes('steps below');
|
|
1967
|
-
if (hasStepContent) {
|
|
1968
|
-
return null;
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
return (_jsx(Box, { sx: {
|
|
1973
|
-
display: 'flex',
|
|
1974
|
-
flexDirection: 'column',
|
|
1975
|
-
alignItems: isUser ? 'flex-end' : 'flex-start',
|
|
1976
|
-
px: padding,
|
|
1977
|
-
py: 1,
|
|
1978
|
-
}, children: _jsxs(Box, { sx: {
|
|
1979
|
-
display: 'flex',
|
|
1980
|
-
gap: 2,
|
|
1981
|
-
flexDirection: isUser ? 'row-reverse' : 'row',
|
|
1982
|
-
alignItems: 'flex-start',
|
|
1983
|
-
}, children: [defaultAvatarConfig.showAvatars && (_jsx(Box, { sx: {
|
|
1984
|
-
width: defaultAvatarConfig.avatarSize,
|
|
1985
|
-
height: defaultAvatarConfig.avatarSize,
|
|
1986
|
-
borderRadius: '50%',
|
|
1987
|
-
bg: isUser
|
|
1988
|
-
? defaultAvatarConfig.userAvatarBg
|
|
1989
|
-
: defaultAvatarConfig.assistantAvatarBg,
|
|
1990
|
-
color: isUser ? 'fg.default' : 'fg.onEmphasis',
|
|
1991
|
-
display: 'flex',
|
|
1992
|
-
alignItems: 'center',
|
|
1993
|
-
justifyContent: 'center',
|
|
1994
|
-
flexShrink: 0,
|
|
1995
|
-
}, children: isUser
|
|
1996
|
-
? defaultAvatarConfig.userAvatar
|
|
1997
|
-
: defaultAvatarConfig.assistantAvatar })), _jsx(Box, { sx: {
|
|
1998
|
-
maxWidth: '85%',
|
|
1999
|
-
p: 2,
|
|
2000
|
-
borderRadius: 2,
|
|
2001
|
-
backgroundColor: isUser
|
|
2002
|
-
? 'accent.emphasis'
|
|
2003
|
-
: 'canvas.subtle',
|
|
2004
|
-
color: isUser ? 'fg.onEmphasis' : 'fg.default',
|
|
2005
|
-
...streamdownCodeBlockStyles,
|
|
2006
|
-
}, children: isUser ? (_jsx(Text, { sx: {
|
|
2007
|
-
fontSize: 1,
|
|
2008
|
-
whiteSpace: 'pre-wrap',
|
|
2009
|
-
wordBreak: 'break-word',
|
|
2010
|
-
}, children: getMessageText(message) })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: getMessageText(message) || (isStreaming ? '...' : '') }) })) })] }) }, message.id));
|
|
2011
|
-
}), showLoadingIndicator && (isLoading || isStreaming) && (_jsx(Box, { sx: {
|
|
2012
|
-
display: 'flex',
|
|
2013
|
-
alignItems: 'flex-start',
|
|
2014
|
-
px: padding,
|
|
2015
|
-
py: 1,
|
|
2016
|
-
}, children: _jsxs(Box, { sx: {
|
|
2017
|
-
display: 'flex',
|
|
2018
|
-
gap: 2,
|
|
2019
|
-
alignItems: 'flex-start',
|
|
2020
|
-
}, children: [defaultAvatarConfig.showAvatars && (_jsx(Box, { sx: {
|
|
2021
|
-
width: defaultAvatarConfig.avatarSize,
|
|
2022
|
-
height: defaultAvatarConfig.avatarSize,
|
|
2023
|
-
borderRadius: '50%',
|
|
2024
|
-
bg: defaultAvatarConfig.assistantAvatarBg,
|
|
2025
|
-
color: 'fg.onEmphasis',
|
|
2026
|
-
display: 'flex',
|
|
2027
|
-
alignItems: 'center',
|
|
2028
|
-
justifyContent: 'center',
|
|
2029
|
-
flexShrink: 0,
|
|
2030
|
-
}, children: defaultAvatarConfig.assistantAvatar })), _jsxs(Box, { sx: {
|
|
2031
|
-
display: 'flex',
|
|
2032
|
-
alignItems: 'center',
|
|
2033
|
-
gap: '4px',
|
|
2034
|
-
p: 2,
|
|
2035
|
-
borderRadius: 2,
|
|
2036
|
-
bg: 'canvas.subtle',
|
|
2037
|
-
minHeight: '32px',
|
|
2038
|
-
}, children: [_jsx(Box, { sx: {
|
|
2039
|
-
width: '8px',
|
|
2040
|
-
height: '8px',
|
|
2041
|
-
borderRadius: '50%',
|
|
2042
|
-
bg: 'fg.muted',
|
|
2043
|
-
animation: 'typingPulse 1.4s ease-in-out infinite',
|
|
2044
|
-
'@keyframes typingPulse': {
|
|
2045
|
-
'0%, 60%, 100%': {
|
|
2046
|
-
transform: 'scale(0.6)',
|
|
2047
|
-
opacity: 0.4,
|
|
2048
|
-
},
|
|
2049
|
-
'30%': {
|
|
2050
|
-
transform: 'scale(1)',
|
|
2051
|
-
opacity: 1,
|
|
2052
|
-
},
|
|
2053
|
-
},
|
|
2054
|
-
} }), _jsx(Box, { sx: {
|
|
2055
|
-
width: '8px',
|
|
2056
|
-
height: '8px',
|
|
2057
|
-
borderRadius: '50%',
|
|
2058
|
-
bg: 'fg.muted',
|
|
2059
|
-
animation: 'typingPulse 1.4s ease-in-out infinite',
|
|
2060
|
-
animationDelay: '0.2s',
|
|
2061
|
-
'@keyframes typingPulse': {
|
|
2062
|
-
'0%, 60%, 100%': {
|
|
2063
|
-
transform: 'scale(0.6)',
|
|
2064
|
-
opacity: 0.4,
|
|
2065
|
-
},
|
|
2066
|
-
'30%': {
|
|
2067
|
-
transform: 'scale(1)',
|
|
2068
|
-
opacity: 1,
|
|
2069
|
-
},
|
|
2070
|
-
},
|
|
2071
|
-
} }), _jsx(Box, { sx: {
|
|
2072
|
-
width: '8px',
|
|
2073
|
-
height: '8px',
|
|
2074
|
-
borderRadius: '50%',
|
|
2075
|
-
bg: 'fg.muted',
|
|
2076
|
-
animation: 'typingPulse 1.4s ease-in-out infinite',
|
|
2077
|
-
animationDelay: '0.4s',
|
|
2078
|
-
'@keyframes typingPulse': {
|
|
2079
|
-
'0%, 60%, 100%': {
|
|
2080
|
-
transform: 'scale(0.6)',
|
|
2081
|
-
opacity: 0.4,
|
|
2082
|
-
},
|
|
2083
|
-
'30%': {
|
|
2084
|
-
transform: 'scale(1)',
|
|
2085
|
-
opacity: 1,
|
|
2086
|
-
},
|
|
2087
|
-
},
|
|
2088
|
-
} })] })] }) })), _jsx("div", { ref: messagesEndRef })] }));
|
|
2089
|
-
};
|
|
2090
|
-
// Render input prompt
|
|
2091
|
-
const renderInputPrompt = () => {
|
|
2092
|
-
const availableTools = configQuery.data?.builtinTools || [];
|
|
2093
|
-
const models = availableModels || configQuery.data?.models || [];
|
|
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) &&
|
|
2095
|
-
(configQuery.data || skillsQuery.data) && (_jsxs(Box, { sx: {
|
|
2096
|
-
display: 'flex',
|
|
2097
|
-
gap: 2,
|
|
2098
|
-
px: padding,
|
|
2099
|
-
py: 1,
|
|
2100
|
-
borderTop: '1px solid',
|
|
2101
|
-
borderColor: 'border.default',
|
|
2102
|
-
alignItems: 'center',
|
|
2103
|
-
bg: 'canvas.subtle',
|
|
2104
|
-
}, children: [showToolsMenu && (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { type: "button", variant: "invisible", size: "small", leadingVisual: ToolsIcon, children: _jsxs(Text, { sx: { fontSize: 0 }, children: ["Tools", getEnabledMcpToolNames().length > 0 &&
|
|
2105
|
-
` (${getEnabledMcpToolNames().length})`] }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "start", width: "large", children: _jsx(Box, { sx: {
|
|
2106
|
-
maxHeight: '60vh',
|
|
2107
|
-
overflowY: 'auto',
|
|
2108
|
-
}, children: _jsxs(ActionList, { children: [codemodeEnabled && (_jsx(ActionList.Group, { title: "Codemode", children: _jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "MCP tools are accessible via Codemode meta-tools (search_tools, list_tool_names, execute_code)." }) }) })), configQuery.data?.mcpServers &&
|
|
2109
|
-
configQuery.data.mcpServers.length > 0 ? (
|
|
2110
|
-
// Filter to only show selected servers (if mcpServers was provided)
|
|
2111
|
-
// If mcpServers is empty array, show no servers
|
|
2112
|
-
configQuery.data.mcpServers.filter(server => !mcpServers || isServerSelected(server)).length > 0 ? (configQuery.data.mcpServers
|
|
2113
|
-
.filter(server => !mcpServers || isServerSelected(server))
|
|
2114
|
-
.map(server => {
|
|
2115
|
-
const serverTools = enabledMcpTools.get(server.id);
|
|
2116
|
-
const allToolNames = server.tools.map(t => t.name);
|
|
2117
|
-
const enabledCount = serverTools?.size ?? 0;
|
|
2118
|
-
const allEnabled = enabledCount === allToolNames.length &&
|
|
2119
|
-
allToolNames.length > 0;
|
|
2120
|
-
return (_jsxs(ActionList.Group, { title: `${server.name}${server.isAvailable ? '' : ' (unavailable)'}`, children: [server.isAvailable &&
|
|
2121
|
-
server.tools.length > 0 && (_jsxs(Box, { sx: {
|
|
2122
|
-
display: 'flex',
|
|
2123
|
-
alignItems: 'center',
|
|
2124
|
-
justifyContent: 'space-between',
|
|
2125
|
-
px: 3,
|
|
2126
|
-
py: 2,
|
|
2127
|
-
borderBottom: '1px solid',
|
|
2128
|
-
borderColor: 'border.muted',
|
|
2129
|
-
}, children: [_jsxs(Text, { id: `toggle-all-${server.id}`, sx: {
|
|
2130
|
-
fontSize: 0,
|
|
2131
|
-
fontWeight: 'semibold',
|
|
2132
|
-
color: 'fg.muted',
|
|
2133
|
-
}, children: ["Enable all (", enabledCount, "/", allToolNames.length, ")"] }), _jsx(ToggleSwitch, { size: "small", checked: allEnabled, onClick: () => toggleAllMcpServerTools(server.id, allToolNames, !allEnabled), "aria-labelledby": `toggle-all-${server.id}` })] })), server.isAvailable &&
|
|
2134
|
-
server.tools.length > 0 ? (server.tools.map(tool => {
|
|
2135
|
-
const isEnabled = serverTools?.has(tool.name) ?? false;
|
|
2136
|
-
return (_jsxs(Box, { sx: {
|
|
2137
|
-
display: 'flex',
|
|
2138
|
-
alignItems: 'center',
|
|
2139
|
-
justifyContent: 'space-between',
|
|
2140
|
-
px: 3,
|
|
2141
|
-
py: 2,
|
|
2142
|
-
'&:hover': {
|
|
2143
|
-
backgroundColor: 'canvas.subtle',
|
|
2144
|
-
},
|
|
2145
|
-
}, children: [_jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { id: `toggle-tool-${server.id}-${tool.name}`, sx: { fontWeight: 'semibold' }, children: tool.name }), tool.description && (_jsx(Text, { sx: {
|
|
2146
|
-
display: 'block',
|
|
2147
|
-
fontSize: 0,
|
|
2148
|
-
color: 'fg.muted',
|
|
2149
|
-
overflow: 'hidden',
|
|
2150
|
-
textOverflow: 'ellipsis',
|
|
2151
|
-
whiteSpace: 'nowrap',
|
|
2152
|
-
}, children: tool.description }))] }), _jsx(ToggleSwitch, { size: "small", checked: isEnabled, onClick: () => toggleMcpTool(server.id, tool.name), "aria-labelledby": `toggle-tool-${server.id}-${tool.name}` })] }, `${server.id}-${tool.name}`));
|
|
2153
|
-
})) : server.isAvailable ? (_jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: {
|
|
2154
|
-
color: 'fg.muted',
|
|
2155
|
-
fontStyle: 'italic',
|
|
2156
|
-
}, children: "No tools discovered" }) })) : (_jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: {
|
|
2157
|
-
color: 'fg.muted',
|
|
2158
|
-
fontStyle: 'italic',
|
|
2159
|
-
}, children: "Server unavailable" }) }))] }, server.id));
|
|
2160
|
-
})) : (_jsx(ActionList.Group, { title: "MCP Servers", children: _jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: {
|
|
2161
|
-
color: 'fg.muted',
|
|
2162
|
-
fontStyle: 'italic',
|
|
2163
|
-
}, children: "No MCP servers selected" }) }) }))) : (_jsx(ActionList.Group, { title: "Available Tools", children: availableTools.length > 0 ? (availableTools.map(tool => (_jsxs(ActionList.Item, { disabled: true, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(Box, { sx: {
|
|
2164
|
-
width: 8,
|
|
2165
|
-
height: 8,
|
|
2166
|
-
borderRadius: '50%',
|
|
2167
|
-
backgroundColor: 'success.emphasis',
|
|
2168
|
-
} }) }), tool.name] }, tool.id)))) : (_jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: {
|
|
2169
|
-
color: 'fg.muted',
|
|
2170
|
-
fontStyle: 'italic',
|
|
2171
|
-
}, children: "No tools available" }) })) }))] }) }) })] })), showSkillsMenu && (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { type: "button", variant: "invisible", size: "small", leadingVisual: BriefcaseIcon, children: _jsxs(Text, { sx: { fontSize: 0 }, children: ["Skills", enabledSkills.size > 0 && ` (${enabledSkills.size})`] }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "start", width: "large", children: _jsx(Box, { sx: {
|
|
2172
|
-
maxHeight: '60vh',
|
|
2173
|
-
overflowY: 'auto',
|
|
2174
|
-
}, children: _jsx(ActionList, { children: skillsQuery.isLoading ? (_jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading skills..." }) })) : skillsQuery.data?.skills &&
|
|
2175
|
-
skillsQuery.data.skills.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
2176
|
-
display: 'flex',
|
|
2177
|
-
alignItems: 'center',
|
|
2178
|
-
justifyContent: 'space-between',
|
|
2179
|
-
px: 3,
|
|
2180
|
-
py: 2,
|
|
2181
|
-
borderBottom: '1px solid',
|
|
2182
|
-
borderColor: 'border.muted',
|
|
2183
|
-
}, children: [_jsxs(Text, { id: "toggle-all-skills", sx: {
|
|
2184
|
-
fontSize: 0,
|
|
2185
|
-
fontWeight: 'semibold',
|
|
2186
|
-
color: 'fg.muted',
|
|
2187
|
-
}, children: ["Enable all (", enabledSkills.size, "/", skillsQuery.data.skills.length, ")"] }), _jsx(ToggleSwitch, { size: "small", checked: enabledSkills.size ===
|
|
2188
|
-
skillsQuery.data.skills.length, onClick: () => toggleAllSkills(skillsQuery.data.skills.map(s => s.id), enabledSkills.size !==
|
|
2189
|
-
skillsQuery.data.skills.length), "aria-labelledby": "toggle-all-skills" })] }), skillsQuery.data.skills.map(skill => (_jsxs(Box, { sx: {
|
|
2190
|
-
display: 'flex',
|
|
2191
|
-
alignItems: 'center',
|
|
2192
|
-
justifyContent: 'space-between',
|
|
2193
|
-
px: 3,
|
|
2194
|
-
py: 2,
|
|
2195
|
-
'&:hover': {
|
|
2196
|
-
backgroundColor: 'canvas.subtle',
|
|
2197
|
-
},
|
|
2198
|
-
}, children: [_jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { id: `toggle-skill-${skill.id}`, sx: { fontWeight: 'semibold' }, children: skill.name }), skill.description && (_jsx(Text, { sx: {
|
|
2199
|
-
display: 'block',
|
|
2200
|
-
fontSize: 0,
|
|
2201
|
-
color: 'fg.muted',
|
|
2202
|
-
overflow: 'hidden',
|
|
2203
|
-
textOverflow: 'ellipsis',
|
|
2204
|
-
whiteSpace: 'nowrap',
|
|
2205
|
-
}, children: skill.description }))] }), _jsx(ToggleSwitch, { size: "small", checked: enabledSkills.has(skill.id), onClick: () => toggleSkill(skill.id), "aria-labelledby": `toggle-skill-${skill.id}` })] }, skill.id)))] })) : (_jsx(ActionList.Item, { disabled: true, children: _jsx(Text, { sx: { color: 'fg.muted', fontStyle: 'italic' }, children: "No skills available" }) })) }) }) })] })), showModelSelector && models.length > 0 && selectedModel && (_jsxs(Box, { sx: {
|
|
2206
|
-
display: 'flex',
|
|
2207
|
-
flexDirection: 'column',
|
|
2208
|
-
alignItems: 'flex-end',
|
|
2209
|
-
}, children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { type: "button", variant: "invisible", size: "small", leadingVisual: AiModelIcon, disabled: isA2AProtocol, sx: isA2AProtocol
|
|
2210
|
-
? { opacity: 0.5, cursor: 'not-allowed' }
|
|
2211
|
-
: undefined, children: _jsx(Text, { sx: { fontSize: 0 }, children: models.find(m => m.id === selectedModel)?.name ||
|
|
2212
|
-
'Select Model' }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "end", children: _jsx(ActionList, { selectionVariant: "single", children: models.map(modelItem => (_jsxs(ActionList.Item, { selected: selectedModel === modelItem.id, onSelect: () => setSelectedModel(modelItem.id), disabled: modelItem.isAvailable === false || isA2AProtocol, sx: modelItem.isAvailable === false
|
|
2213
|
-
? { color: 'fg.muted' }
|
|
2214
|
-
: undefined, children: [modelItem.name, modelItem.isAvailable === false && (_jsx(ActionList.Description, { variant: "block", children: "Missing API key" }))] }, modelItem.id))) }) })] }), isA2AProtocol && (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg', mt: 1 }, children: "A2A: Model set by agent config" }))] }))] }))] }));
|
|
2215
|
-
};
|
|
2216
|
-
return (_jsxs(Box, { className: className, sx: {
|
|
2217
|
-
display: 'flex',
|
|
2218
|
-
flexDirection: 'column',
|
|
2219
|
-
height: '100%',
|
|
2220
|
-
bg: backgroundColor || 'canvas.default',
|
|
2221
|
-
borderRadius: borderRadius,
|
|
2222
|
-
border: border,
|
|
2223
|
-
boxShadow: boxShadow,
|
|
2224
|
-
overflow: 'hidden',
|
|
2225
|
-
}, children: [renderHeader(), showErrors && error && (_jsxs(Box, { sx: {
|
|
2226
|
-
display: 'flex',
|
|
2227
|
-
alignItems: 'center',
|
|
2228
|
-
gap: 2,
|
|
2229
|
-
p: padding,
|
|
2230
|
-
bg: 'danger.subtle',
|
|
2231
|
-
borderBottom: '1px solid',
|
|
2232
|
-
borderColor: 'danger.muted',
|
|
2233
|
-
}, children: [_jsx(AlertIcon, { size: 16 }), _jsx(Text, { sx: { color: 'danger.fg', fontSize: 1 }, children: error.message })] })), _jsx(Box, { sx: { flex: 1, flexGrow: 1, overflow: 'auto', bg: 'canvas.default' }, children: children ? (children) : (_jsx(Box, { sx: {
|
|
2234
|
-
display: 'flex',
|
|
2235
|
-
flexDirection: 'column',
|
|
2236
|
-
minHeight: '100%',
|
|
2237
|
-
bg: 'canvas.default',
|
|
2238
|
-
}, children: renderProtocolMessages() })) }), footerContent, showInput && renderInputPrompt(), showPoweredBy && _jsx(PoweredByTag, { ...poweredByProps })] }));
|
|
2239
|
-
}
|
|
2240
|
-
export default ChatBase;
|