@datalayer/agent-runtimes 1.3.35 → 1.3.36

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.
Files changed (48) hide show
  1. package/lib/chat/ChatFloating.d.ts +8 -2
  2. package/lib/chat/ChatFloating.js +2 -2
  3. package/lib/chat/base/ChatBase.js +10 -6
  4. package/lib/chat/display/EmptyState.js +8 -1
  5. package/lib/chat/presence/Presence.d.ts +15 -1
  6. package/lib/chat/presence/Presence.js +0 -13
  7. package/lib/components/a2ui/datalayer/Chart.d.ts +132 -0
  8. package/lib/components/a2ui/datalayer/Chart.js +173 -0
  9. package/lib/components/a2ui/datalayer/Chat.d.ts +21 -0
  10. package/lib/components/a2ui/datalayer/Chat.js +116 -0
  11. package/lib/components/a2ui/datalayer/Evidence.d.ts +22 -0
  12. package/lib/components/a2ui/datalayer/Evidence.js +73 -0
  13. package/lib/components/a2ui/datalayer/FileUpload.d.ts +22 -0
  14. package/lib/components/a2ui/datalayer/FileUpload.js +107 -0
  15. package/lib/components/a2ui/datalayer/Form.d.ts +12 -0
  16. package/lib/components/a2ui/datalayer/Form.js +46 -0
  17. package/lib/components/a2ui/datalayer/Table.d.ts +20 -0
  18. package/lib/components/a2ui/datalayer/Table.js +103 -0
  19. package/lib/components/a2ui/datalayer/drawn.d.ts +10 -0
  20. package/lib/components/a2ui/datalayer/drawn.js +20 -0
  21. package/lib/components/a2ui/datalayer/implementation.d.ts +17 -0
  22. package/lib/components/a2ui/datalayer/implementation.js +10 -0
  23. package/lib/components/a2ui/datalayer/index.d.ts +23 -0
  24. package/lib/components/a2ui/datalayer/index.js +44 -0
  25. package/lib/components/a2ui/datalayer/parts.d.ts +32 -0
  26. package/lib/components/a2ui/datalayer/parts.js +44 -0
  27. package/lib/components/a2ui/datalayer/schema.d.ts +34 -0
  28. package/lib/components/a2ui/datalayer/schema.js +108 -0
  29. package/lib/components/a2ui/index.d.ts +1 -0
  30. package/lib/components/a2ui/index.js +1 -0
  31. package/lib/components/a2ui/visibleWhen.d.ts +4 -2
  32. package/lib/components/a2ui/visibleWhen.js +6 -3
  33. package/lib/loop/apps/AppRenderer.d.ts +8 -0
  34. package/lib/loop/apps/AppRenderer.js +21 -1
  35. package/lib/loop/embed/AppEmbed.d.ts +16 -1
  36. package/lib/loop/embed/AppEmbed.js +38 -9
  37. package/lib/loop/embed/LoopEmbed.js +2 -0
  38. package/lib/loop/embed/element.js +10 -1
  39. package/lib/loop/embed/embedTheme.d.ts +12 -0
  40. package/lib/loop/embed/embedTheme.js +15 -0
  41. package/lib/loop/plugins/assistant-characters/index.d.ts +10 -1
  42. package/lib/loop/plugins/assistant-characters/index.js +16 -1
  43. package/lib/loop/plugins/chat/ChatView.js +21 -9
  44. package/lib/loop/plugins/chat/index.d.ts +6 -0
  45. package/lib/loop/presets.d.ts +3 -0
  46. package/lib/loop/presets.js +2 -1
  47. package/lib/types/chat.d.ts +11 -0
  48. package/package.json +2 -1
@@ -14,7 +14,7 @@
14
14
  import React from 'react';
15
15
  import { type AssistantCharacter } from './assistant/characters';
16
16
  import type { AssistantCharacterData } from './assistant/formats/types';
17
- import type { ChatCommonProps, ChatViewMode } from '../types';
17
+ import type { ChatCommonProps, ChatViewMode, ThemeOverrides } from '../types';
18
18
  /**
19
19
  * ChatFloating props — extends ChatCommonProps with floating/popup-specific configuration.
20
20
  */
