@datalayer/agent-runtimes 1.3.74 → 1.3.75
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/lib/chat/ChatFloating.d.ts +17 -1
- package/lib/chat/ChatFloating.js +52 -5
- package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
- package/lib/chat/assistant/AssistantContextMenu.js +68 -0
- package/lib/chat/assistant/AssistantStage.d.ts +95 -1
- package/lib/chat/assistant/AssistantStage.js +227 -7
- package/lib/chat/assistant/BalloonParts.d.ts +15 -1
- package/lib/chat/assistant/BalloonParts.js +26 -9
- package/lib/chat/assistant/BalloonVisual.d.ts +10 -2
- package/lib/chat/assistant/BalloonVisual.js +3 -3
- package/lib/chat/assistant/SpeechBalloon.d.ts +47 -1
- package/lib/chat/assistant/SpeechBalloon.js +60 -8
- package/lib/chat/base/ChatBase.js +51 -1
- package/lib/chat/indicators/TypingDots.d.ts +13 -0
- package/lib/chat/indicators/TypingDots.js +21 -0
- package/lib/chat/messages/ChatMessageList.js +1 -15
- package/lib/components/inspector/AgentInspector.d.ts +59 -0
- package/lib/components/inspector/AgentInspector.js +220 -0
- package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
- package/lib/components/inspector/AgentInspectorDialog.js +9 -0
- package/lib/components/inspector/JsonTree.d.ts +24 -0
- package/lib/components/inspector/JsonTree.js +89 -0
- package/lib/components/inspector/a2aInspect.d.ts +56 -0
- package/lib/components/inspector/a2aInspect.js +478 -0
- package/lib/components/inspector/agentInspector.d.ts +179 -0
- package/lib/components/inspector/agentInspector.js +267 -0
- package/lib/components/inspector/chatInspect.d.ts +38 -0
- package/lib/components/inspector/chatInspect.js +65 -0
- package/lib/components/inspector/classify.d.ts +19 -0
- package/lib/components/inspector/classify.js +42 -0
- package/lib/components/inspector/index.d.ts +14 -0
- package/lib/components/inspector/index.js +18 -0
- package/lib/components/teams/A2ATeamGraph.d.ts +42 -1
- package/lib/components/teams/A2ATeamGraph.js +24 -7
- package/lib/components/teams/NotebookPreviewView.js +9 -1
- package/lib/components/teams/TeamNotebook.d.ts +3 -1
- package/lib/components/teams/TeamNotebook.js +3 -3
- package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
- package/lib/components/teams/jupyterThemeVariables.js +441 -0
- package/lib/components/teams/teamConnections.d.ts +9 -1
- package/lib/components/teams/teamConnections.js +20 -0
- package/lib/components/teams/useA2ATeam.d.ts +17 -4
- package/lib/components/teams/useA2ATeam.js +87 -23
- package/lib/examples/AgentA2ATeamExample.d.ts +13 -0
- package/lib/examples/AgentA2ATeamExample.js +61 -13
- package/lib/examples/AssistantExample.js +62 -2
- package/lib/loop/apps/AppRenderer.js +1 -1
- package/lib/loop/core/index.d.ts +17 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/plugins/assistant/index.d.ts +1 -1
- package/lib/loop/plugins/assistant/index.js +8 -3
- package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
- package/lib/types/chat.d.ts +7 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Suspense, lazy, useCallback, useEffect, useRef, useState, } from 'react';
|
|
3
3
|
import { ActionList, ActionMenu, IconButton, useTheme } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
|
-
import { XIcon } from '@primer/octicons-react';
|
|
5
|
+
import { CommentDiscussionIcon, InfoIcon, MuteIcon, PencilIcon, PlusIcon, PulseIcon, ScreenNormalIcon, SquareFillIcon, TrashIcon, UnmuteIcon, XIcon, } from '@primer/octicons-react';
|
|
6
|
+
import { Dialog, Text } from '@primer/react';
|
|
6
7
|
import { assistantCharacter } from './characters';
|
|
7
|
-
import { SpeechBalloon } from './SpeechBalloon';
|
|
8
|
+
import { BalloonHistoryLarge, SpeechBalloon, } from './SpeechBalloon';
|
|
9
|
+
import { conversationHeaderText } from './BalloonParts';
|
|
10
|
+
import { AssistantContextMenu, LONG_PRESS_MS, opensContextMenu, } from './AssistantContextMenu';
|
|
8
11
|
import { BalloonExpandContext, ExpandedVisual, focusExpanded, } from './BalloonVisual';
|
|
9
12
|
import { SpriteCharacter } from './SpriteCharacter';
|
|
10
13
|
import { ASSISTANT_OBSTACLES, POINTER_CALM_MS, boxesMeet, pointerNear, } from './state';
|
|
@@ -58,6 +61,128 @@ const AWAY_CHOICES = [
|
|
|
58
61
|
{ away: 'session', label: 'Hide for this session' },
|
|
59
62
|
{ away: 'always', label: 'Don\u2019t show again' },
|
|
60
63
|
];
|
|
64
|
+
/** The Agent Inspector's dialog: loaded when it is first asked for. */
|
|
65
|
+
const AgentInspectorDialog = lazy(() => import('../../components/inspector/AgentInspectorDialog'));
|
|
66
|
+
/**
|
|
67
|
+
* The entries of the assistant's menu, composed from what the host passes:
|
|
68
|
+
* each shows when what it does is there to do.
|
|
69
|
+
*/
|
|
70
|
+
export function assistantMenuItems(options) {
|
|
71
|
+
const items = [];
|
|
72
|
+
if (options.inspect) {
|
|
73
|
+
items.push({
|
|
74
|
+
id: 'inspect',
|
|
75
|
+
label: 'Inspect the agent…',
|
|
76
|
+
icon: PulseIcon,
|
|
77
|
+
onSelect: options.inspect,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (options.conversationLabel !== false) {
|
|
81
|
+
items.push({
|
|
82
|
+
id: 'conversation',
|
|
83
|
+
label: options.conversationLabel ??
|
|
84
|
+
(options.open ? 'Close the conversation' : 'Open the conversation'),
|
|
85
|
+
icon: CommentDiscussionIcon,
|
|
86
|
+
onSelect: options.onToggle,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (options.busy && options.onStop) {
|
|
90
|
+
items.push({
|
|
91
|
+
id: 'stop',
|
|
92
|
+
label: 'Stop the turn',
|
|
93
|
+
icon: SquareFillIcon,
|
|
94
|
+
onSelect: options.onStop,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (options.onNewChat) {
|
|
98
|
+
items.push({
|
|
99
|
+
id: 'new-chat',
|
|
100
|
+
label: 'New conversation',
|
|
101
|
+
icon: PlusIcon,
|
|
102
|
+
onSelect: options.onNewChat,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (options.onClear) {
|
|
106
|
+
items.push({
|
|
107
|
+
id: 'clear',
|
|
108
|
+
label: 'Clear the conversation',
|
|
109
|
+
icon: TrashIcon,
|
|
110
|
+
onSelect: options.onClear,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (options.suggestions?.length && options.onSuggestion) {
|
|
114
|
+
const send = options.onSuggestion;
|
|
115
|
+
for (const suggestion of options.suggestions) {
|
|
116
|
+
items.push({
|
|
117
|
+
id: `suggestion:${suggestion.label}`,
|
|
118
|
+
label: suggestion.label,
|
|
119
|
+
description: suggestion.prompt,
|
|
120
|
+
group: 'Suggestions',
|
|
121
|
+
disabled: options.busy,
|
|
122
|
+
onSelect: () => send(suggestion),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (options.onBalloonDisplayChange) {
|
|
127
|
+
const change = options.onBalloonDisplayChange;
|
|
128
|
+
items.push({
|
|
129
|
+
id: 'balloon-history',
|
|
130
|
+
label: 'History',
|
|
131
|
+
group: 'Balloon',
|
|
132
|
+
checked: options.balloonDisplay !== 'current',
|
|
133
|
+
onSelect: () => change('history'),
|
|
134
|
+
}, {
|
|
135
|
+
id: 'balloon-current',
|
|
136
|
+
label: 'Current',
|
|
137
|
+
group: 'Balloon',
|
|
138
|
+
checked: options.balloonDisplay === 'current',
|
|
139
|
+
onSelect: () => change('current'),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (options.onChangeCharacter) {
|
|
143
|
+
items.push({
|
|
144
|
+
id: 'character',
|
|
145
|
+
label: 'Change character…',
|
|
146
|
+
icon: PencilIcon,
|
|
147
|
+
onSelect: options.onChangeCharacter,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (options.speech) {
|
|
151
|
+
items.push({
|
|
152
|
+
id: 'speech',
|
|
153
|
+
label: options.speech.muted ? 'Unmute speech' : 'Mute speech',
|
|
154
|
+
icon: options.speech.muted ? UnmuteIcon : MuteIcon,
|
|
155
|
+
onSelect: options.speech.onToggle,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (options.onResetPosition) {
|
|
159
|
+
items.push({
|
|
160
|
+
id: 'reset-position',
|
|
161
|
+
label: 'Reset position',
|
|
162
|
+
icon: ScreenNormalIcon,
|
|
163
|
+
onSelect: options.onResetPosition,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (options.about) {
|
|
167
|
+
items.push({
|
|
168
|
+
id: 'about',
|
|
169
|
+
label: `About ${options.name}`,
|
|
170
|
+
icon: InfoIcon,
|
|
171
|
+
onSelect: options.about,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
items.push(...(options.extra ?? []));
|
|
175
|
+
for (const choice of AWAY_CHOICES) {
|
|
176
|
+
items.push({
|
|
177
|
+
id: `away:${choice.away}`,
|
|
178
|
+
label: choice.label,
|
|
179
|
+
group: 'Send away',
|
|
180
|
+
icon: XIcon,
|
|
181
|
+
onSelect: () => options.onDismiss(choice.away),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return items;
|
|
185
|
+
}
|
|
61
186
|
/** Room the balloon needs above the character before it goes below. */
|
|
62
187
|
const BALLOON_ROOM = 200;
|
|
63
188
|
/** A place's length in pixels, given as a number or as `"<n>px"`. */
|
|
@@ -210,7 +335,7 @@ export function useMouth(stageRef, mouthLevel, speaking) {
|
|
|
210
335
|
};
|
|
211
336
|
}, [stageRef, mouthLevel, speaking]);
|
|
212
337
|
}
|
|
213
|
-
export function AssistantStage({ character, state, size = 88, place, stageRef, onDragStart, open, onToggle, balloon, insist = false, onDismiss, ownRef, decide, mouthLevel, stayPut = false, balloonDisplay = 'history', expandTarget, expandOnArrival = false, }) {
|
|
338
|
+
export function AssistantStage({ character, state, size = 88, place, stageRef, onDragStart, open, onToggle, balloon, insist = false, onDismiss, ownRef, decide, mouthLevel, stayPut = false, balloonDisplay = 'history', expandTarget, expandOnArrival = false, suggestions, onSuggestion, inspector, inspectAgent, conversationLabel, onBalloonDisplayChange, onStop, onNewChat, onClear, onChangeCharacter, speech, onResetPosition, about, contextMenu, }) {
|
|
214
339
|
// A shipped one by id, a drawing contributed by a plugin (T-24), or a
|
|
215
340
|
// character read from a file (T-26).
|
|
216
341
|
const shipped = typeof character === 'string'
|
|
@@ -226,13 +351,48 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
|
|
|
226
351
|
const colorMode = colorScheme?.startsWith('dark') ? 'dark' : 'light';
|
|
227
352
|
const [hovered, setHovered] = useState(false);
|
|
228
353
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
354
|
+
// Its own menu, where it was opened; the inspector and *About* it opens.
|
|
355
|
+
const [menuAt, setMenuAt] = useState(null);
|
|
356
|
+
const [inspecting, setInspecting] = useState(false);
|
|
357
|
+
const [telling, setTelling] = useState(false);
|
|
358
|
+
const characterRef = useRef(null);
|
|
359
|
+
const longPress = useRef(undefined);
|
|
360
|
+
const closeMenu = useCallback(() => setMenuAt(null), []);
|
|
361
|
+
useEffect(() => () => clearTimeout(longPress.current), []);
|
|
229
362
|
// Where the press began: a press that moves is a drag, not a click.
|
|
230
363
|
const pressedAt = useRef(null);
|
|
231
|
-
const aside = useKeepClear(stageRef, ownRef, menuOpen || stayPut);
|
|
364
|
+
const aside = useKeepClear(stageRef, ownRef, menuOpen || menuAt !== null || inspecting || telling || stayPut);
|
|
232
365
|
useMouth(stageRef, mouthLevel, state === 'speaking');
|
|
233
366
|
// A decision being asked, or its answer, keeps the balloon up.
|
|
234
367
|
const [deciding, setDeciding] = useState(false);
|
|
235
368
|
const showBalloon = !aside && !open && !!balloon && (hovered || insist || deciding);
|
|
369
|
+
// Suggestions while it waits for a question.
|
|
370
|
+
const waitsForQuestion = (state === 'idle' || state === 'greeting') &&
|
|
371
|
+
!balloon?.tool &&
|
|
372
|
+
!balloon?.approval &&
|
|
373
|
+
!balloon?.busy;
|
|
374
|
+
const busy = state === 'thinking' || state === 'working' || state === 'speaking';
|
|
375
|
+
const menuItems = assistantMenuItems({
|
|
376
|
+
name,
|
|
377
|
+
open,
|
|
378
|
+
onToggle,
|
|
379
|
+
onDismiss,
|
|
380
|
+
conversationLabel,
|
|
381
|
+
inspect: inspector ? () => setInspecting(true) : undefined,
|
|
382
|
+
balloonDisplay,
|
|
383
|
+
onBalloonDisplayChange,
|
|
384
|
+
suggestions,
|
|
385
|
+
onSuggestion,
|
|
386
|
+
busy,
|
|
387
|
+
onStop,
|
|
388
|
+
onNewChat,
|
|
389
|
+
onClear,
|
|
390
|
+
onChangeCharacter,
|
|
391
|
+
speech,
|
|
392
|
+
onResetPosition,
|
|
393
|
+
about: about ? () => setTelling(true) : undefined,
|
|
394
|
+
extra: contextMenu,
|
|
395
|
+
});
|
|
236
396
|
// The large visual drawn now: kept while the balloon moves on to other
|
|
237
397
|
// words, until it is closed or another one is expanded.
|
|
238
398
|
const visual = balloon?.visual;
|
|
@@ -245,6 +405,17 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
|
|
|
245
405
|
}
|
|
246
406
|
}
|
|
247
407
|
: null;
|
|
408
|
+
// The history, large: drawn from the balloon's messages as they are now.
|
|
409
|
+
const history = balloon?.history;
|
|
410
|
+
const historyVisual = balloonDisplay !== 'current' && history && history.length > 0
|
|
411
|
+
? {
|
|
412
|
+
id: 'balloon-history',
|
|
413
|
+
title: conversationHeaderText(history.length),
|
|
414
|
+
shrink: true,
|
|
415
|
+
render: () => (_jsx(BalloonHistoryLarge, { history: history, tool: balloon?.tool, attachment: balloon?.attachment })),
|
|
416
|
+
}
|
|
417
|
+
: null;
|
|
418
|
+
const shown = expanded?.id === 'balloon-history' ? historyVisual : expanded;
|
|
248
419
|
useEffect(() => {
|
|
249
420
|
if (visual && expandOnArrival && expandTarget) {
|
|
250
421
|
setExpanded(visual);
|
|
@@ -349,8 +520,43 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
|
|
|
349
520
|
'@media (prefers-reduced-motion: reduce)': {
|
|
350
521
|
'& *': { animation: 'none !important' },
|
|
351
522
|
},
|
|
352
|
-
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [
|
|
523
|
+
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [shown && (_jsx(ExpandedVisual, { visual: shown, target: expandTarget, onClose: () => setExpanded(null) })), showBalloon && balloon && (_jsx(BalloonExpandContext.Provider, { value: expand, children: _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, history: balloon.history, onExpand: expand ?? undefined, expandTitle: visual?.title, onExpandHistory: historyVisual
|
|
524
|
+
? () => {
|
|
525
|
+
setExpanded(historyVisual);
|
|
526
|
+
if (expandTarget) {
|
|
527
|
+
requestAnimationFrame(() => focusExpanded(expandTarget));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
: undefined, suggestions: waitsForQuestion ? suggestions : undefined, fullText: balloon.fullText, waiting: (state === 'thinking' ||
|
|
531
|
+
state === 'working' ||
|
|
532
|
+
state === 'waiting') &&
|
|
533
|
+
!balloon.speaking, onSuggestion: onSuggestion, 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, "aria-haspopup": "menu", ref: characterRef, "data-assistant-figure": "", onContextMenu: (event) => {
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
clearTimeout(longPress.current);
|
|
536
|
+
setMenuAt({ x: event.clientX, y: event.clientY });
|
|
537
|
+
}, onKeyDown: (event) => {
|
|
538
|
+
if (opensContextMenu(event)) {
|
|
539
|
+
event.preventDefault();
|
|
540
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
541
|
+
setMenuAt({ x: box.left + box.width / 2, y: box.bottom });
|
|
542
|
+
}
|
|
543
|
+
}, onPointerUp: () => clearTimeout(longPress.current), onPointerCancel: () => clearTimeout(longPress.current), onPointerMove: (event) => {
|
|
544
|
+
const from = pressedAt.current;
|
|
545
|
+
if (from &&
|
|
546
|
+
Math.hypot(event.clientX - from.x, event.clientY - from.y) > 8) {
|
|
547
|
+
clearTimeout(longPress.current);
|
|
548
|
+
}
|
|
549
|
+
}, onPointerDown: (event) => {
|
|
353
550
|
pressedAt.current = { x: event.clientX, y: event.clientY };
|
|
551
|
+
if (event.pointerType === 'touch') {
|
|
552
|
+
const at = { x: event.clientX, y: event.clientY };
|
|
553
|
+
clearTimeout(longPress.current);
|
|
554
|
+
longPress.current = setTimeout(() => {
|
|
555
|
+
// Held, not tapped: the menu, and no click after it.
|
|
556
|
+
pressedAt.current = { x: -1e6, y: -1e6 };
|
|
557
|
+
setMenuAt(at);
|
|
558
|
+
}, LONG_PRESS_MS);
|
|
559
|
+
}
|
|
354
560
|
onDragStart(event);
|
|
355
561
|
}, onClick: (event) => {
|
|
356
562
|
const from = pressedAt.current;
|
|
@@ -381,6 +587,20 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
|
|
|
381
587
|
bg: 'canvas.default',
|
|
382
588
|
borderRadius: '50%',
|
|
383
589
|
boxShadow: 'shadow.small',
|
|
384
|
-
} }) }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsx(ActionList, { children: AWAY_CHOICES.map(choice => (_jsx(ActionList.Item, { "data-assistant-away": choice.away, onSelect: () => onDismiss(choice.away), children: choice.label }, choice.away))) }) })] }) }))
|
|
590
|
+
} }) }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsx(ActionList, { children: AWAY_CHOICES.map(choice => (_jsx(ActionList.Item, { "data-assistant-away": choice.away, onSelect: () => onDismiss(choice.away), children: choice.label }, choice.away))) }) })] }) })), _jsx(AssistantContextMenu, { at: menuAt, items: menuItems, onClose: closeMenu, returnFocusRef: characterRef, label: `${name}\u2019s menu` }), inspecting && (_jsx(Suspense, { fallback: null, children: _jsx(AgentInspectorDialog, { sink: inspector ?? null, agent: inspectAgent, title: `${about?.name ?? name} \u00b7 Agent Inspector`, onClose: () => {
|
|
591
|
+
setInspecting(false);
|
|
592
|
+
characterRef.current?.focus();
|
|
593
|
+
} }) })), telling && about && (_jsx(Dialog, { title: `About ${about.name ?? name}`, onClose: () => {
|
|
594
|
+
setTelling(false);
|
|
595
|
+
characterRef.current?.focus();
|
|
596
|
+
}, "data-assistant-about": "", children: [
|
|
597
|
+
['Agent', about.name],
|
|
598
|
+
['Spec', about.spec],
|
|
599
|
+
['Model', about.model],
|
|
600
|
+
['Runs', about.where],
|
|
601
|
+
['Character', name],
|
|
602
|
+
]
|
|
603
|
+
.filter(([, value]) => value)
|
|
604
|
+
.map(([key, value]) => (_jsxs(Text, { as: "p", sx: { m: 0, mb: 1 }, children: [_jsxs(Text, { sx: { color: 'fg.muted' }, children: [key, ": "] }), value] }, key))) }))] }));
|
|
385
605
|
}
|
|
386
606
|
export default AssistantStage;
|
|
@@ -45,10 +45,24 @@ export declare function ConversationBalloonHeader({ count, }: {
|
|
|
45
45
|
* done — the tool line while a tool runs, else the words — cut to a few
|
|
46
46
|
* lines, never scrolled; and what goes with them (`attachment`).
|
|
47
47
|
*/
|
|
48
|
-
export declare function CurrentBalloonBody({ text, tool, busy, speaking, attachment, }: {
|
|
48
|
+
export declare function CurrentBalloonBody({ text, tool, busy, speaking, attachment, waiting, whole, fullText, onOverflow, }: {
|
|
49
|
+
/**
|
|
50
|
+
* Shown whole: the full text (`fullText`, else `text`), unclamped, in a
|
|
51
|
+
* taller box that scrolls.
|
|
52
|
+
*/
|
|
53
|
+
whole?: boolean;
|
|
54
|
+
/** The text uncut, when `text` was cut to fit (`…`). */
|
|
55
|
+
fullText?: string;
|
|
56
|
+
/** Whether the clamped text overflows its lines: *more* has more to show. */
|
|
57
|
+
onOverflow?: (overflowing: boolean) => void;
|
|
49
58
|
text?: string;
|
|
50
59
|
tool?: BalloonToolLine;
|
|
51
60
|
busy?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* At work with nothing written yet (thinking, working, waiting): the
|
|
63
|
+
* chat's three dots follow the line, or stand alone.
|
|
64
|
+
*/
|
|
65
|
+
waiting?: boolean;
|
|
52
66
|
/** Words are arriving: read out once they have. */
|
|
53
67
|
speaking?: boolean;
|
|
54
68
|
attachment?: ReactNode;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { Text } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
5
|
import { CheckIcon, ToolsIcon, XIcon } from '@primer/octicons-react';
|
|
6
6
|
import { SpecMark, hasMark, marksOfToolCall } from '../marks';
|
|
7
|
+
import { TypingDots } from '../indicators/TypingDots';
|
|
7
8
|
import { toolAnnouncement, toolLineParts, } from './toolLine';
|
|
8
9
|
/** Out of sight, still heard. */
|
|
9
10
|
const VISUALLY_HIDDEN = {
|
|
@@ -129,12 +130,28 @@ export function ConversationBalloonHeader({ count, }) {
|
|
|
129
130
|
* done — the tool line while a tool runs, else the words — cut to a few
|
|
130
131
|
* lines, never scrolled; and what goes with them (`attachment`).
|
|
131
132
|
*/
|
|
132
|
-
export function CurrentBalloonBody({ text, tool, busy = false, speaking = false, attachment, }) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
export function CurrentBalloonBody({ text, tool, busy = false, speaking = false, attachment, waiting = false, whole = false, fullText, onOverflow, }) {
|
|
134
|
+
const textRef = useRef(null);
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
const element = textRef.current;
|
|
137
|
+
if (!element || whole || !onOverflow) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
onOverflow(element.scrollHeight > element.clientHeight + 1);
|
|
141
|
+
}, [text, whole, onOverflow]);
|
|
142
|
+
return (_jsxs(Box, { "data-balloon-current": "", sx: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 }, children: [_jsx(BalloonNow, { busy: busy }), tool ? (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(BalloonToolLineView, { line: tool }), waiting && _jsx(TypingDots, { size: 5 })] })) : text ? (_jsxs(Box, { "aria-live": "polite", "aria-busy": speaking, ref: textRef, "data-balloon-current-text": whole ? 'whole' : '', sx: whole
|
|
143
|
+
? {
|
|
144
|
+
maxHeight: 240,
|
|
145
|
+
overflowY: 'auto',
|
|
146
|
+
overflowWrap: 'anywhere',
|
|
147
|
+
whiteSpace: 'pre-wrap',
|
|
148
|
+
pr: 1,
|
|
149
|
+
}
|
|
150
|
+
: {
|
|
151
|
+
display: '-webkit-box',
|
|
152
|
+
WebkitLineClamp: 4,
|
|
153
|
+
WebkitBoxOrient: 'vertical',
|
|
154
|
+
overflow: 'hidden',
|
|
155
|
+
overflowWrap: 'anywhere',
|
|
156
|
+
}, children: [whole ? (fullText ?? text) : text, waiting && (_jsx(Box, { as: "span", sx: { ml: 2, display: 'inline-flex' }, children: _jsx(TypingDots, { size: 5 }) }))] })) : waiting ? (_jsx(TypingDots, { size: 6 })) : null, attachment, _jsx(ToolLineAnnouncer, { line: tool })] }));
|
|
140
157
|
}
|
|
@@ -19,8 +19,16 @@ export interface BalloonVisual {
|
|
|
19
19
|
id: string;
|
|
20
20
|
/** What it is: `Accounting's notebook`; the dialog's title. */
|
|
21
21
|
title: string;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The visual, large: drawn only once it is expanded — in the page's
|
|
24
|
+
* target, or in a dialog, which already shows `title`.
|
|
25
|
+
*/
|
|
26
|
+
render: (place: 'target' | 'overlay') => ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Large, it shrinks back into the balloon (*Shrink*) rather than closes:
|
|
29
|
+
* the balloon's own content drawn large, such as its history.
|
|
30
|
+
*/
|
|
31
|
+
shrink?: boolean;
|
|
24
32
|
}
|
|
25
33
|
/** Where an expanded visual goes: an element of the page, or none (a dialog). */
|
|
26
34
|
export type BalloonExpandTarget = RefObject<HTMLElement | null>;
|
|
@@ -3,7 +3,7 @@ import { createContext, useContext } from 'react';
|
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { Button, Dialog, IconButton } from '@primer/react';
|
|
5
5
|
import { Box } from '@datalayer/primer-addons';
|
|
6
|
-
import { ScreenFullIcon, XIcon } from '@primer/octicons-react';
|
|
6
|
+
import { ScreenFullIcon, ScreenNormalIcon, XIcon, } from '@primer/octicons-react';
|
|
7
7
|
/**
|
|
8
8
|
* Expands the visual of the balloon it is in: what its compact form (a
|
|
9
9
|
* notebook's preview) calls when it is clicked. None outside such a balloon.
|
|
@@ -29,14 +29,14 @@ export function ExpandedVisual({ visual, target, onClose, }) {
|
|
|
29
29
|
if (!element) {
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
|
-
return createPortal(_jsxs(Box, { "data-balloon-expanded": "target", "data-balloon-visual": visual.id, sx: { position: 'relative', minWidth: 0 }, children: [_jsx(Box, { sx: { position: 'absolute', top: 0, right: 0, zIndex: 1 }, children: _jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label":
|
|
32
|
+
return createPortal(_jsxs(Box, { "data-balloon-expanded": "target", "data-balloon-visual": visual.id, sx: { position: 'relative', minWidth: 0 }, children: [_jsx(Box, { sx: { position: 'absolute', top: 0, right: 0, zIndex: 1 }, children: _jsx(IconButton, { icon: visual.shrink ? ScreenNormalIcon : XIcon, size: "small", variant: "invisible", "aria-label": `${visual.shrink ? 'Shrink' : 'Close'} ${visual.title}`, "data-balloon-expanded-close": "", ...(visual.shrink ? { 'data-balloon-shrink': '' } : {}), onClick: onClose }) }), visual.render('target')] }), element);
|
|
33
33
|
}
|
|
34
34
|
return (_jsx(Dialog, { title: visual.title, onClose: onClose, sx: {
|
|
35
35
|
width: EXPANDED_DIALOG_SIZE.width,
|
|
36
36
|
maxWidth: EXPANDED_DIALOG_SIZE.width,
|
|
37
37
|
height: EXPANDED_DIALOG_SIZE.height,
|
|
38
38
|
maxHeight: EXPANDED_DIALOG_SIZE.height,
|
|
39
|
-
}, children:
|
|
39
|
+
}, children: _jsxs(Box, { "data-balloon-expanded": "overlay", "data-balloon-visual": visual.id, sx: { minWidth: 0 }, children: [visual.shrink && (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: ScreenNormalIcon, onClick: onClose, "data-balloon-shrink": "", sx: { float: 'right' }, children: "Shrink" })), visual.render('overlay')] }) }));
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Brings an expanded visual into view in its target: scrolled to, and the
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* them all — the person's and the agent's, under a header that counts them,
|
|
14
14
|
* scrolled to the newest — rather than the newest line alone.
|
|
15
15
|
*
|
|
16
|
+
* Given suggestions while the agent waits for a question, it offers them as
|
|
17
|
+
* chips under its words: one clicked is sent as the person's prompt.
|
|
18
|
+
*
|
|
16
19
|
* Shown `current` (LOOP T-23), it is the one thing being said or done now:
|
|
17
20
|
* *Now*, then the words as they are written, or the tool being called
|
|
18
21
|
* ("Using **list_invoices**…"), cut to a few lines, and what goes with them
|
|
@@ -26,6 +29,16 @@ import type { JSX, ReactNode } from 'react';
|
|
|
26
29
|
import { type BalloonApproval, type BalloonHistoryMessage } from './state';
|
|
27
30
|
import type { DecisionAsker } from './decisions';
|
|
28
31
|
import type { BalloonDisplay, BalloonToolLine } from './toolLine';
|
|
32
|
+
/** A suggestion the balloon offers: its label, and the prompt it sends. */
|
|
33
|
+
export type BalloonSuggestion = {
|
|
34
|
+
label: string;
|
|
35
|
+
prompt: string;
|
|
36
|
+
};
|
|
37
|
+
/** The suggestions, as chips: each sends its prompt. */
|
|
38
|
+
export declare function BalloonSuggestions({ suggestions, onSuggestion, }: {
|
|
39
|
+
suggestions: readonly BalloonSuggestion[];
|
|
40
|
+
onSuggestion: (suggestion: BalloonSuggestion) => void;
|
|
41
|
+
}): JSX.Element;
|
|
29
42
|
export interface SpeechBalloonProps {
|
|
30
43
|
text: string;
|
|
31
44
|
/** There is more than the balloon holds. */
|
|
@@ -75,10 +88,43 @@ export interface SpeechBalloonProps {
|
|
|
75
88
|
onExpand?: () => void;
|
|
76
89
|
/** What the large visual is, for the *Expand* button: `Accounting's notebook`. */
|
|
77
90
|
expandTitle?: string;
|
|
91
|
+
/** Draws the listed history large (`history` display): its *Expand*. */
|
|
92
|
+
onExpandHistory?: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* What the person may ask, as chips under the words: one clicked is sent
|
|
95
|
+
* as the prompt (`onSuggestion`), as if typed and asked.
|
|
96
|
+
*/
|
|
97
|
+
suggestions?: readonly BalloonSuggestion[];
|
|
98
|
+
onSuggestion?: (suggestion: BalloonSuggestion) => void;
|
|
99
|
+
/**
|
|
100
|
+
* At work with nothing written yet: the chat's three dots — after the
|
|
101
|
+
* current line, or last in the history — until words arrive.
|
|
102
|
+
*/
|
|
103
|
+
waiting?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* The words uncut, when `text` was cut to fit: *more* shows them whole,
|
|
106
|
+
* in the balloon, *less* folds them back.
|
|
107
|
+
*/
|
|
108
|
+
fullText?: string;
|
|
78
109
|
}
|
|
79
110
|
/** How tall the listed conversation grows before it scrolls, in pixels. */
|
|
80
111
|
export declare const BALLOON_HISTORY_MAX_HEIGHT = 220;
|
|
81
112
|
/** Shorter, when something goes with the words under it (a notebook). */
|
|
82
113
|
export declare const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
83
|
-
|
|
114
|
+
/**
|
|
115
|
+
* The conversation in the `history` balloon: a header that counts it and
|
|
116
|
+
* opens it, then every message, the person's to the right, scrolled to the
|
|
117
|
+
* newest.
|
|
118
|
+
*/
|
|
119
|
+
/**
|
|
120
|
+
* The history balloon, large (*Expand*): every message, then the tool being
|
|
121
|
+
* called and what goes with the words, in the overlay or the page's area,
|
|
122
|
+
* scrolled there.
|
|
123
|
+
*/
|
|
124
|
+
export declare function BalloonHistoryLarge({ history, tool, attachment, }: {
|
|
125
|
+
history: readonly BalloonHistoryMessage[];
|
|
126
|
+
tool?: BalloonToolLine;
|
|
127
|
+
attachment?: ReactNode;
|
|
128
|
+
}): JSX.Element;
|
|
129
|
+
export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, approval, decide, onDecisionActive, wide, onDismissPeek, display, tool, speaking, busy, attachment, history, onExpand, expandTitle, suggestions, onSuggestion, waiting, fullText, onExpandHistory, }: SpeechBalloonProps): JSX.Element;
|
|
84
130
|
export default SpeechBalloon;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { Button, IconButton, Text } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
5
|
import { XIcon } from '@primer/octicons-react';
|
|
@@ -8,6 +8,12 @@ import { moreWaiting } from './ConversationBalloon';
|
|
|
8
8
|
import { DecisionAsk } from './DecisionAsk';
|
|
9
9
|
import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
|
|
10
10
|
import { BalloonExpandButton } from './BalloonVisual';
|
|
11
|
+
import { ScreenFullIcon } from '@primer/octicons-react';
|
|
12
|
+
import { TypingDots } from '../indicators/TypingDots';
|
|
13
|
+
/** The suggestions, as chips: each sends its prompt. */
|
|
14
|
+
export function BalloonSuggestions({ suggestions, onSuggestion, }) {
|
|
15
|
+
return (_jsx(Box, { role: "group", "aria-label": "Suggestions", "data-balloon-suggestions": "", sx: { display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2 }, children: suggestions.map(suggestion => (_jsx(Button, { size: "small", "data-balloon-suggestion": suggestion.label, "aria-label": `Ask: ${suggestion.prompt}`, title: suggestion.prompt, onClick: () => onSuggestion(suggestion), sx: { borderRadius: 999 }, children: suggestion.label }, suggestion.label))) }));
|
|
16
|
+
}
|
|
11
17
|
/** How tall the listed conversation grows before it scrolls, in pixels. */
|
|
12
18
|
export const BALLOON_HISTORY_MAX_HEIGHT = 220;
|
|
13
19
|
/** Shorter, when something goes with the words under it (a notebook). */
|
|
@@ -17,7 +23,24 @@ export const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
|
17
23
|
* opens it, then every message, the person's to the right, scrolled to the
|
|
18
24
|
* newest.
|
|
19
25
|
*/
|
|
20
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The history balloon, large (*Expand*): every message, then the tool being
|
|
28
|
+
* called and what goes with the words, in the overlay or the page's area,
|
|
29
|
+
* scrolled there.
|
|
30
|
+
*/
|
|
31
|
+
export function BalloonHistoryLarge({ history, tool, attachment, }) {
|
|
32
|
+
return (_jsxs(Box, { role: "log", "aria-label": "Conversation", "data-balloon-history-large": "", sx: { display: 'flex', flexDirection: 'column', gap: 2, fontSize: 1 }, children: [history.map(message => (_jsx(Box, { "data-balloon-history-message": message.role, sx: {
|
|
33
|
+
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
34
|
+
maxWidth: '80%',
|
|
35
|
+
px: 3,
|
|
36
|
+
py: 2,
|
|
37
|
+
borderRadius: 2,
|
|
38
|
+
bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
39
|
+
whiteSpace: 'pre-wrap',
|
|
40
|
+
overflowWrap: 'anywhere',
|
|
41
|
+
}, children: message.text }, message.id))), tool ? _jsx(BalloonToolLineView, { line: tool }) : null, attachment ? _jsx(Box, { children: attachment }) : null] }));
|
|
42
|
+
}
|
|
43
|
+
function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, onExpand, }) {
|
|
21
44
|
const listRef = useRef(null);
|
|
22
45
|
const newest = history[history.length - 1]?.id;
|
|
23
46
|
useEffect(() => {
|
|
@@ -39,7 +62,7 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
|
|
|
39
62
|
fontWeight: 'semibold',
|
|
40
63
|
color: 'fg.muted',
|
|
41
64
|
'&:hover': { textDecoration: 'underline' },
|
|
42
|
-
}, children: conversationHeaderText(history.length) }), _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
|
|
65
|
+
}, children: conversationHeaderText(history.length) }), onExpand ? (_jsx(IconButton, { icon: ScreenFullIcon, size: "small", variant: "invisible", "aria-label": "Expand the conversation", "data-balloon-history-expand": "", onClick: onExpand, sx: { position: 'absolute', top: '4px', right: '28px' } })) : null, _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
|
|
43
66
|
maxHeight,
|
|
44
67
|
overflowY: 'auto',
|
|
45
68
|
display: 'flex',
|
|
@@ -56,7 +79,19 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
|
|
|
56
79
|
overflowWrap: 'anywhere',
|
|
57
80
|
}, children: message.text }, message.id))) })] }));
|
|
58
81
|
}
|
|
59
|
-
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, history, onExpand, expandTitle, }) {
|
|
82
|
+
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, history, onExpand, expandTitle, suggestions, onSuggestion, waiting = false, fullText, onExpandHistory, }) {
|
|
83
|
+
// *more*: the words whole, when they were cut or overflow their lines.
|
|
84
|
+
const [whole, setWhole] = useState(false);
|
|
85
|
+
const [overflowing, setOverflowing] = useState(false);
|
|
86
|
+
const cut = (!!fullText && fullText !== text) || (!!more && text.endsWith('…'));
|
|
87
|
+
const wholeText = fullText ?? text;
|
|
88
|
+
// Other words, folded again.
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
setWhole(false);
|
|
91
|
+
}, [wholeText.slice(0, 40)]);
|
|
92
|
+
const offered = suggestions && suggestions.length > 0 && onSuggestion && !approval
|
|
93
|
+
? suggestions
|
|
94
|
+
: null;
|
|
60
95
|
const current = display === 'current';
|
|
61
96
|
// History with messages to list: all of them, not the newest line alone.
|
|
62
97
|
const listed = !current && history && history.length > 0 ? history : null;
|
|
@@ -68,8 +103,14 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
68
103
|
? { bottom: `${above}px` }
|
|
69
104
|
: { top: `${above}px` }),
|
|
70
105
|
[align]: 0,
|
|
71
|
-
maxWidth: wide || listed || (current && attachment)
|
|
72
|
-
|
|
106
|
+
maxWidth: wide || listed || offered || whole || (current && attachment)
|
|
107
|
+
? 300
|
|
108
|
+
: current
|
|
109
|
+
? 260
|
|
110
|
+
: 280,
|
|
111
|
+
width: wide || listed || offered || whole || (current && attachment)
|
|
112
|
+
? 300
|
|
113
|
+
: 'max-content',
|
|
73
114
|
px: 3,
|
|
74
115
|
py: 2,
|
|
75
116
|
bg: 'canvas.default',
|
|
@@ -106,7 +147,7 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
106
147
|
font: 'inherit',
|
|
107
148
|
textAlign: 'left',
|
|
108
149
|
cursor: 'pointer',
|
|
109
|
-
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, maxHeight: attachment
|
|
150
|
+
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking, waiting: waiting, whole: whole, fullText: wholeText, onOverflow: setOverflowing }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, onExpand: onExpandHistory, maxHeight: attachment
|
|
110
151
|
? BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT
|
|
111
152
|
: BALLOON_HISTORY_MAX_HEIGHT })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
|
|
112
153
|
flex: 1,
|
|
@@ -120,6 +161,17 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
120
161
|
textAlign: 'left',
|
|
121
162
|
cursor: 'pointer',
|
|
122
163
|
'&:hover': { textDecoration: 'underline' },
|
|
123
|
-
}, 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] }),
|
|
164
|
+
}, 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] }), current && !approval && !tool && (cut || overflowing || whole) ? (_jsx(Box, { as: "button", type: "button", "aria-expanded": whole, "data-balloon-whole": whole ? 'less' : 'more', onClick: () => setWhole(!whole), sx: {
|
|
165
|
+
m: 0,
|
|
166
|
+
mt: 1,
|
|
167
|
+
p: 0,
|
|
168
|
+
border: 0,
|
|
169
|
+
bg: 'transparent',
|
|
170
|
+
color: 'accent.fg',
|
|
171
|
+
font: 'inherit',
|
|
172
|
+
fontSize: 0,
|
|
173
|
+
cursor: 'pointer',
|
|
174
|
+
'&:hover': { textDecoration: 'underline' },
|
|
175
|
+
}, children: whole ? 'less' : 'more' })) : null, listed && tool ? (_jsx(Box, { "data-balloon-history-tool": "", sx: { mt: 1 }, children: _jsx(BalloonToolLineView, { line: tool }) })) : null, !current && waiting ? (_jsx(Box, { "data-balloon-waiting": "", sx: { mt: 1 }, children: _jsx(TypingDots, { size: 6 }) })) : 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] })), offered && onSuggestion ? (_jsx(BalloonSuggestions, { suggestions: offered, onSuggestion: onSuggestion })) : null, attachment ? (_jsxs(Box, { "data-balloon-attachment": "", sx: { mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }, children: [attachment, onExpand ? (_jsx(BalloonExpandButton, { title: expandTitle ?? 'the visual', onExpand: onExpand })) : null] })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
|
|
124
176
|
}
|
|
125
177
|
export default SpeechBalloon;
|