@datalayer/agent-runtimes 1.3.28 → 1.3.29
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 +12 -2
- package/lib/chat/ChatFloating.js +196 -25
- package/lib/chat/assistant/AssistantStage.d.ts +70 -0
- package/lib/chat/assistant/AssistantStage.js +192 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +26 -0
- package/lib/chat/assistant/SpeechBalloon.js +46 -0
- package/lib/chat/assistant/SpriteCharacter.d.ts +26 -0
- package/lib/chat/assistant/SpriteCharacter.js +73 -0
- package/lib/chat/assistant/characters.d.ts +30 -0
- package/lib/chat/assistant/characters.js +41 -0
- package/lib/chat/assistant/formats/acs.d.ts +88 -0
- package/lib/chat/assistant/formats/acs.js +493 -0
- package/lib/chat/assistant/formats/agentCompression.d.ts +5 -0
- package/lib/chat/assistant/formats/agentCompression.js +92 -0
- package/lib/chat/assistant/formats/blobs.d.ts +6 -0
- package/lib/chat/assistant/formats/blobs.js +52 -0
- package/lib/chat/assistant/formats/clippy.d.ts +21 -0
- package/lib/chat/assistant/formats/clippy.js +167 -0
- package/lib/chat/assistant/formats/index.d.ts +13 -0
- package/lib/chat/assistant/formats/index.js +15 -0
- package/lib/chat/assistant/formats/objectLiteral.d.ts +23 -0
- package/lib/chat/assistant/formats/objectLiteral.js +233 -0
- package/lib/chat/assistant/formats/stateAnimations.d.ts +16 -0
- package/lib/chat/assistant/formats/stateAnimations.js +115 -0
- package/lib/chat/assistant/formats/types.d.ts +56 -0
- package/lib/chat/assistant/formats/types.js +11 -0
- package/lib/chat/assistant/state.d.ts +62 -0
- package/lib/chat/assistant/state.js +116 -0
- package/lib/chat/header/ChatViewModeToggle.js +6 -3
- package/lib/{loop/plugins/chat → chat/presence}/Presence.js +1 -1
- package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.d.ts +7 -1
- package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.js +18 -0
- package/lib/chat/viewModes.d.ts +6 -0
- package/lib/chat/viewModes.js +10 -1
- package/lib/examples/ChatAssistantExample.d.ts +8 -0
- package/lib/examples/ChatAssistantExample.js +59 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/main.js +3 -0
- package/lib/loop/apps/AppRenderer.d.ts +1 -1
- package/lib/loop/apps/appspec.d.ts +5 -1
- package/lib/loop/apps/appspec.js +21 -1
- package/lib/loop/apps/checks.js +3 -1
- package/lib/loop/core/index.d.ts +15 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/plugins/assistant-characters/index.d.ts +30 -0
- package/lib/loop/plugins/assistant-characters/index.js +41 -0
- package/lib/loop/plugins/chat/ChatView.js +3 -14
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/page-layout/index.js +5 -1
- package/lib/specs/actions.js +6 -0
- package/lib/specs/apps.d.ts +3 -0
- package/lib/specs/apps.js +793 -65
- package/lib/specs/appspecSchema.js +20 -2
- package/lib/types/agentspecs.d.ts +10 -1
- package/lib/types/chat.d.ts +1 -1
- package/package.json +1 -1
- /package/lib/{loop/plugins/chat → chat/presence}/Presence.d.ts +0 -0
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* @module chat/ChatFloating
|
|
13
13
|
*/
|
|
14
14
|
import React from 'react';
|
|
15
|
+
import { type AssistantCharacter } from './assistant/characters';
|
|
16
|
+
import type { AssistantCharacterData } from './assistant/formats/types';
|
|
15
17
|
import type { ChatCommonProps, ChatViewMode } from '../types';
|
|
16
18
|
/**
|
|
17
19
|
* ChatFloating props — extends ChatCommonProps with floating/popup-specific configuration.
|
|
@@ -67,10 +69,18 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
67
69
|
* - 'floating': Full-height floating panel (pinned to right edge with offset)
|
|
68
70
|
* - 'floating-small': Standard floating popup
|
|
69
71
|
* - 'floating-draggable': The popup with a handle, movable about the viewport
|
|
72
|
+
* - 'assistant': A character on the page that speaks in a balloon (LOOP T-21)
|
|
70
73
|
* - 'panel': Full-height side panel (right edge, no floating offset)
|
|
71
74
|
* @default 'floating'
|
|
72
75
|
*/
|
|
73
|
-
defaultViewMode?: 'floating' | 'floating-small' | 'floating-draggable' | 'panel';
|
|
76
|
+
defaultViewMode?: 'floating' | 'floating-small' | 'floating-draggable' | 'assistant' | 'panel';
|
|
77
|
+
/**
|
|
78
|
+
* The character of the floating assistant: one Datalayer ships, by id
|
|
79
|
+
* (`ASSISTANT_CHARACTERS`, LOOP T-25), or one a person loaded from a file
|
|
80
|
+
* they hold the rights to (`readClippyCharacter`, `readAcsCharacter`, T-26).
|
|
81
|
+
* @default 'paperclip'
|
|
82
|
+
*/
|
|
83
|
+
assistantCharacter?: string | AssistantCharacter | AssistantCharacterData;
|
|
74
84
|
/**
|
|
75
85
|
* Callback when the user switches view mode via the header toggle.
|
|
76
86
|
* The parent component receives the new ChatViewMode value.
|
|
@@ -99,5 +109,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
|
|
|
99
109
|
* ChatFloating component
|
|
100
110
|
* A floating chat window built on ChatBase
|
|
101
111
|
*/
|
|
102
|
-
export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, }: ChatFloatingProps): React.JSX.Element;
|
|
112
|
+
export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, }: ChatFloatingProps): React.JSX.Element;
|
|
103
113
|
export default ChatFloating;
|
package/lib/chat/ChatFloating.js
CHANGED
|
@@ -26,6 +26,11 @@ import { ChatBase } from './base/ChatBase';
|
|
|
26
26
|
import { ButtonGlow } from './display/ButtonGlow';
|
|
27
27
|
import { useViewportDrag } from './useViewportDrag';
|
|
28
28
|
import { disabledChatViewModes, resolveMountPoint } from './viewModes';
|
|
29
|
+
import { AssistantStage } from './assistant/AssistantStage';
|
|
30
|
+
import { SpeechBalloon } from './assistant/SpeechBalloon';
|
|
31
|
+
import { DEFAULT_ASSISTANT_CHARACTER, } from './assistant/characters';
|
|
32
|
+
import { assistantStateOf, latestSaying, newestIsAnswer, keepAway, keptAway, } from './assistant/state';
|
|
33
|
+
import { presenceState, presenceToolOf, } from './presence/presenceStatus';
|
|
29
34
|
import { useChatOpen, useChatMessages, useChatStore, } from '../stores/chatStore';
|
|
30
35
|
import { useChatKeyboardShortcuts, getShortcutDisplay, } from '@datalayer/core/lib/hooks';
|
|
31
36
|
/**
|
|
@@ -47,7 +52,7 @@ function useIsMobile(breakpoint = 640) {
|
|
|
47
52
|
* ChatFloating component
|
|
48
53
|
* A floating chat window built on ChatBase
|
|
49
54
|
*/
|
|
50
|
-
export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, }) {
|
|
55
|
+
export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, }) {
|
|
51
56
|
// Store-based state
|
|
52
57
|
const storeIsOpen = useChatOpen();
|
|
53
58
|
const storeMessages = useChatMessages();
|
|
@@ -96,6 +101,88 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
96
101
|
resetDrag();
|
|
97
102
|
}
|
|
98
103
|
}, [viewMode, resetDrag]);
|
|
104
|
+
/*
|
|
105
|
+
* The floating assistant (LOOP T-21, T-22, T-27): a character that acts out
|
|
106
|
+
* the presence of what answers — read here from the chat's own loading and
|
|
107
|
+
* items — greets when it arrives, says goodbye when sent away, and is
|
|
108
|
+
* dragged about by itself, the conversation following it.
|
|
109
|
+
*/
|
|
110
|
+
const ASSISTANT_SIZE = 88;
|
|
111
|
+
const assistantMode = viewMode === 'assistant' && !isMobile;
|
|
112
|
+
const [chatBusy, setChatBusy] = useState(false);
|
|
113
|
+
const [chatTool, setChatTool] = useState({
|
|
114
|
+
open: false,
|
|
115
|
+
pendingApproval: false,
|
|
116
|
+
});
|
|
117
|
+
const [answering, setAnswering] = useState(false);
|
|
118
|
+
const [arriving, setArriving] = useState(false);
|
|
119
|
+
const [leaving, setLeaving] = useState(false);
|
|
120
|
+
const [assistantAway, setAssistantAway] = useState(keptAway);
|
|
121
|
+
const assistantShown = assistantMode && assistantAway === 'none';
|
|
122
|
+
const callAssistantBack = useCallback(() => {
|
|
123
|
+
keepAway('none');
|
|
124
|
+
setAssistantAway('none');
|
|
125
|
+
}, []);
|
|
126
|
+
/* The agent's newest words, said in the balloon until heard (T-23). */
|
|
127
|
+
const [saying, setSaying] = useState();
|
|
128
|
+
const [heardId, setHeardId] = useState();
|
|
129
|
+
const [freshSaying, setFreshSaying] = useState(false);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!saying || isOpen) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setFreshSaying(true);
|
|
135
|
+
const timer = setTimeout(() => setFreshSaying(false), 12000);
|
|
136
|
+
return () => clearTimeout(timer);
|
|
137
|
+
}, [saying?.id, saying?.text, isOpen]);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (isOpen && saying) {
|
|
140
|
+
setHeardId(saying.id);
|
|
141
|
+
}
|
|
142
|
+
}, [isOpen, saying]);
|
|
143
|
+
const stageRef = useRef(null);
|
|
144
|
+
const stageDrag = useViewportDrag(stageRef);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!assistantShown) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
setArriving(true);
|
|
150
|
+
const timer = setTimeout(() => setArriving(false), 1800);
|
|
151
|
+
return () => clearTimeout(timer);
|
|
152
|
+
}, [assistantShown]);
|
|
153
|
+
const assistantState = assistantStateOf(presenceState(chatBusy, chatTool), {
|
|
154
|
+
arriving,
|
|
155
|
+
leaving,
|
|
156
|
+
speaking: answering,
|
|
157
|
+
});
|
|
158
|
+
const unheard = !!saying && saying.id !== heardId;
|
|
159
|
+
const assistantBalloon = assistantState === 'waiting'
|
|
160
|
+
? { text: 'Waiting for you — open the conversation to answer.' }
|
|
161
|
+
: unheard && saying
|
|
162
|
+
? { text: saying.text, more: saying.more }
|
|
163
|
+
: { text: description };
|
|
164
|
+
const balloonInsists = assistantState === 'greeting' ||
|
|
165
|
+
assistantState === 'waiting' ||
|
|
166
|
+
(unheard && (assistantState === 'speaking' || freshSaying));
|
|
167
|
+
const panelOnLoadingChange = panelProps?.onLoadingChange;
|
|
168
|
+
const panelOnDisplayItemsChange = panelProps?.onDisplayItemsChange;
|
|
169
|
+
const handleLoadingChange = useCallback((loading) => {
|
|
170
|
+
setChatBusy(loading);
|
|
171
|
+
panelOnLoadingChange?.(loading);
|
|
172
|
+
}, [panelOnLoadingChange]);
|
|
173
|
+
const handleDisplayItemsChange = useCallback((items) => {
|
|
174
|
+
const tool = presenceToolOf(items);
|
|
175
|
+
setChatTool(previous => previous.open === tool.open &&
|
|
176
|
+
previous.pendingApproval === tool.pendingApproval
|
|
177
|
+
? previous
|
|
178
|
+
: tool);
|
|
179
|
+
setAnswering(newestIsAnswer(items));
|
|
180
|
+
const said = latestSaying(items);
|
|
181
|
+
setSaying(previous => previous?.id === said?.id && previous?.text === said?.text
|
|
182
|
+
? previous
|
|
183
|
+
: said);
|
|
184
|
+
panelOnDisplayItemsChange?.(items);
|
|
185
|
+
}, [panelOnDisplayItemsChange]);
|
|
99
186
|
// Handle view mode changes from the header segmented toggle
|
|
100
187
|
const handleChatViewModeChange = useCallback((mode) => {
|
|
101
188
|
if (mode === 'sidebar' && sidebarDisabled) {
|
|
@@ -115,12 +202,16 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
115
202
|
setFocusTrigger(prev => prev + 1);
|
|
116
203
|
}
|
|
117
204
|
else {
|
|
118
|
-
// The floating modes stay within ChatFloating
|
|
205
|
+
// The floating modes stay within ChatFloating; picking the assistant
|
|
206
|
+
// calls it back, however long it was sent away for (T-27).
|
|
207
|
+
if (mode === 'assistant') {
|
|
208
|
+
callAssistantBack();
|
|
209
|
+
}
|
|
119
210
|
setViewMode(mode);
|
|
120
211
|
setFocusTrigger(prev => prev + 1);
|
|
121
212
|
onViewModeChange?.(mode);
|
|
122
213
|
}
|
|
123
|
-
}, [onViewModeChange, sidebarDisabled]);
|
|
214
|
+
}, [onViewModeChange, sidebarDisabled, callAssistantBack]);
|
|
124
215
|
// Detect whether the chat is mounted inside a parent that is intentionally
|
|
125
216
|
// configured for docked side panels (Notebook example lays the chat out as a
|
|
126
217
|
// flex sibling). Those hosts dock the panel in-flow. Plain content pages
|
|
@@ -272,6 +363,42 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
272
363
|
// Reset animating after duration
|
|
273
364
|
setTimeout(() => setIsAnimating(false), animationDuration);
|
|
274
365
|
}, [isOpen, setIsOpen, onOpen, onClose, animationDuration]);
|
|
366
|
+
/*
|
|
367
|
+
* The assistant sent away (T-27): goodbye first, then the popup's round
|
|
368
|
+
* button in its place — which calls it back when it went for the page, and
|
|
369
|
+
* opens the chat when it went for the session or for good.
|
|
370
|
+
*/
|
|
371
|
+
const dismissAssistant = useCallback((away) => {
|
|
372
|
+
if (isOpen) {
|
|
373
|
+
handleToggle();
|
|
374
|
+
}
|
|
375
|
+
keepAway(away);
|
|
376
|
+
setLeaving(true);
|
|
377
|
+
setTimeout(() => {
|
|
378
|
+
setLeaving(false);
|
|
379
|
+
setAssistantAway(away);
|
|
380
|
+
}, 600);
|
|
381
|
+
}, [isOpen, handleToggle]);
|
|
382
|
+
/*
|
|
383
|
+
* The conversation as the assistant's balloon: above the character, its
|
|
384
|
+
* right edge on the character's, and never off the screen.
|
|
385
|
+
*/
|
|
386
|
+
const balloonPlace = () => {
|
|
387
|
+
const widthPx = typeof width === 'number' ? width : 400;
|
|
388
|
+
const heightPx = typeof height === 'number' ? height : 550;
|
|
389
|
+
if (stageDrag.position) {
|
|
390
|
+
return {
|
|
391
|
+
left: Math.max(8, stageDrag.position.left + ASSISTANT_SIZE - widthPx),
|
|
392
|
+
top: Math.max(8, stageDrag.position.top - heightPx - 16),
|
|
393
|
+
right: 'auto',
|
|
394
|
+
bottom: 'auto',
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const corner = getPositionStyles();
|
|
398
|
+
return position.startsWith('bottom')
|
|
399
|
+
? { ...corner, bottom: offset + ASSISTANT_SIZE + 16 }
|
|
400
|
+
: { ...corner, top: offset + ASSISTANT_SIZE + 16 };
|
|
401
|
+
};
|
|
275
402
|
// Handle new chat
|
|
276
403
|
const handleNewChat = useCallback(() => {
|
|
277
404
|
if (useStoreMode) {
|
|
@@ -437,6 +564,11 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
437
564
|
}
|
|
438
565
|
: getPositionStyles()
|
|
439
566
|
: {}),
|
|
567
|
+
...(assistantShown
|
|
568
|
+
? balloonPlace()
|
|
569
|
+
: assistantMode && !isMobile
|
|
570
|
+
? getPositionStyles()
|
|
571
|
+
: {}),
|
|
440
572
|
...(viewMode === 'panel' && !isMobile
|
|
441
573
|
? {
|
|
442
574
|
...(panelInFlow || dockedInMount
|
|
@@ -468,7 +600,8 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
468
600
|
? `${popupWidth}px`
|
|
469
601
|
: popupWidth
|
|
470
602
|
: (viewMode === 'floating-small' ||
|
|
471
|
-
viewMode === 'floating-draggable'
|
|
603
|
+
viewMode === 'floating-draggable' ||
|
|
604
|
+
viewMode === 'assistant') &&
|
|
472
605
|
!isMobile
|
|
473
606
|
? typeof popupWidth === 'number'
|
|
474
607
|
? `${popupWidth}px`
|
|
@@ -487,7 +620,8 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
487
620
|
: viewMode === 'floating' && !isMobile
|
|
488
621
|
? '100%'
|
|
489
622
|
: (viewMode === 'floating-small' ||
|
|
490
|
-
viewMode === 'floating-draggable'
|
|
623
|
+
viewMode === 'floating-draggable' ||
|
|
624
|
+
viewMode === 'assistant') &&
|
|
491
625
|
!isMobile
|
|
492
626
|
? typeof popupHeight === 'number'
|
|
493
627
|
? `${popupHeight}px`
|
|
@@ -502,7 +636,11 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
502
636
|
bg: 'canvas.default',
|
|
503
637
|
border: '1px solid',
|
|
504
638
|
borderColor: 'border.default',
|
|
505
|
-
borderRadius: viewMode === 'panel' || isMobile
|
|
639
|
+
borderRadius: viewMode === 'panel' || isMobile
|
|
640
|
+
? 0
|
|
641
|
+
: assistantMode
|
|
642
|
+
? 'var(--theme-radius-bubble, 16px)'
|
|
643
|
+
: '12px',
|
|
506
644
|
boxShadow: viewMode === 'panel' ? 'shadow.none' : 'shadow.extra-large',
|
|
507
645
|
overflow: 'hidden',
|
|
508
646
|
transform: viewMode === 'panel'
|
|
@@ -550,30 +688,56 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
550
688
|
...poweredByProps,
|
|
551
689
|
}, renderToolResult: renderToolResult, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
|
|
552
690
|
showAvatars: true,
|
|
553
|
-
}, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, ...panelProps, children: children })] }));
|
|
554
|
-
|
|
691
|
+
}, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, ...panelProps, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: children })] }));
|
|
692
|
+
/*
|
|
693
|
+
* The floating popup's button. Words not yet heard make it blink for the
|
|
694
|
+
* person's attention, until the chat is opened; hovered, they show in a
|
|
695
|
+
* balloon above it in place of the tooltip.
|
|
696
|
+
*/
|
|
697
|
+
const chatButton = (_jsx(IconButton, { icon: buttonIcon ? buttonIcon : CommentDiscussionIcon, "aria-label": buttonTooltip, onClick: assistantMode && assistantAway === 'page'
|
|
698
|
+
? callAssistantBack
|
|
699
|
+
: handleToggle, size: "large", sx: {
|
|
700
|
+
width: 56,
|
|
701
|
+
height: 56,
|
|
702
|
+
borderRadius: '50%',
|
|
703
|
+
bg: brandColor || 'accent.emphasis',
|
|
704
|
+
color: 'fg.onEmphasis',
|
|
705
|
+
boxShadow: 'shadow.large',
|
|
706
|
+
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
|
707
|
+
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
708
|
+
// Words not yet heard: the circle blinks for the person's
|
|
709
|
+
// attention, and stops once the chat is opened.
|
|
710
|
+
...(unheard
|
|
711
|
+
? {
|
|
712
|
+
animation: 'chatFloatingBlink 1.1s ease-in-out infinite',
|
|
713
|
+
'@keyframes chatFloatingBlink': {
|
|
714
|
+
'0%, 100%': {
|
|
715
|
+
filter: 'brightness(1)',
|
|
716
|
+
boxShadow: '0 0 0 0 rgba(0,0,0,0)',
|
|
717
|
+
},
|
|
718
|
+
'50%': {
|
|
719
|
+
filter: 'brightness(1.25)',
|
|
720
|
+
boxShadow: '0 0 0 6px var(--bgColor-accent-muted, rgba(84,174,255,0.4))',
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
'@media (prefers-reduced-motion: reduce)': { animation: 'none' },
|
|
724
|
+
}
|
|
725
|
+
: {}),
|
|
726
|
+
'&:hover': {
|
|
727
|
+
bg: brandColor || 'accent.emphasis',
|
|
728
|
+
boxShadow: 'shadow.extra-large',
|
|
729
|
+
},
|
|
730
|
+
} }));
|
|
731
|
+
return (_jsxs(_Fragment, { children: [assistantShown && (_jsx(AssistantStage, { character: assistantCharacter, state: assistantState, size: ASSISTANT_SIZE, place: stageDrag.position
|
|
732
|
+
? { left: stageDrag.position.left, top: stageDrag.position.top }
|
|
733
|
+
: getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, insist: balloonInsists, onDismiss: dismissAssistant })), showButton && !isOpen && !assistantShown && (_jsx(Box, { sx: {
|
|
555
734
|
...getPositionStyles(),
|
|
556
735
|
}, children: _jsxs(Box, { sx: {
|
|
557
736
|
position: 'relative',
|
|
558
737
|
display: 'inline-flex',
|
|
559
738
|
// The glow's halos go behind the button, not behind the page.
|
|
560
739
|
isolation: 'isolate',
|
|
561
|
-
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [buttonGlow && _jsx(ButtonGlow, { color: brandColor }), _jsx(Tooltip, { text: `${buttonTooltip}${shortcutHint ? ` (${shortcutHint})` : ''}`, direction: position.includes('right') ? 'w' : 'e', children: _jsx(
|
|
562
|
-
? buttonIcon
|
|
563
|
-
: CommentDiscussionIcon, "aria-label": buttonTooltip, onClick: handleToggle, size: "large", sx: {
|
|
564
|
-
width: 56,
|
|
565
|
-
height: 56,
|
|
566
|
-
borderRadius: '50%',
|
|
567
|
-
bg: brandColor || 'accent.emphasis',
|
|
568
|
-
color: 'fg.onEmphasis',
|
|
569
|
-
boxShadow: 'shadow.large',
|
|
570
|
-
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
|
|
571
|
-
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
|
572
|
-
'&:hover': {
|
|
573
|
-
bg: brandColor || 'accent.emphasis',
|
|
574
|
-
boxShadow: 'shadow.extra-large',
|
|
575
|
-
},
|
|
576
|
-
} }) }), messages.length > 0 && (_jsx(Box, { sx: {
|
|
740
|
+
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [buttonGlow && _jsx(ButtonGlow, { color: brandColor }), isHovered && saying ? (_jsx(SpeechBalloon, { text: saying.text, more: saying.more, onOpen: handleToggle, above: 64, side: position.startsWith('bottom') ? 'above' : 'below', align: position.includes('right') ? 'right' : 'left', tailAt: 28 })) : null, saying ? (chatButton) : (_jsx(Tooltip, { text: `${buttonTooltip}${shortcutHint ? ` (${shortcutHint})` : ''}`, direction: position.includes('right') ? 'w' : 'e', children: chatButton })), messages.length > 0 && (_jsx(Box, { sx: {
|
|
577
741
|
position: 'absolute',
|
|
578
742
|
top: 0,
|
|
579
743
|
right: 0,
|
|
@@ -589,7 +753,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
589
753
|
fontSize: 0,
|
|
590
754
|
fontWeight: 'bold',
|
|
591
755
|
pointerEvents: 'none',
|
|
592
|
-
}, children: _jsx(Text, { sx: { fontSize: 0 }, children: messages.length > 99 ? '99+' : messages.length }) })),
|
|
756
|
+
}, children: _jsx(Text, { sx: { fontSize: 0 }, children: messages.length > 99 ? '99+' : messages.length }) })), unheard && (_jsx(Box, { sx: {
|
|
593
757
|
position: 'absolute',
|
|
594
758
|
top: 0,
|
|
595
759
|
left: 0,
|
|
@@ -610,6 +774,13 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
|
|
|
610
774
|
opacity: 0,
|
|
611
775
|
},
|
|
612
776
|
},
|
|
777
|
+
// Still, as a ring held around the button, for a reader
|
|
778
|
+
// who asks the system for reduced motion (T-28).
|
|
779
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
780
|
+
animation: 'none',
|
|
781
|
+
transform: 'scale(1.15)',
|
|
782
|
+
opacity: 0.6,
|
|
783
|
+
},
|
|
613
784
|
} }))] }) })), isMobile && isOpen && (_jsx(Box, { sx: {
|
|
614
785
|
position: 'fixed',
|
|
615
786
|
top: 0,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The floating assistant on the page (LOOP T-21, T-22, T-27): a character in
|
|
3
|
+
* a corner, acting out what the application is doing, with a balloon that
|
|
4
|
+
* says a word while the conversation is closed. Clicked, it opens the
|
|
5
|
+
* conversation; dragged, it moves, and the conversation follows; sent away,
|
|
6
|
+
* it says goodbye and leaves a small way back.
|
|
7
|
+
*
|
|
8
|
+
* Its motions are one set for every character, by the parts each drawing
|
|
9
|
+
* names (`assistant-body`, `-pupils`, `-lids`, `-mouth`), and nothing moves
|
|
10
|
+
* for a reader who asks the system for reduced motion.
|
|
11
|
+
*
|
|
12
|
+
* @module chat/assistant/AssistantStage
|
|
13
|
+
*/
|
|
14
|
+
import type { JSX, RefObject } from 'react';
|
|
15
|
+
import { type AssistantCharacter } from './characters';
|
|
16
|
+
import type { AssistantCharacterData } from './formats/types';
|
|
17
|
+
import type { AssistantAway, AssistantState } from './state';
|
|
18
|
+
/**
|
|
19
|
+
* Where the balloon goes so that it stays inside the window (T-23): toward
|
|
20
|
+
* the middle of the page from whichever half the character stands in, and
|
|
21
|
+
* below it when it stands too near the top.
|
|
22
|
+
*/
|
|
23
|
+
export declare function balloonSide(place: {
|
|
24
|
+
left?: unknown;
|
|
25
|
+
top?: unknown;
|
|
26
|
+
right?: unknown;
|
|
27
|
+
bottom?: unknown;
|
|
28
|
+
}, viewport?: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
}): {
|
|
32
|
+
side: 'above' | 'below';
|
|
33
|
+
align: 'left' | 'right';
|
|
34
|
+
};
|
|
35
|
+
export interface AssistantStageProps {
|
|
36
|
+
/** The character: one Datalayer ships, by id (T-25), or one loaded from a file (T-26). */
|
|
37
|
+
character: string | AssistantCharacter | AssistantCharacterData;
|
|
38
|
+
/** What it acts out (T-22). */
|
|
39
|
+
state: AssistantState;
|
|
40
|
+
/** Its size, in pixels. */
|
|
41
|
+
size?: number;
|
|
42
|
+
/** Where it sits once moved, or the corner it starts in. */
|
|
43
|
+
place: {
|
|
44
|
+
left: number;
|
|
45
|
+
top: number;
|
|
46
|
+
} | React.CSSProperties;
|
|
47
|
+
/** The element the drag measures. */
|
|
48
|
+
stageRef: RefObject<HTMLDivElement | null>;
|
|
49
|
+
/** Starts a drag. */
|
|
50
|
+
onDragStart: (event: React.PointerEvent<HTMLElement>) => void;
|
|
51
|
+
/** The conversation is open: the balloon above is the chat itself. */
|
|
52
|
+
open: boolean;
|
|
53
|
+
/** Open or close the conversation. */
|
|
54
|
+
onToggle: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* What the balloon says while the conversation is closed: the agent's
|
|
57
|
+
* newest words, as the Office Assistant said them (T-23), or a welcome.
|
|
58
|
+
* `more` adds *Open the conversation* for the rest.
|
|
59
|
+
*/
|
|
60
|
+
balloon?: {
|
|
61
|
+
text: string;
|
|
62
|
+
more?: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** Show the balloon without being hovered: something new to say. */
|
|
65
|
+
insist?: boolean;
|
|
66
|
+
/** Send it away (T-27): for the page, for the session, or for good. */
|
|
67
|
+
onDismiss: (away: AssistantAway) => void;
|
|
68
|
+
}
|
|
69
|
+
export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, }: AssistantStageProps): JSX.Element;
|
|
70
|
+
export default AssistantStage;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { ActionList, ActionMenu, IconButton } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { XIcon } from '@primer/octicons-react';
|
|
6
|
+
import { assistantCharacter } from './characters';
|
|
7
|
+
import { SpeechBalloon } from './SpeechBalloon';
|
|
8
|
+
import { SpriteCharacter } from './SpriteCharacter';
|
|
9
|
+
/** The motions, by the state the stage is in. */
|
|
10
|
+
const MOTIONS = {
|
|
11
|
+
'@keyframes assistantFloat': {
|
|
12
|
+
'0%, 100%': { transform: 'translateY(0)' },
|
|
13
|
+
'50%': { transform: 'translateY(-3px)' },
|
|
14
|
+
},
|
|
15
|
+
'@keyframes assistantBlink': {
|
|
16
|
+
'0%, 94%, 100%': { transform: 'scaleY(0)' },
|
|
17
|
+
'97%': { transform: 'scaleY(1)' },
|
|
18
|
+
},
|
|
19
|
+
'@keyframes assistantTilt': {
|
|
20
|
+
'0%, 100%': { transform: 'rotate(-5deg)' },
|
|
21
|
+
'50%': { transform: 'rotate(-8deg) translateY(-2px)' },
|
|
22
|
+
},
|
|
23
|
+
'@keyframes assistantWork': {
|
|
24
|
+
'0%, 100%': { transform: 'rotate(-3deg) translateY(0)' },
|
|
25
|
+
'50%': { transform: 'rotate(3deg) translateY(-4px)' },
|
|
26
|
+
},
|
|
27
|
+
'@keyframes assistantAttention': {
|
|
28
|
+
'0%, 100%': { transform: 'scale(1) rotate(0)' },
|
|
29
|
+
'20%': { transform: 'scale(1.08) rotate(-6deg)' },
|
|
30
|
+
'40%': { transform: 'scale(1.08) rotate(6deg)' },
|
|
31
|
+
'60%': { transform: 'scale(1)' },
|
|
32
|
+
},
|
|
33
|
+
'@keyframes assistantHop': {
|
|
34
|
+
'0%, 100%': { transform: 'translateY(0)' },
|
|
35
|
+
'30%': { transform: 'translateY(-14px)' },
|
|
36
|
+
'55%': { transform: 'translateY(0)' },
|
|
37
|
+
'75%': { transform: 'translateY(-6px)' },
|
|
38
|
+
},
|
|
39
|
+
'@keyframes assistantTalk': {
|
|
40
|
+
'0%, 100%': { transform: 'scaleY(1)' },
|
|
41
|
+
'50%': { transform: 'scaleY(2.2)' },
|
|
42
|
+
},
|
|
43
|
+
'@keyframes assistantLeave': {
|
|
44
|
+
from: { transform: 'scale(1)', opacity: 1 },
|
|
45
|
+
to: { transform: 'scale(0.2) translateY(20px)', opacity: 0 },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
const BODY = '& .assistant-body';
|
|
49
|
+
/** How long it may be sent away for, in the menu's words (T-27). */
|
|
50
|
+
const AWAY_CHOICES = [
|
|
51
|
+
{ away: 'page', label: 'Hide for now' },
|
|
52
|
+
{ away: 'session', label: 'Hide for this session' },
|
|
53
|
+
{ away: 'always', label: 'Don\u2019t show again' },
|
|
54
|
+
];
|
|
55
|
+
/** Room the balloon needs above the character before it goes below. */
|
|
56
|
+
const BALLOON_ROOM = 200;
|
|
57
|
+
/**
|
|
58
|
+
* Where the balloon goes so that it stays inside the window (T-23): toward
|
|
59
|
+
* the middle of the page from whichever half the character stands in, and
|
|
60
|
+
* below it when it stands too near the top.
|
|
61
|
+
*/
|
|
62
|
+
export function balloonSide(place, viewport = {
|
|
63
|
+
width: typeof window === 'undefined' ? 1280 : window.innerWidth,
|
|
64
|
+
height: typeof window === 'undefined' ? 800 : window.innerHeight,
|
|
65
|
+
}) {
|
|
66
|
+
const left = typeof place.left === 'number' ? place.left : undefined;
|
|
67
|
+
const top = typeof place.top === 'number' ? place.top : undefined;
|
|
68
|
+
const align = left !== undefined
|
|
69
|
+
? left < viewport.width / 2
|
|
70
|
+
? 'left'
|
|
71
|
+
: 'right'
|
|
72
|
+
: place.left !== undefined && place.right === undefined
|
|
73
|
+
? 'left'
|
|
74
|
+
: 'right';
|
|
75
|
+
const side = top !== undefined
|
|
76
|
+
? top < BALLOON_ROOM
|
|
77
|
+
? 'below'
|
|
78
|
+
: 'above'
|
|
79
|
+
: place.top !== undefined && place.bottom === undefined
|
|
80
|
+
? 'below'
|
|
81
|
+
: 'above';
|
|
82
|
+
return { side, align };
|
|
83
|
+
}
|
|
84
|
+
export function AssistantStage({ character, state, size = 88, place, stageRef, onDragStart, open, onToggle, balloon, insist = false, onDismiss, }) {
|
|
85
|
+
// A shipped one by id, a drawing contributed by a plugin (T-24), or a
|
|
86
|
+
// character read from a file (T-26).
|
|
87
|
+
const shipped = typeof character === 'string'
|
|
88
|
+
? assistantCharacter(character)
|
|
89
|
+
: 'Drawing' in character
|
|
90
|
+
? character
|
|
91
|
+
: undefined;
|
|
92
|
+
const name = shipped
|
|
93
|
+
? shipped.name
|
|
94
|
+
: character.name;
|
|
95
|
+
const [hovered, setHovered] = useState(false);
|
|
96
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
97
|
+
// Where the press began: a press that moves is a drag, not a click.
|
|
98
|
+
const pressedAt = useRef(null);
|
|
99
|
+
const showBalloon = !open && !!balloon && (hovered || insist);
|
|
100
|
+
return (_jsxs(Box, { ref: stageRef, "data-assistant-state": state, sx: {
|
|
101
|
+
position: 'fixed',
|
|
102
|
+
zIndex: 1002,
|
|
103
|
+
...place,
|
|
104
|
+
width: size,
|
|
105
|
+
height: size,
|
|
106
|
+
...MOTIONS,
|
|
107
|
+
'& .assistant-lids ellipse': {
|
|
108
|
+
transformBox: 'fill-box',
|
|
109
|
+
transformOrigin: 'top',
|
|
110
|
+
transform: 'scaleY(0)',
|
|
111
|
+
animation: 'assistantBlink 5.2s ease-in-out infinite',
|
|
112
|
+
},
|
|
113
|
+
[BODY]: {
|
|
114
|
+
transformBox: 'fill-box',
|
|
115
|
+
transformOrigin: '50% 100%',
|
|
116
|
+
},
|
|
117
|
+
'& .assistant-pupils': {
|
|
118
|
+
transition: 'transform var(--theme-motion-status, 120ms) var(--theme-motion-easing, ease)',
|
|
119
|
+
},
|
|
120
|
+
'& .assistant-mouth': {
|
|
121
|
+
transformBox: 'fill-box',
|
|
122
|
+
transformOrigin: 'center',
|
|
123
|
+
},
|
|
124
|
+
'&[data-assistant-state="idle"] .assistant-body': {
|
|
125
|
+
animation: 'assistantFloat 3.2s ease-in-out infinite',
|
|
126
|
+
},
|
|
127
|
+
'&[data-assistant-state="thinking"] .assistant-body': {
|
|
128
|
+
animation: 'assistantTilt 2.4s ease-in-out infinite',
|
|
129
|
+
},
|
|
130
|
+
'&[data-assistant-state="thinking"] .assistant-pupils': {
|
|
131
|
+
transform: 'translate(-1px, -3px)',
|
|
132
|
+
},
|
|
133
|
+
'&[data-assistant-state="working"] .assistant-body': {
|
|
134
|
+
animation: 'assistantWork 0.7s ease-in-out infinite',
|
|
135
|
+
},
|
|
136
|
+
'&[data-assistant-state="working"] .assistant-pupils': {
|
|
137
|
+
transform: 'translate(2px, 2px)',
|
|
138
|
+
},
|
|
139
|
+
'&[data-assistant-state="waiting"] .assistant-body': {
|
|
140
|
+
animation: 'assistantAttention 1.4s ease-in-out infinite',
|
|
141
|
+
},
|
|
142
|
+
'&[data-assistant-state="greeting"] .assistant-body': {
|
|
143
|
+
animation: 'assistantHop 0.9s ease-out 2',
|
|
144
|
+
},
|
|
145
|
+
'&[data-assistant-state="speaking"] .assistant-mouth': {
|
|
146
|
+
animation: 'assistantTalk 0.24s ease-in-out infinite',
|
|
147
|
+
},
|
|
148
|
+
'&[data-assistant-state="speaking"] .assistant-body': {
|
|
149
|
+
animation: 'assistantFloat 1.6s ease-in-out infinite',
|
|
150
|
+
},
|
|
151
|
+
'&[data-assistant-state="goodbye"] .assistant-body': {
|
|
152
|
+
animation: 'assistantLeave 0.6s ease-in forwards',
|
|
153
|
+
},
|
|
154
|
+
// One still frame per state for a reader who asks for no motion.
|
|
155
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
156
|
+
'& *': { animation: 'none !important' },
|
|
157
|
+
},
|
|
158
|
+
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [showBalloon && balloon && (_jsx(SpeechBalloon, { text: balloon.text, more: balloon.more, 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) => {
|
|
159
|
+
pressedAt.current = { x: event.clientX, y: event.clientY };
|
|
160
|
+
onDragStart(event);
|
|
161
|
+
}, onClick: (event) => {
|
|
162
|
+
const from = pressedAt.current;
|
|
163
|
+
pressedAt.current = null;
|
|
164
|
+
if (from &&
|
|
165
|
+
Math.hypot(event.clientX - from.x, event.clientY - from.y) > 4) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
onToggle();
|
|
169
|
+
}, sx: {
|
|
170
|
+
display: 'block',
|
|
171
|
+
width: size,
|
|
172
|
+
height: size,
|
|
173
|
+
p: 0,
|
|
174
|
+
border: 0,
|
|
175
|
+
bg: 'transparent',
|
|
176
|
+
cursor: 'grab',
|
|
177
|
+
touchAction: 'none',
|
|
178
|
+
filter: 'drop-shadow(0 6px 10px rgba(0, 0, 0, 0.18))',
|
|
179
|
+
'&:active': { cursor: 'grabbing' },
|
|
180
|
+
'&:focus-visible': {
|
|
181
|
+
outline: '2px solid',
|
|
182
|
+
outlineColor: 'var(--focus-outlineColor, var(--fgColor-accent))',
|
|
183
|
+
outlineOffset: 2,
|
|
184
|
+
borderRadius: '50%',
|
|
185
|
+
},
|
|
186
|
+
}, children: shipped ? (_jsx(shipped.Drawing, { size: size })) : (_jsx(SpriteCharacter, { character: character, state: state, size: size })) }), (hovered || menuOpen) && state !== 'goodbye' && (_jsx(Box, { sx: { position: 'absolute', top: -6, right: -6 }, children: _jsxs(ActionMenu, { open: menuOpen, onOpenChange: setMenuOpen, children: [_jsx(ActionMenu.Anchor, { children: _jsx(IconButton, { icon: XIcon, "aria-label": `Send ${name} away`, size: "small", variant: "invisible", "data-assistant-dismiss": "", sx: {
|
|
187
|
+
bg: 'canvas.default',
|
|
188
|
+
borderRadius: '50%',
|
|
189
|
+
boxShadow: 'shadow.small',
|
|
190
|
+
} }) }), _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))) }) })] }) }))] }));
|
|
191
|
+
}
|
|
192
|
+
export default AssistantStage;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A speech balloon over something that stands for the agent — the floating
|
|
3
|
+
* assistant's character, or the floating popup's button while the chat is
|
|
4
|
+
* closed: the agent's newest words, as the Office Assistant said them
|
|
5
|
+
* (LOOP T-23), with *Open the conversation* when there is more.
|
|
6
|
+
*
|
|
7
|
+
* @module chat/assistant/SpeechBalloon
|
|
8
|
+
*/
|
|
9
|
+
import type { JSX } from 'react';
|
|
10
|
+
export interface SpeechBalloonProps {
|
|
11
|
+
text: string;
|
|
12
|
+
/** There is more than the balloon holds. */
|
|
13
|
+
more?: boolean;
|
|
14
|
+
/** Opens the conversation. */
|
|
15
|
+
onOpen: () => void;
|
|
16
|
+
/** How far from what it stands over, in pixels: above it, or below. */
|
|
17
|
+
above: number;
|
|
18
|
+
/** Above what speaks (the default), or below it when it sits at the top. */
|
|
19
|
+
side?: 'above' | 'below';
|
|
20
|
+
/** Which edge it is aligned to, toward the page's inside. */
|
|
21
|
+
align: 'left' | 'right';
|
|
22
|
+
/** Where the tail points, from that edge, in pixels. */
|
|
23
|
+
tailAt: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, }: SpeechBalloonProps): JSX.Element;
|
|
26
|
+
export default SpeechBalloon;
|