@@ -28,6 +28,12 @@ export interface ChatFloatingProps extends ChatCommonProps {
28
28
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
29
29
  /** Default open state */
30
30
  defaultOpen?: boolean;
31
+ /**
32
+ * Properties laid over the theme the conversation wears, by mode — an
33
+ * application's accent (LOOP T-05): the chat sets its theme again inside
34
+ * it, so the accent the page around it set does not reach its bubbles.
35
+ */
36
+ themeOverrides?: ThemeOverrides;
31
37
  /** width */
32
38
  width?: number | string;
33
39
  /** height */
@@ -109,5 +115,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
109
115
  * ChatFloating component
110
116
  * A floating chat window built on ChatBase
111
117
  */
112
- export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, }: ChatFloatingProps): React.JSX.Element;
118
+ export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, }: ChatFloatingProps): React.JSX.Element;
113
119
  export default ChatFloating;
@@ -59,7 +59,7 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
59
59
  * ChatFloating component
60
60
  * A floating chat window built on ChatBase
61
61
  */
62
- export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, }) {
62
+ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, }) {
63
63
  // Store-based state
64
64
  const storeIsOpen = useChatOpen();
65
65
  const storeMessages = useChatMessages();
@@ -698,7 +698,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
698
698
  borderColor: 'border.muted',
699
699
  touchAction: 'none',
700
700
  '&:active': { cursor: 'grabbing' },
701
- }, children: _jsx(GrabberIcon, { size: 16 }) })), _jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, themeVariant: themeVariant, colorMode: colorMode, autoFocus: isOpen, focusTrigger: focusTrigger, launching: launching, launchingMessage: launchingMessage, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
701
+ }, children: _jsx(GrabberIcon, { size: 16 }) })), _jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, themeVariant: themeVariant, themeOverrides: themeOverrides, colorMode: colorMode, autoFocus: isOpen, focusTrigger: focusTrigger, launching: launching, launchingMessage: launchingMessage, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
702
702
  showNewChat: showNewChatButton,
703
703
  showClear: showClearButton && messages.length > 0,
704
704
  showSettings: showSettingsButton && !!onSettingsClick,
@@ -141,7 +141,7 @@ const ThemedJupyterReactTheme = JupyterReactTheme;
141
141
  * chat height/flex chain is preserved); CSS custom properties and inherited
142
142
  * properties (color, font) still cascade through it.
143
143
  */
