@datalayer/agent-runtimes 1.3.71 → 1.3.72

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 (44) hide show
  1. package/lib/chat/ChatFloating.d.ts +16 -1
  2. package/lib/chat/ChatFloating.js +62 -17
  3. package/lib/chat/assistant/AssistantStage.d.ts +17 -2
  4. package/lib/chat/assistant/AssistantStage.js +3 -3
  5. package/lib/chat/assistant/BalloonParts.d.ts +55 -0
  6. package/lib/chat/assistant/BalloonParts.js +140 -0
  7. package/lib/chat/assistant/ConversationBalloon.d.ts +5 -0
  8. package/lib/chat/assistant/ConversationBalloon.js +5 -0
  9. package/lib/chat/assistant/SpeechBalloon.d.ts +23 -2
  10. package/lib/chat/assistant/SpeechBalloon.js +22 -8
  11. package/lib/chat/assistant/toolLine.d.ts +69 -0
  12. package/lib/chat/assistant/toolLine.js +118 -0
  13. package/lib/components/teams/A2ATeamGraph.d.ts +12 -0
  14. package/lib/components/teams/A2ATeamGraph.js +35 -8
  15. package/lib/components/teams/NotebookPreview.d.ts +39 -0
  16. package/lib/components/teams/NotebookPreview.js +68 -0
  17. package/lib/components/teams/NotebookPreviewView.d.ts +21 -0
  18. package/lib/components/teams/NotebookPreviewView.js +42 -0
  19. package/lib/components/teams/TeamNotebook.js +16 -1
  20. package/lib/components/teams/a2aTeamFlow.d.ts +5 -0
  21. package/lib/components/teams/a2aTeamFlow.js +11 -0
  22. package/lib/components/teams/index.d.ts +1 -0
  23. package/lib/components/teams/index.js +1 -0
  24. package/lib/components/teams/useA2ATeam.d.ts +8 -0
  25. package/lib/components/teams/useA2ATeam.js +38 -3
  26. package/lib/examples/AgentA2ATeamExample.d.ts +8 -4
  27. package/lib/examples/AgentA2ATeamExample.js +2 -2
  28. package/lib/examples/ChatAssistantExample.js +4 -2
  29. package/lib/examples/ChatAssistantGalleryExample.d.ts +5 -1
  30. package/lib/examples/ChatAssistantGalleryExample.js +40 -10
  31. package/lib/examples/utils/AssistantGalleryGrid.d.ts +14 -11
  32. package/lib/examples/utils/AssistantGalleryGrid.js +42 -1
  33. package/lib/examples/utils/assistantGallery.d.ts +49 -1
  34. package/lib/examples/utils/assistantGallery.js +52 -1
  35. package/lib/loop/apps/appspec.js +6 -0
  36. package/lib/loop/apps/checks.js +2 -0
  37. package/lib/loop/embed/AppEmbed.js +3 -1
  38. package/lib/loop/plugins/assistant/index.d.ts +14 -1
  39. package/lib/loop/plugins/assistant/index.js +39 -13
  40. package/lib/specs/appspecSchema.js +18 -0
  41. package/lib/stories/loop/ReferenceScreens.d.ts +1 -1
  42. package/lib/stories/loop/ReferenceScreens.js +20 -3
  43. package/lib/types/agentspecs.d.ts +8 -0
  44. package/package.json +1 -1
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import React from 'react';
19
19
  import type { ChatVoice } from '../voice';
20
+ import { type BalloonDisplay, type BalloonToolLine } from './assistant/toolLine';
20
21
  import { type AssistantCharacter } from './assistant/characters';
21
22
  import type { AssistantCharacterData } from './assistant/formats/types';
22
23
  import { type AssistantSaying } from './assistant/state';
@@ -38,6 +39,8 @@ export type FloatingConversation = {
38
39
  saying?: AssistantSaying;
39
40
  /** Whether its newest item is the answer being written. */
40
41
  answering: boolean;
42
+ /** The tool it is calling now, or just called: said in the balloon. */
43
+ tool?: BalloonToolLine;
41
44
  };
42
45
  /**
43
46
  * ChatFloating props — extends ChatCommonProps with floating/popup-specific configuration.
@@ -111,6 +114,18 @@ export interface ChatFloatingProps extends ChatCommonProps {
111
114
  * @default 'paperclip'
112
115
  */
113
116
  assistantCharacter?: string | AssistantCharacter | AssistantCharacterData;
117
+ /**
118
+ * How the floating assistant's balloon shows the conversation (LOOP
119
+ * T-23; an Appspec's `interface.balloon`):
120
+ * - 'history': every message, scrolled, under a header that counts them,
121
+ * the composer last; closed, a peek of the newest words.
122
+ * - 'current': only what it says or does now — the answer being written,
123
+ * or the tool it calls — in one compact balloon, *Now* on it; open, that
124
+ * line and the composer, nothing to scroll.
125
+ * Either way a tool call is said in plain words: "Using list_invoices…".
126
+ * @default 'history'
127
+ */
128
+ balloonDisplay?: BalloonDisplay;
114
129
  /**
115
130
  * The runtime the floating assistant asks typed decisions at — its
116
131
  * `/api/v1/configure/inference/decisions`, as the `decide` tool asks — and
@@ -162,5 +177,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
162
177
  * ChatFloating component
163
178
  * A floating chat window built on ChatBase
164
179
  */
