@datalayer/agent-runtimes 1.3.75 → 1.3.78
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/agents/AgentDetails.d.ts +16 -2
- package/lib/agents/AgentDetails.js +381 -272
- package/lib/chat/ChatFloating.d.ts +12 -2
- package/lib/chat/ChatFloating.js +48 -11
- package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
- package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
- package/lib/chat/assistant/AssistantStage.d.ts +51 -5
- package/lib/chat/assistant/AssistantStage.js +60 -10
- package/lib/chat/assistant/BalloonParts.d.ts +18 -4
- package/lib/chat/assistant/BalloonParts.js +46 -41
- package/lib/chat/assistant/BalloonVisual.js +2 -1
- package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
- package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +8 -6
- package/lib/chat/assistant/SpeechBalloon.js +29 -30
- package/lib/chat/assistant/assistantDetails.d.ts +84 -0
- package/lib/chat/assistant/assistantDetails.js +140 -0
- package/lib/chat/assistant/formats/files.d.ts +23 -0
- package/lib/chat/assistant/formats/files.js +48 -0
- package/lib/chat/assistant/formats/index.d.ts +2 -0
- package/lib/chat/assistant/formats/index.js +1 -0
- package/lib/chat/assistant/state.d.ts +7 -14
- package/lib/chat/assistant/state.js +21 -20
- package/lib/chat/assistant/toolLine.d.ts +16 -0
- package/lib/chat/assistant/toolLine.js +46 -0
- package/lib/chat/base/ChatBase.js +20 -0
- package/lib/chat/base/loopMessage.d.ts +37 -0
- package/lib/chat/base/loopMessage.js +37 -0
- package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
- package/lib/chat/messages/ChatMarkdown.js +18 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +24 -18
- package/lib/chat/styles/streamdownStyles.js +3 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/ToolCallDisplay.js +8 -7
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
- package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
- package/lib/components/teams/A2ATeamGraph.d.ts +30 -3
- package/lib/components/teams/A2ATeamGraph.js +264 -41
- package/lib/components/teams/TeamNotebookView.js +15 -0
- package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
- package/lib/components/teams/teamNotebookKernel.js +17 -0
- package/lib/components/teams/useA2ATeam.d.ts +10 -6
- package/lib/components/teams/useA2ATeam.js +29 -8
- package/lib/examples/AgentA2ATeamExample.js +6 -0
- package/lib/examples/AssistantExample.js +3 -24
- package/lib/examples/AssistantGalleryExample.js +2 -1
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +1 -1
- package/lib/examples/utils/assistantGallery.d.ts +7 -6
- package/lib/examples/utils/assistantGallery.js +23 -8
- package/lib/jupyter/exec/Python.d.ts +0 -1
- package/lib/jupyter/exec/Python.js +0 -32
- package/lib/jupyter/exec/Snippets.d.ts +4 -12
- package/lib/jupyter/exec/Snippets.js +0 -8
- package/lib/loop/apps/AppFeedback.js +3 -1
- package/lib/loop/apps/AppKept.d.ts +22 -0
- package/lib/loop/apps/AppKept.js +36 -0
- package/lib/loop/apps/AppRenderer.d.ts +11 -1
- package/lib/loop/apps/AppRenderer.js +21 -3
- package/lib/loop/apps/checks.d.ts +7 -0
- package/lib/loop/apps/checks.js +39 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/kept.d.ts +30 -0
- package/lib/loop/apps/kept.js +52 -0
- package/lib/loop/apps/records.d.ts +12 -4
- package/lib/loop/apps/records.js +13 -5
- package/lib/loop/apps/saved.d.ts +30 -0
- package/lib/loop/apps/saved.js +40 -0
- package/lib/loop/embed/AppEmbed.d.ts +8 -1
- package/lib/loop/embed/AppEmbed.js +4 -4
- package/lib/loop/embed/LoopEmbed.d.ts +6 -1
- package/lib/loop/embed/LoopEmbed.js +4 -2
- package/lib/loop/embed/element.js +1 -1
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
- package/lib/loop/plugins/agents/browserService.d.ts +7 -1
- package/lib/loop/plugins/agents/browserService.js +4 -0
- package/lib/loop/plugins/agents/plugin.d.ts +16 -0
- package/lib/loop/plugins/agents/switchable.d.ts +3 -0
- package/lib/loop/plugins/agents/switchable.js +1 -0
- package/lib/loop/plugins/app-activity/index.js +3 -0
- package/lib/loop/plugins/app-computer/index.js +3 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
- package/lib/loop/plugins/app-rules/index.js +3 -0
- package/lib/loop/plugins/assistant/index.d.ts +7 -2
- package/lib/loop/plugins/assistant/index.js +53 -4
- package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
- package/lib/loop/plugins/assistant-characters/index.js +17 -0
- package/lib/loop/plugins/chat/ChatView.js +4 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
- package/lib/loop/presets.d.ts +4 -0
- package/lib/loop/presets.js +3 -1
- package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
- package/lib/loop/shell/LoopWorkspace.js +5 -4
- package/lib/loop/shell/SidebarRail.d.ts +53 -0
- package/lib/loop/shell/SidebarRail.js +74 -0
- package/lib/loop/shell/index.d.ts +2 -0
- package/lib/loop/shell/index.js +2 -0
- package/lib/loop/shell/paneMotion.d.ts +21 -0
- package/lib/loop/shell/paneMotion.js +37 -0
- package/lib/specs/apps.js +8 -5
- package/lib/specs/uiPlugins.js +529 -0
- package/lib/types/agentspecs.d.ts +5 -3
- package/lib/types/chat.d.ts +5 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_ui_plugins.py +112 -2
|
@@ -2,10 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { Text } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
|
-
import { CheckIcon, ToolsIcon, XIcon } from '@primer/octicons-react';
|
|
6
|
-
import { SpecMark, hasMark, marksOfToolCall } from '../marks';
|
|
7
5
|
import { TypingDots } from '../indicators/TypingDots';
|
|
8
|
-
import {
|
|
6
|
+
import { ChatMessageList } from '../messages/ChatMessageList';
|
|
7
|
+
import { ChatMarkdown } from '../messages/ChatMarkdown';
|
|
8
|
+
import { agentRuntimeStore } from '../../stores/agentRuntimeStore';
|
|
9
|
+
import { toolAnnouncement, displayItemOfLine, } from './toolLine';
|
|
9
10
|
/** Out of sight, still heard. */
|
|
10
11
|
const VISUALLY_HIDDEN = {
|
|
11
12
|
position: 'absolute',
|
|
@@ -15,42 +16,48 @@ const VISUALLY_HIDDEN = {
|
|
|
15
16
|
clip: 'rect(0 0 0 0)',
|
|
16
17
|
whiteSpace: 'nowrap',
|
|
17
18
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/** How the balloon's chat items know their people: no avatars are drawn. */
|
|
20
|
+
const BALLOON_AVATARS = {
|
|
21
|
+
userAvatar: null,
|
|
22
|
+
assistantAvatar: null,
|
|
23
|
+
showAvatars: false,
|
|
24
|
+
avatarSize: 0,
|
|
25
|
+
userAvatarBg: 'neutral.muted',
|
|
26
|
+
assistantAvatarBg: 'accent.emphasis',
|
|
22
27
|
};
|
|
23
28
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
29
|
+
* Inline approvals answered in the balloon go where the chat's go: the
|
|
30
|
+
* shared monitoring socket.
|
|
31
|
+
*/
|
|
32
|
+
async function respondInBalloon(_toolCallId, result) {
|
|
33
|
+
if (result && typeof result === 'object') {
|
|
34
|
+
const record = result;
|
|
35
|
+
if (record.type === 'tool-approval-decision' &&
|
|
36
|
+
typeof record.approved === 'boolean' &&
|
|
37
|
+
typeof record.approvalId === 'string') {
|
|
38
|
+
agentRuntimeStore
|
|
39
|
+
.getState()
|
|
40
|
+
.sendDecision(record.approvalId, record.approved);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The conversation's items in the balloon, drawn by the chat's own
|
|
46
|
+
* components (`ChatMessageList`): markdown, code blocks, tool calls and
|
|
47
|
+
* their approvals, notes, the three dots — in the compact density, or the
|
|
48
|
+
* chat's own when drawn large.
|
|
49
|
+
*/
|
|
50
|
+
export function BalloonChatItems({ items, density = 'compact', waiting = false, }) {
|
|
51
|
+
const end = useRef(null);
|
|
52
|
+
return (_jsx(Box, { "data-balloon-chat-items": "", sx: { minWidth: 0 }, children: _jsx(ChatMessageList, { displayItems: items, isLoading: waiting, isStreaming: false, showLoadingIndicator: waiting, hideMessagesAfterToolUI: false, avatarConfig: BALLOON_AVATARS, padding: 0, onRespond: respondInBalloon, messagesEndRef: end, emptyContent: waiting ? _jsx(TypingDots, { size: 6 }) : null, density: density }) }));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The tool being called, in the balloon: the chat's tool call card, compact,
|
|
56
|
+
* with its runtime's own words (`Asking Accounting…`) as its summary.
|
|
27
57
|
* Seen, not read out: {@link ToolLineAnnouncer} says it once per change.
|
|
28
58
|
*/
|
|
29
|
-
export function
|
|
30
|
-
|
|
31
|
-
const marks = line.tool ? marksOfToolCall(line.tool, undefined) : null;
|
|
32
|
-
const Icon = line.phase === 'done'
|
|
33
|
-
? CheckIcon
|
|
34
|
-
: line.phase === 'failed'
|
|
35
|
-
? XIcon
|
|
36
|
-
: ToolsIcon;
|
|
37
|
-
return (_jsxs(Box, { as: "span", "aria-hidden": "true", "data-balloon-tool": line.phase, "data-balloon-tool-name": line.tool || undefined, sx: {
|
|
38
|
-
display: 'inline-flex',
|
|
39
|
-
alignItems: 'center',
|
|
40
|
-
gap: 1,
|
|
41
|
-
minWidth: 0,
|
|
42
|
-
maxWidth: '100%',
|
|
43
|
-
color: line.phase === 'failed' ? 'danger.fg' : 'fg.default',
|
|
44
|
-
}, children: [_jsx(Box, { as: "span", sx: {
|
|
45
|
-
display: 'inline-flex',
|
|
46
|
-
flexShrink: 0,
|
|
47
|
-
color: PHASE_COLOR[line.phase],
|
|
48
|
-
}, children: line.phase === 'running' && hasMark(marks) ? (_jsx(SpecMark, { icon: marks?.icon, emoji: marks?.emoji, size: 14 })) : (_jsx(Icon, { size: 14 })) }), _jsxs(Box, { as: "span", sx: {
|
|
49
|
-
minWidth: 0,
|
|
50
|
-
overflow: 'hidden',
|
|
51
|
-
textOverflow: 'ellipsis',
|
|
52
|
-
whiteSpace: 'nowrap',
|
|
53
|
-
}, children: [before, name ? (_jsx(Box, { as: "strong", sx: { fontWeight: 'semibold' }, children: name })) : null, after] })] }));
|
|
59
|
+
export function BalloonToolCall({ line, waiting = false, }) {
|
|
60
|
+
return (_jsx(Box, { "aria-hidden": "true", "data-balloon-tool": line.phase, "data-balloon-tool-name": line.tool || undefined, sx: { minWidth: 0, width: '100%' }, children: _jsx(BalloonChatItems, { items: [displayItemOfLine(line)], waiting: waiting }) }));
|
|
54
61
|
}
|
|
55
62
|
/**
|
|
56
63
|
* What a screen reader hears of the tool lines, politely: once when a call
|
|
@@ -139,19 +146,17 @@ export function CurrentBalloonBody({ text, tool, busy = false, speaking = false,
|
|
|
139
146
|
}
|
|
140
147
|
onOverflow(element.scrollHeight > element.clientHeight + 1);
|
|
141
148
|
}, [text, whole, onOverflow]);
|
|
142
|
-
return (_jsxs(Box, { "data-balloon-current": "", sx: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 }, children: [_jsx(BalloonNow, { busy: busy }), tool ? (
|
|
149
|
+
return (_jsxs(Box, { "data-balloon-current": "", sx: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 }, children: [_jsx(BalloonNow, { busy: busy }), tool ? (_jsx(BalloonToolCall, { line: tool, waiting: waiting })) : text ? (_jsxs(Box, { "aria-live": "polite", "aria-busy": speaking, ref: textRef, "data-balloon-current-text": whole ? 'whole' : '', sx: whole
|
|
143
150
|
? {
|
|
144
151
|
maxHeight: 240,
|
|
145
152
|
overflowY: 'auto',
|
|
146
153
|
overflowWrap: 'anywhere',
|
|
147
|
-
whiteSpace: 'pre-wrap',
|
|
148
154
|
pr: 1,
|
|
149
155
|
}
|
|
150
156
|
: {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
WebkitBoxOrient: 'vertical',
|
|
157
|
+
// About four lines; *more* shows the rest.
|
|
158
|
+
maxHeight: '5.6em',
|
|
154
159
|
overflow: 'hidden',
|
|
155
160
|
overflowWrap: 'anywhere',
|
|
156
|
-
}, children: [whole ? (fullText ?? text) : text, waiting && (_jsx(Box, { as: "span", sx: { ml: 2, display: 'inline-flex' }, children: _jsx(TypingDots, { size: 5 }) }))] })) : waiting ? (_jsx(TypingDots, { size: 6 })) : null, attachment, _jsx(ToolLineAnnouncer, { line: tool })] }));
|
|
161
|
+
}, children: [_jsx(ChatMarkdown, { density: "compact", text: whole ? (fullText ?? text) : text }), waiting && (_jsx(Box, { as: "span", sx: { ml: 2, display: 'inline-flex' }, children: _jsx(TypingDots, { size: 5 }) }))] })) : waiting ? (_jsx(TypingDots, { size: 6 })) : null, attachment, _jsx(ToolLineAnnouncer, { line: tool })] }));
|
|
157
162
|
}
|
|
@@ -31,12 +31,13 @@ export function ExpandedVisual({ visual, target, onClose, }) {
|
|
|
31
31
|
}
|
|
32
32
|
return createPortal(_jsxs(Box, { "data-balloon-expanded": "target", "data-balloon-visual": visual.id, sx: { position: 'relative', minWidth: 0 }, children: [_jsx(Box, { sx: { position: 'absolute', top: 0, right: 0, zIndex: 1 }, children: _jsx(IconButton, { icon: visual.shrink ? ScreenNormalIcon : XIcon, size: "small", variant: "invisible", "aria-label": `${visual.shrink ? 'Shrink' : 'Close'} ${visual.title}`, "data-balloon-expanded-close": "", ...(visual.shrink ? { 'data-balloon-shrink': '' } : {}), onClick: onClose }) }), visual.render('target')] }), element);
|
|
33
33
|
}
|
|
34
|
+
// The dialog's × (and Esc) is what shrinks it back: no Shrink of its own.
|
|
34
35
|
return (_jsx(Dialog, { title: visual.title, onClose: onClose, sx: {
|
|
35
36
|
width: EXPANDED_DIALOG_SIZE.width,
|
|
36
37
|
maxWidth: EXPANDED_DIALOG_SIZE.width,
|
|
37
38
|
height: EXPANDED_DIALOG_SIZE.height,
|
|
38
39
|
maxHeight: EXPANDED_DIALOG_SIZE.height,
|
|
39
|
-
}, children:
|
|
40
|
+
}, children: _jsx(Box, { "data-balloon-expanded": "overlay", "data-balloon-visual": visual.id, sx: { minWidth: 0 }, children: visual.render('overlay') }) }));
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Brings an expanded visual into view in its target: scrolled to, and the
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* *Code Sandbox Details…*, from the assistant's menu: where the agent's
|
|
3
|
+
* code runs, and what is in it.
|
|
4
|
+
*
|
|
5
|
+
* A compact summary first — the kind and where, its URL (without its
|
|
6
|
+
* tokens), the kernel (name, id, language, status), a cloud runtime's uid,
|
|
7
|
+
* environment and time left, a local server's agent, Pyodide's version and
|
|
8
|
+
* packages, when it started and was last active — each field shown only
|
|
9
|
+
* when it is known. Then the kernel's variables: jupyter-react's
|
|
10
|
+
* `KernelVariables`, with its renderers, ipywidgets drawn live where the
|
|
11
|
+
* page talks to the kernel.
|
|
12
|
+
*
|
|
13
|
+
* How the variables are read, by what the page can reach:
|
|
14
|
+
* - a kernel connection the host holds (the browser's Pyodide kernel):
|
|
15
|
+
* listed live, refreshed after each execution;
|
|
16
|
+
* - a Jupyter server and the sandbox's kernel id (a local server, a cloud
|
|
17
|
+
* runtime): a connection to that kernel is opened here, and closed with
|
|
18
|
+
* the dialog;
|
|
19
|
+
* - neither (an `eval` sandbox, a provider's, a server whose token the page
|
|
20
|
+
* does not hold): the agent-runtimes server runs the same snippet in the
|
|
21
|
+
* agent's sandbox (`POST /api/v1/sandbox/execute`), listed on demand.
|
|
22
|
+
*
|
|
23
|
+
* Loaded when it is first opened, never with the character.
|
|
24
|
+
*
|
|
25
|
+
* @module chat/assistant/SandboxDetailsDialog
|
|
26
|
+
*/
|
|
27
|
+
import type { JSX } from 'react';
|
|
28
|
+
import { type AssistantSandbox } from './assistantDetails';
|
|
29
|
+
export type SandboxDetailsDialogProps = {
|
|
30
|
+
sandbox: AssistantSandbox;
|
|
31
|
+
/** The agent's name, for the title. */
|
|
32
|
+
name: string;
|
|
33
|
+
onClose: () => void;
|
|
34
|
+
};
|
|
35
|
+
/** Pyodide's version and loaded packages, asked of the kernel. */
|
|
36
|
+
type PyodideFacts = {
|
|
37
|
+
version?: string;
|
|
38
|
+
python?: string;
|
|
39
|
+
packages: string[];
|
|
40
|
+
};
|
|
41
|
+
/** Pyodide's facts out of what the snippet printed. */
|
|
42
|
+
export declare function parsePyodideFacts(stdout: string): PyodideFacts | undefined;
|
|
43
|
+
export declare function SandboxDetailsDialog({ sandbox, name, onClose, }: SandboxDetailsDialogProps): JSX.Element;
|
|
44
|
+
export default SandboxDetailsDialog;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Button, Dialog, Heading, IconButton, Label, Link, Text, } from '@primer/react';
|
|
4
|
+
import { CheckIcon, CopyIcon } from '@primer/octicons-react';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { KernelAPI, KernelConnection, ServerConnection, } from '@jupyterlab/services';
|
|
7
|
+
import { KernelVariables, executeSilently, } from '@datalayer/jupyter-react';
|
|
8
|
+
import { NO_SANDBOX_YET, SANDBOX_KIND_LABELS, displayUrl, sandboxIsLive, sandboxExecuteOverHttp, timeLeft, } from './assistantDetails';
|
|
9
|
+
/** A status, as a Primer label. */
|
|
10
|
+
function StatusLabel({ status }) {
|
|
11
|
+
const variant = status === 'running' || status === 'idle'
|
|
12
|
+
? 'success'
|
|
13
|
+
: status === 'busy' || status === 'starting'
|
|
14
|
+
? 'attention'
|
|
15
|
+
: status === 'error' || status === 'dead'
|
|
16
|
+
? 'danger'
|
|
17
|
+
: 'secondary';
|
|
18
|
+
return (_jsx(Label, { variant: variant, size: "small", "data-sandbox-status": status, children: status }));
|
|
19
|
+
}
|
|
20
|
+
/** A URL, linked, with a copy button. */
|
|
21
|
+
function UrlValue({ url }) {
|
|
22
|
+
const [copied, setCopied] = useState(false);
|
|
23
|
+
return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, minWidth: 0 }, children: [_jsx(Link, { href: url, target: "_blank", rel: "noreferrer", sx: { fontFamily: 'mono', fontSize: 0, wordBreak: 'break-all' }, children: url }), _jsx(IconButton, { icon: copied ? CheckIcon : CopyIcon, "aria-label": "Copy the URL", size: "small", variant: "invisible", onClick: () => {
|
|
24
|
+
void navigator.clipboard?.writeText(url).then(() => {
|
|
25
|
+
setCopied(true);
|
|
26
|
+
setTimeout(() => setCopied(false), 1500);
|
|
27
|
+
});
|
|
28
|
+
} })] }));
|
|
29
|
+
}
|
|
30
|
+
/** A moment, for a reader. */
|
|
31
|
+
function moment(value) {
|
|
32
|
+
if (value === undefined || value === '') {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const at = typeof value === 'number'
|
|
36
|
+
? new Date(value < 1e12 ? value * 1000 : value)
|
|
37
|
+
: new Date(value);
|
|
38
|
+
return Number.isNaN(at.getTime()) ? undefined : at.toLocaleString();
|
|
39
|
+
}
|
|
40
|
+
const PYODIDE_MARKER = '__DL_PYODIDE__';
|
|
41
|
+
const PYODIDE_SNIPPET = `def __dl_pyodide():
|
|
42
|
+
import json, sys
|
|
43
|
+
facts = {'python': sys.version.split()[0], 'packages': []}
|
|
44
|
+
try:
|
|
45
|
+
import pyodide
|
|
46
|
+
facts['version'] = pyodide.__version__
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
try:
|
|
50
|
+
import pyodide_js
|
|
51
|
+
facts['packages'] = sorted(str(k) for k in pyodide_js.loadedPackages.object_keys())
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
print('${PYODIDE_MARKER}' + json.dumps(facts) + '${PYODIDE_MARKER}')
|
|
55
|
+
try:
|
|
56
|
+
__dl_pyodide()
|
|
57
|
+
finally:
|
|
58
|
+
del __dl_pyodide
|
|
59
|
+
`;
|
|
60
|
+
/** Pyodide's facts out of what the snippet printed. */
|
|
61
|
+
export function parsePyodideFacts(stdout) {
|
|
62
|
+
const end = stdout.lastIndexOf(PYODIDE_MARKER);
|
|
63
|
+
const start = end > 0 ? stdout.lastIndexOf(PYODIDE_MARKER, end - 1) : -1;
|
|
64
|
+
if (start < 0) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const facts = JSON.parse(stdout.slice(start + PYODIDE_MARKER.length, end));
|
|
69
|
+
return {
|
|
70
|
+
version: facts.version,
|
|
71
|
+
python: facts.python,
|
|
72
|
+
packages: Array.isArray(facts.packages) ? facts.packages : [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** The kernel connection the dialog lists from: the host's, or one opened here. */
|
|
80
|
+
function useKernelConnection(sandbox) {
|
|
81
|
+
const [opened, setOpened] = useState(null);
|
|
82
|
+
const { connection, url, token, kernelId, kind } = sandbox;
|
|
83
|
+
const live = sandboxIsLive(sandbox);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (connection || !url || !kernelId || kind === 'browser' || !live) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const base = url.replace(/\/+$/, '') + '/';
|
|
89
|
+
const serverSettings = ServerConnection.makeSettings({
|
|
90
|
+
baseUrl: base,
|
|
91
|
+
wsUrl: base.replace(/^http/, 'ws'),
|
|
92
|
+
token: token ?? '',
|
|
93
|
+
appendToken: true,
|
|
94
|
+
});
|
|
95
|
+
const opening = new KernelConnection({
|
|
96
|
+
model: { id: kernelId, name: '' },
|
|
97
|
+
serverSettings,
|
|
98
|
+
handleComms: true,
|
|
99
|
+
});
|
|
100
|
+
setOpened(opening);
|
|
101
|
+
return () => {
|
|
102
|
+
setOpened(null);
|
|
103
|
+
opening.dispose();
|
|
104
|
+
};
|
|
105
|
+
}, [connection, url, token, kernelId, kind, live]);
|
|
106
|
+
return live ? (connection ?? opened) : null;
|
|
107
|
+
}
|
|
108
|
+
export function SandboxDetailsDialog({ sandbox, name, onClose, }) {
|
|
109
|
+
const connection = useKernelConnection(sandbox);
|
|
110
|
+
const [kernel, setKernel] = useState({});
|
|
111
|
+
const [pyodide, setPyodide] = useState();
|
|
112
|
+
const [showPackages, setShowPackages] = useState(false);
|
|
113
|
+
// The kernel, as it says it is: its name, language, status, last activity.
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!connection) {
|
|
116
|
+
setKernel({});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
let cancelled = false;
|
|
120
|
+
const update = (next) => !cancelled && setKernel(current => ({ ...current, ...next }));
|
|
121
|
+
update({
|
|
122
|
+
id: connection.id,
|
|
123
|
+
name: connection.name || undefined,
|
|
124
|
+
status: connection.status,
|
|
125
|
+
});
|
|
126
|
+
connection.info
|
|
127
|
+
.then(info => update({
|
|
128
|
+
language: [info.language_info?.name, info.language_info?.version]
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.join(' '),
|
|
131
|
+
}))
|
|
132
|
+
.catch(() => undefined);
|
|
133
|
+
if (sandbox.kind !== 'browser') {
|
|
134
|
+
KernelAPI.getKernelModel(connection.id, connection.serverSettings)
|
|
135
|
+
.then(model => update({
|
|
136
|
+
name: model?.name || undefined,
|
|
137
|
+
lastActivity: model?.last_activity,
|
|
138
|
+
}))
|
|
139
|
+
.catch(() => undefined);
|
|
140
|
+
}
|
|
141
|
+
const onStatus = (_, status) => update({ status });
|
|
142
|
+
connection.statusChanged.connect(onStatus);
|
|
143
|
+
return () => {
|
|
144
|
+
cancelled = true;
|
|
145
|
+
connection.statusChanged.disconnect(onStatus);
|
|
146
|
+
};
|
|
147
|
+
}, [connection, sandbox.kind]);
|
|
148
|
+
// Pyodide's version and packages, asked once of the browser kernel.
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (sandbox.kind !== 'browser' || !connection) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
let cancelled = false;
|
|
154
|
+
executeSilently(connection, PYODIDE_SNIPPET)
|
|
155
|
+
.then(({ stdout }) => {
|
|
156
|
+
if (!cancelled) {
|
|
157
|
+
setPyodide(parsePyodideFacts(stdout));
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
.catch(() => undefined);
|
|
161
|
+
return () => {
|
|
162
|
+
cancelled = true;
|
|
163
|
+
};
|
|
164
|
+
}, [connection, sandbox.kind]);
|
|
165
|
+
// Without a connection, the agent's server runs the snippet in its sandbox.
|
|
166
|
+
const execute = useMemo(() =>
|
|
167
|
+
// Not before the sandbox is up: the server would start one to answer.
|
|
168
|
+
!connection && sandbox.serverUrl && sandboxIsLive(sandbox)
|
|
169
|
+
? sandboxExecuteOverHttp(sandbox.serverUrl, sandbox.agentId)
|
|
170
|
+
: undefined, [connection, sandbox.serverUrl, sandbox.agentId]);
|
|
171
|
+
const url = displayUrl(sandbox.url ?? sandbox.serverUrl);
|
|
172
|
+
const left = timeLeft(sandbox.expiresAt);
|
|
173
|
+
const facts = [];
|
|
174
|
+
const add = (key, value) => {
|
|
175
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
176
|
+
facts.push([key, value]);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
add('Environment', sandbox.environment);
|
|
180
|
+
add('URL', url ? _jsx(UrlValue, { url: url }) : undefined);
|
|
181
|
+
add('Kernel', kernel.name || kernel.id ? (_jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: [kernel.name, kernel.id?.slice(0, 8)].filter(Boolean).join(' · ') })) : undefined);
|
|
182
|
+
add('Language', kernel.language);
|
|
183
|
+
add('Kernel status', kernel.status ? _jsx(StatusLabel, { status: kernel.status }) : undefined);
|
|
184
|
+
add('Agent', sandbox.kind === 'local' || sandbox.kind === 'runtime'
|
|
185
|
+
? sandbox.agentId
|
|
186
|
+
: undefined);
|
|
187
|
+
add('Runtime', sandbox.runtimeUid);
|
|
188
|
+
add('Time left', left);
|
|
189
|
+
add('Burning rate', sandbox.burningRate !== undefined
|
|
190
|
+
? `${sandbox.burningRate} credits/h`
|
|
191
|
+
: undefined);
|
|
192
|
+
add('Pyodide', pyodide?.version);
|
|
193
|
+
add('Packages', pyodide && pyodide.packages.length > 0 ? (_jsxs(Box, { children: [_jsx(Button, { size: "small", variant: "invisible", onClick: () => setShowPackages(!showPackages), "aria-expanded": showPackages, sx: { px: 1 }, children: `${pyodide.packages.length} loaded` }), showPackages && (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, fontFamily: 'mono', fontSize: 0 }, "data-sandbox-packages": "", children: pyodide.packages.join(', ') }))] })) : undefined);
|
|
194
|
+
add('Started', moment(sandbox.startedAt));
|
|
195
|
+
add('Last activity', moment(sandbox.lastActivity ?? kernel.lastActivity));
|
|
196
|
+
return (_jsx(Dialog, { title: `${name} · Code Sandbox`, onClose: onClose, width: "xlarge", height: "large", children: _jsxs(Box, { "data-assistant-sandbox-details": "", sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [_jsxs(Box, { "data-sandbox-summary": "", sx: {
|
|
197
|
+
p: 3,
|
|
198
|
+
bg: 'canvas.subtle',
|
|
199
|
+
border: '1px solid',
|
|
200
|
+
borderColor: 'border.default',
|
|
201
|
+
borderRadius: 2,
|
|
202
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 2 }, children: [_jsxs(Text, { sx: { fontWeight: 'semibold' }, "data-sandbox-kind": sandbox.kind, children: [SANDBOX_KIND_LABELS[sandbox.kind], sandbox.kind === 'runtime' && sandbox.variant
|
|
203
|
+
? ` (${sandbox.variant})`
|
|
204
|
+
: ''] }), sandbox.status && _jsx(StatusLabel, { status: sandbox.status })] }), _jsx(Box, { as: "dl", sx: {
|
|
205
|
+
display: 'grid',
|
|
206
|
+
gridTemplateColumns: 'max-content minmax(0, 1fr)',
|
|
207
|
+
columnGap: 3,
|
|
208
|
+
rowGap: 1,
|
|
209
|
+
m: 0,
|
|
210
|
+
fontSize: 1,
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
}, children: facts.map(([key, value]) => (_jsxs(Box, { sx: { display: 'contents' }, children: [_jsx(Box, { as: "dt", sx: { color: 'fg.muted' }, children: key }), _jsx(Box, { as: "dd", sx: { m: 0, minWidth: 0 }, "data-sandbox-fact": key, children: value })] }, key))) })] }), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: "Variables" }), sandboxIsLive(sandbox) ? (_jsx(KernelVariables, { connection: connection, execute: execute, maxHeight: "calc(80vh - 360px)" })) : (_jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, "data-sandbox-not-running": "", children: NO_SANDBOX_YET }))] })] }) }));
|
|
213
|
+
}
|
|
214
|
+
export default SandboxDetailsDialog;
|
|
@@ -26,9 +26,10 @@
|
|
|
26
26
|
* @module chat/assistant/SpeechBalloon
|
|
27
27
|
*/
|
|
28
28
|
import type { JSX, ReactNode } from 'react';
|
|
29
|
-
import { type BalloonApproval
|
|
29
|
+
import { type BalloonApproval } from './state';
|
|
30
|
+
import type { DisplayItem } from '../../types/chat';
|
|
30
31
|
import type { DecisionAsker } from './decisions';
|
|
31
|
-
import type
|
|
32
|
+
import { type BalloonDisplay, type BalloonToolLine } from './toolLine';
|
|
32
33
|
/** A suggestion the balloon offers: its label, and the prompt it sends. */
|
|
33
34
|
export type BalloonSuggestion = {
|
|
34
35
|
label: string;
|
|
@@ -77,10 +78,11 @@ export interface SpeechBalloonProps {
|
|
|
77
78
|
/** What goes with the words, under them: a notebook given, read-only. */
|
|
78
79
|
attachment?: ReactNode;
|
|
79
80
|
/**
|
|
80
|
-
* The conversation
|
|
81
|
-
*
|
|
81
|
+
* The conversation (`history`), in the chat's own model — its messages
|
|
82
|
+
* and tool calls, as the chat holds them — listed in the balloon by the
|
|
83
|
+
* chat's own components, in place of the newest line alone.
|
|
82
84
|
*/
|
|
83
|
-
history?: readonly
|
|
85
|
+
history?: readonly DisplayItem[];
|
|
84
86
|
/**
|
|
85
87
|
* What goes with the words is a large visual (a notebook): an *Expand*
|
|
86
88
|
* button under it draws it large, named by `expandTitle`.
|
|
@@ -122,7 +124,7 @@ export declare const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
|
122
124
|
* scrolled there.
|
|
123
125
|
*/
|
|
124
126
|
export declare function BalloonHistoryLarge({ history, tool, attachment, }: {
|
|
125
|
-
history: readonly
|
|
127
|
+
history: readonly DisplayItem[];
|
|
126
128
|
tool?: BalloonToolLine;
|
|
127
129
|
attachment?: ReactNode;
|
|
128
130
|
}): JSX.Element;
|
|
@@ -3,13 +3,13 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
3
3
|
import { Button, IconButton, Text } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
5
|
import { XIcon } from '@primer/octicons-react';
|
|
6
|
-
import { ASSISTANT_WORDS
|
|
6
|
+
import { ASSISTANT_WORDS } from './state';
|
|
7
7
|
import { moreWaiting } from './ConversationBalloon';
|
|
8
8
|
import { DecisionAsk } from './DecisionAsk';
|
|
9
|
-
import {
|
|
9
|
+
import { BalloonChatItems, BalloonToolCall, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
|
|
10
|
+
import { conversationCount, withToolCall, } from './toolLine';
|
|
10
11
|
import { BalloonExpandButton } from './BalloonVisual';
|
|
11
12
|
import { ScreenFullIcon } from '@primer/octicons-react';
|
|
12
|
-
import { TypingDots } from '../indicators/TypingDots';
|
|
13
13
|
/** The suggestions, as chips: each sends its prompt. */
|
|
14
14
|
export function BalloonSuggestions({ suggestions, onSuggestion, }) {
|
|
15
15
|
return (_jsx(Box, { role: "group", "aria-label": "Suggestions", "data-balloon-suggestions": "", sx: { display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2 }, children: suggestions.map(suggestion => (_jsx(Button, { size: "small", "data-balloon-suggestion": suggestion.label, "aria-label": `Ask: ${suggestion.prompt}`, title: suggestion.prompt, onClick: () => onSuggestion(suggestion), sx: { borderRadius: 999 }, children: suggestion.label }, suggestion.label))) }));
|
|
@@ -29,18 +29,9 @@ export const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
|
29
29
|
* scrolled there.
|
|
30
30
|
*/
|
|
31
31
|
export function BalloonHistoryLarge({ history, tool, attachment, }) {
|
|
32
|
-
return (_jsxs(Box, { role: "log", "aria-label": "Conversation", "data-balloon-history-large": "", sx: { display: 'flex', flexDirection: 'column', gap: 2
|
|
33
|
-
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
34
|
-
maxWidth: '80%',
|
|
35
|
-
px: 3,
|
|
36
|
-
py: 2,
|
|
37
|
-
borderRadius: 2,
|
|
38
|
-
bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
39
|
-
whiteSpace: 'pre-wrap',
|
|
40
|
-
overflowWrap: 'anywhere',
|
|
41
|
-
}, children: message.text }, message.id))), tool ? _jsx(BalloonToolLineView, { line: tool }) : null, attachment ? _jsx(Box, { children: attachment }) : null] }));
|
|
32
|
+
return (_jsxs(Box, { role: "log", "aria-label": "Conversation", "data-balloon-history-large": "", sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(BalloonChatItems, { items: withToolCall(history, tool), density: "comfortable" }), attachment ? _jsx(Box, { children: attachment }) : null] }));
|
|
42
33
|
}
|
|
43
|
-
function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, onExpand, }) {
|
|
34
|
+
function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, onExpand, tool, waiting = false, }) {
|
|
44
35
|
const listRef = useRef(null);
|
|
45
36
|
const newest = history[history.length - 1]?.id;
|
|
46
37
|
useEffect(() => {
|
|
@@ -48,7 +39,21 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
|
|
|
48
39
|
if (list) {
|
|
49
40
|
list.scrollTop = list.scrollHeight;
|
|
50
41
|
}
|
|
51
|
-
}, [history.length, newest]);
|
|
42
|
+
}, [history.length, newest, waiting, tool?.id, tool?.phase]);
|
|
43
|
+
// The chat's markdown draws after the list does: kept at the newest as
|
|
44
|
+
// what is drawn grows.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const list = listRef.current;
|
|
47
|
+
const content = list?.firstElementChild;
|
|
48
|
+
if (!list || !content || typeof ResizeObserver === 'undefined') {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const observer = new ResizeObserver(() => {
|
|
52
|
+
list.scrollTop = list.scrollHeight;
|
|
53
|
+
});
|
|
54
|
+
observer.observe(content);
|
|
55
|
+
return () => observer.disconnect();
|
|
56
|
+
}, []);
|
|
52
57
|
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: {
|
|
53
58
|
m: 0,
|
|
54
59
|
p: 0,
|
|
@@ -62,22 +67,13 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
|
|
|
62
67
|
fontWeight: 'semibold',
|
|
63
68
|
color: 'fg.muted',
|
|
64
69
|
'&:hover': { textDecoration: 'underline' },
|
|
65
|
-
}, children: conversationHeaderText(history
|
|
70
|
+
}, children: conversationHeaderText(conversationCount(history)) }), onExpand ? (_jsx(IconButton, { icon: ScreenFullIcon, size: "small", variant: "invisible", "aria-label": "Expand the conversation", "data-balloon-history-expand": "", onClick: onExpand, sx: { position: 'absolute', top: '4px', right: '28px' } })) : null, _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
|
|
66
71
|
maxHeight,
|
|
67
72
|
overflowY: 'auto',
|
|
68
73
|
display: 'flex',
|
|
69
74
|
flexDirection: 'column',
|
|
70
|
-
gap: 1,
|
|
71
75
|
pr: 1,
|
|
72
|
-
}, children:
|
|
73
|
-
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
74
|
-
maxWidth: '88%',
|
|
75
|
-
px: 2,
|
|
76
|
-
py: 1,
|
|
77
|
-
borderRadius: 2,
|
|
78
|
-
bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
79
|
-
overflowWrap: 'anywhere',
|
|
80
|
-
}, children: message.text }, message.id))) })] }));
|
|
76
|
+
}, children: _jsx(BalloonChatItems, { items: withToolCall(history, tool), waiting: waiting }) })] }));
|
|
81
77
|
}
|
|
82
78
|
export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, approval, decide, onDecisionActive, wide = false, onDismissPeek, display = 'history', tool, speaking = false, busy = false, attachment, history, onExpand, expandTitle, suggestions, onSuggestion, waiting = false, fullText, onExpandHistory, }) {
|
|
83
79
|
// *more*: the words whole, when they were cut or overflow their lines.
|
|
@@ -147,9 +143,12 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
147
143
|
font: 'inherit',
|
|
148
144
|
textAlign: 'left',
|
|
149
145
|
cursor: 'pointer',
|
|
150
|
-
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking, waiting: waiting, whole: whole, fullText: wholeText, onOverflow: setOverflowing }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, onExpand: onExpandHistory, maxHeight: attachment
|
|
146
|
+
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking, waiting: waiting, whole: whole, fullText: wholeText, onOverflow: setOverflowing }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, onExpand: onExpandHistory, tool: tool, waiting: waiting, maxHeight: attachment
|
|
151
147
|
? BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT
|
|
152
|
-
: BALLOON_HISTORY_MAX_HEIGHT })) :
|
|
148
|
+
: BALLOON_HISTORY_MAX_HEIGHT })) : tool ? (
|
|
149
|
+
// The tool being called: the chat's card, not inside the peek's
|
|
150
|
+
// button (the card is a button of its own).
|
|
151
|
+
_jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: _jsx(BalloonToolCall, { line: tool }) })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
|
|
153
152
|
flex: 1,
|
|
154
153
|
minWidth: 0,
|
|
155
154
|
m: 0,
|
|
@@ -161,7 +160,7 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
161
160
|
textAlign: 'left',
|
|
162
161
|
cursor: 'pointer',
|
|
163
162
|
'&:hover': { textDecoration: 'underline' },
|
|
164
|
-
}, children:
|
|
163
|
+
}, children: _jsx(Box, { as: "span", role: "status", "aria-live": "polite", children: text }) })), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": "Dismiss", "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), current && !approval && !tool && (cut || overflowing || whole) ? (_jsx(Box, { as: "button", type: "button", "aria-expanded": whole, "data-balloon-whole": whole ? 'less' : 'more', onClick: () => setWhole(!whole), sx: {
|
|
165
164
|
m: 0,
|
|
166
165
|
mt: 1,
|
|
167
166
|
p: 0,
|
|
@@ -172,6 +171,6 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
172
171
|
fontSize: 0,
|
|
173
172
|
cursor: 'pointer',
|
|
174
173
|
'&:hover': { textDecoration: 'underline' },
|
|
175
|
-
}, children: whole ? 'less' : 'more' })) : null, listed &&
|
|
174
|
+
}, children: whole ? 'less' : 'more' })) : null, !current && !listed && waiting ? (_jsx(Box, { "data-balloon-waiting": "", sx: { mt: 1 }, children: _jsx(BalloonChatItems, { items: [], waiting: true }) })) : null, approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })), offered && onSuggestion ? (_jsx(BalloonSuggestions, { suggestions: offered, onSuggestion: onSuggestion })) : null, attachment ? (_jsxs(Box, { "data-balloon-attachment": "", sx: { mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }, children: [attachment, onExpand ? (_jsx(BalloonExpandButton, { title: expandTitle ?? 'the visual', onExpand: onExpand })) : null] })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
|
|
176
175
|
}
|
|
177
176
|
export default SpeechBalloon;
|