@datalayer/agent-runtimes 1.3.73 → 1.3.74
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 +9 -2
- package/lib/chat/ChatFloating.js +30 -9
- package/lib/chat/assistant/AssistantStage.d.ts +29 -5
- package/lib/chat/assistant/AssistantStage.js +20 -2
- package/lib/chat/assistant/BalloonVisual.d.ts +57 -0
- package/lib/chat/assistant/BalloonVisual.js +58 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +22 -2
- package/lib/chat/assistant/SpeechBalloon.js +61 -7
- package/lib/chat/assistant/state.d.ts +19 -0
- package/lib/chat/assistant/state.js +44 -17
- package/lib/components/teams/A2ATeamGraph.d.ts +12 -2
- package/lib/components/teams/A2ATeamGraph.js +14 -5
- package/lib/components/teams/NotebookPreview.d.ts +11 -2
- package/lib/components/teams/NotebookPreview.js +6 -3
- package/lib/components/teams/TeamNotebook.d.ts +7 -0
- package/lib/components/teams/TeamNotebook.js +13 -1
- package/lib/examples/AgentA2ATeamExample.d.ts +3 -2
- package/lib/examples/AgentA2ATeamExample.js +6 -2
- package/lib/examples/{ChatAssistantExample.d.ts → AssistantExample.d.ts} +2 -2
- package/lib/examples/{ChatAssistantExample.js → AssistantExample.js} +7 -5
- package/lib/examples/{ChatAssistantGalleryExample.d.ts → AssistantGalleryExample.d.ts} +2 -2
- package/lib/examples/{ChatAssistantGalleryExample.js → AssistantGalleryExample.js} +45 -13
- package/lib/examples/example-selector.js +4 -2
- package/lib/examples/exampleGroups.d.ts +9 -0
- package/lib/examples/exampleGroups.js +56 -0
- package/lib/examples/index.d.ts +1 -1
- package/lib/examples/index.js +1 -1
- package/lib/examples/main.js +3 -43
- package/lib/examples/utils/AssistantGalleryGrid.d.ts +6 -1
- package/lib/examples/utils/AssistantGalleryGrid.js +16 -3
- package/lib/examples/utils/assistantGallery.d.ts +51 -2
- package/lib/examples/utils/assistantGallery.js +32 -7
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import React from 'react';
|
|
19
19
|
import type { ChatVoice } from '../voice';
|
|
20
20
|
import { type BalloonDisplay, type BalloonToolLine } from './assistant/toolLine';
|
|
21
|
+
import type { BalloonExpandTarget } from './assistant/BalloonVisual';
|
|
21
22
|
import { type AssistantCharacter } from './assistant/characters';
|
|
22
23
|
import type { AssistantCharacterData } from './assistant/formats/types';
|
|
23
24
|
import { type AssistantSaying } from './assistant/state';
|
|
@@ -118,7 +119,7 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
118
119
|
* How the floating assistant's balloon shows the conversation (LOOP
|
|
119
120
|
* T-23; an Appspec's `interface.balloon`):
|
|
120
121
|
* - 'history': every message, scrolled, under a header that counts them,
|
|
121
|
-
* the composer last; closed,
|
|
122
|
+
* the composer last; closed, the balloon lists the messages too.
|
|
122
123
|
* - 'current': only what it says or does now — the answer being written,
|
|
123
124
|
* or the tool it calls — in one compact balloon, *Now* on it; open, that
|
|
124
125
|
* line and the composer, nothing to scroll.
|
|
@@ -126,6 +127,12 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
126
127
|
* @default 'history'
|
|
127
128
|
*/
|
|
128
129
|
balloonDisplay?: BalloonDisplay;
|
|
130
|
+
/**
|
|
131
|
+
* Where a large visual the assistant's balloon carries (a notebook) is
|
|
132
|
+
* drawn when expanded: an element of the page, through a portal; unsaid,
|
|
133
|
+
* a large dialog over the page.
|
|
134
|
+
*/
|
|
135
|
+
expandTarget?: BalloonExpandTarget;
|
|
129
136
|
/**
|
|
130
137
|
* The runtime the floating assistant asks typed decisions at — its
|
|
131
138
|
* `/api/v1/configure/inference/decisions`, as the `decide` tool asks — and
|
|
@@ -177,5 +184,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
177
184
|
* ChatFloating component
|
|
178
185
|
* A floating chat window built on ChatBase
|
|
179
186
|
*/
|
|
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;
|
|
187
|
+
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, expandTarget, conversation, decisions, voice, }: ChatFloatingProps): React.JSX.Element;
|
|
181
188
|
export default ChatFloating;
|
package/lib/chat/ChatFloating.js
CHANGED
|
@@ -40,7 +40,7 @@ import { ConversationBalloonHeader, CurrentBalloonBody, } from './assistant/Ball
|
|
|
40
40
|
import { DEFAULT_BALLOON_DISPLAY, conversationCount, newestToolLine, toolLineText, } from './assistant/toolLine';
|
|
41
41
|
import { SpeechBalloon } from './assistant/SpeechBalloon';
|
|
42
42
|
import { DEFAULT_ASSISTANT_CHARACTER, } from './assistant/characters';
|
|
43
|
-
import { assistantStateOf, latestSaying, newestIsAnswer, keepAway, keptAway, ASSISTANT_WORDS, } from './assistant/state';
|
|
43
|
+
import { assistantStateOf, balloonHistoryOf, latestSaying, newestIsAnswer, keepAway, keptAway, ASSISTANT_WORDS, } from './assistant/state';
|
|
44
44
|
import { presenceState, presenceToolOf, } from './presence/presenceStatus';
|
|
45
45
|
import { useChatOpen, useChatMessages, useChatStore, } from '../stores/chatStore';
|
|
46
46
|
import { useChatKeyboardShortcuts, getShortcutDisplay, } from '@datalayer/core/lib/hooks';
|
|
@@ -70,7 +70,7 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
|
|
|
70
70
|
* ChatFloating component
|
|
71
71
|
* A floating chat window built on ChatBase
|
|
72
72
|
*/
|
|
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, }) {
|
|
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, expandTarget, conversation, decisions, voice, }) {
|
|
74
74
|
// Store-based state
|
|
75
75
|
const storeIsOpen = useChatOpen();
|
|
76
76
|
const storeMessages = useChatMessages();
|
|
@@ -136,6 +136,8 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
136
136
|
// The tool it calls now, and how many messages the history holds (T-23).
|
|
137
137
|
const [ownTool, setOwnTool] = useState();
|
|
138
138
|
const [messageCount, setMessageCount] = useState(0);
|
|
139
|
+
// The conversation's messages, listed in the closed `history` balloon.
|
|
140
|
+
const [ownHistory, setOwnHistory] = useState([]);
|
|
139
141
|
/*
|
|
140
142
|
* Its voice (VOICE.md V1): the answers said by the speech service, as
|
|
141
143
|
* they are written (VO-20), the state following the sound (VO-22).
|
|
@@ -259,6 +261,11 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
259
261
|
const toolSaid = toolLine && (toolLine.phase === 'running' || atWork) ? toolLine : undefined;
|
|
260
262
|
// Current: what is said now — thinking until the answer's words arrive.
|
|
261
263
|
const currentWords = atWork && !answering ? 'Thinking…' : saying?.text || undefined;
|
|
264
|
+
// History: the closed balloon lists the conversation, not its newest line.
|
|
265
|
+
const listedHistory = !showsCurrent && !conversation && ownHistory.length > 0
|
|
266
|
+
? ownHistory
|
|
267
|
+
: undefined;
|
|
268
|
+
const withHistory = (said) => listedHistory ? { ...said, history: listedHistory } : said;
|
|
262
269
|
const assistantBalloon = assistantState === 'paused'
|
|
263
270
|
? { text: ASSISTANT_WORDS.paused }
|
|
264
271
|
: speakerState.sentence
|
|
@@ -267,11 +274,18 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
267
274
|
: speakerState.refused
|
|
268
275
|
? { text: speakerState.refused }
|
|
269
276
|
: balloonApproval
|
|
270
|
-
? {
|
|
277
|
+
? withHistory({
|
|
278
|
+
text: ASSISTANT_WORDS.approval,
|
|
279
|
+
approval: balloonApproval,
|
|
280
|
+
})
|
|
271
281
|
: assistantState === 'waiting'
|
|
272
282
|
? { text: ASSISTANT_WORDS.waiting }
|
|
273
283
|
: toolSaid
|
|
274
|
-
? {
|
|
284
|
+
? withHistory({
|
|
285
|
+
text: toolLineText(toolSaid),
|
|
286
|
+
tool: toolSaid,
|
|
287
|
+
busy: atWork,
|
|
288
|
+
})
|
|
275
289
|
: showsCurrent && (unheard || atWork)
|
|
276
290
|
? // Current: the words as they are written, whole.
|
|
277
291
|
{
|
|
@@ -287,15 +301,16 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
287
301
|
: undefined,
|
|
288
302
|
}
|
|
289
303
|
: unheard && saying
|
|
290
|
-
? {
|
|
291
|
-
// A peek: the first words; the rest is in the
|
|
304
|
+
? withHistory({
|
|
305
|
+
// A peek: the first words; the rest is in the
|
|
306
|
+
// conversation, listed when there is one.
|
|
292
307
|
...peekLine(saying.text),
|
|
293
308
|
onDismiss: () => {
|
|
294
309
|
setHeardId(saying.id);
|
|
295
310
|
setFreshSaying(false);
|
|
296
311
|
},
|
|
297
|
-
}
|
|
298
|
-
: { text: description };
|
|
312
|
+
})
|
|
313
|
+
: withHistory({ text: description });
|
|
299
314
|
const balloonInsists = !!speakerState.sentence ||
|
|
300
315
|
assistantState === 'greeting' ||
|
|
301
316
|
assistantState === 'waiting' ||
|
|
@@ -321,6 +336,12 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
321
336
|
? previous
|
|
322
337
|
: line);
|
|
323
338
|
setMessageCount(conversationCount(items));
|
|
339
|
+
const listed = balloonHistoryOf(items);
|
|
340
|
+
setOwnHistory(previous => previous.length === listed.length &&
|
|
341
|
+
previous.every((message, index) => message.id === listed[index].id &&
|
|
342
|
+
message.text === listed[index].text)
|
|
343
|
+
? previous
|
|
344
|
+
: listed);
|
|
324
345
|
setVoiceItems(items);
|
|
325
346
|
const said = latestSaying(items);
|
|
326
347
|
setSaying(previous => previous?.id === said?.id && previous?.text === said?.text
|
|
@@ -1050,7 +1071,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
1050
1071
|
left: px(stageDrag.position.left),
|
|
1051
1072
|
top: px(stageDrag.position.top),
|
|
1052
1073
|
}
|
|
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: {
|
|
1074
|
+
: getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, balloonDisplay: balloonDisplay, expandTarget: expandTarget, insist: balloonInsists, onDismiss: dismissAssistant, ownRef: popupRef, mouthLevel: mouthLevel })), showButton && !isOpen && !assistantShown && (_jsx(Box, { sx: {
|
|
1054
1075
|
...getPositionStyles(),
|
|
1055
1076
|
}, children: _jsxs(Box, { sx: {
|
|
1056
1077
|
position: 'relative',
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { JSX, ReactNode, RefObject } from 'react';
|
|
18
18
|
import { type AssistantCharacter } from './characters';
|
|
19
|
+
import { type BalloonExpandTarget, type BalloonVisual } from './BalloonVisual';
|
|
19
20
|
import type { AssistantCharacterData } from './formats/types';
|
|
20
21
|
import type { DecisionAsker } from './decisions';
|
|
21
22
|
import type { BalloonDisplay, BalloonToolLine } from './toolLine';
|
|
22
|
-
import { type AssistantAway, type AssistantState, type BalloonApproval } from './state';
|
|
23
|
+
import { type AssistantAway, type AssistantState, type BalloonApproval, type BalloonHistoryMessage } from './state';
|
|
23
24
|
/**
|
|
24
25
|
* Where the balloon goes so that it stays inside the window (T-23): toward
|
|
25
26
|
* the middle of the page from whichever half the character stands in, and
|
|
@@ -88,11 +89,34 @@ export interface AssistantStageProps {
|
|
|
88
89
|
busy?: boolean;
|
|
89
90
|
/** What goes with the words (`current`): a notebook given, read-only. */
|
|
90
91
|
attachment?: ReactNode;
|
|
92
|
+
/**
|
|
93
|
+
* The conversation's messages (`history`): the balloon lists them all,
|
|
94
|
+
* counted and scrolled, rather than the newest line alone.
|
|
95
|
+
*/
|
|
96
|
+
history?: readonly BalloonHistoryMessage[];
|
|
97
|
+
/**
|
|
98
|
+
* The large visual `attachment` is the compact form of (a notebook):
|
|
99
|
+
* the balloon offers *Expand*, and draws it large — into
|
|
100
|
+
* `expandTarget`, or in a dialog over the page.
|
|
101
|
+
*/
|
|
102
|
+
visual?: BalloonVisual;
|
|
91
103
|
};
|
|
92
104
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
105
|
+
* Where a balloon's large visual is drawn when expanded: an element of
|
|
106
|
+
* the page (through a portal), such as the area under a team's graph;
|
|
107
|
+
* without one, a large dialog over the page.
|
|
108
|
+
*/
|
|
109
|
+
expandTarget?: BalloonExpandTarget;
|
|
110
|
+
/**
|
|
111
|
+
* Draw a large visual into `expandTarget` as soon as it arrives, without
|
|
112
|
+
* waiting for *Expand*: a team's notebook, under its graph.
|
|
113
|
+
*/
|
|
114
|
+
expandOnArrival?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* How the balloon shows the conversation (LOOP T-23): the conversation
|
|
117
|
+
* (`history`, the default — closed, the messages in `balloon.history`, or
|
|
118
|
+
* a peek of the newest when none are given; open, the whole history), or
|
|
119
|
+
* the one thing being said or done now (`current`).
|
|
96
120
|
*/
|
|
97
121
|
balloonDisplay?: BalloonDisplay;
|
|
98
122
|
/** Show the balloon without being hovered: something new to say. */
|
|
@@ -129,5 +153,5 @@ export declare function mouthOpening(level: number): 0 | 1 | 2 | 3;
|
|
|
129
153
|
* `data-assistant-mouth`, each frame, without drawing the character again.
|
|
130
154
|
*/
|
|
131
155
|
export declare function useMouth(stageRef: RefObject<HTMLElement | null>, mouthLevel: (() => number) | undefined, speaking: boolean): void;
|
|
132
|
-
export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, ownRef, decide, mouthLevel, stayPut, balloonDisplay, }: AssistantStageProps): JSX.Element;
|
|
156
|
+
export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, ownRef, decide, mouthLevel, stayPut, balloonDisplay, expandTarget, expandOnArrival, }: AssistantStageProps): JSX.Element;
|
|
133
157
|
export default AssistantStage;
|
|
@@ -5,6 +5,7 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
5
5
|
import { XIcon } from '@primer/octicons-react';
|
|
6
6
|
import { assistantCharacter } from './characters';
|
|
7
7
|
import { SpeechBalloon } from './SpeechBalloon';
|
|
8
|
+
import { BalloonExpandContext, ExpandedVisual, focusExpanded, } from './BalloonVisual';
|
|
8
9
|
import { SpriteCharacter } from './SpriteCharacter';
|
|
9
10
|
import { ASSISTANT_OBSTACLES, POINTER_CALM_MS, boxesMeet, pointerNear, } from './state';
|
|
10
11
|
/** The motions, by the state the stage is in. */
|
|
@@ -209,7 +210,7 @@ export function useMouth(stageRef, mouthLevel, speaking) {
|
|
|
209
210
|
};
|
|
210
211
|
}, [stageRef, mouthLevel, speaking]);
|
|
211
212
|
}
|
|
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
|
+
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, }) {
|
|
213
214
|
// A shipped one by id, a drawing contributed by a plugin (T-24), or a
|
|
214
215
|
// character read from a file (T-26).
|
|
215
216
|
const shipped = typeof character === 'string'
|
|
@@ -232,6 +233,23 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
|
|
|
232
233
|
// A decision being asked, or its answer, keeps the balloon up.
|
|
233
234
|
const [deciding, setDeciding] = useState(false);
|
|
234
235
|
const showBalloon = !aside && !open && !!balloon && (hovered || insist || deciding);
|
|
236
|
+
// The large visual drawn now: kept while the balloon moves on to other
|
|
237
|
+
// words, until it is closed or another one is expanded.
|
|
238
|
+
const visual = balloon?.visual;
|
|
239
|
+
const [expanded, setExpanded] = useState(null);
|
|
240
|
+
const expand = visual
|
|
241
|
+
? () => {
|
|
242
|
+
setExpanded(visual);
|
|
243
|
+
if (expandTarget) {
|
|
244
|
+
requestAnimationFrame(() => focusExpanded(expandTarget));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
: null;
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (visual && expandOnArrival && expandTarget) {
|
|
250
|
+
setExpanded(visual);
|
|
251
|
+
}
|
|
252
|
+
}, [visual?.id, expandOnArrival, !!expandTarget]);
|
|
235
253
|
return (_jsxs(Box, { ref: stageRef, "data-assistant-state": state, "data-assistant-aside": aside, "data-assistant-balloon": balloonDisplay, sx: {
|
|
236
254
|
position: 'fixed',
|
|
237
255
|
zIndex: 1002,
|
|
@@ -331,7 +349,7 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
|
|
|
331
349
|
'@media (prefers-reduced-motion: reduce)': {
|
|
332
350
|
'& *': { animation: 'none !important' },
|
|
333
351
|
},
|
|
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) => {
|
|
352
|
+
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [expanded && (_jsx(ExpandedVisual, { visual: expanded, 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, 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
353
|
pressedAt.current = { x: event.clientX, y: event.clientY };
|
|
336
354
|
onDragStart(event);
|
|
337
355
|
}, onClick: (event) => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A large visual a balloon carries (LOOP T-23): a notebook today, a chart or
|
|
3
|
+
* a table later. The balloon shows it compact — its `attachment`, such as
|
|
4
|
+
* the read-only preview of a notebook — with an *Expand* button; expanded,
|
|
5
|
+
* the visual is drawn large: into the element the host names
|
|
6
|
+
* (`expandTarget`, through a portal), or, when it names none, in a large
|
|
7
|
+
* dialog over the page — Esc closes it, and the focus stays in it while it
|
|
8
|
+
* is open.
|
|
9
|
+
*
|
|
10
|
+
* What the expanded visual is, the visual says (`render`): for a notebook,
|
|
11
|
+
* the one that runs and is edited on the browser sandbox (`TeamNotebook`).
|
|
12
|
+
*
|
|
13
|
+
* @module chat/assistant/BalloonVisual
|
|
14
|
+
*/
|
|
15
|
+
import type { JSX, ReactNode, RefObject } from 'react';
|
|
16
|
+
/** A large visual a balloon carries, and how it is drawn large. */
|
|
17
|
+
export interface BalloonVisual {
|
|
18
|
+
/** Its id: a new one is a new visual. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** What it is: `Accounting's notebook`; the dialog's title. */
|
|
21
|
+
title: string;
|
|
22
|
+
/** The visual, large: drawn only once it is expanded. */
|
|
23
|
+
render: () => ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/** Where an expanded visual goes: an element of the page, or none (a dialog). */
|
|
26
|
+
export type BalloonExpandTarget = RefObject<HTMLElement | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Expands the visual of the balloon it is in: what its compact form (a
|
|
29
|
+
* notebook's preview) calls when it is clicked. None outside such a balloon.
|
|
30
|
+
*/
|
|
31
|
+
export declare const BalloonExpandContext: import("react").Context<(() => void) | null>;
|
|
32
|
+
/** The balloon's way to expand its visual, if it carries one. */
|
|
33
|
+
export declare function useBalloonExpand(): (() => void) | null;
|
|
34
|
+
/** The balloon's *Expand* button. */
|
|
35
|
+
export declare function BalloonExpandButton({ title, onExpand, }: {
|
|
36
|
+
title: string;
|
|
37
|
+
onExpand: () => void;
|
|
38
|
+
}): JSX.Element;
|
|
39
|
+
/** How large the dialog is when no target is named: most of the window. */
|
|
40
|
+
export declare const EXPANDED_DIALOG_SIZE: {
|
|
41
|
+
readonly width: "80vw";
|
|
42
|
+
readonly height: "80vh";
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The visual, expanded: into `target` when it names an element, else in a
|
|
46
|
+
* large dialog over the page, closed with Esc or its close button.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ExpandedVisual({ visual, target, onClose, }: {
|
|
49
|
+
visual: BalloonVisual;
|
|
50
|
+
target?: BalloonExpandTarget;
|
|
51
|
+
onClose: () => void;
|
|
52
|
+
}): JSX.Element | null;
|
|
53
|
+
/**
|
|
54
|
+
* Brings an expanded visual into view in its target: scrolled to, and the
|
|
55
|
+
* first element that takes the focus there (a notebook's section) focused.
|
|
56
|
+
*/
|
|
57
|
+
export declare function focusExpanded(target: BalloonExpandTarget | undefined): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { Button, Dialog, IconButton } from '@primer/react';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { ScreenFullIcon, XIcon } from '@primer/octicons-react';
|
|
7
|
+
/**
|
|
8
|
+
* Expands the visual of the balloon it is in: what its compact form (a
|
|
9
|
+
* notebook's preview) calls when it is clicked. None outside such a balloon.
|
|
10
|
+
*/
|
|
11
|
+
export const BalloonExpandContext = createContext(null);
|
|
12
|
+
/** The balloon's way to expand its visual, if it carries one. */
|
|
13
|
+
export function useBalloonExpand() {
|
|
14
|
+
return useContext(BalloonExpandContext);
|
|
15
|
+
}
|
|
16
|
+
/** The balloon's *Expand* button. */
|
|
17
|
+
export function BalloonExpandButton({ title, onExpand, }) {
|
|
18
|
+
return (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: ScreenFullIcon, "aria-label": `Expand ${title}`, "data-balloon-expand": "", onClick: onExpand, sx: { alignSelf: 'flex-start', px: 1 }, children: "Expand" }));
|
|
19
|
+
}
|
|
20
|
+
/** How large the dialog is when no target is named: most of the window. */
|
|
21
|
+
export const EXPANDED_DIALOG_SIZE = { width: '80vw', height: '80vh' };
|
|
22
|
+
/**
|
|
23
|
+
* The visual, expanded: into `target` when it names an element, else in a
|
|
24
|
+
* large dialog over the page, closed with Esc or its close button.
|
|
25
|
+
*/
|
|
26
|
+
export function ExpandedVisual({ visual, target, onClose, }) {
|
|
27
|
+
const element = target?.current;
|
|
28
|
+
if (target) {
|
|
29
|
+
if (!element) {
|
|
30
|
+
return null;
|
|
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": `Close ${visual.title}`, "data-balloon-expanded-close": "", onClick: onClose }) }), visual.render()] }), element);
|
|
33
|
+
}
|
|
34
|
+
return (_jsx(Dialog, { title: visual.title, onClose: onClose, sx: {
|
|
35
|
+
width: EXPANDED_DIALOG_SIZE.width,
|
|
36
|
+
maxWidth: EXPANDED_DIALOG_SIZE.width,
|
|
37
|
+
height: EXPANDED_DIALOG_SIZE.height,
|
|
38
|
+
maxHeight: EXPANDED_DIALOG_SIZE.height,
|
|
39
|
+
}, children: _jsx(Box, { "data-balloon-expanded": "overlay", "data-balloon-visual": visual.id, sx: { minWidth: 0 }, children: visual.render() }) }));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Brings an expanded visual into view in its target: scrolled to, and the
|
|
43
|
+
* first element that takes the focus there (a notebook's section) focused.
|
|
44
|
+
*/
|
|
45
|
+
export function focusExpanded(target) {
|
|
46
|
+
const element = target?.current;
|
|
47
|
+
if (!element) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const still = typeof window !== 'undefined' &&
|
|
51
|
+
!!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
52
|
+
element.scrollIntoView?.({
|
|
53
|
+
behavior: still ? 'auto' : 'smooth',
|
|
54
|
+
block: 'start',
|
|
55
|
+
});
|
|
56
|
+
const focusable = element.querySelector('[tabindex="-1"]') ?? element;
|
|
57
|
+
focusable.focus?.({ preventScroll: true });
|
|
58
|
+
}
|
|
@@ -9,6 +9,10 @@
|
|
|
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 `history` with the conversation's messages (`history`), it lists
|
|
13
|
+
* them all — the person's and the agent's, under a header that counts them,
|
|
14
|
+
* scrolled to the newest — rather than the newest line alone.
|
|
15
|
+
*
|
|
12
16
|
* Shown `current` (LOOP T-23), it is the one thing being said or done now:
|
|
13
17
|
* *Now*, then the words as they are written, or the tool being called
|
|
14
18
|
* ("Using **list_invoices**…"), cut to a few lines, and what goes with them
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
* @module chat/assistant/SpeechBalloon
|
|
20
24
|
*/
|
|
21
25
|
import type { JSX, ReactNode } from 'react';
|
|
22
|
-
import { type BalloonApproval } from './state';
|
|
26
|
+
import { type BalloonApproval, type BalloonHistoryMessage } from './state';
|
|
23
27
|
import type { DecisionAsker } from './decisions';
|
|
24
28
|
import type { BalloonDisplay, BalloonToolLine } from './toolLine';
|
|
25
29
|
export interface SpeechBalloonProps {
|
|
@@ -59,6 +63,22 @@ export interface SpeechBalloonProps {
|
|
|
59
63
|
busy?: boolean;
|
|
60
64
|
/** What goes with the words, under them: a notebook given, read-only. */
|
|
61
65
|
attachment?: ReactNode;
|
|
66
|
+
/**
|
|
67
|
+
* The conversation's messages (`history`): listed in the balloon, every
|
|
68
|
+
* one, in place of the newest line alone.
|
|
69
|
+
*/
|
|
70
|
+
history?: readonly BalloonHistoryMessage[];
|
|
71
|
+
/**
|
|
72
|
+
* What goes with the words is a large visual (a notebook): an *Expand*
|
|
73
|
+
* button under it draws it large, named by `expandTitle`.
|
|
74
|
+
*/
|
|
75
|
+
onExpand?: () => void;
|
|
76
|
+
/** What the large visual is, for the *Expand* button: `Accounting's notebook`. */
|
|
77
|
+
expandTitle?: string;
|
|
62
78
|
}
|
|
63
|
-
|
|
79
|
+
/** How tall the listed conversation grows before it scrolls, in pixels. */
|
|
80
|
+
export declare const BALLOON_HISTORY_MAX_HEIGHT = 220;
|
|
81
|
+
/** Shorter, when something goes with the words under it (a notebook). */
|
|
82
|
+
export declare const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
83
|
+
export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, approval, decide, onDecisionActive, wide, onDismissPeek, display, tool, speaking, busy, attachment, history, onExpand, expandTitle, }: SpeechBalloonProps): JSX.Element;
|
|
64
84
|
export default SpeechBalloon;
|
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
2
3
|
import { Button, IconButton, Text } from '@primer/react';
|
|
3
4
|
import { Box } from '@datalayer/primer-addons';
|
|
4
5
|
import { XIcon } from '@primer/octicons-react';
|
|
5
|
-
import { ASSISTANT_WORDS } from './state';
|
|
6
|
+
import { ASSISTANT_WORDS, } from './state';
|
|
6
7
|
import { moreWaiting } from './ConversationBalloon';
|
|
7
8
|
import { DecisionAsk } from './DecisionAsk';
|
|
8
|
-
import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, } from './BalloonParts';
|
|
9
|
-
|
|
9
|
+
import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
|
|
10
|
+
import { BalloonExpandButton } from './BalloonVisual';
|
|
11
|
+
/** How tall the listed conversation grows before it scrolls, in pixels. */
|
|
12
|
+
export const BALLOON_HISTORY_MAX_HEIGHT = 220;
|
|
13
|
+
/** Shorter, when something goes with the words under it (a notebook). */
|
|
14
|
+
export const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
15
|
+
/**
|
|
16
|
+
* The conversation in the `history` balloon: a header that counts it and
|
|
17
|
+
* opens it, then every message, the person's to the right, scrolled to the
|
|
18
|
+
* newest.
|
|
19
|
+
*/
|
|
20
|
+
function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, }) {
|
|
21
|
+
const listRef = useRef(null);
|
|
22
|
+
const newest = history[history.length - 1]?.id;
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const list = listRef.current;
|
|
25
|
+
if (list) {
|
|
26
|
+
list.scrollTop = list.scrollHeight;
|
|
27
|
+
}
|
|
28
|
+
}, [history.length, newest]);
|
|
29
|
+
return (_jsxs(Box, { "data-balloon-history": "", sx: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }, children: [_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-header": "", onClick: onOpen, sx: {
|
|
30
|
+
m: 0,
|
|
31
|
+
p: 0,
|
|
32
|
+
pb: 1,
|
|
33
|
+
border: 0,
|
|
34
|
+
bg: 'transparent',
|
|
35
|
+
font: 'inherit',
|
|
36
|
+
textAlign: 'left',
|
|
37
|
+
cursor: 'pointer',
|
|
38
|
+
fontSize: 0,
|
|
39
|
+
fontWeight: 'semibold',
|
|
40
|
+
color: 'fg.muted',
|
|
41
|
+
'&:hover': { textDecoration: 'underline' },
|
|
42
|
+
}, children: conversationHeaderText(history.length) }), _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
|
|
43
|
+
maxHeight,
|
|
44
|
+
overflowY: 'auto',
|
|
45
|
+
display: 'flex',
|
|
46
|
+
flexDirection: 'column',
|
|
47
|
+
gap: 1,
|
|
48
|
+
pr: 1,
|
|
49
|
+
}, children: history.map(message => (_jsx(Box, { "data-balloon-history-message": message.role, sx: {
|
|
50
|
+
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
51
|
+
maxWidth: '88%',
|
|
52
|
+
px: 2,
|
|
53
|
+
py: 1,
|
|
54
|
+
borderRadius: 2,
|
|
55
|
+
bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
56
|
+
overflowWrap: 'anywhere',
|
|
57
|
+
}, children: message.text }, message.id))) })] }));
|
|
58
|
+
}
|
|
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, }) {
|
|
10
60
|
const current = display === 'current';
|
|
61
|
+
// History with messages to list: all of them, not the newest line alone.
|
|
62
|
+
const listed = !current && history && history.length > 0 ? history : null;
|
|
11
63
|
return (_jsxs(Box, { "data-speech-balloon": "", "data-balloon-display": display, sx: {
|
|
12
64
|
position: 'absolute',
|
|
13
65
|
// Pixels, as strings: a number here is read as the theme's space
|
|
@@ -16,8 +68,8 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
16
68
|
? { bottom: `${above}px` }
|
|
17
69
|
: { top: `${above}px` }),
|
|
18
70
|
[align]: 0,
|
|
19
|
-
maxWidth: wide || (current && attachment) ? 300 : current ? 260 : 280,
|
|
20
|
-
width: wide || (current && attachment) ? 300 : 'max-content',
|
|
71
|
+
maxWidth: wide || listed || (current && attachment) ? 300 : current ? 260 : 280,
|
|
72
|
+
width: wide || listed || (current && attachment) ? 300 : 'max-content',
|
|
21
73
|
px: 3,
|
|
22
74
|
py: 2,
|
|
23
75
|
bg: 'canvas.default',
|
|
@@ -54,7 +106,9 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
54
106
|
font: 'inherit',
|
|
55
107
|
textAlign: 'left',
|
|
56
108
|
cursor: 'pointer',
|
|
57
|
-
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking }) })) : (_jsx(
|
|
109
|
+
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, maxHeight: attachment
|
|
110
|
+
? BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT
|
|
111
|
+
: BALLOON_HISTORY_MAX_HEIGHT })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
|
|
58
112
|
flex: 1,
|
|
59
113
|
minWidth: 0,
|
|
60
114
|
m: 0,
|
|
@@ -66,6 +120,6 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
66
120
|
textAlign: 'left',
|
|
67
121
|
cursor: 'pointer',
|
|
68
122
|
'&:hover': { textDecoration: 'underline' },
|
|
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] })),
|
|
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] }), listed && tool ? (_jsx(Box, { "data-balloon-history-tool": "", sx: { mt: 1 }, children: _jsx(BalloonToolLineView, { line: tool }) })) : 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] })), 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 })] }));
|
|
70
124
|
}
|
|
71
125
|
export default SpeechBalloon;
|
|
@@ -79,6 +79,25 @@ export interface AssistantSaying {
|
|
|
79
79
|
}
|
|
80
80
|
/** How much of a message is kept as the agent's saying before it is cut. */
|
|
81
81
|
export declare const SAYING_LIMIT = 220;
|
|
82
|
+
/**
|
|
83
|
+
* A message's content as plain words, for a balloon: its text parts joined,
|
|
84
|
+
* code blocks left out, links and Markdown read as plain words.
|
|
85
|
+
*/
|
|
86
|
+
export declare function plainWordsOf(content: unknown): string;
|
|
87
|
+
/** A message of the conversation, as the `history` balloon lists it. */
|
|
88
|
+
export interface BalloonHistoryMessage {
|
|
89
|
+
id: string;
|
|
90
|
+
/** Who said it: the person, or the agent. */
|
|
91
|
+
role: 'user' | 'assistant';
|
|
92
|
+
/** Its words, plain. */
|
|
93
|
+
text: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The conversation as the `history` balloon lists it (T-23): the person's
|
|
97
|
+
* and the agent's messages with words, in order — not the tool calls, not
|
|
98
|
+
* an empty message — the same messages its header counts.
|
|
99
|
+
*/
|
|
100
|
+
export declare function balloonHistoryOf(items: readonly unknown[]): BalloonHistoryMessage[];
|
|
82
101
|
/**
|
|
83
102
|
* The newest thing the agent said, as the Office Assistant said it: in the
|
|
84
103
|
* balloon, while the conversation is closed (T-23). Only its own messages
|
|
@@ -52,6 +52,49 @@ export const ASSISTANT_WORDS = {
|
|
|
52
52
|
};
|
|
53
53
|
/** How much of a message is kept as the agent's saying before it is cut. */
|
|
54
54
|
export const SAYING_LIMIT = 220;
|
|
55
|
+
/**
|
|
56
|
+
* A message's content as plain words, for a balloon: its text parts joined,
|
|
57
|
+
* code blocks left out, links and Markdown read as plain words.
|
|
58
|
+
*/
|
|
59
|
+
export function plainWordsOf(content) {
|
|
60
|
+
const raw = typeof content === 'string'
|
|
61
|
+
? content
|
|
62
|
+
: Array.isArray(content)
|
|
63
|
+
? content
|
|
64
|
+
.map(part => part &&
|
|
65
|
+
typeof part === 'object' &&
|
|
66
|
+
part.type === 'text'
|
|
67
|
+
? String(part.text ?? '')
|
|
68
|
+
: '')
|
|
69
|
+
.join(' ')
|
|
70
|
+
: '';
|
|
71
|
+
return raw
|
|
72
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
73
|
+
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
74
|
+
.replace(/[*_`#>]+/g, '')
|
|
75
|
+
.replace(/\s+/g, ' ')
|
|
76
|
+
.trim();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The conversation as the `history` balloon lists it (T-23): the person's
|
|
80
|
+
* and the agent's messages with words, in order — not the tool calls, not
|
|
81
|
+
* an empty message — the same messages its header counts.
|
|
82
|
+
*/
|
|
83
|
+
export function balloonHistoryOf(items) {
|
|
84
|
+
const history = [];
|
|
85
|
+
items.forEach((entry, index) => {
|
|
86
|
+
const item = entry;
|
|
87
|
+
if ((item.role !== 'user' && item.role !== 'assistant') ||
|
|
88
|
+
typeof item.toolName === 'string') {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const text = plainWordsOf(item.content);
|
|
92
|
+
if (text) {
|
|
93
|
+
history.push({ id: String(item.id ?? index), role: item.role, text });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return history;
|
|
97
|
+
}
|
|
55
98
|
/**
|
|
56
99
|
* The newest thing the agent said, as the Office Assistant said it: in the
|
|
57
100
|
* balloon, while the conversation is closed (T-23). Only its own messages
|
|
@@ -64,23 +107,7 @@ export function latestSaying(items) {
|
|
|
64
107
|
if (item.role !== 'assistant' || typeof item.toolName === 'string') {
|
|
65
108
|
continue;
|
|
66
109
|
}
|
|
67
|
-
const
|
|
68
|
-
? item.content
|
|
69
|
-
: Array.isArray(item.content)
|
|
70
|
-
? item.content
|
|
71
|
-
.map(part => part &&
|
|
72
|
-
typeof part === 'object' &&
|
|
73
|
-
part.type === 'text'
|
|
74
|
-
? String(part.text ?? '')
|
|
75
|
-
: '')
|
|
76
|
-
.join(' ')
|
|
77
|
-
: '';
|
|
78
|
-
const plain = raw
|
|
79
|
-
.replace(/```[\s\S]*?```/g, ' ')
|
|
80
|
-
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
81
|
-
.replace(/[*_`#>]+/g, '')
|
|
82
|
-
.replace(/\s+/g, ' ')
|
|
83
|
-
.trim();
|
|
110
|
+
const plain = plainWordsOf(item.content);
|
|
84
111
|
if (!plain) {
|
|
85
112
|
continue;
|
|
86
113
|
}
|