165
- 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, conversation, decisions, voice, }: ChatFloatingProps): React.JSX.Element;
180
+ 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, balloonDisplay, conversation, decisions, voice, }: ChatFloatingProps): React.JSX.Element;
166
181
  export default ChatFloating;
@@ -35,7 +35,9 @@ import { decisionsAskerAt } from './assistant/decisions';
35
35
  import { DecisionAsk } from './assistant/DecisionAsk';
36
36
  import { ServerSpeaker } from '../voice/speaker';
37
37
  import { useSpokenAnswers } from '../voice/useSpokenAnswers';
38
- import { BalloonApprovalMessage, CONVERSATION_BALLOON_WIDTH, ConversationBalloonClose, balloonTailAt, conversationBalloonHeight, conversationBalloonSx, peekLine, } from './assistant/ConversationBalloon';
38
+ import { BalloonApprovalMessage, CONVERSATION_BALLOON_WIDTH, ConversationBalloonClose, balloonTailAt, conversationBalloonHeight, conversationBalloonSx, CURRENT_BALLOON_HEIGHT, peekLine, } from './assistant/ConversationBalloon';
39
+ import { ConversationBalloonHeader, CurrentBalloonBody, } from './assistant/BalloonParts';
40
+ import { DEFAULT_BALLOON_DISPLAY, conversationCount, newestToolLine, toolLineText, } from './assistant/toolLine';
39
41
  import { SpeechBalloon } from './assistant/SpeechBalloon';
40
42
  import { DEFAULT_ASSISTANT_CHARACTER, } from './assistant/characters';
41
43
  import { assistantStateOf, latestSaying, newestIsAnswer, keepAway, keptAway, ASSISTANT_WORDS, } from './assistant/state';
@@ -68,7 +70,7 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
68
70
  * ChatFloating component
69
71
  * A floating chat window built on ChatBase
70
72
  */
71
- 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, conversation, decisions, voice, }) {
73
+ 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, balloonDisplay = DEFAULT_BALLOON_DISPLAY, conversation, decisions, voice, }) {
72
74
  // Store-based state
73
75
  const storeIsOpen = useChatOpen();
74
76
  const storeMessages = useChatMessages();
@@ -131,6 +133,9 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
131
133
  pendingApproval: false,
132
134
  });
133
135
  const [ownAnswering, setAnswering] = useState(false);
