@datalayer/agent-runtimes 1.5.43 → 1.5.45
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/dist-embed/chunks/1f30a-DxwAt2Ba.js +1 -0
- package/dist-embed/chunks/1f310-AqUM9a8q.js +1 -0
- package/dist-embed/chunks/1f324-8MHLOhTj.js +1 -0
- package/dist-embed/chunks/1f326-YSBr3aXI.js +1 -0
- package/dist-embed/chunks/1f327-Ie-nQBNO.js +1 -0
- package/dist-embed/chunks/1f33e-Cfl2WM63.js +1 -0
- package/dist-embed/chunks/1f372-3XLDlXkE.js +1 -0
- package/dist-embed/chunks/1f373-BPUJt2bp.js +1 -0
- package/dist-embed/chunks/1f39b-CSNtbAXr.js +1 -0
- package/dist-embed/chunks/1f3ab-BUCNQHz1.js +1 -0
- package/dist-embed/chunks/1f41b-Dre1z0gP.js +1 -0
- package/dist-embed/chunks/1f441-D8yah4C4.js +1 -0
- package/dist-embed/chunks/1f465-CPle6Hjq.js +1 -0
- package/dist-embed/chunks/1f4b9-aacFyckt.js +1 -0
- package/dist-embed/chunks/1f4be-DPAcAU4R.js +1 -0
- package/dist-embed/chunks/1f4c1-Ch6aJ0cj.js +1 -0
- package/dist-embed/chunks/1f4c2-Dx7BKmLO.js +1 -0
- package/dist-embed/chunks/1f4c4-CNvjvC5k.js +1 -0
- package/dist-embed/chunks/1f4d3-GTxRgjv7.js +1 -0
- package/dist-embed/chunks/1f4d6-COOZY-Q_.js +1 -0
- package/dist-embed/chunks/1f4dc-BhoW4cjD.js +1 -0
- package/dist-embed/chunks/1f4e1-DezU8ow-.js +1 -0
- package/dist-embed/chunks/1f4e2-DsStZsI-.js +1 -0
- package/dist-embed/chunks/1f4e7-BBi7c_zx.js +1 -0
- package/dist-embed/chunks/1f500-D9xRo-IT.js +1 -0
- package/dist-embed/chunks/1f501-CzKeDPcw.js +1 -0
- package/dist-embed/chunks/1f504-BYIHMoDP.js +1 -0
- package/dist-embed/chunks/1f52d-2QxCLKhU.js +1 -0
- package/dist-embed/chunks/1f552-4d0Y0kn7.js +1 -0
- package/dist-embed/chunks/1f58b-Cp8F_HBg.js +1 -0
- package/dist-embed/chunks/1f5b2-D6BUULuh.js +1 -0
- package/dist-embed/chunks/1f5c3-DjkgJLaz.js +1 -0
- package/dist-embed/chunks/1f5d1-C4Kpe4HL.js +1 -0
- package/dist-embed/chunks/1f5dc-DyLO7aB9.js +1 -0
- package/dist-embed/chunks/1f604-CEekcaYE.js +1 -0
- package/dist-embed/chunks/1f917-CyyS9fgf.js +1 -0
- package/dist-embed/chunks/1f9d1-200d-2696-D9eSB-0q.js +1 -0
- package/dist-embed/chunks/1fa79-DHhIZbWB.js +1 -0
- package/dist-embed/chunks/1fa9d-DUI9e77m.js +1 -0
- package/dist-embed/chunks/1fa9f-10ALsMaD.js +1 -0
- package/dist-embed/chunks/23f3-ASjqFsbT.js +1 -0
- package/dist-embed/chunks/2611-BnnmLm-K.js +1 -0
- package/dist-embed/chunks/2702-CfLyp35r.js +1 -0
- package/dist-embed/chunks/270f-CLwo30mk.js +1 -0
- package/dist-embed/chunks/2728-Qg6be-6p.js +1 -0
- package/dist-embed/chunks/2b1c-Uc1aRwAf.js +1 -0
- package/dist-embed/chunks/{ChatView-kCmBbnDa.js → ChatView-Dy-IgGFL.js} +1 -1
- package/dist-embed/datalayer-app-main.js +111 -106
- package/lib/apps/plugins/chat/ChatView.js +2 -2
- package/lib/chat/ChatFloating.js +1 -1
- package/lib/chat/ChatInline.js +43 -22
- package/lib/chat/base/ChatBase.js +66 -10
- package/lib/chat/messages/ChatMessageList.d.ts +7 -1
- package/lib/chat/messages/ChatMessageList.js +8 -2
- package/lib/components/agents/AgentSummary.d.ts +2 -24
- package/lib/components/agents/AgentSummary.js +3 -19
- package/lib/hooks/useChat.js +110 -144
- package/lib/portable/activity.d.ts +78 -0
- package/lib/portable/activity.js +129 -0
- package/lib/portable/fixtures.d.ts +7 -0
- package/lib/portable/fixtures.js +50 -0
- package/lib/portable/index.d.ts +4 -1
- package/lib/portable/index.js +7 -1
- package/lib/portable/lifecycle.d.ts +77 -0
- package/lib/portable/lifecycle.js +135 -0
- package/lib/portable/runtimes.d.ts +68 -0
- package/lib/portable/runtimes.js +69 -0
- package/lib/protocols/VercelAIAdapter.js +11 -0
- package/lib/specs/voices.js +62 -0
- package/lib/types/chat.d.ts +10 -0
- package/lib/voice/ComposerVoice.js +12 -3
- package/lib/voice/VoiceMenu.d.ts +2 -1
- package/lib/voice/VoiceMenu.js +2 -2
- package/lib/voice/captions.d.ts +31 -0
- package/lib/voice/captions.js +156 -0
- package/lib/voice/chatVoice.d.ts +5 -2
- package/lib/voice/chatVoice.js +10 -4
- package/lib/voice/choices.d.ts +2 -0
- package/lib/voice/choices.js +6 -0
- package/lib/voice/handsFree.d.ts +12 -0
- package/lib/voice/handsFree.js +18 -0
- package/lib/voice/index.d.ts +1 -0
- package/lib/voice/index.js +1 -0
- package/lib/voice/sayAloud.d.ts +25 -0
- package/lib/voice/sayAloud.js +34 -0
- package/lib/voice/serverHearing.d.ts +63 -2
- package/lib/voice/serverHearing.js +311 -7
- package/lib/voice/settings.d.ts +12 -0
- package/lib/voice/settings.js +9 -0
- package/package.json +1 -1
- package/scripts/voice/measure.py +77 -17
|
@@ -1702,8 +1702,8 @@ export default function ChatView({ workspace }) {
|
|
|
1702
1702
|
// opening stops it, and *Stop speaking* while it speaks.
|
|
1703
1703
|
speaking: speaking, onStopSpeaking: () => controlsRef.current?.stopSpeaking?.(),
|
|
1704
1704
|
// Hands-free (VO-12, VO-13): the run in progress, stopped when
|
|
1705
|
-
// the person speaks over it.
|
|
1706
|
-
running: busy, onStopRun: () => controlsRef.current?.stop?.(), onInterimChange: setVoiceInterim,
|
|
1705
|
+
// the person speaks over it, and the answer marked interrupted.
|
|
1706
|
+
running: busy, onStopRun: () => controlsRef.current?.stop?.(), onInterrupted: () => controlsRef.current?.markInterrupted(), onInterimChange: setVoiceInterim,
|
|
1707
1707
|
// The menu offers when answers are heard where the
|
|
1708
1708
|
// application lets them be (VO-21, VO-30).
|
|
1709
1709
|
answers: Boolean(config?.voice?.output &&
|
package/lib/chat/ChatFloating.js
CHANGED
|
@@ -1030,7 +1030,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
1030
1030
|
...poweredByProps,
|
|
1031
1031
|
}, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
|
|
1032
1032
|
showAvatars: true,
|
|
1033
|
-
}, 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 ?? suggestionPrompt, inspector: inspector, 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, onReadAloud: speaker ? readAnswerAloud : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, onSendReady: handleSendReady, children: conversationBalloon && currentOpen ? (
|
|
1033
|
+
}, 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 ?? suggestionPrompt, inspector: inspector, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, voiceSentence: speakerState.sentence, onStopSpeaking: speaker ? stopSpeaking : undefined, onReadAloud: speaker ? readAnswerAloud : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, onSendReady: handleSendReady, children: conversationBalloon && currentOpen ? (
|
|
1034
1034
|
// Current: the one thing said or done now, over the composer.
|
|
1035
1035
|
_jsx(Box, { px: 3, pt: 3, pb: 2, overflow: "hidden", children: _jsx(CurrentBalloonBody, { text: toolSaid ? undefined : (currentWords ?? description), fullText: toolSaid ? undefined : currentWhole, tool: toolSaid, busy: atWork, speaking: answering && atWork, waiting: atWork && !answering }) })) : (children) }))] }));
|
|
1036
1036
|
/*
|
package/lib/chat/ChatInline.js
CHANGED
|
@@ -6,7 +6,18 @@ import { CopyIcon, ArrowLeftIcon, SyncIcon, TrashIcon, CheckIcon, PlusIcon, Penc
|
|
|
6
6
|
import { SparklesIcon } from '@datalayer/icons-react';
|
|
7
7
|
import { AGUIAdapter } from '../protocols';
|
|
8
8
|
import { generateMessageId } from '../types/messages';
|
|
9
|
-
import {
|
|
9
|
+
import { BROWSER_LIMITS, emptyConversation, reduceConversation, } from '../portable/conversation';
|
|
10
|
+
import { conversationEventsOf } from '../portable/adapterEvents';
|
|
11
|
+
/** The answer so far: the newest assistant message of the conversation. */
|
|
12
|
+
function answerOf(state) {
|
|
13
|
+
for (let index = state.items.length - 1; index >= 0; index -= 1) {
|
|
14
|
+
const item = state.items[index];
|
|
15
|
+
if (item.kind === 'message' && item.role === 'assistant') {
|
|
16
|
+
return item.text;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
10
21
|
/**
|
|
11
22
|
* Pre-defined AI action groups
|
|
12
23
|
*/
|
|
@@ -79,8 +90,15 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
79
90
|
const [input, setInput] = useState('');
|
|
80
91
|
// AI state
|
|
81
92
|
const [aiState, setAiState] = useState('initial');
|
|
82
|
-
//
|
|
83
|
-
|
|
93
|
+
// The one-shot conversation (MOBILE M-030): `reduceConversation`'s, as in
|
|
94
|
+
// ChatBase and on the phone; the response is its newest assistant message.
|
|
95
|
+
const [conversation, setConversation] = useState(() => emptyConversation(BROWSER_LIMITS));
|
|
96
|
+
const conversationRef = useRef(conversation);
|
|
97
|
+
const changeConversation = useCallback((next) => {
|
|
98
|
+
conversationRef.current = next;
|
|
99
|
+
setConversation(next);
|
|
100
|
+
}, []);
|
|
101
|
+
const aiResponse = useMemo(() => answerOf(conversation), [conversation]);
|
|
84
102
|
// Previous prompt for regeneration
|
|
85
103
|
const [previousPrompt, setPreviousPrompt] = useState('');
|
|
86
104
|
// Current page in options menu (for sub-menus like translate)
|
|
@@ -112,20 +130,23 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
112
130
|
adapterRef.current = adapter;
|
|
113
131
|
// Subscribe to events
|
|
114
132
|
unsubscribeRef.current = adapter.subscribe((event) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
if (event.type === 'error') {
|
|
134
|
+
console.error('[ChatInline] Protocol error:', event.error);
|
|
135
|
+
setError(event.error?.message || 'Unknown error');
|
|
136
|
+
setAiState('initial');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// The event, as the conversation reads it: one reducer whatever the protocol.
|
|
140
|
+
try {
|
|
141
|
+
const inputs = conversationEventsOf(event, conversationRef.current);
|
|
142
|
+
if (inputs.length) {
|
|
143
|
+
changeConversation(inputs.reduce((kept, input) => reduceConversation(kept, input), conversationRef.current));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (failure) {
|
|
147
|
+
console.error('[ChatInline] Protocol event not read:', failure);
|
|
148
|
+
setError(failure instanceof Error ? failure.message : String(failure));
|
|
149
|
+
setAiState('initial');
|
|
129
150
|
}
|
|
130
151
|
});
|
|
131
152
|
// Connect
|
|
@@ -134,7 +155,7 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
134
155
|
unsubscribeRef.current?.();
|
|
135
156
|
adapterRef.current?.disconnect();
|
|
136
157
|
};
|
|
137
|
-
}, [protocol]);
|
|
158
|
+
}, [protocol, changeConversation]);
|
|
138
159
|
// Submit prompt to AI
|
|
139
160
|
const submitPrompt = useCallback(async (prompt) => {
|
|
140
161
|
if (!adapterRef.current) {
|
|
@@ -145,7 +166,7 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
145
166
|
setInput('');
|
|
146
167
|
setPreviousPrompt(prompt);
|
|
147
168
|
setError(null);
|
|
148
|
-
|
|
169
|
+
changeConversation(emptyConversation(BROWSER_LIMITS));
|
|
149
170
|
// Build system message with selected text context
|
|
150
171
|
const systemContext = `Do not surround your answer in quote marks. Only return the answer, nothing else. The user is selecting this text:
|
|
151
172
|
|
|
@@ -178,7 +199,7 @@ ${selectedText || ''}
|
|
|
178
199
|
setError(err.message);
|
|
179
200
|
setAiState('initial');
|
|
180
201
|
}
|
|
181
|
-
}, [selectedText]);
|
|
202
|
+
}, [selectedText, changeConversation]);
|
|
182
203
|
// Handle pending prompt from toolbar dropdown
|
|
183
204
|
useEffect(() => {
|
|
184
205
|
if (pendingPrompt && aiState === 'initial') {
|
|
@@ -235,8 +256,8 @@ ${selectedText || ''}
|
|
|
235
256
|
// Handle back to options
|
|
236
257
|
const handleBackToOptions = useCallback(() => {
|
|
237
258
|
setAiState('initial');
|
|
238
|
-
|
|
239
|
-
}, []);
|
|
259
|
+
changeConversation(emptyConversation(BROWSER_LIMITS));
|
|
260
|
+
}, [changeConversation]);
|
|
240
261
|
// Handle discard
|
|
241
262
|
const handleDiscard = useCallback(() => {
|
|
242
263
|
onClose?.();
|
|
@@ -25,7 +25,7 @@ import { SkeletonText } from '@primer/react/experimental';
|
|
|
25
25
|
import { SidebarExpandIcon } from '@primer/octicons-react';
|
|
26
26
|
import { notebookStore, JupyterReactTheme } from '@datalayer/jupyter-react';
|
|
27
27
|
import { Box, setupPrimerPortals, useThemeStore, getColorPalette, getThemeConfig, useSystemColorMode, } from '@datalayer/primer-addons';
|
|
28
|
-
import { AlertIcon, PersonIcon } from '@primer/octicons-react';
|
|
28
|
+
import { AlertIcon, PersonIcon, UnmuteIcon } from '@primer/octicons-react';
|
|
29
29
|
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
30
30
|
import { QueryClientProvider, QueryClientContext } from '@tanstack/react-query';
|
|
31
31
|
import { useCoreStore } from '@datalayer/core';
|
|
@@ -52,11 +52,14 @@ import { EphemeralSurfaceControl } from '../EphemeralSurfaceControl';
|
|
|
52
52
|
import { ChatMessageList, } from '../messages/ChatMessageList';
|
|
53
53
|
import { InputPrompt } from '../prompt/InputPrompt';
|
|
54
54
|
import { ComposerVoice, spokenOf } from '../../voice/ComposerVoice';
|
|
55
|
+
import { answerSpokenOver } from '../../voice/handsFree';
|
|
55
56
|
import { ReadAloud, readsAloud } from '../../voice/ReadAloud';
|
|
56
57
|
import { settledVoice } from '../../voice/chatVoice';
|
|
57
58
|
import { useVoiceSettings } from '../../voice/useVoiceSettings';
|
|
58
59
|
import { useVoiceSpeaker } from '../../voice/useVoiceSpeaker';
|
|
59
60
|
import { useSpokenAnswers } from '../../voice/useSpokenAnswers';
|
|
61
|
+
import { saidMessageId, useSaidSentence } from '../../voice/captions';
|
|
62
|
+
import { lineToSay } from '../../voice/sayAloud';
|
|
60
63
|
import { isFloatingChatViewMode } from '../viewModes';
|
|
61
64
|
import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
|
|
62
65
|
import { initialModelId, isOffered, offeredModels as offeredModelsFor, } from './modelChoice';
|
|
@@ -423,7 +426,7 @@ function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = t
|
|
|
423
426
|
// Mode selection
|
|
424
427
|
useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, onSendReady, onLoadingChange, onSpeakingChange, onContextSnapshot, enableStreaming = false,
|
|
425
428
|
// Extended props
|
|
426
|
-
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, onStopSpeaking, onReadAloud, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
|
|
429
|
+
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, voiceSentence, onStopSpeaking, onReadAloud, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
|
|
427
430
|
// Tool invocation hooks
|
|
428
431
|
onToolCallStart, onToolCallComplete, inspector: inspectorGiven,
|
|
429
432
|
// Identity/Authorization props
|
|
@@ -1819,6 +1822,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1819
1822
|
recorderRef.current = null;
|
|
1820
1823
|
}
|
|
1821
1824
|
const inspectAnswerRef = useRef('');
|
|
1825
|
+
// A line the agent says aloud, from a run's `voice.say` (VOICE.md VO-35).
|
|
1826
|
+
const voiceSayRef = useRef(undefined);
|
|
1822
1827
|
const handleRespondRef = useRef(null);
|
|
1823
1828
|
// A decision made outside the chat — a host page's own approval banner —
|
|
1824
1829
|
// is answered exactly as the tool card's buttons answer it: through the
|
|
@@ -2801,6 +2806,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2801
2806
|
// What the server says besides the conversation's text comes on
|
|
2802
2807
|
// the ui socket (REACTOR_UI RU-W); the run's own — `loop.session`
|
|
2803
2808
|
// among them — are the conversation's.
|
|
2809
|
+
if (event.activity) {
|
|
2810
|
+
voiceSayRef.current?.(event.activity);
|
|
2811
|
+
}
|
|
2804
2812
|
read();
|
|
2805
2813
|
break;
|
|
2806
2814
|
case 'done':
|
|
@@ -2968,8 +2976,45 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2968
2976
|
const ownVoice = useVoiceSpeaker(settled, !hostSpeaks);
|
|
2969
2977
|
useSpokenAnswers(displayItems, isLoading, !hostSpeaks && settled?.output === 'always', ownVoice.speaker);
|
|
2970
2978
|
const speakingNow = voiceSpeaking || ownVoice.state.speaking;
|
|
2979
|
+
// The sentence being said, marked in the conversation (VO-65).
|
|
2980
|
+
const sentenceSaid = hostSpeaks ? voiceSentence : ownVoice.state.sentence;
|
|
2981
|
+
const saidConversationRef = useRef(null);
|
|
2982
|
+
useSaidSentence(saidConversationRef, sentenceSaid, displayItems);
|
|
2983
|
+
/*
|
|
2984
|
+
* A line the agent wants heard, not written (VO-35): a caption while the
|
|
2985
|
+
* run goes on, said by the speaker where answers are said by themselves.
|
|
2986
|
+
*/
|
|
2987
|
+
const [voiceSaid, setVoiceSaid] = useState('');
|
|
2988
|
+
voiceSayRef.current = activity => {
|
|
2989
|
+
const line = lineToSay(activity);
|
|
2990
|
+
if (!line) {
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
setVoiceSaid(line);
|
|
2994
|
+
if (settled?.output === 'always') {
|
|
2995
|
+
if (onReadAloud) {
|
|
2996
|
+
onReadAloud(line);
|
|
2997
|
+
}
|
|
2998
|
+
else {
|
|
2999
|
+
ownVoice.speaker?.say(line);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
};
|
|
3003
|
+
useEffect(() => {
|
|
3004
|
+
if (!isLoading) {
|
|
3005
|
+
setVoiceSaid('');
|
|
3006
|
+
}
|
|
3007
|
+
}, [isLoading]);
|
|
2971
3008
|
// Answers spoken over, hands-free (VOICE.md VO-13): marked *interrupted*.
|
|
2972
3009
|
const [interruptedIds, setInterruptedIds] = useState(() => new Set());
|
|
3010
|
+
// The conversation as shown, read when the person speaks over an answer.
|
|
3011
|
+
const shownItemsRef = useRef([]);
|
|
3012
|
+
const markInterrupted = useCallback(() => {
|
|
3013
|
+
const spokenOver = answerSpokenOver(shownItemsRef.current.filter((item) => !isToolCallMessage(item)));
|
|
3014
|
+
if (spokenOver) {
|
|
3015
|
+
setInterruptedIds(ids => new Set([...ids, spokenOver]));
|
|
3016
|
+
}
|
|
3017
|
+
}, []);
|
|
2973
3018
|
const stopSpeaking = onStopSpeaking ?? (ownVoice.speaker ? ownVoice.stop : undefined);
|
|
2974
3019
|
const readAnswerAloud = onReadAloud ?? (ownVoice.speaker ? ownVoice.read : undefined);
|
|
2975
3020
|
const handleSend = useCallback(async (messageOverride,
|
|
@@ -3339,6 +3384,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3339
3384
|
thread: () => threadIdRef.current,
|
|
3340
3385
|
// Its speaker, for a host whose composer holds the microphone (VO-10).
|
|
3341
3386
|
stopSpeaking,
|
|
3387
|
+
// The answer spoken over, marked for a host's own composer (VO-13).
|
|
3388
|
+
markInterrupted,
|
|
3342
3389
|
});
|
|
3343
3390
|
return () => {
|
|
3344
3391
|
onSendReady(null);
|
|
@@ -3351,6 +3398,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3351
3398
|
handleStop,
|
|
3352
3399
|
handleNewChat,
|
|
3353
3400
|
stopSpeaking,
|
|
3401
|
+
markInterrupted,
|
|
3354
3402
|
]);
|
|
3355
3403
|
// Streaming state, for a host that draws the prompt.
|
|
3356
3404
|
useEffect(() => {
|
|
@@ -3717,6 +3765,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3717
3765
|
...displayItems,
|
|
3718
3766
|
]
|
|
3719
3767
|
: displayItems;
|
|
3768
|
+
shownItemsRef.current = welcomedItems;
|
|
3769
|
+
const saidId = saidMessageId(welcomedItems, sentenceSaid);
|
|
3720
3770
|
/*
|
|
3721
3771
|
* *Read aloud* under each answer the agent has finished (VOICE.md VO-21),
|
|
3722
3772
|
* when the host reads answers aloud on request.
|
|
@@ -3732,7 +3782,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3732
3782
|
return (_jsxs(_Fragment, { children: [interruptedIds.has(message.id) ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontStyle: 'italic' }, "data-interrupted": "", children: "interrupted" })) : null, readAnswerAloud && readsAloud(message, text, writingId) ? (_jsx(ReadAloud, { onRead: () => readAnswerAloud(text) })) : null] }));
|
|
3733
3783
|
}
|
|
3734
3784
|
: undefined;
|
|
3735
|
-
const messagesContent = children ? (children) : (_jsxs(Box, { display: "flex", flexDirection: "column", minHeight: 0,
|
|
3785
|
+
const messagesContent = children ? (children) : (_jsxs(Box, { ref: saidConversationRef, display: "flex", flexDirection: "column", minHeight: 0,
|
|
3736
3786
|
/*
|
|
3737
3787
|
A reading column, not a full-bleed sheet.
|
|
3738
3788
|
|
|
@@ -3745,16 +3795,12 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3745
3795
|
// Tool calls are drawn by the page's Reactor plugins; a named
|
|
3746
3796
|
// notebook's cell surfaces stand in for the default row of its tools,
|
|
3747
3797
|
// so every chat with a headless notebook gets the same transcript.
|
|
3748
|
-
notebookToolSurfacesId: notebookToolSurfacesId, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, showTurnFooters: showTurnFooter, renderMessageFooter: readAloudFooter, agentUsage: agentUsage, onRemoveItems: handleRemoveItems, mcpServers: filteredMcpServers, emptyContent: launching ? null : (_jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: resolvedDescription, suggestions: resolvedSuggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill })) }), trailingContent] }));
|
|
3798
|
+
notebookToolSurfacesId: notebookToolSurfacesId, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, showTurnFooters: showTurnFooter, renderMessageFooter: readAloudFooter, saidMessageId: saidId, agentUsage: agentUsage, onRemoveItems: handleRemoveItems, mcpServers: filteredMcpServers, emptyContent: launching ? null : (_jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: resolvedDescription, suggestions: resolvedSuggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill })) }), trailingContent] }));
|
|
3749
3799
|
// The microphone, in the composer's footer (VO-10, VO-11).
|
|
3750
3800
|
const voiceControl = settled && settled.input !== 'off' ? (_jsx(ComposerVoice, { voice: settled, input: input, setInput: setInput, disabled: disabled || launching, speaking: speakingNow, onStopSpeaking: stopSpeaking,
|
|
3751
3801
|
// Hands-free (VO-12, VO-13): the run in progress, stopped when the
|
|
3752
3802
|
// person speaks over it, and the answer marked interrupted.
|
|
3753
|
-
running: isLoading, onStopRun: handleStop, onInterrupted:
|
|
3754
|
-
if (writingId) {
|
|
3755
|
-
setInterruptedIds(ids => new Set([...ids, writingId]));
|
|
3756
|
-
}
|
|
3757
|
-
}, onInterimChange: setVoiceInterim,
|
|
3803
|
+
running: isLoading, onStopRun: handleStop, onInterrupted: markInterrupted, onInterimChange: setVoiceInterim,
|
|
3758
3804
|
// The menu offers when answers are heard where the application
|
|
3759
3805
|
// lets them be (VO-21, VO-30).
|
|
3760
3806
|
answers: Boolean(voice?.output && voice.output !== 'off' && voice.speechUrl), onSpoken: metadata => {
|
|
@@ -3762,7 +3808,17 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3762
3808
|
}, send: (text, metadata) => void handleSend(text, undefined, metadata) })) : undefined;
|
|
3763
3809
|
// Why an answer could not be heard, in a sentence, by the composer.
|
|
3764
3810
|
const voiceRefused = !hostSpeaks && ownVoice.state.refused ? (_jsx(Text, { role: "alert", sx: { fontSize: 0, color: 'danger.fg', maxWidth: 260 }, "data-voice-refused": "", children: ownVoice.state.refused })) : null;
|
|
3765
|
-
|
|
3811
|
+
// The line said aloud, written as it is said: captions always (VO-65).
|
|
3812
|
+
const voiceSayCaption = voiceSaid ? (_jsxs(Text, { "aria-live": "polite", sx: {
|
|
3813
|
+
fontSize: 0,
|
|
3814
|
+
fontStyle: 'italic',
|
|
3815
|
+
color: 'fg.muted',
|
|
3816
|
+
maxWidth: 260,
|
|
3817
|
+
display: 'inline-flex',
|
|
3818
|
+
alignItems: 'center',
|
|
3819
|
+
gap: 1,
|
|
3820
|
+
}, "data-voice-say": "", children: [_jsx(UnmuteIcon, { size: 12, "aria-label": "Said aloud" }), voiceSaid] })) : null;
|
|
3821
|
+
const inputToolbar = showInput ? (_jsx(InputPrompt, { footerExtras: voiceRefused || voiceSayCaption ? (_jsxs(_Fragment, { children: [voiceSayCaption, voiceRefused, voiceControl] })) : (voiceControl), interim: voiceInterim, input: input, setInput: setInput, isLoading: isLoading, kernelStatus: liveKernelStatus, connectionConfirmed: connectionConfirmed, placeholder: placeholder,
|
|
3766
3822
|
/*
|
|
3767
3823
|
The same openers the empty state offers, typed into the box they would
|
|
3768
3824
|
be typed into.
|
|
@@ -78,6 +78,12 @@ export interface ChatMessageListProps {
|
|
|
78
78
|
* `note`, which has no footer of its own.
|
|
79
79
|
*/
|
|
80
80
|
renderMessageFooter?: (message: ChatMessage) => ReactNode;
|
|
81
|
+
/**
|
|
82
|
+
* The answer a sentence is being said from (VOICE.md VO-65): its row is
|
|
83
|
+
* marked `aria-current` and `data-voice-saying`, with a bar beside it —
|
|
84
|
+
* the sentence itself is underlined by `useSaidSentence`.
|
|
85
|
+
*/
|
|
86
|
+
saidMessageId?: string;
|
|
81
87
|
/**
|
|
82
88
|
* The MCP servers the agent has, with the tools each serves: a tool call is
|
|
83
89
|
* drawn with the marks of the server it belongs to. Skills, frontend tools
|
|
@@ -109,5 +115,5 @@ export interface SubagentChatPanelProps {
|
|
|
109
115
|
* approval decisions are sent over the shared monitoring socket.
|
|
110
116
|
*/
|
|
111
117
|
export declare function SubagentChatPanel({ toolCallId, subagentName: subagentNameHint, height, avatarConfig, }: SubagentChatPanelProps): React.ReactElement | null;
|
|
112
|
-
export declare function ChatMessageList({ displayItems, actOn, upload, isLoading, isStreaming, showLoadingIndicator, hideMessagesAfterToolUI, avatarConfig, padding, notebookToolSurfacesId, emptyContent, messagesEndRef, onRespond, approvalConfig, showTurnFooters, agentUsage, onRemoveItems, renderMessageFooter, mcpServers, density, }: ChatMessageListProps): import("react").JSX.Element;
|
|
118
|
+
export declare function ChatMessageList({ displayItems, actOn, upload, isLoading, isStreaming, showLoadingIndicator, hideMessagesAfterToolUI, avatarConfig, padding, notebookToolSurfacesId, emptyContent, messagesEndRef, onRespond, approvalConfig, showTurnFooters, agentUsage, onRemoveItems, renderMessageFooter, saidMessageId, mcpServers, density, }: ChatMessageListProps): import("react").JSX.Element;
|
|
113
119
|
export {};
|
|
@@ -366,7 +366,7 @@ function DefaultToolCallRenderer({ item, onRespond, mcpServers, density, }) {
|
|
|
366
366
|
// ---------------------------------------------------------------------------
|
|
367
367
|
// Component
|
|
368
368
|
// ---------------------------------------------------------------------------
|
|
369
|
-
export function ChatMessageList({ displayItems, actOn, upload, isLoading, isStreaming, showLoadingIndicator, hideMessagesAfterToolUI, avatarConfig, padding, notebookToolSurfacesId, emptyContent, messagesEndRef, onRespond, approvalConfig, showTurnFooters = false, agentUsage, onRemoveItems, renderMessageFooter, mcpServers, density = 'comfortable', }) {
|
|
369
|
+
export function ChatMessageList({ displayItems, actOn, upload, isLoading, isStreaming, showLoadingIndicator, hideMessagesAfterToolUI, avatarConfig, padding, notebookToolSurfacesId, emptyContent, messagesEndRef, onRespond, approvalConfig, showTurnFooters = false, agentUsage, onRemoveItems, renderMessageFooter, saidMessageId, mcpServers, density = 'comfortable', }) {
|
|
370
370
|
/*
|
|
371
371
|
* Who draws a tool call: the Reactor plugins of the page, when it has a
|
|
372
372
|
* platform. Each plugin binds its own tools (`ToolBindings`) and renders its
|
|
@@ -541,7 +541,13 @@ export function ChatMessageList({ displayItems, actOn, upload, isLoading, isStre
|
|
|
541
541
|
}
|
|
542
542
|
// The host's footer, asked once: a row only when it draws something.
|
|
543
543
|
const messageFooter = renderMessageFooter?.(message);
|
|
544
|
-
|
|
544
|
+
// The answer being said (VO-65): marked by a bar and for assistive tech.
|
|
545
|
+
const said = saidMessageId !== undefined && message.id === saidMessageId;
|
|
546
|
+
return (_jsx(Box, { "data-chat-message": message.role, "data-voice-saying": said ? '' : undefined, "aria-current": said ? 'true' : undefined, sx: said
|
|
547
|
+
? {
|
|
548
|
+
boxShadow: 'inset 3px 0 0 var(--borderColor-accent-emphasis, #0969da)',
|
|
549
|
+
}
|
|
550
|
+
: undefined, display: "flex", flexDirection: "column", alignItems: isUser ? 'flex-end' : 'flex-start', px: padding, py: compact ? '2px' : 1, children: _jsxs(Box, { display: "flex", gap: 2, flexDirection: isUser ? 'row-reverse' : 'row', alignItems: "flex-start",
|
|
545
551
|
/*
|
|
546
552
|
Full width, always.
|
|
547
553
|
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
location: string;
|
|
5
|
-
specId?: string;
|
|
6
|
-
/**
|
|
7
|
-
* The framework turning this agent's loop, as its spec declares it —
|
|
8
|
-
* `pydantic-ai` server-side, `vercel-ai` in the page.
|
|
9
|
-
*/
|
|
10
|
-
harness?: string;
|
|
11
|
-
/**
|
|
12
|
-
* The runtime variant the host chose: `<location>-<harness>`.
|
|
13
|
-
*
|
|
14
|
-
* Shown next to the harness rather than instead of it, because they answer
|
|
15
|
-
* different questions and can disagree: the harness is what the spec asked
|
|
16
|
-
* for, the variant is what it actually got.
|
|
17
|
-
*/
|
|
18
|
-
variant?: string;
|
|
19
|
-
status?: string;
|
|
20
|
-
baseUrl?: string;
|
|
21
|
-
sandboxBaseUrl?: string;
|
|
22
|
-
agentId?: string;
|
|
23
|
-
isReady?: boolean;
|
|
24
|
-
error?: string;
|
|
25
|
-
}
|
|
2
|
+
import { type AgentSummaryData } from '../../portable/runtimes';
|
|
3
|
+
export type { AgentSummaryData } from '../../portable/runtimes';
|
|
26
4
|
export interface AgentSummaryProps {
|
|
27
5
|
summary: AgentSummaryData | null;
|
|
28
6
|
title?: string;
|
|
@@ -7,19 +7,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
7
7
|
import { Spinner } from '@primer/react';
|
|
8
8
|
import { Box } from '@datalayer/primer-addons';
|
|
9
9
|
import { strip } from '@datalayer/core/lib/utils';
|
|
10
|
-
|
|
11
|
-
* Statuses that represent an agent actively being created/started. The spinner
|
|
12
|
-
* only shows for these; app-navigation states ("selected", "switching") and
|
|
13
|
-
* settled states ("ready", "running", ...) never spin.
|
|
14
|
-
*/
|
|
15
|
-
const CREATING_STATUSES = new Set([
|
|
16
|
-
'creating',
|
|
17
|
-
'starting',
|
|
18
|
-
'launching',
|
|
19
|
-
'connecting',
|
|
20
|
-
'pending',
|
|
21
|
-
'resuming',
|
|
22
|
-
]);
|
|
10
|
+
import { isCreatingAgent, } from '../../portable/runtimes';
|
|
23
11
|
const MAX_AGENT_NAME_LENGTH = 32;
|
|
24
12
|
const toSandboxStatusUrl = (value) => {
|
|
25
13
|
const raw = String(value || '').trim();
|
|
@@ -95,12 +83,8 @@ export const AgentSummary = ({ summary, title = 'Active Agent', }) => {
|
|
|
95
83
|
if (!summary) {
|
|
96
84
|
return null;
|
|
97
85
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// ready, has no id yet, and its status is an active creation/startup state.
|
|
101
|
-
const isCreating = !summary.isReady &&
|
|
102
|
-
!summary.agentId &&
|
|
103
|
-
CREATING_STATUSES.has(normalizedStatus);
|
|
86
|
+
// Only show the spinner while the agent is genuinely being created.
|
|
87
|
+
const isCreating = isCreatingAgent(summary);
|
|
104
88
|
const hasError = Boolean(summary.error);
|
|
105
89
|
// The agent is optional: some examples run only a code sandbox. Show the
|
|
106
90
|
// agent status when there is an agent (or one is being created).
|