144
- function ThemedChatBoundary({ children, themeVariant, colorMode, }) {
144
+ function ThemedChatBoundary({ children, themeVariant, colorMode, themeOverrides, }) {
145
145
  const storeColorMode = useThemeStore(s => s.colorMode);
146
146
  const storeThemeVariant = useThemeStore(s => s.theme);
147
147
  const resolvedColorMode = colorMode ?? storeColorMode;
@@ -149,9 +149,13 @@ function ThemedChatBoundary({ children, themeVariant, colorMode, }) {
149
149
  const systemMode = useSystemColorMode();
150
150
  const themeConfig = getThemeConfig(resolvedThemeVariant);
151
151
  const resolvedMode = resolvedColorMode === 'auto' ? systemMode : resolvedColorMode;
152
- const modeStyles = resolvedMode === 'dark'
153
- ? themeConfig.themeStyles.dark
154
- : themeConfig.themeStyles.light;
152
+ const modeStyles = {
153
+ ...(resolvedMode === 'dark'
154
+ ? themeConfig.themeStyles.dark
155
+ : themeConfig.themeStyles.light),
156
+ // What the host lays over the theme: an application's accent (T-05).
157
+ ...themeOverrides?.[resolvedMode],
158
+ };
155
159
  const themeBackground = modeStyles.backgroundColor ?? '';
156
160
  return (_jsx(ThemedJupyterReactTheme, { colormode: resolvedMode, theme: themeConfig.primerTheme, backgroundColor: themeBackground, loadJupyterLabCss: false, style: {
157
161
  ...modeStyles,
@@ -463,7 +467,7 @@ function parseApprovedMcpToolsByServer(mcpStatusData) {
463
467
  * ChatBase component — Universal chat panel supporting store, protocol, and custom modes.
464
468
  */
465
469
  export function ChatBase(props) {
466
- const { agentRuntimeConfig, protocol: protocolProp, useStore: useStoreMode = true, disableInternalJupyterTheme = false, themeVariant, colorMode, } = props;
470
+ const { agentRuntimeConfig, protocol: protocolProp, useStore: useStoreMode = true, disableInternalJupyterTheme = false, themeVariant, colorMode, themeOverrides, } = props;
467
471
  // Resolve protocol: string Protocol overrides type in agentRuntimeConfig or
468
472
  // is combined with a full ProtocolConfig object.
469
473
  const protocolType = typeof protocolProp === 'string' ? protocolProp : undefined;
@@ -490,7 +494,7 @@ export function ChatBase(props) {
490
494
  useStore: effectiveUseStoreMode,
491
495
  };
492
496
  const content = _jsx(ChatBaseInner, { ...innerProps });
493
- const wrappedContent = disableInternalJupyterTheme ? (content) : (_jsx(ThemedChatBoundary, { themeVariant: themeVariant, colorMode: colorMode, children: content }));
497
+ const wrappedContent = disableInternalJupyterTheme ? (content) : (_jsx(ThemedChatBoundary, { themeVariant: themeVariant, colorMode: colorMode, themeOverrides: themeOverrides, children: content }));
494
498
  if (!existingQueryClient) {
495
499
  return (_jsx(QueryClientProvider, { client: internalQueryClient, children: wrappedContent }));
496
500
  }
@@ -60,7 +60,14 @@ export function ChatEmptyState({ emptyState, brandIcon, description, suggestions
60
60
  color: 'var(--button-primary-fgColor-rest)',
61
61
  borderColor: 'accent.emphasis',
62
62
  },
63
- }, onClick: () => handleSuggestionClick(suggestion), children: _jsx(Truncate, { inline: true, title: suggestion.message, maxWidth: "100%", sx: { minWidth: 0 }, children: suggestion.title }) }, index))) }));
63
+ }, onClick: () => handleSuggestionClick(suggestion),
64
+ // A chip is a button to the keyboard as well as to the pointer.
65
+ role: "button", tabIndex: 0, onKeyDown: (event) => {
66
+ if (event.key === 'Enter' || event.key === ' ') {
67
+ event.preventDefault();
68
+ handleSuggestionClick(suggestion);
69
+ }
70
+ }, children: _jsx(Truncate, { inline: true, title: suggestion.message, maxWidth: "100%", sx: { minWidth: 0 }, children: suggestion.title }) }, index))) }));
64
71
  return (_jsx(ThemeProvider, { children: _jsxs(Box, { sx: {
65
72
  display: 'flex',
66
73
  flexDirection: 'column',
@@ -1,6 +1,20 @@
1
+ /**
2
+ * An application's presence in its chat (LOOP T-08): its face, with a quiet
3
+ * motion around it while it thinks or works, and one line saying what it is
4
+ * doing. The face is never redrawn; the motion is a soft ring in the accent,
5
+ * still under `prefers-reduced-motion`, and the line changes at the theme's
6
+ * status pace (T-10).
7
+ *
8
+ * @module chat/presence/Presence
9
+ */
10
+ import type { ReactNode } from 'react';
1
11
  import { type PresenceState } from './presenceStatus';
2
12
  export declare function PresenceFace({ face, size, state, }: {
3
- face: string;
13
+ /**
14
+ * The face: an emoji, drawn at `size`, or a host's own drawing of it — an
15
+ * avatar on its disc (I-07) — as wide as `size`.
16
+ */
17
+ face: ReactNode;
4
18
  size: number;
5
19
  state: PresenceState;
6
20
  }): import("react").JSX.Element;
@@ -1,17 +1,4 @@
1
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
- * An application's presence in its chat (LOOP T-08): its face, with a quiet
8
- * motion around it while it thinks or works, and one line saying what it is
9
- * doing. The face is never redrawn; the motion is a soft ring in the accent,
10
- * still under `prefers-reduced-motion`, and the line changes at the theme's
11
- * status pace (T-10).
12
- *
13
- * @module chat/presence/Presence
14
- */
15
2
  import { Box, Text } from '@primer/react';
16
3
  import { PRESENCE_LINES } from './presenceStatus';
17
4
  const ACCENT = 'var(--loop-accent, var(--fgColor-accent))';
@@ -0,0 +1,132 @@
1
+ import { type OwnCommon } from './implementation';
2
+ export type ChartKind = 'bar' | 'line' | 'scatter' | 'area';
3
+ export type ChartProps = OwnCommon & {
4
+ title?: string;
5
+ kind: ChartKind;
6
+ x: string;
7
+ y: string;
8
+ series?: string;
9
+ points?: unknown;
10
+ };
11
+ /** The page's colours, read from Primer's variables where the chart is drawn. */
12
+ export type ChartColours = {
13
+ series: string[];
14
+ text: string;
15
+ muted: string;
16
+ grid: string;
17
+ };
18
+ /** The points a chart can draw, or why it cannot. */
19
+ export declare function chartPoints(points: unknown): {
20
+ points: Record<string, unknown>[];
21
+ } | {
22
+ problem: string;
23
+ };
24
+ /** The series a chart draws: one per value of `series`, or one. */
25
+ export declare function chartSeries(points: Record<string, unknown>[], props: Pick<ChartProps, 'x' | 'y' | 'series'>): {
26
+ name: string;
27
+ points: Record<string, unknown>[];
28
+ }[];
29
+ /**
30
+ * The ECharts option a chart draws: categories along the bottom for a bar,
31
+ * a line or an area (in the order the points give them), numbers both ways
32
+ * for a scatter.
33
+ */
34
+ export declare function chartOption(points: Record<string, unknown>[], props: Pick<ChartProps, 'kind' | 'x' | 'y' | 'series'>, colours: ChartColours): {
35
+ aria: {
36
+ enabled: boolean;
37
+ };
38
+ animation: boolean;
39
+ color: string[];
40
+ textStyle: {
41
+ color: string;
42
+ };
43
+ grid: {
44
+ left: number;
45
+ right: number;
46
+ top: number;
47
+ bottom: number;
48
+ };
49
+ legend: {
50
+ top: number;
51
+ textStyle: {
52
+ color: string;
53
+ };
54
+ } | undefined;
55
+ tooltip: {
56
+ trigger: string;
57
+ };
58
+ xAxis: {
59
+ axisLine: {
60
+ lineStyle: {
61
+ color: string;
62
+ };
63
+ };
64
+ axisLabel: {
65
+ color: string;
66
+ };
67
+ splitLine: {
68
+ lineStyle: {
69
+ color: string;
70
+ };
71
+ };
72
+ nameTextStyle: {
73
+ color: string;
74
+ };
75
+ type: string;
76
+ name: string;
77
+ nameLocation: string;
78
+ nameGap: number;
79
+ } | {
80
+ axisLine: {
81
+ lineStyle: {
82
+ color: string;
83
+ };
84
+ };
85
+ axisLabel: {
86
+ color: string;
87
+ };
88
+ splitLine: {
89
+ lineStyle: {
90
+ color: string;
91
+ };
92
+ };
93
+ nameTextStyle: {
94
+ color: string;
95
+ };
96
+ type: string;
97
+ data: string[];
98
+ name: string;
99
+ nameLocation: string;
100
+ nameGap: number;
101
+ };
102
+ yAxis: {
103
+ axisLine: {
104
+ lineStyle: {
105
+ color: string;
106
+ };
107
+ };
108
+ axisLabel: {
109
+ color: string;
110
+ };
111
+ splitLine: {
112
+ lineStyle: {
113
+ color: string;
114
+ };
115
+ };
116
+ nameTextStyle: {
117
+ color: string;
118
+ };
119
+ type: string;
120
+ name: string;
121
+ };
122
+ series: {
123
+ name: string;
124
+ type: "line" | "bar" | "scatter";
125
+ areaStyle: {} | undefined;
126
+ data: number[][] | (number | null)[];
127
+ }[];
128
+ };
129
+ export declare function ChartView({ props }: {
130
+ props: ChartProps;
131
+ }): import("react").JSX.Element;
132
+ export declare const Chart: import("@a2ui/react/v0_9").ReactComponentImplementation;
@@ -0,0 +1,173 @@
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
+ * Chart (LOOP C-18): the points an application publishes, drawn as a bar, a
8
+ * line, an area or a scatter — one field along the bottom, one measured, a
9
+ * series apart for each value of a third. Drawn with ECharts, the chart
10
+ * library agent-runtimes already draws its usage and context charts with,
11
+ * in the page's colours (read from Primer's variables, so the chart follows
12
+ * the theme and the colour mode). Its numbers are also a table under *The
13
+ * numbers*, reached by keyboard and read by assistive technologies, which a
14
+ * drawing is not.
15
+ *
16
+ * @module components/a2ui/datalayer/Chart
17
+ */
18
+ import { useLayoutEffect, useMemo, useRef, useState } from 'react';
19
+ import ReactECharts from 'echarts-for-react';
20
+ import { Text } from '@primer/react';
21
+ import { Box } from '@datalayer/primer-addons';
22
+ import { BlockFrame, Problem, Quiet, asWords, isRecord } from './parts';
23
+ import { ownImplementation } from './implementation';
24
+ /** Primer's light colours, for a page that sets no variables. */
25
+ const FALLBACK = {
26
+ series: ['#0969da', '#1a7f37', '#9a6700', '#8250df', '#cf222e', '#bc4c00'],
27
+ text: '#1f2328',
28
+ muted: '#59636e',
29
+ grid: '#d1d9e0',
30
+ };
31
+ const SERIES_VARIABLES = [
32
+ '--fgColor-accent',
33
+ '--fgColor-success',
34
+ '--fgColor-attention',
35
+ '--fgColor-done',
36
+ '--fgColor-danger',
37
+ '--fgColor-severe',
38
+ ];
39
+ function readColours(element) {
40
+ if (!element) {
41
+ return FALLBACK;
42
+ }
43
+ const style = getComputedStyle(element);
44
+ const read = (name, fallback) => style.getPropertyValue(name).trim() || fallback;
45
+ return {
46
+ series: SERIES_VARIABLES.map((name, index) => read(name, FALLBACK.series[index])),
47
+ text: read('--fgColor-default', FALLBACK.text),
48
+ muted: read('--fgColor-muted', FALLBACK.muted),
49
+ grid: read('--borderColor-muted', FALLBACK.grid),
50
+ };
51
+ }
52
+ /** The points a chart can draw, or why it cannot. */
53
+ export function chartPoints(points) {
54
+ if (points === undefined || points === null) {
55
+ return { points: [] };
56
+ }
57
+ if (!Array.isArray(points) || points.some(point => !isRecord(point))) {
58
+ return { problem: 'What it shows is not a list of points.' };
59
+ }
60
+ return { points: points };
61
+ }
62
+ /** The series a chart draws: one per value of `series`, or one. */
63
+ export function chartSeries(points, props) {
64
+ if (!props.series) {
65
+ return [{ name: props.y, points }];
66
+ }
67
+ const names = [];
68
+ for (const point of points) {
69
+ const name = asWords(point[props.series]);
70
+ if (!names.includes(name)) {
71
+ names.push(name);
72
+ }
73
+ }
74
+ return names.map(name => ({
75
+ name,
76
+ points: points.filter(point => asWords(point[props.series]) === name),
77
+ }));
78
+ }
79
+ /**
80
+ * The ECharts option a chart draws: categories along the bottom for a bar,
81
+ * a line or an area (in the order the points give them), numbers both ways
82
+ * for a scatter.
83
+ */
84
+ export function chartOption(points, props, colours) {
85
+ const series = chartSeries(points, props);
86
+ const scatter = props.kind === 'scatter';
87
+ const categories = [];
88
+ for (const point of points) {
89
+ const name = asWords(point[props.x]);
90
+ if (!categories.includes(name)) {
91
+ categories.push(name);
92
+ }
93
+ }
94
+ const axis = {
95
+ axisLine: { lineStyle: { color: colours.grid } },
96
+ axisLabel: { color: colours.muted },
97
+ splitLine: { lineStyle: { color: colours.grid } },
98
+ nameTextStyle: { color: colours.muted },
99
+ };
100
+ return {
101
+ aria: { enabled: true },
102
+ animation: false,
103
+ color: colours.series,
104
+ textStyle: { color: colours.text },
105
+ grid: { left: 48, right: 16, top: series.length > 1 ? 48 : 32, bottom: 40 },
106
+ legend: series.length > 1
107
+ ? { top: 0, textStyle: { color: colours.text } }
108
+ : undefined,
109
+ tooltip: { trigger: scatter ? 'item' : 'axis' },
110
+ xAxis: scatter
111
+ ? {
112
+ type: 'value',
113
+ name: props.x,
114
+ nameLocation: 'middle',
115
+ nameGap: 26,
116
+ ...axis,
117
+ }
118
+ : {
119
+ type: 'category',
120
+ data: categories,
121
+ name: props.x,
122
+ nameLocation: 'middle',
123
+ nameGap: 26,
124
+ ...axis,
125
+ },
126
+ yAxis: { type: 'value', name: props.y, ...axis },
127
+ series: series.map(one => ({
128
+ name: one.name,
129
+ type: props.kind === 'area' ? 'line' : props.kind,
130
+ areaStyle: props.kind === 'area' ? {} : undefined,
131
+ data: scatter
132
+ ? one.points.map(point => [
133
+ Number(point[props.x]),
134
+ Number(point[props.y]),
135
+ ])
136
+ : categories.map(category => {
137
+ const point = one.points.find(candidate => asWords(candidate[props.x]) === category);
138
+ return point ? Number(point[props.y]) : null;
139
+ }),
140
+ })),
141
+ };
142
+ }
143
+ export function ChartView({ props }) {
144
+ const { title, kind, x, y, series } = props;
145
+ const read = useMemo(() => chartPoints(props.points), [props.points]);
146
+ const frame = useRef(null);
147
+ const [colours, setColours] = useState(FALLBACK);
148
+ useLayoutEffect(() => {
149
+ setColours(readColours(frame.current));
150
+ }, []);
151
+ const points = 'points' in read ? read.points : [];
152
+ const option = useMemo(() => chartOption(points, { kind, x, y, series }, colours), [points, kind, x, y, series, colours]);
153
+ const columns = series ? [x, series, y] : [x, y];
154
+ const label = `${title || 'Chart'}: ${y} by ${x}, as a ${kind}`;
155
+ return (_jsx(BlockFrame, { title: title, label: "Chart", weight: props.weight, testId: "a2ui-chart", children: _jsx(Box, { ref: frame, children: 'problem' in read ? (_jsx(Problem, { children: read.problem })) : points.length === 0 ? (_jsx(Quiet, { children: "No points yet." })) : (_jsxs(Box, { as: "figure", "aria-label": label, sx: { m: 0 }, children: [_jsx("div", { "aria-hidden": "true", children: _jsx(ReactECharts, { option: option, style: { height: 240, width: '100%' }, opts: { renderer: 'svg' }, notMerge: true, lazyUpdate: true }) }), _jsxs(Box, { as: "details", sx: { fontSize: 1 }, children: [_jsx(Box, { as: "summary", sx: {
156
+ cursor: 'pointer',
157
+ color: 'fg.muted',
158
+ '&:focus-visible': {
159
+ outline: '2px solid',
160
+ outlineColor: 'accent.fg',
161
+ },
162
+ }, children: _jsx(Text, { children: "The numbers" }) }), _jsxs(Box, { as: "table", sx: {
163
+ mt: 1,
164
+ borderCollapse: 'collapse',
165
+ '& th, & td': {
166
+ textAlign: 'left',
167
+ pr: 3,
168
+ py: '2px',
169
+ },
170
+ '& th': { color: 'fg.muted', fontWeight: 'semibold' },
171
+ }, children: [_jsx("thead", { children: _jsx("tr", { children: columns.map(column => (_jsx("th", { scope: "col", children: column }, column))) }) }), _jsx("tbody", { children: points.map((point, index) => (_jsx("tr", { children: columns.map(column => (_jsx("td", { children: asWords(point[column]) }, column))) }, index))) })] })] })] })) }) }));
172
+ }
173
+ export const Chart = ownImplementation('Chart', ChartView);
@@ -0,0 +1,21 @@
1
+ import type { DisplayItem } from '../../../types/chat';
2
+ import { type OwnCommon } from './implementation';
3
+ export type ChatProps = OwnCommon & {
4
+ welcome?: string;
5
+ placeholder?: string;
6
+ starters?: string[];
7
+ show_tools?: boolean;
8
+ messages?: unknown;
9
+ message?: unknown;
10
+ setMessage: (message: string) => void;
11
+ };
12
+ /** The transcript a chat draws from what is published, or why it cannot. */
13
+ export declare function chatItems(messages: unknown, showTools: boolean): {
14
+ items: DisplayItem[];
15
+ } | {
16
+ problem: string;
17
+ };
18
+ export declare function ChatView({ props }: {
19
+ props: ChatProps;
20
+ }): import("react").JSX.Element;
21
+ export declare const Chat: import("@a2ui/react/v0_9").ReactComponentImplementation;
@@ -0,0 +1,116 @@
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
+ * Chat (LOOP C-18): the conversation with the application, drawn by the
8
+ * chat's own parts — its transcript (`ChatMessageList`, the one every chat
9
+ * draws its turns with), its welcome and starters (`ChatEmptyState`) and its
10
+ * composer (`InputPromptText`) — from what the application publishes.
11
+ *
12
+ * It shows the messages its `messages` binding points at, each
13
+ * `{role: 'user' | 'assistant', text}`, or `{role: 'tool', name, args,
14
+ * result}` for a tool call, shown only while `show_tools` holds. Before the
15
+ * first message it says its welcome and offers its starters. A message
16
+ * written in the composer — or a starter chosen — is written where its
17
+ * `message` binding points, then its action is dispatched (the application's
18
+ * `send`), so the action's context reads the message just written, as a
19
+ * basic input's action reads its value.
20
+ *
21
+ * @module components/a2ui/datalayer/Chat
22
+ */
23
+ import { createRef, useMemo, useState } from 'react';
24
+ import { IconButton } from '@primer/react';
25
+ import { DependabotIcon, PaperAirplaneIcon, PersonIcon, } from '@primer/octicons-react';
26
+ import { Box } from '@datalayer/primer-addons';
27
+ import { ChatMessageList } from '../../../chat/messages/ChatMessageList';
28
+ import { ChatEmptyState } from '../../../chat/display/EmptyState';
29
+ import { InputPromptText } from '../../../chat/prompt/InputPromptText';
30
+ import { BlockFrame, CARD_RADIUS, Problem, asWords, isRecord } from './parts';
31
+ import { ownImplementation } from './implementation';
32
+ /** The defaults the catalog gives. */
33
+ const PLACEHOLDER = 'Ask anything';
34
+ const AVATARS = {
35
+ userAvatar: _jsx(PersonIcon, { size: 14 }),
36
+ assistantAvatar: _jsx(DependabotIcon, { size: 14 }),
37
+ showAvatars: true,
38
+ avatarSize: 24,
39
+ userAvatarBg: 'neutral.muted',
40
+ assistantAvatarBg: 'accent.emphasis',
41
+ };
42
+ /** The epoch: a published message carries no time, and the transcript shows none. */
43
+ const NO_TIME = new Date(0);
44
+ /** The transcript a chat draws from what is published, or why it cannot. */
45
+ export function chatItems(messages, showTools) {
46
+ if (messages === undefined || messages === null) {
47
+ return { items: [] };
48
+ }
49
+ if (!Array.isArray(messages)) {
50
+ return { problem: 'What it shows is not a list of messages.' };
51
+ }
52
+ const items = [];
53
+ for (const [index, message] of messages.entries()) {
54
+ const id = `m${index}`;
55
+ if (!isRecord(message)) {
56
+ return { problem: `Message ${index + 1} is not a message.` };
57
+ }
58
+ if (message.role === 'tool') {
59
+ if (showTools) {
60
+ items.push({
61
+ id,
62
+ type: 'tool-call',
63
+ toolCallId: id,
64
+ toolName: asWords(message.name) || 'tool',
65
+ args: isRecord(message.args) ? message.args : {},
66
+ result: message.result,
67
+ status: 'complete',
68
+ });
69
+ }
70
+ continue;
71
+ }
72
+ if (message.role !== 'user' && message.role !== 'assistant') {
73
+ return {
74
+ problem: `Message ${index + 1} is from ${asWords(message.role) || 'nobody'}: a message is the user's, the assistant's or a tool's.`,
75
+ };
76
+ }
77
+ items.push({
78
+ id,
79
+ role: message.role,
80
+ content: asWords(message.text ?? message.content),
81
+ createdAt: NO_TIME,
82
+ });
83
+ }
84
+ return { items };
85
+ }
86
+ export function ChatView({ props }) {
87
+ const { welcome, starters = [], setMessage, action } = props;
88
+ const placeholder = props.placeholder ?? PLACEHOLDER;
89
+ const showTools = props.show_tools ?? true;
90
+ const read = useMemo(() => chatItems(props.messages, showTools), [props.messages, showTools]);
91
+ const [draft, setDraft] = useState('');
92
+ const messagesEnd = useMemo(() => createRef(), []);
93
+ const inputRef = useMemo(() => createRef(), []);
94
+ const send = (text) => {
95
+ const message = text.trim();
96
+ if (!message) {
97
+ return;
98
+ }
99
+ setMessage(message);
100
+ action?.();
101
+ setDraft('');
102
+ };
103
+ return (_jsxs(BlockFrame, { label: "Chat", weight: props.weight, testId: "a2ui-chat", children: ['problem' in read ? (_jsx(Problem, { children: read.problem })) : (_jsx(Box, { sx: {
104
+ maxHeight: 420,
105
+ overflowY: 'auto',
106
+ borderRadius: CARD_RADIUS,
107
+ }, children: _jsx(ChatMessageList, { displayItems: read.items, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: AVATARS, padding: 2, emptyContent: _jsx(ChatEmptyState, { description: "", emptyState: { title: welcome || 'Start a conversation' }, suggestions: starters.map(starter => ({
108
+ title: starter,
109
+ message: starter,
110
+ })), onSuggestionSubmit: suggestion => send(suggestion.message) }), messagesEndRef: messagesEnd, onRespond: async () => undefined }) })), _jsxs(Box, { sx: {
111
+ display: 'flex',
112
+ alignItems: 'flex-end',
113
+ gap: 2,
114
+ }, children: [_jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: _jsx(InputPromptText, { value: draft, onChange: setDraft, placeholder: placeholder, onSubmit: () => send(draft), inputRef: inputRef }) }), _jsx(IconButton, { icon: PaperAirplaneIcon, "aria-label": "Send", variant: "primary", disabled: !draft.trim(), onClick: () => send(draft) })] })] }));
115
+ }
116
+ export const Chat = ownImplementation('Chat', ChatView);
@@ -0,0 +1,22 @@
1
+ import { type OwnCommon } from './implementation';
2
+ export type EvidenceProps = OwnCommon & {
3
+ title?: string;
4
+ show_passages?: boolean;
5
+ max_items?: number;
6
+ sources?: unknown;
7
+ };
8
+ export type Source = {
9
+ title: string;
10
+ url: string;
11
+ passage: string;
12
+ };
13
+ /** The sources a block can show, or why it cannot. */
14
+ export declare function evidenceSources(sources: unknown): {
15
+ sources: Source[];
16
+ } | {
17
+ problem: string;
18
+ };
19
+ export declare function EvidenceView({ props }: {
20
+ props: EvidenceProps;
21
+ }): import("react").JSX.Element;
22
+ export declare const Evidence: import("@a2ui/react/v0_9").ReactComponentImplementation;