136
+ // The tool it calls now, and how many messages the history holds (T-23).
137
+ const [ownTool, setOwnTool] = useState();
138
+ const [messageCount, setMessageCount] = useState(0);
134
139
  /*
135
140
  * Its voice (VOICE.md V1): the answers said by the speech service, as
136
141
  * they are written (VO-20), the state following the sound (VO-22).
@@ -187,6 +192,8 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
187
192
  const [ownSaying, setSaying] = useState();
188
193
  const saying = conversation ? conversation.saying : ownSaying;
189
194
  const answering = conversation ? conversation.answering : ownAnswering;
195
+ const toolLine = conversation ? conversation.tool : ownTool;
196
+ const showsCurrent = balloonDisplay === 'current';
190
197
  const [heardId, setHeardId] = useState();
191
198
  const [freshSaying, setFreshSaying] = useState(false);
192
199
  useEffect(() => {
@@ -244,6 +251,14 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
244
251
  const assistantDecide = useMemo(() => decisions?.serverUrl
245
252
  ? decisionsAskerAt(decisions.serverUrl, decisions.token)
246
253
  : undefined, [decisions?.serverUrl, decisions?.token]);
254
+ // At work: a turn runs, or the host's conversation says it does.
255
+ const atWork = assistantState === 'thinking' ||
256
+ assistantState === 'working' ||
257
+ assistantState === 'speaking';
258
+ // The tool line, while its call runs or its turn goes on (T-23).
259
+ const toolSaid = toolLine && (toolLine.phase === 'running' || atWork) ? toolLine : undefined;
260
+ // Current: what is said now — thinking until the answer's words arrive.
261
+ const currentWords = atWork && !answering ? 'Thinking…' : saying?.text || undefined;
247
262
  const assistantBalloon = assistantState === 'paused'
248
263
  ? { text: ASSISTANT_WORDS.paused }
249
264
  : speakerState.sentence
@@ -255,20 +270,38 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
255
270
  ? { text: ASSISTANT_WORDS.approval, approval: balloonApproval }
256
271
  : assistantState === 'waiting'
257
272
  ? { text: ASSISTANT_WORDS.waiting }
258
- : unheard && saying
259
- ? {
260
- // A peek: the first words; the rest is in the conversation.
261
- ...peekLine(saying.text),
262
- onDismiss: () => {
263
- setHeardId(saying.id);
264
- setFreshSaying(false);
265
- },
266
- }
267
- : { text: description };
273
+ : toolSaid
274
+ ? { text: toolLineText(toolSaid), tool: toolSaid, busy: atWork }
275
+ : showsCurrent && (unheard || atWork)
276
+ ? // Current: the words as they are written, whole.
277
+ {
278
+ text: currentWords ?? description,
279
+ more: saying?.more,
280
+ speaking: answering && atWork,
281
+ busy: atWork,
282
+ onDismiss: saying
283
+ ? () => {
284
+ setHeardId(saying.id);
285
+ setFreshSaying(false);
286
+ }
287
+ : undefined,
288
+ }
289
+ : unheard && saying
290
+ ? {
291
+ // A peek: the first words; the rest is in the conversation.
292
+ ...peekLine(saying.text),
293
+ onDismiss: () => {
294
+ setHeardId(saying.id);
295
+ setFreshSaying(false);
296
+ },
297
+ }
298
+ : { text: description };
268
299
  const balloonInsists = !!speakerState.sentence ||
269
300
  assistantState === 'greeting' ||
270
301
  assistantState === 'waiting' ||
271
302
  !!balloonApproval ||
303
+ !!toolSaid ||
304
+ (showsCurrent && atWork) ||
272
305
  (unheard && (assistantState === 'speaking' || freshSaying));
273
306
  const panelOnLoadingChange = panelProps?.onLoadingChange;
274
307
  const panelOnDisplayItemsChange = panelProps?.onDisplayItemsChange;
@@ -283,6 +316,11 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
283
316
  ? previous
284
317
  : tool);
285
318
  setAnswering(newestIsAnswer(items));
319
+ const line = newestToolLine(items);
320
+ setOwnTool(previous => previous?.id === line?.id && previous?.phase === line?.phase
321
+ ? previous
322
+ : line);
323
+ setMessageCount(conversationCount(items));
286
324
  setVoiceItems(items);
287
325
  const said = latestSaying(items);
288
326
  setSaying(previous => previous?.id === said?.id && previous?.text === said?.text
@@ -499,9 +537,14 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
499
537
  const viewportWidth = typeof window === 'undefined' ? 1280 : window.innerWidth;
500
538
  const viewportHeight = typeof window === 'undefined' ? 800 : window.innerHeight;
501
539
  const conversationHeight = conversationBalloonHeight(viewportHeight);
540
+ // Open, `current` is the line and the composer; `history`, 60% of the window.
541
+ const currentOpen = showsCurrent && !conversation;
542
+ const openBalloonHeight = currentOpen
543
+ ? CURRENT_BALLOON_HEIGHT
544
+ : conversationHeight;
502
545
  const balloonGeometry = () => {
503
546
  const widthPx = CONVERSATION_BALLOON_WIDTH;
504
- const heightPx = conversationHeight;
547
+ const heightPx = openBalloonHeight;
505
548
  if (stageDrag.position) {
506
549
  // Toward the page's inside: above the character, below it, or beside
507
550
  // it when the window is too short for either.
@@ -713,7 +756,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
713
756
  const popupHeight = isMobile
714
757
  ? '100%'
715
758
  : conversationBalloon
716
- ? conversationHeight
759
+ ? openBalloonHeight
717
760
  : height;
718
761
  // Mobile full-screen styles
719
762
  const mobileStyles = isMobile
@@ -902,7 +945,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
902
945
  ? 'canvas.subtle'
903
946
  : 'canvas.default')
904
947
  : {}),
905
- }, "data-conversation-balloon": conversationBalloon ? '' : undefined, children: [conversationBalloon ? (_jsx(ConversationBalloonClose, { onClose: handleToggle })) : null, viewMode === 'floating-draggable' && !isMobile && (_jsx(Box, { onPointerDown: drag.onHandlePointerDown, "aria-label": "Move the chat", sx: {
948
+ }, "data-conversation-balloon": conversationBalloon ? '' : undefined, "data-balloon-display": conversationBalloon ? balloonDisplay : undefined, children: [conversationBalloon ? (_jsx(ConversationBalloonClose, { onClose: handleToggle })) : null, conversationBalloon && !currentOpen ? (_jsx(ConversationBalloonHeader, { count: messageCount })) : null, viewMode === 'floating-draggable' && !isMobile && (_jsx(Box, { onPointerDown: drag.onHandlePointerDown, "aria-label": "Move the chat", sx: {
906
949
  display: 'flex',
907
950
  alignItems: 'center',
908
951
  justifyContent: 'center',
@@ -957,7 +1000,9 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
957
1000
  ...poweredByProps,
958
1001
  }, renderToolResult: renderToolResult, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
959
1002
  showAvatars: true,
960
- }, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: children }))] }));
1003
+ }, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: conversationBalloon && currentOpen ? (
1004
+ // Current: the one thing said or done now, over the composer.
1005
+ _jsx(Box, { sx: { px: 3, pt: 3, pb: 2, overflow: 'hidden' }, children: _jsx(CurrentBalloonBody, { text: toolSaid ? undefined : (currentWords ?? description), tool: toolSaid, busy: atWork, speaking: answering && atWork }) })) : (children) }))] }));
961
1006
  /*
962
1007
  * The floating popup's button. Words not yet heard make it blink for the
963
1008
  * person's attention, until the chat is opened; hovered, they show in a
@@ -1005,7 +1050,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1005
1050
  left: px(stageDrag.position.left),
1006
1051
  top: px(stageDrag.position.top),
1007
1052
  }
1008
- : getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, insist: balloonInsists, onDismiss: dismissAssistant, ownRef: popupRef, mouthLevel: mouthLevel })), showButton && !isOpen && !assistantShown && (_jsx(Box, { sx: {
1053
+ : getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, balloonDisplay: balloonDisplay, insist: balloonInsists, onDismiss: dismissAssistant, ownRef: popupRef, mouthLevel: mouthLevel })), showButton && !isOpen && !assistantShown && (_jsx(Box, { sx: {
1009
1054
  ...getPositionStyles(),
1010
1055
  }, children: _jsxs(Box, { sx: {
1011
1056
  position: 'relative',
@@ -14,10 +14,11 @@
14
14
  *
15
15
  * @module chat/assistant/AssistantStage
16
16
  */
