@datalayer/agent-runtimes 1.0.4 → 1.0.5
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 +34 -0
- package/lib/App.js +1 -1
- package/lib/agents/AgentDetails.d.ts +22 -1
- package/lib/agents/AgentDetails.js +34 -47
- package/lib/api/index.d.ts +0 -1
- package/lib/api/index.js +4 -2
- package/lib/chat/Chat.d.ts +5 -106
- package/lib/chat/Chat.js +4 -4
- package/lib/chat/ChatFloating.d.ts +7 -140
- package/lib/chat/ChatFloating.js +2 -2
- package/lib/chat/ChatPopupStandalone.d.ts +8 -47
- package/lib/chat/ChatPopupStandalone.js +3 -3
- package/lib/chat/ChatSidebar.d.ts +4 -69
- package/lib/chat/ChatSidebar.js +2 -2
- package/lib/chat/ChatStandalone.d.ts +4 -54
- package/lib/chat/ChatStandalone.js +3 -3
- package/lib/chat/base/ChatBase.js +1083 -157
- package/lib/chat/header/ChatHeaderBase.d.ts +11 -6
- package/lib/chat/header/ChatHeaderBase.js +18 -16
- package/lib/chat/indicators/McpStatusIndicator.d.ts +7 -4
- package/lib/chat/indicators/McpStatusIndicator.js +7 -32
- package/lib/chat/indicators/SandboxStatusIndicator.d.ts +4 -1
- package/lib/chat/indicators/SandboxStatusIndicator.js +9 -9
- package/lib/chat/indicators/SkillsStatusIndicator.d.ts +7 -0
- package/lib/chat/indicators/SkillsStatusIndicator.js +88 -0
- package/lib/chat/indicators/index.d.ts +1 -0
- package/lib/chat/indicators/index.js +1 -0
- package/lib/chat/messages/ChatMessageList.d.ts +1 -1
- package/lib/chat/messages/ChatMessageList.js +108 -113
- package/lib/chat/prompt/InputFooter.d.ts +19 -6
- package/lib/chat/prompt/InputFooter.js +71 -18
- package/lib/chat/prompt/InputPrompt.d.ts +3 -1
- package/lib/chat/prompt/InputPrompt.js +4 -4
- package/lib/chat/prompt/InputPromptFooter.js +1 -1
- package/lib/chat/prompt/InputPromptLexical.d.ts +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +12 -5
- package/lib/chat/prompt/InputPromptText.d.ts +3 -1
- package/lib/chat/prompt/InputPromptText.js +2 -2
- package/lib/chat/tools/ToolApprovalBanner.js +1 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +3 -1
- package/lib/chat/tools/ToolCallDisplay.js +2 -2
- package/lib/chat/usage/TokenUsageBar.js +20 -2
- package/lib/client/AgentRuntimesClientContext.d.ts +53 -0
- package/lib/client/AgentRuntimesClientContext.js +55 -0
- package/lib/client/AgentsMixin.d.ts +0 -18
- package/lib/client/AgentsMixin.js +6 -30
- package/lib/client/IAgentRuntimesClient.d.ts +215 -0
- package/lib/client/IAgentRuntimesClient.js +5 -0
- package/lib/client/SdkAgentRuntimesClient.d.ts +151 -0
- package/lib/client/SdkAgentRuntimesClient.js +134 -0
- package/lib/client/index.d.ts +4 -1
- package/lib/client/index.js +3 -1
- package/lib/components/NotificationEventCard.js +5 -1
- package/lib/config/AgentConfiguration.js +3 -3
- package/lib/context/ContextDistribution.d.ts +3 -1
- package/lib/context/ContextDistribution.js +8 -27
- package/lib/context/ContextInspector.d.ts +3 -1
- package/lib/context/ContextInspector.js +19 -67
- package/lib/context/ContextPanel.d.ts +3 -1
- package/lib/context/ContextPanel.js +104 -64
- package/lib/context/ContextUsage.d.ts +3 -1
- package/lib/context/ContextUsage.js +3 -3
- package/lib/context/CostTracker.d.ts +9 -3
- package/lib/context/CostTracker.js +26 -47
- package/lib/context/CostUsageChart.d.ts +12 -0
- package/lib/context/CostUsageChart.js +378 -0
- package/lib/context/GraphFlowChart.d.ts +16 -0
- package/lib/context/GraphFlowChart.js +182 -0
- package/lib/context/TokenUsageChart.d.ts +8 -1
- package/lib/context/TokenUsageChart.js +349 -211
- package/lib/context/TurnGraphChart.d.ts +39 -0
- package/lib/context/TurnGraphChart.js +538 -0
- package/lib/context/otelWsPool.d.ts +20 -0
- package/lib/context/otelWsPool.js +69 -0
- package/lib/examples/A2UiComponentGalleryExample.d.ts +0 -17
- package/lib/examples/A2UiComponentGalleryExample.js +315 -522
- package/lib/examples/A2UiContactCardExample.d.ts +0 -18
- package/lib/examples/A2UiContactCardExample.js +154 -411
- package/lib/examples/A2UiRestaurantExample.d.ts +0 -30
- package/lib/examples/A2UiRestaurantExample.js +114 -212
- package/lib/examples/A2UiViewerExample.d.ts +0 -18
- package/lib/examples/A2UiViewerExample.js +283 -532
- package/lib/examples/AgUiBackendToolRenderingExample.js +1 -1
- package/lib/examples/AgUiHaikuGenUiExample.d.ts +1 -1
- package/lib/examples/AgUiHaikuGenUiExample.js +1 -1
- package/lib/examples/AgentCheckpointsExample.js +13 -27
- package/lib/examples/AgentCodemodeExample.d.ts +4 -6
- package/lib/examples/AgentCodemodeExample.js +591 -169
- package/lib/examples/AgentEvalsExample.js +12 -16
- package/lib/examples/AgentGuardrailsExample.js +370 -64
- package/lib/examples/AgentHooksExample.d.ts +3 -0
- package/lib/examples/AgentHooksExample.js +104 -0
- package/lib/examples/AgentMCPExample.d.ts +3 -0
- package/lib/examples/AgentMCPExample.js +480 -0
- package/lib/examples/AgentMemoryExample.js +13 -17
- package/lib/examples/AgentMonitoringExample.js +260 -199
- package/lib/examples/AgentNotificationsExample.js +49 -17
- package/lib/examples/AgentOtelExample.js +2 -3
- package/lib/examples/AgentOutputsExample.d.ts +11 -6
- package/lib/examples/AgentOutputsExample.js +382 -81
- package/lib/examples/AgentParametersExample.d.ts +3 -0
- package/lib/examples/AgentParametersExample.js +246 -0
- package/lib/examples/AgentSandboxExample.d.ts +2 -2
- package/lib/examples/AgentSandboxExample.js +68 -40
- package/lib/examples/AgentSkillsExample.js +91 -99
- package/lib/examples/{AgentspecExample.js → AgentSpecsExample.js} +10 -21
- package/lib/examples/AgentSubagentsExample.d.ts +14 -0
- package/lib/examples/AgentSubagentsExample.js +228 -0
- package/lib/examples/AgentToolApprovalsExample.js +29 -557
- package/lib/examples/AgentTriggersExample.js +819 -565
- package/lib/examples/ChatCustomExample.js +11 -24
- package/lib/examples/ChatExample.js +7 -24
- package/lib/examples/CopilotKitLexicalExample.js +2 -1
- package/lib/examples/CopilotKitNotebookExample.js +2 -1
- package/lib/examples/HomeExample.d.ts +15 -0
- package/lib/examples/HomeExample.js +77 -0
- package/lib/examples/Lexical2Example.js +4 -2
- package/lib/examples/{LexicalExample.d.ts → LexicalAgentExample.d.ts} +4 -4
- package/lib/examples/{LexicalExample.js → LexicalAgentExample.js} +65 -16
- package/lib/examples/{LexicalSidebarExample.d.ts → LexicalAgentSidebarExample.d.ts} +5 -5
- package/lib/examples/LexicalAgentSidebarExample.js +261 -0
- package/lib/examples/NotebookAgentExample.d.ts +9 -0
- package/lib/examples/NotebookAgentExample.js +192 -0
- package/lib/examples/{NotebookSidebarExample.d.ts → NotebookAgentSidebarExample.d.ts} +2 -2
- package/lib/examples/NotebookAgentSidebarExample.js +221 -0
- package/lib/examples/{DatalayerNotebookExample.d.ts → NotebookCollaborationExample.d.ts} +4 -4
- package/lib/examples/{DatalayerNotebookExample.js → NotebookCollaborationExample.js} +3 -3
- package/lib/examples/NotebookExample.d.ts +4 -7
- package/lib/examples/NotebookExample.js +14 -146
- package/lib/examples/components/AuthRequiredView.d.ts +6 -0
- package/lib/examples/components/AuthRequiredView.js +33 -0
- package/lib/examples/components/ExampleWrapper.d.ts +7 -0
- package/lib/examples/components/ExampleWrapper.js +25 -6
- package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.js +1 -1
- package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.js +1 -1
- package/lib/examples/{ag-ui → components}/haiku/index.d.ts +1 -1
- package/lib/examples/{ag-ui → components}/haiku/index.js +1 -1
- package/lib/examples/components/index.d.ts +3 -0
- package/lib/examples/components/index.js +4 -0
- package/lib/examples/{ag-ui → components}/weather/index.d.ts +1 -1
- package/lib/examples/{ag-ui → components}/weather/index.js +1 -1
- package/lib/examples/example-selector.d.ts +17 -4
- package/lib/examples/example-selector.js +107 -41
- package/lib/examples/index.d.ts +9 -6
- package/lib/examples/index.js +9 -6
- package/lib/examples/main.js +217 -27
- package/lib/examples/utils/a2ui.d.ts +18 -0
- package/lib/examples/utils/a2ui.js +69 -0
- package/lib/examples/utils/a2uiMarkdownProvider.d.ts +7 -0
- package/lib/examples/utils/a2uiMarkdownProvider.js +9 -0
- package/lib/examples/utils/agentId.d.ts +18 -0
- package/lib/examples/utils/agentId.js +54 -0
- package/lib/examples/utils/agents/earthquake-detector.json +11 -11
- package/lib/examples/utils/agents/sales-forecaster.json +11 -11
- package/lib/examples/utils/agents/social-post-generator.json +11 -11
- package/lib/examples/utils/agents/stock-market.json +11 -11
- package/lib/examples/utils/examplesStore.js +82 -27
- package/lib/hooks/index.d.ts +8 -8
- package/lib/hooks/index.js +7 -7
- package/lib/hooks/useA2A.d.ts +2 -3
- package/lib/hooks/useAIAgentsWebSocket.d.ts +43 -4
- package/lib/hooks/useAIAgentsWebSocket.js +118 -12
- package/lib/hooks/useAcp.d.ts +1 -2
- package/lib/hooks/useAgUi.d.ts +1 -1
- package/lib/hooks/{useAgents.d.ts → useAgentRuntimes.d.ts} +39 -2
- package/lib/hooks/{useAgents.js → useAgentRuntimes.js} +125 -15
- package/lib/hooks/useAgentsCatalog.js +1 -1
- package/lib/hooks/useAgentsService.d.ts +2 -2
- package/lib/hooks/useAgentsService.js +7 -7
- package/lib/hooks/useCheckpoints.js +1 -1
- package/lib/hooks/useConfig.d.ts +4 -1
- package/lib/hooks/useConfig.js +10 -3
- package/lib/hooks/useContextSnapshot.d.ts +9 -4
- package/lib/hooks/useContextSnapshot.js +9 -37
- package/lib/hooks/useMonitoring.js +3 -0
- package/lib/hooks/useSandbox.d.ts +20 -8
- package/lib/hooks/useSandbox.js +105 -40
- package/lib/hooks/useSkills.d.ts +23 -5
- package/lib/hooks/useSkills.js +94 -39
- package/lib/hooks/useToolApprovals.d.ts +60 -36
- package/lib/hooks/useToolApprovals.js +318 -69
- package/lib/hooks/useVercelAI.d.ts +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -0
- package/lib/inference/index.d.ts +0 -1
- package/lib/middleware/index.d.ts +0 -1
- package/lib/protocols/AGUIAdapter.js +6 -0
- package/lib/protocols/VercelAIAdapter.d.ts +7 -0
- package/lib/protocols/VercelAIAdapter.js +59 -7
- package/lib/specs/agents/agents.d.ts +10 -0
- package/lib/specs/agents/agents.js +2139 -262
- package/lib/specs/agents/index.js +3 -1
- package/lib/specs/envvars.d.ts +1 -0
- package/lib/specs/envvars.js +38 -20
- package/lib/specs/evals.js +6 -6
- package/lib/specs/events.d.ts +3 -10
- package/lib/specs/events.js +127 -84
- package/lib/specs/frontendTools.js +2 -2
- package/lib/specs/guardrails.d.ts +0 -7
- package/lib/specs/guardrails.js +240 -159
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/mcpServers.js +35 -6
- package/lib/specs/memory.d.ts +0 -2
- package/lib/specs/memory.js +4 -17
- package/lib/specs/models.js +25 -5
- package/lib/specs/notifications.js +102 -18
- package/lib/specs/outputs.js +15 -9
- package/lib/specs/personas.d.ts +41 -0
- package/lib/specs/personas.js +168 -0
- package/lib/specs/skills.d.ts +2 -1
- package/lib/specs/skills.js +41 -23
- package/lib/specs/teams/index.js +3 -1
- package/lib/specs/teams/teams.js +468 -348
- package/lib/specs/tools.js +4 -4
- package/lib/specs/triggers.js +61 -11
- package/lib/stores/agentRuntimeStore.d.ts +204 -0
- package/lib/stores/agentRuntimeStore.js +636 -0
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/tools/adapters/copilotkit/lexicalHooks.d.ts +1 -2
- package/lib/tools/adapters/copilotkit/lexicalHooks.js +1 -3
- package/lib/tools/adapters/copilotkit/notebookHooks.d.ts +1 -2
- package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -3
- package/lib/tools/index.d.ts +0 -2
- package/lib/tools/index.js +0 -1
- package/lib/types/agentspecs.d.ts +50 -1
- package/lib/types/chat.d.ts +309 -8
- package/lib/types/context.d.ts +27 -0
- package/lib/types/cost.d.ts +2 -2
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +2 -0
- package/lib/types/mcp.d.ts +8 -0
- package/lib/types/models.d.ts +2 -2
- package/lib/types/personas.d.ts +25 -0
- package/lib/types/personas.js +5 -0
- package/lib/types/skills.d.ts +43 -1
- package/lib/types/stream.d.ts +110 -0
- package/lib/types/stream.js +36 -0
- package/lib/utils/utils.d.ts +9 -5
- package/lib/utils/utils.js +9 -5
- package/package.json +13 -9
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_events.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +102 -6
- package/scripts/codegen/generate_events.py +35 -13
- package/scripts/codegen/generate_personas.py +319 -0
- package/scripts/codegen/generate_skills.py +9 -9
- package/scripts/sync-jupyter.sh +26 -7
- package/lib/api/tool-approvals.d.ts +0 -62
- package/lib/api/tool-approvals.js +0 -145
- package/lib/examples/LexicalSidebarExample.js +0 -163
- package/lib/examples/NotebookSidebarExample.js +0 -119
- package/lib/examples/NotebookSimpleExample.d.ts +0 -6
- package/lib/examples/NotebookSimpleExample.js +0 -22
- package/lib/examples/ag-ui/index.d.ts +0 -10
- package/lib/examples/ag-ui/index.js +0 -16
- package/lib/hooks/useAgentsRegistry.d.ts +0 -10
- package/lib/hooks/useAgentsRegistry.js +0 -20
- package/lib/stores/agentsStore.d.ts +0 -123
- package/lib/stores/agentsStore.js +0 -270
- /package/lib/examples/{AgentspecExample.d.ts → AgentSpecsExample.d.ts} +0 -0
- /package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.js +0 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { jsx as _jsx, 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
|
+
* TurnGraphChart — ECharts force-directed graph built from OTEL traces.
|
|
8
|
+
*
|
|
9
|
+
* Each graph run emitted by ``run_graph_with_telemetry`` /
|
|
10
|
+
* ``run_beta_graph_with_telemetry`` produces one OTEL trace whose spans map
|
|
11
|
+
* directly to pydantic-graph nodes. This component:
|
|
12
|
+
*
|
|
13
|
+
* 1. Fetches recent traces from the Datalayer OTEL service using
|
|
14
|
+
* ``createOtelClient`` from ``@datalayer/core/lib/otel`` (TypeScript-side
|
|
15
|
+
* fetch, Python-side emission).
|
|
16
|
+
* 2. Filters and groups spans by ``agent.id`` attribute and ``trace_id``.
|
|
17
|
+
* 3. Renders the latest run as an ECharts ``graph`` type (force-directed),
|
|
18
|
+
* with:
|
|
19
|
+
* - Node size ∝ span duration
|
|
20
|
+
* - Node colour by type (start / step / end / error)
|
|
21
|
+
* - Edges following the parent→child span hierarchy
|
|
22
|
+
* - Rich hover tooltips (duration, type, error)
|
|
23
|
+
* 4. Shows a small "run history" selector so the user can browse past runs.
|
|
24
|
+
*/
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
26
|
+
import ReactECharts from 'echarts-for-react';
|
|
27
|
+
import { createOtelClient, useOtelWebSocket } from '@datalayer/core/lib/otel';
|
|
28
|
+
import { Box } from '@datalayer/primer-addons';
|
|
29
|
+
import { Text } from '@primer/react';
|
|
30
|
+
// ── Colour palette ─────────────────────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Colours and symbols mirror the pydantic-graph beta node vocabulary
|
|
33
|
+
// (https://pydantic.dev/docs/ai/graph/beta):
|
|
34
|
+
//
|
|
35
|
+
// - step → executes an async function
|
|
36
|
+
// - decision → conditional branching
|
|
37
|
+
// - spread → parallel processing of iterables
|
|
38
|
+
// - broadcast → send the same data to multiple parallel paths
|
|
39
|
+
// - join → aggregate results from parallel execution (reducer)
|
|
40
|
+
// - reducer → alias for the reducer half of a join
|
|
41
|
+
// - start/end → graph entry / exit markers
|
|
42
|
+
const NODE_COLORS = {
|
|
43
|
+
root: '#58a6ff',
|
|
44
|
+
start: '#58a6ff',
|
|
45
|
+
step: '#3fb950',
|
|
46
|
+
end: '#f85149',
|
|
47
|
+
end_or_continue: '#d29922',
|
|
48
|
+
join: '#bc8cff',
|
|
49
|
+
reducer: '#a371f7',
|
|
50
|
+
decision: '#f0883e',
|
|
51
|
+
broadcast: '#2dd4bf',
|
|
52
|
+
spread: '#79c0ff',
|
|
53
|
+
error: '#da3633',
|
|
54
|
+
parallel: '#79c0ff',
|
|
55
|
+
default: '#8b949e',
|
|
56
|
+
};
|
|
57
|
+
/** Mapping of node type → ECharts symbol shape, for at-a-glance recognition. */
|
|
58
|
+
const NODE_SYMBOLS = {
|
|
59
|
+
root: 'circle',
|
|
60
|
+
start: 'circle',
|
|
61
|
+
step: 'roundRect',
|
|
62
|
+
decision: 'diamond',
|
|
63
|
+
broadcast: 'triangle',
|
|
64
|
+
spread: 'arrow',
|
|
65
|
+
join: 'pin',
|
|
66
|
+
reducer: 'pin',
|
|
67
|
+
end: 'circle',
|
|
68
|
+
end_or_continue: 'diamond',
|
|
69
|
+
parallel: 'arrow',
|
|
70
|
+
error: 'circle',
|
|
71
|
+
default: 'roundRect',
|
|
72
|
+
};
|
|
73
|
+
/** Human-readable legend entries used by the UI. */
|
|
74
|
+
const LEGEND_ENTRIES = [
|
|
75
|
+
{ type: 'start', label: 'Start', description: 'Graph entry point' },
|
|
76
|
+
{ type: 'step', label: 'Step', description: 'Async function execution' },
|
|
77
|
+
{ type: 'decision', label: 'Decision', description: 'Conditional branching' },
|
|
78
|
+
{
|
|
79
|
+
type: 'broadcast',
|
|
80
|
+
label: 'Broadcast',
|
|
81
|
+
description: 'Send data to multiple parallel paths',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'spread',
|
|
85
|
+
label: 'Spread',
|
|
86
|
+
description: 'Parallel processing of iterables',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'join',
|
|
90
|
+
label: 'Join',
|
|
91
|
+
description: 'Aggregate parallel results (reducer)',
|
|
92
|
+
},
|
|
93
|
+
{ type: 'end', label: 'End', description: 'Graph exit point' },
|
|
94
|
+
];
|
|
95
|
+
function spanColor(span) {
|
|
96
|
+
if (span.status_code === 'ERROR')
|
|
97
|
+
return NODE_COLORS.error;
|
|
98
|
+
const rawType = span.attributes?.['graph.node.type'] ?? '';
|
|
99
|
+
const nodeType = rawType.toLowerCase();
|
|
100
|
+
if (nodeType in NODE_COLORS) {
|
|
101
|
+
return (NODE_COLORS[nodeType] ?? NODE_COLORS.step);
|
|
102
|
+
}
|
|
103
|
+
if (span.span_name === 'agent.graph.run')
|
|
104
|
+
return NODE_COLORS.root;
|
|
105
|
+
return NODE_COLORS.step;
|
|
106
|
+
}
|
|
107
|
+
function formatDur(ms) {
|
|
108
|
+
if (ms < 1)
|
|
109
|
+
return '<1ms';
|
|
110
|
+
if (ms < 1000)
|
|
111
|
+
return `${ms.toFixed(1)}ms`;
|
|
112
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
113
|
+
}
|
|
114
|
+
function nanoToIso(val) {
|
|
115
|
+
const n = Number(val);
|
|
116
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
117
|
+
return '';
|
|
118
|
+
if (n > 1e15)
|
|
119
|
+
return new Date(n / 1e6).toISOString();
|
|
120
|
+
if (n > 1e12)
|
|
121
|
+
return new Date(n / 1e3).toISOString();
|
|
122
|
+
return new Date(n).toISOString();
|
|
123
|
+
}
|
|
124
|
+
function parseAttrs(raw) {
|
|
125
|
+
if (typeof raw === 'object' && raw !== null) {
|
|
126
|
+
return raw;
|
|
127
|
+
}
|
|
128
|
+
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
132
|
+
return parsed;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
function normalizeSpan(raw) {
|
|
142
|
+
const source = raw;
|
|
143
|
+
const durationMs = source.duration_ms != null
|
|
144
|
+
? Number(source.duration_ms)
|
|
145
|
+
: source.duration_ns != null
|
|
146
|
+
? Number(source.duration_ns) / 1e6
|
|
147
|
+
: 0;
|
|
148
|
+
return {
|
|
149
|
+
trace_id: String(source.trace_id ?? ''),
|
|
150
|
+
span_id: String(source.span_id ?? ''),
|
|
151
|
+
parent_span_id: source.parent_span_id != null && String(source.parent_span_id).length > 0
|
|
152
|
+
? String(source.parent_span_id)
|
|
153
|
+
: undefined,
|
|
154
|
+
span_name: String(source.span_name ?? source.operation_name ?? source.name ?? ''),
|
|
155
|
+
service_name: String(source.service_name ?? ''),
|
|
156
|
+
kind: String(source.kind ?? source.span_kind ?? 'INTERNAL'),
|
|
157
|
+
start_time: typeof source.start_time === 'string' && source.start_time.length > 0
|
|
158
|
+
? source.start_time
|
|
159
|
+
: nanoToIso(source.start_time_unix_nano),
|
|
160
|
+
end_time: typeof source.end_time === 'string' && source.end_time.length > 0
|
|
161
|
+
? source.end_time
|
|
162
|
+
: nanoToIso(source.end_time_unix_nano),
|
|
163
|
+
duration_ms: Number.isFinite(durationMs) ? durationMs : 0,
|
|
164
|
+
status_code: source.status_code != null ? String(source.status_code) : undefined,
|
|
165
|
+
status_message: source.status_message != null ? String(source.status_message) : undefined,
|
|
166
|
+
attributes: parseAttrs(source.attributes),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function isGraphSpan(span) {
|
|
170
|
+
return (span.span_name === 'agent.graph.run' ||
|
|
171
|
+
span.span_name.startsWith('graph.node.'));
|
|
172
|
+
}
|
|
173
|
+
// ── ECharts option builder ─────────────────────────────────────────────────
|
|
174
|
+
/**
|
|
175
|
+
* Compute a left-to-right layered DAG layout for the spans.
|
|
176
|
+
*
|
|
177
|
+
* Execution traces are strictly hierarchical (parent → child spans), so a
|
|
178
|
+
* fixed layered layout produces a far cleaner visual than force-directed
|
|
179
|
+
* relaxation — matching the "Layout: none + manual positions" pattern used
|
|
180
|
+
* by several ECharts graph examples for DAGs.
|
|
181
|
+
*
|
|
182
|
+
* Each span is placed at:
|
|
183
|
+
* x = depth * columnWidth
|
|
184
|
+
* y = indexWithinLayer * rowHeight - verticalCentre
|
|
185
|
+
*
|
|
186
|
+
* Root spans (no parent or parent missing from trace) anchor depth 0.
|
|
187
|
+
*/
|
|
188
|
+
function computeLayout(spans) {
|
|
189
|
+
const spanById = new Map(spans.map(s => [s.span_id, s]));
|
|
190
|
+
const childrenByParent = new Map();
|
|
191
|
+
const roots = [];
|
|
192
|
+
for (const s of spans) {
|
|
193
|
+
if (s.parent_span_id && spanById.has(s.parent_span_id)) {
|
|
194
|
+
const list = childrenByParent.get(s.parent_span_id) ?? [];
|
|
195
|
+
list.push(s.span_id);
|
|
196
|
+
childrenByParent.set(s.parent_span_id, list);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
roots.push(s.span_id);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Preserve execution order within a layer by sorting children by start time.
|
|
203
|
+
for (const [parent, kids] of childrenByParent) {
|
|
204
|
+
kids.sort((a, b) => {
|
|
205
|
+
const sa = spanById.get(a)?.start_time ?? '';
|
|
206
|
+
const sb = spanById.get(b)?.start_time ?? '';
|
|
207
|
+
return sa.localeCompare(sb);
|
|
208
|
+
});
|
|
209
|
+
childrenByParent.set(parent, kids);
|
|
210
|
+
}
|
|
211
|
+
// BFS depth assignment from every root.
|
|
212
|
+
const depthById = new Map();
|
|
213
|
+
const queue = roots.map(r => [r, 0]);
|
|
214
|
+
while (queue.length > 0) {
|
|
215
|
+
const next = queue.shift();
|
|
216
|
+
if (!next)
|
|
217
|
+
break;
|
|
218
|
+
const [spanId, depth] = next;
|
|
219
|
+
if (depthById.has(spanId))
|
|
220
|
+
continue;
|
|
221
|
+
depthById.set(spanId, depth);
|
|
222
|
+
for (const child of childrenByParent.get(spanId) ?? []) {
|
|
223
|
+
queue.push([child, depth + 1]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Defensive: any span we never reached (orphan) → depth 0.
|
|
227
|
+
for (const s of spans) {
|
|
228
|
+
if (!depthById.has(s.span_id))
|
|
229
|
+
depthById.set(s.span_id, 0);
|
|
230
|
+
}
|
|
231
|
+
// Bucket spans by depth.
|
|
232
|
+
const byDepth = new Map();
|
|
233
|
+
for (const [spanId, depth] of depthById) {
|
|
234
|
+
const list = byDepth.get(depth) ?? [];
|
|
235
|
+
list.push(spanId);
|
|
236
|
+
byDepth.set(depth, list);
|
|
237
|
+
}
|
|
238
|
+
// Preserve start-time order within each depth layer.
|
|
239
|
+
for (const [depth, ids] of byDepth) {
|
|
240
|
+
ids.sort((a, b) => {
|
|
241
|
+
const sa = spanById.get(a)?.start_time ?? '';
|
|
242
|
+
const sb = spanById.get(b)?.start_time ?? '';
|
|
243
|
+
return sa.localeCompare(sb);
|
|
244
|
+
});
|
|
245
|
+
byDepth.set(depth, ids);
|
|
246
|
+
}
|
|
247
|
+
const columnWidth = 180;
|
|
248
|
+
const rowHeight = 90;
|
|
249
|
+
const positions = new Map();
|
|
250
|
+
for (const [depth, ids] of byDepth) {
|
|
251
|
+
const count = ids.length;
|
|
252
|
+
const totalHeight = (count - 1) * rowHeight;
|
|
253
|
+
const yStart = -totalHeight / 2;
|
|
254
|
+
ids.forEach((spanId, idx) => {
|
|
255
|
+
positions.set(spanId, {
|
|
256
|
+
x: depth * columnWidth,
|
|
257
|
+
y: yStart + idx * rowHeight,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return positions;
|
|
262
|
+
}
|
|
263
|
+
function buildOption(run) {
|
|
264
|
+
const { spans } = run;
|
|
265
|
+
const spanById = new Map(spans.map(s => [s.span_id, s]));
|
|
266
|
+
const maxDur = Math.max(...spans.map(s => s.duration_ms ?? 1), 1);
|
|
267
|
+
const positions = computeLayout(spans);
|
|
268
|
+
const nodes = spans.map(s => {
|
|
269
|
+
const isRoot = s.span_name === 'agent.graph.run';
|
|
270
|
+
const dur = s.duration_ms ?? 1;
|
|
271
|
+
const nodeId = s.attributes?.['graph.node.id'] ??
|
|
272
|
+
s.span_id.slice(0, 8);
|
|
273
|
+
const nodeType = s.attributes?.['graph.node.type'] ??
|
|
274
|
+
(isRoot ? 'root' : 'step');
|
|
275
|
+
const status = s.attributes?.['graph.node.status'] ??
|
|
276
|
+
'completed';
|
|
277
|
+
const label = isRoot ? '▶ Run' : nodeId.replace('__', '');
|
|
278
|
+
const hasError = s.status_code === 'ERROR' ||
|
|
279
|
+
status === 'error' ||
|
|
280
|
+
!!s.attributes?.['error.message'];
|
|
281
|
+
const symbol = NODE_SYMBOLS[nodeType.toLowerCase()] ??
|
|
282
|
+
(isRoot ? 'circle' : NODE_SYMBOLS.default);
|
|
283
|
+
const pos = positions.get(s.span_id) ?? { x: 0, y: 0 };
|
|
284
|
+
return {
|
|
285
|
+
id: s.span_id,
|
|
286
|
+
name: label,
|
|
287
|
+
x: pos.x,
|
|
288
|
+
y: pos.y,
|
|
289
|
+
symbol,
|
|
290
|
+
symbolSize: isRoot ? 44 : 22 + Math.round((dur / maxDur) * 26),
|
|
291
|
+
itemStyle: {
|
|
292
|
+
color: hasError ? NODE_COLORS.error : spanColor(s),
|
|
293
|
+
borderWidth: isRoot ? 3 : dur > maxDur * 0.5 ? 2 : 1,
|
|
294
|
+
borderColor: hasError ? NODE_COLORS.error : 'rgba(240,246,252,0.25)',
|
|
295
|
+
shadowBlur: isRoot ? 12 : 4,
|
|
296
|
+
shadowColor: 'rgba(0,0,0,0.35)',
|
|
297
|
+
},
|
|
298
|
+
label: {
|
|
299
|
+
show: true,
|
|
300
|
+
position: 'bottom',
|
|
301
|
+
distance: 8,
|
|
302
|
+
fontSize: isRoot ? 12 : 11,
|
|
303
|
+
color: '#c9d1d9',
|
|
304
|
+
formatter: label,
|
|
305
|
+
backgroundColor: 'rgba(13,17,23,0.7)',
|
|
306
|
+
padding: [2, 6],
|
|
307
|
+
borderRadius: 3,
|
|
308
|
+
},
|
|
309
|
+
value: dur,
|
|
310
|
+
// Custom tooltip via series-level formatter below.
|
|
311
|
+
_meta: {
|
|
312
|
+
nodeId,
|
|
313
|
+
nodeType,
|
|
314
|
+
status,
|
|
315
|
+
dur,
|
|
316
|
+
isRoot,
|
|
317
|
+
error: s.attributes?.['error.message'],
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
const links = [];
|
|
322
|
+
for (const s of spans) {
|
|
323
|
+
if (s.parent_span_id && spanById.has(s.parent_span_id)) {
|
|
324
|
+
const isError = s.status_code === 'ERROR';
|
|
325
|
+
links.push({
|
|
326
|
+
source: s.parent_span_id,
|
|
327
|
+
target: s.span_id,
|
|
328
|
+
lineStyle: {
|
|
329
|
+
color: isError ? NODE_COLORS.error : '#6e7681',
|
|
330
|
+
width: isError ? 2 : 1.4,
|
|
331
|
+
curveness: 0.15,
|
|
332
|
+
opacity: 0.9,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
backgroundColor: 'transparent',
|
|
339
|
+
tooltip: {
|
|
340
|
+
trigger: 'item',
|
|
341
|
+
backgroundColor: '#161b22',
|
|
342
|
+
borderColor: '#30363d',
|
|
343
|
+
textStyle: { color: '#c9d1d9', fontSize: 12 },
|
|
344
|
+
formatter: (params) => {
|
|
345
|
+
const meta = params?.data?._meta;
|
|
346
|
+
if (!meta)
|
|
347
|
+
return '';
|
|
348
|
+
const lines = [
|
|
349
|
+
`<b>${meta.nodeId}</b>`,
|
|
350
|
+
`Type: ${meta.nodeType}`,
|
|
351
|
+
`Status: ${meta.status}`,
|
|
352
|
+
`Duration: ${formatDur(Number(meta.dur))}`,
|
|
353
|
+
];
|
|
354
|
+
if (meta.error) {
|
|
355
|
+
lines.push(`<span style="color:${NODE_COLORS.error}">Error: ${meta.error}</span>`);
|
|
356
|
+
}
|
|
357
|
+
return lines.join('<br/>');
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
series: [
|
|
361
|
+
{
|
|
362
|
+
type: 'graph',
|
|
363
|
+
// Layered DAG: positions are precomputed per-node, so 'none' gives
|
|
364
|
+
// us a stable, non-jittery layout (mirrors the static DAG samples
|
|
365
|
+
// in https://echarts.apache.org/examples/en/index.html#chart-type-graph).
|
|
366
|
+
layout: 'none',
|
|
367
|
+
roam: true,
|
|
368
|
+
draggable: true,
|
|
369
|
+
data: nodes,
|
|
370
|
+
links,
|
|
371
|
+
edgeSymbol: ['none', 'arrow'],
|
|
372
|
+
edgeSymbolSize: [0, 9],
|
|
373
|
+
lineStyle: { opacity: 0.9, curveness: 0.15 },
|
|
374
|
+
emphasis: {
|
|
375
|
+
focus: 'adjacency',
|
|
376
|
+
lineStyle: { width: 3, opacity: 1 },
|
|
377
|
+
label: { show: true },
|
|
378
|
+
},
|
|
379
|
+
labelLayout: { hideOverlap: true },
|
|
380
|
+
autoCurveness: true,
|
|
381
|
+
zoom: 1,
|
|
382
|
+
animation: true,
|
|
383
|
+
animationDuration: 400,
|
|
384
|
+
animationEasingUpdate: 'cubicOut',
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
export const TurnGraphChart = ({ serviceName, agentId, runUrl, apiKey, autoRefreshMs = 10_000, height = 320, style, }) => {
|
|
390
|
+
const [runs, setRuns] = useState([]);
|
|
391
|
+
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
392
|
+
const [loading, setLoading] = useState(false);
|
|
393
|
+
const [error, setError] = useState(null);
|
|
394
|
+
const mountedRef = useRef(true);
|
|
395
|
+
const upsertRunsFromSpans = useCallback((incoming, mode = 'merge') => {
|
|
396
|
+
const normalized = incoming.map(normalizeSpan);
|
|
397
|
+
const spansByTrace = new Map();
|
|
398
|
+
for (const span of normalized) {
|
|
399
|
+
if (!isGraphSpan(span))
|
|
400
|
+
continue;
|
|
401
|
+
if (agentId &&
|
|
402
|
+
span.attributes?.['agent.id'] &&
|
|
403
|
+
span.attributes['agent.id'] !== agentId) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const list = spansByTrace.get(span.trace_id) ?? [];
|
|
407
|
+
list.push(span);
|
|
408
|
+
spansByTrace.set(span.trace_id, list);
|
|
409
|
+
}
|
|
410
|
+
const incomingRuns = [];
|
|
411
|
+
for (const [traceId, spans] of spansByTrace.entries()) {
|
|
412
|
+
const root = spans.find(s => s.span_name === 'agent.graph.run') ?? spans[0];
|
|
413
|
+
if (!root)
|
|
414
|
+
continue;
|
|
415
|
+
incomingRuns.push({
|
|
416
|
+
traceId,
|
|
417
|
+
rootSpan: root,
|
|
418
|
+
spans,
|
|
419
|
+
startTime: new Date(root.start_time),
|
|
420
|
+
durationMs: root.duration_ms ?? 0,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
setRuns(prev => {
|
|
424
|
+
const merged = new Map();
|
|
425
|
+
if (mode === 'merge') {
|
|
426
|
+
for (const run of prev)
|
|
427
|
+
merged.set(run.traceId, run);
|
|
428
|
+
}
|
|
429
|
+
for (const run of incomingRuns)
|
|
430
|
+
merged.set(run.traceId, run);
|
|
431
|
+
const sorted = Array.from(merged.values()).sort((a, b) => b.startTime.getTime() - a.startTime.getTime());
|
|
432
|
+
return sorted.slice(0, 20);
|
|
433
|
+
});
|
|
434
|
+
setSelectedIdx(prev => Math.max(0, prev));
|
|
435
|
+
}, [agentId]);
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
mountedRef.current = true;
|
|
438
|
+
return () => {
|
|
439
|
+
mountedRef.current = false;
|
|
440
|
+
};
|
|
441
|
+
}, []);
|
|
442
|
+
const fetchData = useCallback(async () => {
|
|
443
|
+
if (!runUrl || !apiKey)
|
|
444
|
+
return;
|
|
445
|
+
setLoading(true);
|
|
446
|
+
setError(null);
|
|
447
|
+
try {
|
|
448
|
+
const client = createOtelClient({ baseUrl: runUrl, token: apiKey });
|
|
449
|
+
const result = await client.fetchTraces({ serviceName, limit: 200 });
|
|
450
|
+
if (mountedRef.current)
|
|
451
|
+
upsertRunsFromSpans(result.data, 'replace');
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
if (mountedRef.current) {
|
|
455
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
if (mountedRef.current)
|
|
460
|
+
setLoading(false);
|
|
461
|
+
}
|
|
462
|
+
}, [runUrl, apiKey, serviceName, upsertRunsFromSpans]);
|
|
463
|
+
const { connected: wsConnected, error: wsError } = useOtelWebSocket({
|
|
464
|
+
baseUrl: runUrl,
|
|
465
|
+
token: apiKey,
|
|
466
|
+
callbacks: {
|
|
467
|
+
onTraces: spans => {
|
|
468
|
+
upsertRunsFromSpans(spans, 'merge');
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
useEffect(() => {
|
|
473
|
+
void fetchData();
|
|
474
|
+
// WebSocket is the primary live feed; keep HTTP polling only as fallback.
|
|
475
|
+
if (autoRefreshMs > 0 && !wsConnected) {
|
|
476
|
+
const id = setInterval(() => void fetchData(), autoRefreshMs);
|
|
477
|
+
return () => clearInterval(id);
|
|
478
|
+
}
|
|
479
|
+
}, [fetchData, autoRefreshMs, wsConnected]);
|
|
480
|
+
const selectedRun = runs[selectedIdx];
|
|
481
|
+
const option = useMemo(() => (selectedRun ? buildOption(selectedRun) : null), [selectedRun]);
|
|
482
|
+
// Not configured — caller didn't pass auth.
|
|
483
|
+
if (!runUrl || !apiKey)
|
|
484
|
+
return null;
|
|
485
|
+
if (loading && runs.length === 0) {
|
|
486
|
+
return (_jsx(Box, { sx: { color: 'fg.muted', fontSize: 1, py: 2 }, children: "Loading OTEL traces\u2026" }));
|
|
487
|
+
}
|
|
488
|
+
if (error && runs.length === 0) {
|
|
489
|
+
return (_jsxs(Box, { sx: { color: 'danger.fg', fontSize: 0, py: 1 }, children: ["OTEL trace fetch failed: ", error] }));
|
|
490
|
+
}
|
|
491
|
+
if (!option) {
|
|
492
|
+
return (_jsx(Box, { sx: { color: 'fg.muted', fontSize: 1, py: 2 }, children: "No graph trace data yet \u2014 run a pydantic-graph agent to see execution turns here." }));
|
|
493
|
+
}
|
|
494
|
+
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 1 }, children: [runs.length > 1 && (_jsx(Box, { sx: {
|
|
495
|
+
display: 'flex',
|
|
496
|
+
gap: 1,
|
|
497
|
+
flexWrap: 'wrap',
|
|
498
|
+
mb: 1,
|
|
499
|
+
}, children: runs.slice(0, 8).map((run, idx) => (_jsxs(Box, { onClick: () => setSelectedIdx(idx), sx: {
|
|
500
|
+
px: 2,
|
|
501
|
+
py: '2px',
|
|
502
|
+
borderRadius: 2,
|
|
503
|
+
fontSize: 0,
|
|
504
|
+
cursor: 'pointer',
|
|
505
|
+
border: '1px solid',
|
|
506
|
+
borderColor: idx === selectedIdx ? 'accent.emphasis' : 'border.default',
|
|
507
|
+
bg: idx === selectedIdx ? 'accent.subtle' : 'canvas.subtle',
|
|
508
|
+
color: idx === selectedIdx ? 'accent.fg' : 'fg.muted',
|
|
509
|
+
userSelect: 'none',
|
|
510
|
+
whiteSpace: 'nowrap',
|
|
511
|
+
}, children: ["#", runs.length - idx, " \u00A0", run.startTime.toLocaleTimeString([], {
|
|
512
|
+
hour: '2-digit',
|
|
513
|
+
minute: '2-digit',
|
|
514
|
+
second: '2-digit',
|
|
515
|
+
}), run.durationMs ? ` (${formatDur(run.durationMs)})` : ''] }, run.traceId))) })), _jsx(Box, { sx: {
|
|
516
|
+
display: 'flex',
|
|
517
|
+
gap: 2,
|
|
518
|
+
flexWrap: 'wrap',
|
|
519
|
+
alignItems: 'center',
|
|
520
|
+
fontSize: 0,
|
|
521
|
+
color: 'fg.muted',
|
|
522
|
+
px: 1,
|
|
523
|
+
py: '2px',
|
|
524
|
+
}, "aria-label": "Graph node legend", children: LEGEND_ENTRIES.map(entry => (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, title: entry.description, children: [_jsx(Box, { "aria-hidden": true, sx: {
|
|
525
|
+
width: 10,
|
|
526
|
+
height: 10,
|
|
527
|
+
borderRadius: entry.type === 'decision'
|
|
528
|
+
? 0
|
|
529
|
+
: entry.type === 'broadcast' || entry.type === 'spread'
|
|
530
|
+
? '2px'
|
|
531
|
+
: '50%',
|
|
532
|
+
transform: entry.type === 'decision' ? 'rotate(45deg)' : 'none',
|
|
533
|
+
bg: NODE_COLORS[entry.type] ?? NODE_COLORS.default,
|
|
534
|
+
} }), _jsx(Text, { sx: { fontSize: 0 }, children: entry.label })] }, entry.type))) }), _jsx(ReactECharts, { option: option, style: { height, width: '100%', ...style }, opts: { renderer: 'svg' }, notMerge: false }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [selectedRun.spans.length - 1, " node(s)", selectedRun.durationMs
|
|
535
|
+
? ` · ${formatDur(selectedRun.durationMs)} total`
|
|
536
|
+
: '', wsConnected ? ' · ws live' : ' · ws disconnected', wsError ? ` · ws error: ${wsError}` : '', loading ? ' · refreshing…' : '', error ? ` · fetch error: ${error}` : ''] })] }));
|
|
537
|
+
};
|
|
538
|
+
export default TurnGraphChart;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared WebSocket connection pool for the OTEL service.
|
|
3
|
+
*
|
|
4
|
+
* Multiple chart components that subscribe to the same OTEL WS URL will
|
|
5
|
+
* share a single underlying WebSocket connection via reference counting.
|
|
6
|
+
*/
|
|
7
|
+
type OtelWsMessage = {
|
|
8
|
+
signal?: string;
|
|
9
|
+
data?: Array<Record<string, unknown>>;
|
|
10
|
+
count?: number;
|
|
11
|
+
};
|
|
12
|
+
type MessageListener = (msg: OtelWsMessage) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Subscribe to a shared OTEL WebSocket connection.
|
|
15
|
+
*
|
|
16
|
+
* Returns an unsubscribe function. When the last subscriber unsubscribes,
|
|
17
|
+
* the underlying WebSocket is closed and removed from the pool.
|
|
18
|
+
*/
|
|
19
|
+
export declare function subscribeOtelWs(wsUrl: string, listener: MessageListener): () => void;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
const pool = new Map();
|
|
6
|
+
function connectEntry(entry) {
|
|
7
|
+
if (entry.disposed || entry.listeners.size === 0)
|
|
8
|
+
return;
|
|
9
|
+
const ws = new WebSocket(entry.url);
|
|
10
|
+
ws.onmessage = (event) => {
|
|
11
|
+
try {
|
|
12
|
+
const msg = JSON.parse(event.data);
|
|
13
|
+
for (const listener of entry.listeners) {
|
|
14
|
+
listener(msg);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Ignore unparseable messages.
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
ws.onerror = () => { };
|
|
22
|
+
ws.onclose = () => {
|
|
23
|
+
entry.ws = null;
|
|
24
|
+
if (entry.disposed || entry.listeners.size === 0)
|
|
25
|
+
return;
|
|
26
|
+
entry.reconnectTimer = setTimeout(() => connectEntry(entry), 2000);
|
|
27
|
+
};
|
|
28
|
+
entry.ws = ws;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to a shared OTEL WebSocket connection.
|
|
32
|
+
*
|
|
33
|
+
* Returns an unsubscribe function. When the last subscriber unsubscribes,
|
|
34
|
+
* the underlying WebSocket is closed and removed from the pool.
|
|
35
|
+
*/
|
|
36
|
+
export function subscribeOtelWs(wsUrl, listener) {
|
|
37
|
+
let entry = pool.get(wsUrl);
|
|
38
|
+
if (!entry) {
|
|
39
|
+
entry = {
|
|
40
|
+
ws: null,
|
|
41
|
+
listeners: new Set(),
|
|
42
|
+
reconnectTimer: null,
|
|
43
|
+
disposed: false,
|
|
44
|
+
url: wsUrl,
|
|
45
|
+
};
|
|
46
|
+
pool.set(wsUrl, entry);
|
|
47
|
+
}
|
|
48
|
+
entry.listeners.add(listener);
|
|
49
|
+
// If the WS isn't connected yet (first subscriber or after full teardown),
|
|
50
|
+
// start the connection.
|
|
51
|
+
if (!entry.ws && !entry.disposed) {
|
|
52
|
+
connectEntry(entry);
|
|
53
|
+
}
|
|
54
|
+
return () => {
|
|
55
|
+
if (!entry)
|
|
56
|
+
return;
|
|
57
|
+
entry.listeners.delete(listener);
|
|
58
|
+
if (entry.listeners.size === 0) {
|
|
59
|
+
entry.disposed = true;
|
|
60
|
+
if (entry.reconnectTimer) {
|
|
61
|
+
clearTimeout(entry.reconnectTimer);
|
|
62
|
+
entry.reconnectTimer = null;
|
|
63
|
+
}
|
|
64
|
+
entry.ws?.close();
|
|
65
|
+
entry.ws = null;
|
|
66
|
+
pool.delete(wsUrl);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A2UiComponentGalleryExample
|
|
3
|
-
*
|
|
4
|
-
* Showcases the full catalog of A2UI components rendered via @a2ui/react.
|
|
5
|
-
* Uses static A2UI JSON messages (no backend required) to demonstrate:
|
|
6
|
-
* - Text, Button, CheckBox, Slider, TextField, DateTimeInput
|
|
7
|
-
* - MultipleChoice (default, chips, filterable)
|
|
8
|
-
* - Row, Column, Card, List, Tabs, Divider, Modal
|
|
9
|
-
* - Icon, Image, Video, AudioPlayer
|
|
10
|
-
*
|
|
11
|
-
* All messages follow the A2UI three-step pattern:
|
|
12
|
-
* beginRendering → surfaceUpdate → dataModelUpdate
|
|
13
|
-
*/
|
|
14
1
|
import React from 'react';
|
|
15
|
-
/**
|
|
16
|
-
* A2UiComponentGalleryExample — Kitchen sink of all A2UI components.
|
|
17
|
-
* No backend required — uses static A2UI JSON messages rendered client-side.
|
|
18
|
-
*/
|
|
19
2
|
declare const A2UiComponentGalleryExample: React.FC;
|
|
20
3
|
export default A2UiComponentGalleryExample;
|