17
- import type { JSX, RefObject } from 'react';
17
+ import type { JSX, ReactNode, RefObject } from 'react';
18
18
  import { type AssistantCharacter } from './characters';
19
19
  import type { AssistantCharacterData } from './formats/types';
20
20
  import type { DecisionAsker } from './decisions';
21
+ import type { BalloonDisplay, BalloonToolLine } from './toolLine';
21
22
  import { type AssistantAway, type AssistantState, type BalloonApproval } from './state';
22
23
  /**
23
24
  * Where the balloon goes so that it stays inside the window (T-23): toward
@@ -79,7 +80,21 @@ export interface AssistantStageProps {
79
80
  approval?: BalloonApproval;
80
81
  /** Puts the peek away (a × beside its line); never an approval's. */
81
82
  onDismiss?: () => void;
83
+ /** The tool being called: "Using list_invoices…" in place of the words. */
84
+ tool?: BalloonToolLine;
85
+ /** Words are being written (`current`): read out once all have arrived. */
86
+ speaking?: boolean;
87
+ /** The agent is at work (`current`): *Now* breathes. */
88
+ busy?: boolean;
89
+ /** What goes with the words (`current`): a notebook given, read-only. */
90
+ attachment?: ReactNode;
82
91
  };
92
+ /**
93
+ * How the balloon shows the conversation (LOOP T-23): a peek of it
94
+ * (`history`, the default — open, the conversation is the whole history),
95
+ * or the one thing being said or done now (`current`).
96
+ */
97
+ balloonDisplay?: BalloonDisplay;
83
98
  /** Show the balloon without being hovered: something new to say. */
84
99
  insist?: boolean;
85
100
  /** Send it away (T-27): for the page, for the session, or for good. */
@@ -114,5 +129,5 @@ export declare function mouthOpening(level: number): 0 | 1 | 2 | 3;
114
129
  * `data-assistant-mouth`, each frame, without drawing the character again.
115
130
  */
116
131
  export declare function useMouth(stageRef: RefObject<HTMLElement | null>, mouthLevel: (() => number) | undefined, speaking: boolean): void;
117
- export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, ownRef, decide, mouthLevel, stayPut, }: AssistantStageProps): JSX.Element;
132
+ export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, ownRef, decide, mouthLevel, stayPut, balloonDisplay, }: AssistantStageProps): JSX.Element;
118
133
  export default AssistantStage;
@@ -209,7 +209,7 @@ export function useMouth(stageRef, mouthLevel, speaking) {
209
209
  };
210
210
  }, [stageRef, mouthLevel, speaking]);
211
211
  }
212
- export function AssistantStage({ character, state, size = 88, place, stageRef, onDragStart, open, onToggle, balloon, insist = false, onDismiss, ownRef, decide, mouthLevel, stayPut = false, }) {
212
+ export function AssistantStage({ character, state, size = 88, place, stageRef, onDragStart, open, onToggle, balloon, insist = false, onDismiss, ownRef, decide, mouthLevel, stayPut = false, balloonDisplay = 'history', }) {
213
213
  // A shipped one by id, a drawing contributed by a plugin (T-24), or a
214
214
  // character read from a file (T-26).
215
215
  const shipped = typeof character === 'string'
@@ -232,7 +232,7 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
232
232
  // A decision being asked, or its answer, keeps the balloon up.
233
233
  const [deciding, setDeciding] = useState(false);
234
234
  const showBalloon = !aside && !open && !!balloon && (hovered || insist || deciding);
235
- return (_jsxs(Box, { ref: stageRef, "data-assistant-state": state, "data-assistant-aside": aside, sx: {
235
+ return (_jsxs(Box, { ref: stageRef, "data-assistant-state": state, "data-assistant-aside": aside, "data-assistant-balloon": balloonDisplay, sx: {
236
236
  position: 'fixed',
237
237
  zIndex: 1002,
238
238
  ...place,
@@ -331,7 +331,7 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
331
331
  '@media (prefers-reduced-motion: reduce)': {
332
332
  '& *': { animation: 'none !important' },
333
333
  },
334
- }, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [showBalloon && balloon && (_jsx(SpeechBalloon, { text: balloon.text, more: balloon.more, approval: balloon.approval, onDismissPeek: balloon.onDismiss, decide: decide, onDecisionActive: setDeciding, wide: deciding, onOpen: onToggle, above: size + 8, side: balloonSide(place).side, align: balloonSide(place).align, tailAt: size / 2 })), _jsx(Box, { as: "button", type: "button", "aria-label": open ? `Close the conversation with ${name}` : `Talk to ${name}`, "aria-expanded": open, onPointerDown: (event) => {
334
+ }, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [showBalloon && balloon && (_jsx(SpeechBalloon, { text: balloon.text, more: balloon.more, approval: balloon.approval, onDismissPeek: balloon.onDismiss, display: balloonDisplay, tool: balloon.tool, speaking: balloon.speaking, busy: balloon.busy, attachment: balloon.attachment, decide: decide, onDecisionActive: setDeciding, wide: deciding, onOpen: onToggle, above: size + 8, side: balloonSide(place).side, align: balloonSide(place).align, tailAt: size / 2 })), _jsx(Box, { as: "button", type: "button", "aria-label": open ? `Close the conversation with ${name}` : `Talk to ${name}`, "aria-expanded": open, onPointerDown: (event) => {
335
335
  pressedAt.current = { x: event.clientX, y: event.clientY };
336
336
  onDragStart(event);
337
337
  }, onClick: (event) => {
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The parts the two balloon displays are drawn with (LOOP T-23): the tool
3
+ * line ("Using **list_invoices**…"), what a screen reader hears of it, the
4
+ * `current` balloon's *Now*, and the `history` balloon's header
5
+ * ("Conversation · 6").
6
+ *
7
+ * @module chat/assistant/BalloonParts
8
+ */
9
+ import type { JSX, ReactNode } from 'react';
10
+ import { type BalloonToolLine } from './toolLine';
11
+ /**
12
+ * A tool call in the balloon, in plain words: its mark when the catalogue
13
+ * gives one (an MCP server's, a skill's, a tool set's), else a tool; "Using
14
+ * **list_invoices**…", "Done: **list_invoices**", "**list_invoices** failed".
15
+ * Seen, not read out: {@link ToolLineAnnouncer} says it once per change.
16
+ */
17
+ export declare function BalloonToolLineView({ line, }: {
18
+ line: BalloonToolLine;
19
+ }): JSX.Element;
20
+ /**
21
+ * What a screen reader hears of the tool lines, politely: once when a call
22
+ * starts and once when it ends, never while it runs nor while words stream.
23
+ */
24
+ export declare function ToolLineAnnouncer({ line, }: {
25
+ line?: BalloonToolLine;
26
+ }): JSX.Element;
27
+ /**
28
+ * The `current` balloon's mark: *Now*, with a dot that breathes while the
29
+ * agent is at work — still for a reader who asks for reduced motion.
30
+ */
31
+ export declare function BalloonNow({ busy }: {
32
+ busy?: boolean;
33
+ }): JSX.Element;
34
+ /** How the `history` balloon names itself: "Conversation · 6". */
35
+ export declare function conversationHeaderText(count: number): string;
36
+ /**
37
+ * The `history` balloon's header: what it holds — the conversation, and how
38
+ * many messages — over the scrolled history and the composer.
39
+ */
40
+ export declare function ConversationBalloonHeader({ count, }: {
41
+ count: number;
42
+ }): JSX.Element;
43
+ /**
44
+ * What the `current` balloon holds: *Now*, then the one thing being said or
45
+ * done — the tool line while a tool runs, else the words — cut to a few
46
+ * lines, never scrolled; and what goes with them (`attachment`).
47
+ */
48
+ export declare function CurrentBalloonBody({ text, tool, busy, speaking, attachment, }: {
49
+ text?: string;
50
+ tool?: BalloonToolLine;
51
+ busy?: boolean;
52
+ /** Words are arriving: read out once they have. */
53
+ speaking?: boolean;
54
+ attachment?: ReactNode;
55
+ }): JSX.Element;
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Text } from '@primer/react';
4
+ import { Box } from '@datalayer/primer-addons';
5
+ import { CheckIcon, ToolsIcon, XIcon } from '@primer/octicons-react';
6
+ import { SpecMark, hasMark, marksOfToolCall } from '../marks';
7
+ import { toolAnnouncement, toolLineParts, } from './toolLine';
8
+ /** Out of sight, still heard. */
9
+ const VISUALLY_HIDDEN = {
10
+ position: 'absolute',
11
+ width: 1,
12
+ height: 1,
13
+ overflow: 'hidden',
14
+ clip: 'rect(0 0 0 0)',
15
+ whiteSpace: 'nowrap',
16
+ };
17
+ const PHASE_COLOR = {
18
+ running: 'fg.muted',
19
+ done: 'success.fg',
20
+ failed: 'danger.fg',
21
+ };
22
+ /**
23
+ * A tool call in the balloon, in plain words: its mark when the catalogue
24
+ * gives one (an MCP server's, a skill's, a tool set's), else a tool; "Using
25
+ * **list_invoices**…", "Done: **list_invoices**", "**list_invoices** failed".
26
+ * Seen, not read out: {@link ToolLineAnnouncer} says it once per change.
27
+ */
28
+ export function BalloonToolLineView({ line, }) {
29
+ const { before, name, after } = toolLineParts(line);
30
+ const marks = line.tool ? marksOfToolCall(line.tool, undefined) : null;
31
+ const Icon = line.phase === 'done'
32
+ ? CheckIcon
33
+ : line.phase === 'failed'
34
+ ? XIcon
35
+ : ToolsIcon;
36
+ return (_jsxs(Box, { as: "span", "aria-hidden": "true", "data-balloon-tool": line.phase, "data-balloon-tool-name": line.tool || undefined, sx: {
37
+ display: 'inline-flex',
38
+ alignItems: 'center',
39
+ gap: 1,
40
+ minWidth: 0,
41
+ maxWidth: '100%',
42
+ color: line.phase === 'failed' ? 'danger.fg' : 'fg.default',
43
+ }, children: [_jsx(Box, { as: "span", sx: {
44
+ display: 'inline-flex',
45
+ flexShrink: 0,
46
+ color: PHASE_COLOR[line.phase],
47
+ }, children: line.phase === 'running' && hasMark(marks) ? (_jsx(SpecMark, { icon: marks?.icon, emoji: marks?.emoji, size: 14 })) : (_jsx(Icon, { size: 14 })) }), _jsxs(Box, { as: "span", sx: {
48
+ minWidth: 0,
49
+ overflow: 'hidden',
50
+ textOverflow: 'ellipsis',
51
+ whiteSpace: 'nowrap',
52
+ }, children: [before, name ? (_jsx(Box, { as: "strong", sx: { fontWeight: 'semibold' }, children: name })) : null, after] })] }));
53
+ }
54
+ /**
55
+ * What a screen reader hears of the tool lines, politely: once when a call
56
+ * starts and once when it ends, never while it runs nor while words stream.
57
+ */
58
+ export function ToolLineAnnouncer({ line, }) {
59
+ const last = useRef(undefined);
60
+ const [said, setSaid] = useState('');
61
+ useEffect(() => {
62
+ const words = toolAnnouncement(line, last.current);
63
+ if (line && words) {
64
+ last.current = { id: line.id, phase: line.phase };
65
+ setSaid(words);
66
+ }
67
+ }, [line?.id, line?.phase]);
68
+ return (_jsx(Box, { as: "span", role: "status", "aria-live": "polite", "data-balloon-announce": "", sx: VISUALLY_HIDDEN, children: said }));
69
+ }
70
+ /**
71
+ * The `current` balloon's mark: *Now*, with a dot that breathes while the
72
+ * agent is at work — still for a reader who asks for reduced motion.
73
+ */
74
+ export function BalloonNow({ busy = false }) {
75
+ return (_jsxs(Box, { as: "span", "data-balloon-now": busy ? 'busy' : '', sx: {
76
+ display: 'inline-flex',
77
+ alignItems: 'center',
78
+ gap: '6px',
79
+ fontSize: 0,
80
+ fontWeight: 'semibold',
81
+ color: 'accent.fg',
82
+ textTransform: 'uppercase',
83
+ letterSpacing: '0.04em',
84
+ '@keyframes balloonNowBreathe': {
85
+ '0%, 100%': { opacity: 1, transform: 'scale(1)' },
86
+ '50%': { opacity: 0.35, transform: 'scale(0.7)' },
87
+ },
88
+ '& [data-balloon-now-dot]': {
89
+ width: 6,
90
+ height: 6,
91
+ borderRadius: '50%',
92
+ bg: 'accent.emphasis',
93
+ animation: busy
94
+ ? 'balloonNowBreathe 1.2s ease-in-out infinite'
95
+ : 'none',
96
+ },
97
+ '@media (prefers-reduced-motion: reduce)': {
98
+ '& [data-balloon-now-dot]': { animation: 'none' },
99
+ },
100
+ }, children: [_jsx(Box, { as: "span", "data-balloon-now-dot": "", "aria-hidden": "true" }), "Now"] }));
101
+ }
102
+ /** How the `history` balloon names itself: "Conversation · 6". */
103
+ export function conversationHeaderText(count) {
104
+ return count > 0 ? `Conversation · ${count}` : 'Conversation';
105
+ }
106
+ /**
107
+ * The `history` balloon's header: what it holds — the conversation, and how
108
+ * many messages — over the scrolled history and the composer.
109
+ */
110
+ export function ConversationBalloonHeader({ count, }) {
111
+ return (_jsx(Box, { "data-balloon-header": "", sx: {
112
+ flexShrink: 0,
113
+ display: 'flex',
114
+ alignItems: 'center',
115
+ gap: 2,
116
+ // Room for the close control over the corner.
117
+ pl: 3,
118
+ pr: 6,
119
+ py: 2,
120
+ borderBottom: '1px solid',
121
+ borderColor: 'border.muted',
122
+ bg: 'canvas.default',
123
+ borderTopLeftRadius: 'var(--theme-radius-bubble, 16px)',
124
+ borderTopRightRadius: 'var(--theme-radius-bubble, 16px)',
125
+ }, children: _jsx(Text, { sx: { fontSize: 0, fontWeight: 'semibold', color: 'fg.muted' }, children: conversationHeaderText(count) }) }));
126
+ }
127
+ /**
128
+ * What the `current` balloon holds: *Now*, then the one thing being said or
129
+ * done — the tool line while a tool runs, else the words — cut to a few
130
+ * lines, never scrolled; and what goes with them (`attachment`).
131
+ */
132
+ export function CurrentBalloonBody({ text, tool, busy = false, speaking = false, attachment, }) {
133
+ return (_jsxs(Box, { "data-balloon-current": "", sx: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 }, children: [_jsx(BalloonNow, { busy: busy }), tool ? (_jsx(BalloonToolLineView, { line: tool })) : text ? (_jsx(Box, { "aria-live": "polite", "aria-busy": speaking, "data-balloon-current-text": "", sx: {
134
+ display: '-webkit-box',
135
+ WebkitLineClamp: 4,
136
+ WebkitBoxOrient: 'vertical',
137
+ overflow: 'hidden',
138
+ overflowWrap: 'anywhere',
139
+ }, children: text })) : null, attachment, _jsx(ToolLineAnnouncer, { line: tool })] }));
140
+ }
@@ -18,6 +18,11 @@ export declare const CONVERSATION_BALLOON_WIDTH = 380;
18
18
  /** The open balloon's tallest, in pixels, and its share of the window. */
19
19
  export declare const CONVERSATION_BALLOON_MAX_HEIGHT = 560;
20
20
  export declare const CONVERSATION_BALLOON_VIEWPORT_SHARE = 0.6;
21
+ /**
22
+ * The open balloon's height when it shows only what is said now
23
+ * (`current`): the line and the composer, nothing to scroll.
24
+ */
25
+ export declare const CURRENT_BALLOON_HEIGHT = 230;
21
26
  /** The open balloon's height for a window this tall: 60% of it, 560px at most. */
22
27
  export declare function conversationBalloonHeight(viewportHeight: number): number;
23
28
  /**
@@ -9,6 +9,11 @@ export const CONVERSATION_BALLOON_WIDTH = 380;
9
9
  /** The open balloon's tallest, in pixels, and its share of the window. */
10
10
  export const CONVERSATION_BALLOON_MAX_HEIGHT = 560;
11
11
  export const CONVERSATION_BALLOON_VIEWPORT_SHARE = 0.6;
12
+ /**
13
+ * The open balloon's height when it shows only what is said now
14
+ * (`current`): the line and the composer, nothing to scroll.
15
+ */
16
+ export const CURRENT_BALLOON_HEIGHT = 230;
12
17
  /** The open balloon's height for a window this tall: 60% of it, 560px at most. */
13
18
  export function conversationBalloonHeight(viewportHeight) {
14
19
  return Math.max(240, Math.min(CONVERSATION_BALLOON_MAX_HEIGHT, Math.round(viewportHeight * CONVERSATION_BALLOON_VIEWPORT_SHARE)));
@@ -9,11 +9,19 @@
9
9
  * the conversation is elsewhere (the LOOP workspace), *Ask a decision*: a
10
10
  * typed decision asked of Jev, its answer said back in the balloon.
11
11
  *
12
+ * Shown `current` (LOOP T-23), it is the one thing being said or done now:
13
+ * *Now*, then the words as they are written, or the tool being called
14
+ * ("Using **list_invoices**…"), cut to a few lines, and what goes with them
15
+ * (a notebook given, read-only). Either way a tool line stands in for the
16
+ * peek while a tool runs, and a screen reader hears it once as it starts and
17
+ * once as it ends.
18
+ *
12
19
  * @module chat/assistant/SpeechBalloon
13
20
  */
14
- import type { JSX } from 'react';
21
+ import type { JSX, ReactNode } from 'react';
15
22
  import { type BalloonApproval } from './state';
16
23
  import type { DecisionAsker } from './decisions';
24
+ import type { BalloonDisplay, BalloonToolLine } from './toolLine';
17
25
  export interface SpeechBalloonProps {
18
26
  text: string;
19
27
  /** There is more than the balloon holds. */
@@ -38,6 +46,19 @@ export interface SpeechBalloonProps {
38
46
  wide?: boolean;
39
47
  /** Puts the peek away: a × beside its line. Never for an approval. */
40
48
  onDismissPeek?: () => void;
49
+ /**
50
+ * How it is shown: a peek of the conversation (`history`, the default),
51
+ * or the one thing being said or done now (`current`).
52
+ */
53
+ display?: BalloonDisplay;
54
+ /** The tool being called: said in place of the words while it runs. */
55
+ tool?: BalloonToolLine;
56
+ /** Words are being written: read out once they have all arrived. */
57
+ speaking?: boolean;
58
+ /** The agent is at work: *Now* breathes. */
59
+ busy?: boolean;
60
+ /** What goes with the words, under them: a notebook given, read-only. */
61
+ attachment?: ReactNode;
41
62
  }
42
- export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, approval, decide, onDecisionActive, wide, onDismissPeek, }: SpeechBalloonProps): JSX.Element;
63
+ export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, approval, decide, onDecisionActive, wide, onDismissPeek, display, tool, speaking, busy, attachment, }: SpeechBalloonProps): JSX.Element;
43
64
  export default SpeechBalloon;
@@ -5,8 +5,10 @@ import { XIcon } from '@primer/octicons-react';
5
5
  import { ASSISTANT_WORDS } from './state';
6
6
  import { moreWaiting } from './ConversationBalloon';
7
7
  import { DecisionAsk } from './DecisionAsk';
8
- export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, approval, decide, onDecisionActive, wide = false, onDismissPeek, }) {
9
- return (_jsxs(Box, { role: "status", "aria-live": "polite", "data-speech-balloon": "", sx: {
8
+ import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, } from './BalloonParts';
9
+ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, approval, decide, onDecisionActive, wide = false, onDismissPeek, display = 'history', tool, speaking = false, busy = false, attachment, }) {
10
+ const current = display === 'current';
11
+ return (_jsxs(Box, { "data-speech-balloon": "", "data-balloon-display": display, sx: {
10
12
  position: 'absolute',
11
13
  // Pixels, as strings: a number here is read as the theme's space
12
14
  // scale (`-7` would be `-space[7]`, 48px).
@@ -14,14 +16,15 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
14
16
  ? { bottom: `${above}px` }
15
17
  : { top: `${above}px` }),
16
18
  [align]: 0,
17
- maxWidth: wide ? 300 : 280,
18
- width: wide ? 300 : 'max-content',
19
+ maxWidth: wide || (current && attachment) ? 300 : current ? 260 : 280,
20
+ width: wide || (current && attachment) ? 300 : 'max-content',
19
21
  px: 3,
20
22
  py: 2,
21
23
  bg: 'canvas.default',
22
24
  color: 'fg.default',
23
25
  border: '1px solid',
24
- borderColor: 'border.default',
26
+ // The current balloon is the agent's voice now: its accent's edge.
27
+ borderColor: current ? 'accent.muted' : 'border.default',
25
28
  borderRadius: 'var(--theme-radius-bubble, 16px)',
26
29
  boxShadow: 'shadow.medium',
27
30
  fontSize: 1,
@@ -37,10 +40,21 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
37
40
  bg: 'canvas.default',
38
41
  borderRight: '1px solid',
39
42
  borderBottom: '1px solid',
40
- borderColor: 'border.default',
43
+ borderColor: current ? 'accent.muted' : 'border.default',
41
44
  transform: side === 'above' ? 'rotate(45deg)' : 'rotate(225deg)',
42
45
  },
43
- }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'flex-start', gap: 1 }, children: [_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
46
+ }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'flex-start', gap: 1 }, children: [current && !approval ? (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", onClick: onOpen, sx: {
47
+ flex: 1,
48
+ minWidth: 0,
49
+ m: 0,
50
+ p: 0,
51
+ border: 0,
52
+ bg: 'transparent',
53
+ color: 'inherit',
54
+ font: 'inherit',
55
+ textAlign: 'left',
56
+ cursor: 'pointer',
57
+ }, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking }) })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
44
58
  flex: 1,
45
59
  minWidth: 0,
46
60
  m: 0,
@@ -52,6 +66,6 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
52
66
  textAlign: 'left',
53
67
  cursor: 'pointer',
54
68
  '&:hover': { textDecoration: 'underline' },
55
- }, children: text }), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": "Dismiss", "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })), decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
69
+ }, children: tool ? (_jsx(BalloonToolLineView, { line: tool })) : (_jsx(Box, { as: "span", role: "status", "aria-live": "polite", children: text })) })), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": "Dismiss", "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })), current && attachment ? (_jsx(Box, { "data-balloon-attachment": "", sx: { mt: 2 }, children: attachment })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
56
70
  }
57
71
  export default SpeechBalloon;