@theoremjs/react 0.3.4-next.413.gb7a2b75 → 0.3.4-next.417.g04684a9
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/README.md +2 -0
- package/package.json +3 -3
- package/ui/ChatComposerBar.js +22 -10
- package/ui/ChatTranscript.js +4 -14
- package/ui/ConsoleFrame.js +3 -3
- package/ui/SidePanel.d.ts +5 -0
- package/ui/SidePanel.js +15 -2
- package/ui/TheoremChat.js +5 -2
- package/ui/TraceInspectorPanel.js +39 -7
- package/ui/labels.d.ts +19 -3
- package/ui/labels.js +19 -3
package/README.md
CHANGED
|
@@ -477,3 +477,5 @@ Use `labels` to replace any message, for each locale:
|
|
|
477
477
|
License: MIT.
|
|
478
478
|
|
|
479
479
|
Set `--theorem-pane-background` on a containing element to change the fill of raised panes within it. The default is `--color-background-card`.
|
|
480
|
+
|
|
481
|
+
Set `--theorem-pane-edge` on a containing element to change the gap its raised panes keep from the page's end and bottom edges, for example `0px` where the page around them is already inset. The default is 12px at the end and 12px or 16px at the bottom.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/react",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.417.g04684a9",
|
|
4
4
|
"description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@stylexjs/stylex": "0.19.0",
|
|
61
61
|
"react": "^19.0.0",
|
|
62
62
|
"react-dom": "^19.0.0",
|
|
63
|
-
"@theoremjs/agents": "^0.3.4-next.
|
|
64
|
-
"@theoremjs/web": "^0.3.4-next.
|
|
63
|
+
"@theoremjs/agents": "^0.3.4-next.417.g04684a9",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.417.g04684a9"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/ChatComposerBar.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Badge } from '@astryxdesign/core/Badge';
|
|
3
2
|
import { Button } from '@astryxdesign/core/Button';
|
|
4
3
|
import { ChatComposer, ChatComposerDrawer, ChatComposerInput, ChatSendButton, } from '@astryxdesign/core/Chat';
|
|
5
4
|
import { DropdownMenu } from '@astryxdesign/core/DropdownMenu';
|
|
@@ -11,8 +10,9 @@ import { StackItem } from '@astryxdesign/core/Stack';
|
|
|
11
10
|
import { Text } from '@astryxdesign/core/Text';
|
|
12
11
|
import { Thumbnail } from '@astryxdesign/core/Thumbnail';
|
|
13
12
|
import { Token } from '@astryxdesign/core/Token';
|
|
13
|
+
import { Tooltip as HoverTip } from '@astryxdesign/core/Tooltip';
|
|
14
14
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
15
|
-
import { IconAdjustmentsHorizontal, IconArrowDown, IconArrowUp, IconBrain, IconCornerDownLeft, IconInputAi, IconMicrophone, IconPaperclip, IconPencil, IconPlayerRecordFilled, IconSend, IconStack2, IconX, } from '@tabler/icons-react';
|
|
15
|
+
import { IconAdjustmentsHorizontal, IconArchive, IconArrowBigRightLines, IconArrowDown, IconArrowUp, IconBrain, IconCornerDownLeft, IconInputAi, IconListNumbers, IconMicrophone, IconPaperclip, IconPencil, IconPlayerRecordFilled, IconSend, IconStack2, IconSteeringWheel, IconX, } from '@tabler/icons-react';
|
|
16
16
|
import { attachmentIssueText, labelText } from '@theoremjs/agents';
|
|
17
17
|
import { composerPendingPreview, effortSelectEnabled, interfaceEffortOptions, interfaceModelOptions, modelSelectEnabled, } from '@theoremjs/agents/interface';
|
|
18
18
|
import { composerActionState, composerDrawerSummary, isStashShortcut, resolveComposerHint, STASH_SHORTCUT, } from '@theoremjs/web/client';
|
|
@@ -39,6 +39,13 @@ function useObjectUrls(files, keep) {
|
|
|
39
39
|
}, [urls]);
|
|
40
40
|
return urls;
|
|
41
41
|
}
|
|
42
|
+
/** Each way to send, by the same icon in the send menu and on a waiting message. */
|
|
43
|
+
const SEND_ICON = {
|
|
44
|
+
queue: IconListNumbers,
|
|
45
|
+
steer: IconSteeringWheel,
|
|
46
|
+
send_now: IconArrowBigRightLines,
|
|
47
|
+
stash: IconArchive,
|
|
48
|
+
};
|
|
42
49
|
function PendingRow(props) {
|
|
43
50
|
const { message } = props;
|
|
44
51
|
const t = useLabels();
|
|
@@ -49,7 +56,8 @@ function PendingRow(props) {
|
|
|
49
56
|
const moveUp = t('@theorem.composer.pending.move_up');
|
|
50
57
|
const moveDown = t('@theorem.composer.pending.move_down');
|
|
51
58
|
const remove = t('@theorem.composer.pending.remove');
|
|
52
|
-
|
|
59
|
+
const Kind = SEND_ICON[message.kind];
|
|
60
|
+
return (_jsxs(HStack, { gap: 1, align: "center", width: "100%", children: [_jsx(HoverTip, { content: t(`@theorem.composer.pending.${message.kind}.description`), children: _jsx(Token, { label: t(`@theorem.composer.pending.${message.kind}`), isLabelHidden: true, color: message.kind === 'steer' ? 'blue' : 'default', icon: _jsx(Kind, { size: 14 }) }) }), _jsx(StackItem, { size: "fill", style: { width: 0 }, children: _jsx(Text, { size: "sm", maxLines: 1, hasTruncateTooltip: true, children: composerPendingPreview(message) }) }), _jsxs(HStack, { gap: 0.5, children: [_jsx(IconButton, { label: edit, tooltip: edit, size: "sm", variant: "ghost", icon: icon(IconPencil), onClick: () => props.onRestore(message.id) }), message.kind === 'stash' ? (_jsx(IconButton, { label: queue, tooltip: queue, size: "sm", variant: "ghost", icon: icon(IconCornerDownLeft), onClick: () => props.onQueue(message.id) })) : null, _jsx(IconButton, { label: sendNow, tooltip: sendNow, size: "sm", variant: "ghost", icon: icon(IconSend), onClick: () => props.onSendNow(message.id) }), _jsx(IconButton, { label: moveUp, tooltip: moveUp, size: "sm", variant: "ghost", icon: icon(IconArrowUp), onClick: () => props.onMove(message.id, 'up') }), _jsx(IconButton, { label: moveDown, tooltip: moveDown, size: "sm", variant: "ghost", icon: icon(IconArrowDown), onClick: () => props.onMove(message.id, 'down') }), _jsx(IconButton, { label: remove, tooltip: remove, size: "sm", variant: "ghost", icon: icon(IconX), onClick: () => props.onRemove(message.id) })] })] }));
|
|
53
61
|
}
|
|
54
62
|
/** The model and effort choices this profile offers; efforts only for a chosen model. */
|
|
55
63
|
function generationOptions(iface, selectedModel) {
|
|
@@ -128,13 +136,17 @@ function SendMenu({ actions, onAction, }) {
|
|
|
128
136
|
isIconOnly: true,
|
|
129
137
|
icon: _jsx(IconStack2, { size: 16 }),
|
|
130
138
|
variant: 'ghost',
|
|
131
|
-
}, hasChevron: false, placement: "above", items: actions.map((action) =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
}, hasChevron: false, placement: "above", items: actions.map((action) => {
|
|
140
|
+
const Glyph = SEND_ICON[action];
|
|
141
|
+
return {
|
|
142
|
+
id: action,
|
|
143
|
+
label: t(`@theorem.composer.menu.${action}`),
|
|
144
|
+
description: t(`@theorem.composer.menu.${action}.description`),
|
|
145
|
+
icon: _jsx(Glyph, { size: 16 }),
|
|
146
|
+
...(action === 'stash' ? { endContent: _jsx(Kbd, { keys: STASH_SHORTCUT }) } : {}),
|
|
147
|
+
onClick: () => onAction(action),
|
|
148
|
+
};
|
|
149
|
+
}) }));
|
|
138
150
|
}
|
|
139
151
|
function RecordButton({ recording, onToggle }) {
|
|
140
152
|
const t = useLabels();
|
package/ui/ChatTranscript.js
CHANGED
|
@@ -5,7 +5,6 @@ import { Citation } from '@astryxdesign/core/Citation';
|
|
|
5
5
|
import { ClickableCard } from '@astryxdesign/core/ClickableCard';
|
|
6
6
|
import { Collapsible } from '@astryxdesign/core/Collapsible';
|
|
7
7
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
8
|
-
import { useStreamingText } from '@astryxdesign/core/hooks';
|
|
9
8
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
10
9
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
11
10
|
import { useLocale } from '@astryxdesign/core/i18n';
|
|
@@ -316,22 +315,13 @@ function Sources({ block }) {
|
|
|
316
315
|
return (_jsx(HStack, { gap: 1, wrap: "wrap", "aria-label": t('@theorem.transcript.sources'), children: citations.map((citation, i) => (_jsx(Citation, { source: { title: citation.title, url: citation.href, src: citation.icon }, number: i + 1 }, citation.key))) }));
|
|
317
316
|
}
|
|
318
317
|
/**
|
|
319
|
-
* Streamed text (the reply, or the latest thought).
|
|
320
|
-
* the stream
|
|
321
|
-
*
|
|
322
|
-
* `useStreamingText` has caught up with everything received.
|
|
318
|
+
* Streamed text (the reply, or the latest thought). Astryx reveals it while
|
|
319
|
+
* the stream is open, and shows it whole once it closes: the reveal never
|
|
320
|
+
* holds back text that has arrived.
|
|
323
321
|
*/
|
|
324
322
|
function StreamedMarkdown({ text, streaming, density, components, }) {
|
|
325
|
-
const [revealing, setRevealing] = useState(streaming);
|
|
326
|
-
if (streaming && !revealing)
|
|
327
|
-
setRevealing(true);
|
|
328
323
|
const plugins = useMarkdownPlugins();
|
|
329
|
-
|
|
330
|
-
useEffect(() => {
|
|
331
|
-
if (!streaming && shown.length >= text.length)
|
|
332
|
-
setRevealing(false);
|
|
333
|
-
}, [streaming, shown, text]);
|
|
334
|
-
return (_jsx(Markdown, { isStreaming: revealing, density: density, components: components, plugins: plugins, children: text }));
|
|
324
|
+
return (_jsx(Markdown, { isStreaming: streaming, density: density, components: components, plugins: plugins, children: text }));
|
|
335
325
|
}
|
|
336
326
|
function BodyBlock({ block, streaming }) {
|
|
337
327
|
if (block.kind === 'text')
|
package/ui/ConsoleFrame.js
CHANGED
|
@@ -12,7 +12,7 @@ import { TextArea } from '@astryxdesign/core/TextArea';
|
|
|
12
12
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
13
13
|
import { Children, createContext, use, useMemo, useRef, useSyncExternalStore, } from 'react';
|
|
14
14
|
import { useLabels } from "./labels-provider.js";
|
|
15
|
-
import { PanelBody, PanelScroll, RAISED, SidePanel, SidePanelHeader } from "./SidePanel.js";
|
|
15
|
+
import { PANEL_EDGE, PanelBody, PanelScroll, paneEdge, RAISED, SidePanel, SidePanelHeader, } from "./SidePanel.js";
|
|
16
16
|
import { textRows } from "./StateFields.js";
|
|
17
17
|
import { WithTrace } from "./TraceInspectorPanel.js";
|
|
18
18
|
import { CONSOLE_CARD } from "./trace-patterns.js";
|
|
@@ -38,7 +38,7 @@ function useNarrow() {
|
|
|
38
38
|
* `wide` is a pair stacked on a narrow page, which takes the page's width.
|
|
39
39
|
*/
|
|
40
40
|
function ConsoleSingle({ flush, wide, maxWidth, children, }) {
|
|
41
|
-
return (_jsx(VStack, { height: "100%",
|
|
41
|
+
return (_jsx(VStack, { height: "100%", paddingInlineStart: PANEL_EDGE, style: { ...(flush ? FLUSH_CARD : undefined), paddingInlineEnd: paneEdge(PANEL_EDGE) }, children: _jsx(Card, { variant: "transparent", height: "100%", padding: 0, style: flush ? { overflowY: 'auto' } : { ...RAISED, overflowY: 'auto' }, children: _jsx(Center, { axis: "horizontal", width: "100%", minHeight: "100%", children: _jsx(VStack, { width: "100%", maxWidth: wide ? undefined : maxWidth, gap: 3, padding: 4, children: children }) }) }) }));
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* A console's page, for decisions and hosts alike: the trace's toggle in the
|
|
@@ -46,7 +46,7 @@ function ConsoleSingle({ flush, wide, maxWidth, children, }) {
|
|
|
46
46
|
*/
|
|
47
47
|
export function ConsoleFrame({ inspector, maxWidth, flush = false, columns = false, requestLabel, className, style, children, }) {
|
|
48
48
|
const layoutRef = useRef(null);
|
|
49
|
-
return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", className: className, style: style, header: inspector.toggle ? _jsx(SidePanelHeader, { children: inspector.toggle }) : undefined, content: _jsx(LayoutContent, { padding: 0, isScrollable: false, children: _jsx(ConsoleBody, { layoutRef: layoutRef, flush: flush, columns: columns, maxWidth: maxWidth, requestLabel: requestLabel, children: children }) }) }) }));
|
|
49
|
+
return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", className: className, style: style, header: inspector.toggle ? _jsx(SidePanelHeader, { children: inspector.toggle }) : undefined, content: _jsx(LayoutContent, { padding: 0, isScrollable: false, children: _jsx(VStack, { height: "100%", paddingBlockStart: PANEL_EDGE, children: _jsx(ConsoleBody, { layoutRef: layoutRef, flush: flush, columns: columns, maxWidth: maxWidth, requestLabel: requestLabel, children: children }) }) }) }) }));
|
|
50
50
|
}
|
|
51
51
|
/** The console's panels: a request and response side by side when there is room, else one column. */
|
|
52
52
|
function ConsoleBody({ layoutRef, flush, columns, maxWidth, requestLabel, children, }) {
|
package/ui/SidePanel.d.ts
CHANGED
|
@@ -59,6 +59,11 @@ export declare const RAISED: {
|
|
|
59
59
|
/** A docked panel's inset: 12px off the outer edge, 16px off the bottom (the page inset). */
|
|
60
60
|
export declare const PANEL_EDGE = 3;
|
|
61
61
|
export declare const PANEL_BOTTOM = 4;
|
|
62
|
+
/**
|
|
63
|
+
* A raised pane's gap from the page's end or bottom edge: `--theorem-pane-edge` where a host
|
|
64
|
+
* sets it (0 where the host already insets its page), else `spacing`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function paneEdge(spacing: typeof PANEL_EDGE | typeof PANEL_BOTTOM): string;
|
|
62
67
|
/** The raised card every docked panel and the content beside one share. */
|
|
63
68
|
export declare function PanelBody({ padding, inset, className, children, }: {
|
|
64
69
|
padding?: 0;
|
package/ui/SidePanel.js
CHANGED
|
@@ -57,9 +57,19 @@ export const RAISED = {
|
|
|
57
57
|
/** A docked panel's inset: 12px off the outer edge, 16px off the bottom (the page inset). */
|
|
58
58
|
export const PANEL_EDGE = 3;
|
|
59
59
|
export const PANEL_BOTTOM = 4;
|
|
60
|
+
/**
|
|
61
|
+
* A raised pane's gap from the page's end or bottom edge: `--theorem-pane-edge` where a host
|
|
62
|
+
* sets it (0 where the host already insets its page), else `spacing`.
|
|
63
|
+
*/
|
|
64
|
+
export function paneEdge(spacing) {
|
|
65
|
+
return `var(--theorem-pane-edge, var(--spacing-${String(spacing)}))`;
|
|
66
|
+
}
|
|
60
67
|
/** The raised card every docked panel and the content beside one share. */
|
|
61
68
|
export function PanelBody({ padding, inset = 'end', className, children, }) {
|
|
62
|
-
return (_jsx(VStack, { height: "100%", className: className, paddingInlineStart: inset === 'end' ? undefined : PANEL_EDGE,
|
|
69
|
+
return (_jsx(VStack, { height: "100%", className: className, paddingInlineStart: inset === 'end' ? undefined : PANEL_EDGE, style: {
|
|
70
|
+
paddingInlineEnd: inset === 'start' ? undefined : paneEdge(PANEL_EDGE),
|
|
71
|
+
paddingBlockEnd: paneEdge(PANEL_BOTTOM),
|
|
72
|
+
}, children: _jsx(Card, { variant: "transparent", height: "100%", padding: padding, style: RAISED, children: children }) }));
|
|
63
73
|
}
|
|
64
74
|
/** The scroll inside a panel card. The same area the trace uses, so a short body sizes to its rows. */
|
|
65
75
|
export function PanelScroll({ label, padding, children, }) {
|
|
@@ -95,7 +105,10 @@ export function useWidth() {
|
|
|
95
105
|
*/
|
|
96
106
|
export function RaisedPane({ id, header, children, }) {
|
|
97
107
|
const { ref, width } = useWidth();
|
|
98
|
-
return (_jsx(Layout, { height: "fill", style: { position: 'relative' }, header: header, content: _jsx(LayoutContent, { padding: 0, isScrollable: false, children: _jsx(VStack, { height: "100%",
|
|
108
|
+
return (_jsx(Layout, { height: "fill", style: { position: 'relative' }, header: header, content: _jsx(LayoutContent, { padding: 0, isScrollable: false, children: _jsx(VStack, { height: "100%", paddingInlineStart: PANEL_EDGE, paddingBlockStart: PANEL_EDGE, style: {
|
|
109
|
+
paddingInlineEnd: paneEdge(PANEL_EDGE),
|
|
110
|
+
paddingBlockEnd: paneEdge(PANEL_EDGE),
|
|
111
|
+
}, children: _jsx(Card, { variant: "transparent", height: "100%", padding: 0, style: RAISED, children: _jsx("div", { id: id, ref: ref, style: { height: '100%' }, children: children(isSidePanelWide(width)) }) }) }) }) }));
|
|
99
112
|
}
|
|
100
113
|
/** A raised pane's content. Narrow, the detail takes the overview's place; wide, it sits beside it. */
|
|
101
114
|
export function PaneLayout({ isWide, label, detailLabel, lead, overview, detail, }) {
|
package/ui/TheoremChat.js
CHANGED
|
@@ -83,8 +83,9 @@ function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth
|
|
|
83
83
|
// why: the composer says when a turn failed. Without one, the chat says it where the composer was.
|
|
84
84
|
const composer = hasComposer ? (_jsx(ChatComposerForChat, { chat: chat, iface: iface, placeholder: placeholder, inputRef: inputRef })) : (chat.failure && _jsx(Banner, { status: "error", role: "alert", title: chat.failure.error }));
|
|
85
85
|
const docked = { composer, composerRef, maxWidth };
|
|
86
|
-
return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { height: "fill", className: className, style: { position: 'relative', ...style }, header: header, content: _jsx(LayoutContent, { padding: 0, children: landing ? (_jsx(ChatLanding, { ...docked, handle: handle, emptyState: emptyState })) : (_jsx(ChatThread, { ...docked, chat: chat, iface: iface, blocks: blocks, handle: handle, density: density, scrollRef: scrollRef, usage: usage })) }) }) }));
|
|
86
|
+
return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { height: "fill", className: className, style: { position: 'relative', ...style }, header: header, content: _jsx(LayoutContent, { padding: 0, isScrollable: landing, children: landing ? (_jsx(ChatLanding, { ...docked, handle: handle, emptyState: emptyState })) : (_jsx(ChatThread, { ...docked, chat: chat, iface: iface, blocks: blocks, handle: handle, density: density, scrollRef: scrollRef, usage: usage })) }) }) }));
|
|
87
87
|
}
|
|
88
|
+
const CHAT_SCROLL = { overscrollBehavior: 'none' };
|
|
88
89
|
/**
|
|
89
90
|
* The chat before its first message: the greeting, and the composer under it.
|
|
90
91
|
* why: paddingInline={3} matches the dock's inset so the composer keeps its width when it moves.
|
|
@@ -99,7 +100,9 @@ function ChatLanding({ composer, composerRef, maxWidth, handle, emptyState, }) {
|
|
|
99
100
|
* bottom margin.
|
|
100
101
|
*/
|
|
101
102
|
function ChatThread({ composer, composerRef, maxWidth, chat, iface, blocks, handle, density, scrollRef, usage, }) {
|
|
102
|
-
return (_jsx(VStack, { height: "100%", children: _jsx(ChatLayout, { density: density, scrollRef: scrollRef,
|
|
103
|
+
return (_jsx(VStack, { height: "100%", children: _jsx(ChatLayout, { density: density, scrollRef: scrollRef,
|
|
104
|
+
// why: Scrolling past either end does not bounce the transcript and its docked composer.
|
|
105
|
+
style: CHAT_SCROLL, composer: composer && (_jsx(VStack, { paddingBlockEnd: 3, children: _jsx(ChatColumn, { ref: composerRef, maxWidth: maxWidth, children: composer }) })), children: _jsx(ChatColumn, { maxWidth: maxWidth, children: _jsx(ChatTranscript, { blocks: blocks, handle: handle, streaming: chat.streaming, delivery: chat.delivery, answering: chat.answering, usage: usage, labels: iface.labels, imageOutput: iface.type === 'image'
|
|
103
106
|
? { ratio: parseAspectRatio(iface.image.aspectRatio) }
|
|
104
107
|
: undefined, onToolDecision: (index, action) => {
|
|
105
108
|
void chat.handleToolDecision(index, action);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
3
|
-
import {
|
|
3
|
+
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
4
|
+
import { IconActivity, IconDownload } from '@tabler/icons-react';
|
|
4
5
|
import { cloneElement, createContext, isValidElement, lazy, Suspense, use, useCallback, useId, useRef, useState, } from 'react';
|
|
6
|
+
import { useTraceRecords } from "../hooks/use-trace-records.js";
|
|
5
7
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
6
8
|
import { InPlace, isSidePanelWide, RaisedPane, SidePanel, SidePanelHeader, SidePanelToggle, useSidePanel, useWidth, } from "./SidePanel.js";
|
|
7
9
|
// why: The inspector and its charts are fetched when the trace first opens, not with the chat.
|
|
@@ -43,16 +45,46 @@ export function useTraceInspector(iface, traces, open) {
|
|
|
43
45
|
const view = isOpen ? openTrace(placement === 'panel', id, traces, toggle) : null;
|
|
44
46
|
return { toggle, view, labels, offered: true };
|
|
45
47
|
}
|
|
46
|
-
/** The open trace: filling the host's docked panel, or as its own panel
|
|
48
|
+
/** The open trace: filling the host's docked panel, or as its own panel; Download, then the toggle, on top. */
|
|
47
49
|
function openTrace(docked, id, traces, toggle) {
|
|
50
|
+
const header = (_jsxs(SidePanelHeader, { floating: true, children: [_jsx(TraceDownload, { traces: traces }), toggle] }));
|
|
48
51
|
if (docked)
|
|
49
|
-
return _jsx(TraceFill, { id: id, traces: traces });
|
|
50
|
-
return
|
|
52
|
+
return _jsx(TraceFill, { id: id, header: header, traces: traces });
|
|
53
|
+
return _jsx(TraceView, { id: id, header: header, traces: traces });
|
|
51
54
|
}
|
|
52
55
|
/** The trace body, filling the docked panel. The panel supplies the raised card. */
|
|
53
|
-
function TraceFill({ id, traces }) {
|
|
56
|
+
function TraceFill({ id, header, traces, }) {
|
|
54
57
|
const { ref, width } = useWidth();
|
|
55
|
-
return (
|
|
58
|
+
return (_jsxs("div", { id: id, ref: ref, style: { height: '100%', position: 'relative' }, children: [header, _jsx(Suspense, { fallback: null, children: _jsx(TraceFeedBody, { traces: traces, isWide: isSidePanelWide(width) }) })] }));
|
|
59
|
+
}
|
|
60
|
+
/** The agent the records ran, which names their file. */
|
|
61
|
+
function tracedAgent(records) {
|
|
62
|
+
for (const record of records) {
|
|
63
|
+
for (const span of record.spans) {
|
|
64
|
+
const name = span.attributes['gen_ai.agent.name'];
|
|
65
|
+
if (typeof name === 'string' && name)
|
|
66
|
+
return name;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return 'trace';
|
|
70
|
+
}
|
|
71
|
+
/** Saves the records on screen as `<agent>-<time>.trace.json`, the file the studio imports. */
|
|
72
|
+
function TraceDownload({ traces }) {
|
|
73
|
+
const t = useLabels();
|
|
74
|
+
const records = useTraceRecords(traces);
|
|
75
|
+
if (records.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
const label = t('@theorem.panel.trace.download');
|
|
78
|
+
return (_jsx(IconButton, { label: label, variant: "ghost", tooltip: label, icon: _jsx(Icon, { icon: IconDownload, size: "sm" }), onClick: () => {
|
|
79
|
+
const stamp = new Date().toISOString().replaceAll(':', '-').slice(0, 19);
|
|
80
|
+
const file = new Blob([JSON.stringify(records, null, 2)], { type: 'application/json' });
|
|
81
|
+
const url = URL.createObjectURL(file);
|
|
82
|
+
const link = document.createElement('a');
|
|
83
|
+
link.href = url;
|
|
84
|
+
link.download = `${tracedAgent(records)}-${stamp}.trace.json`;
|
|
85
|
+
link.click();
|
|
86
|
+
URL.revokeObjectURL(url);
|
|
87
|
+
} }));
|
|
56
88
|
}
|
|
57
89
|
/** The surface, with the trace docked at its inline end. A host-driven trace stays in place. */
|
|
58
90
|
function TraceDock({ inspector, children }) {
|
package/ui/labels.d.ts
CHANGED
|
@@ -138,15 +138,27 @@ export declare const THEOREM_UI_CATALOG: {
|
|
|
138
138
|
};
|
|
139
139
|
readonly '@theorem.composer.pending.steer': {
|
|
140
140
|
readonly defaultMessage: "Steering";
|
|
141
|
-
readonly description: "
|
|
141
|
+
readonly description: "Name of a waiting steering message, read by screen readers on its icon.";
|
|
142
|
+
};
|
|
143
|
+
readonly '@theorem.composer.pending.steer.description': {
|
|
144
|
+
readonly defaultMessage: "Steering: the agent reads it at its next step.";
|
|
145
|
+
readonly description: "Tooltip on a waiting steering message.";
|
|
142
146
|
};
|
|
143
147
|
readonly '@theorem.composer.pending.queue': {
|
|
144
148
|
readonly defaultMessage: "Queued";
|
|
145
|
-
readonly description: "
|
|
149
|
+
readonly description: "Name of a waiting queued message, read by screen readers on its icon.";
|
|
150
|
+
};
|
|
151
|
+
readonly '@theorem.composer.pending.queue.description': {
|
|
152
|
+
readonly defaultMessage: "Queued: it sends once this reply finishes.";
|
|
153
|
+
readonly description: "Tooltip on a waiting queued message.";
|
|
146
154
|
};
|
|
147
155
|
readonly '@theorem.composer.pending.stash': {
|
|
148
156
|
readonly defaultMessage: "Stashed";
|
|
149
|
-
readonly description: "
|
|
157
|
+
readonly description: "Name of a waiting stashed message, read by screen readers on its icon.";
|
|
158
|
+
};
|
|
159
|
+
readonly '@theorem.composer.pending.stash.description': {
|
|
160
|
+
readonly defaultMessage: "Stashed: it waits here until you send it.";
|
|
161
|
+
readonly description: "Tooltip on a waiting stashed message.";
|
|
150
162
|
};
|
|
151
163
|
readonly '@theorem.composer.pending.edit': {
|
|
152
164
|
readonly defaultMessage: "Edit";
|
|
@@ -951,6 +963,10 @@ export declare const THEOREM_UI_CATALOG: {
|
|
|
951
963
|
readonly defaultMessage: "No spans match";
|
|
952
964
|
readonly description: "Trace panel, when the search matches nothing.";
|
|
953
965
|
};
|
|
966
|
+
readonly '@theorem.panel.trace.download': {
|
|
967
|
+
readonly defaultMessage: "Download trace";
|
|
968
|
+
readonly description: "Saves the trace's records as a file.";
|
|
969
|
+
};
|
|
954
970
|
readonly '@theorem.panel.trace.back': {
|
|
955
971
|
readonly defaultMessage: "Back";
|
|
956
972
|
readonly description: "Leaves an open span for its turn.";
|
package/ui/labels.js
CHANGED
|
@@ -125,15 +125,27 @@ export const THEOREM_UI_CATALOG = {
|
|
|
125
125
|
},
|
|
126
126
|
'@theorem.composer.pending.steer': {
|
|
127
127
|
defaultMessage: 'Steering',
|
|
128
|
-
description: '
|
|
128
|
+
description: 'Name of a waiting steering message, read by screen readers on its icon.',
|
|
129
|
+
},
|
|
130
|
+
'@theorem.composer.pending.steer.description': {
|
|
131
|
+
defaultMessage: 'Steering: the agent reads it at its next step.',
|
|
132
|
+
description: 'Tooltip on a waiting steering message.',
|
|
129
133
|
},
|
|
130
134
|
'@theorem.composer.pending.queue': {
|
|
131
135
|
defaultMessage: 'Queued',
|
|
132
|
-
description: '
|
|
136
|
+
description: 'Name of a waiting queued message, read by screen readers on its icon.',
|
|
137
|
+
},
|
|
138
|
+
'@theorem.composer.pending.queue.description': {
|
|
139
|
+
defaultMessage: 'Queued: it sends once this reply finishes.',
|
|
140
|
+
description: 'Tooltip on a waiting queued message.',
|
|
133
141
|
},
|
|
134
142
|
'@theorem.composer.pending.stash': {
|
|
135
143
|
defaultMessage: 'Stashed',
|
|
136
|
-
description: '
|
|
144
|
+
description: 'Name of a waiting stashed message, read by screen readers on its icon.',
|
|
145
|
+
},
|
|
146
|
+
'@theorem.composer.pending.stash.description': {
|
|
147
|
+
defaultMessage: 'Stashed: it waits here until you send it.',
|
|
148
|
+
description: 'Tooltip on a waiting stashed message.',
|
|
137
149
|
},
|
|
138
150
|
'@theorem.composer.pending.edit': {
|
|
139
151
|
defaultMessage: 'Edit',
|
|
@@ -908,6 +920,10 @@ export const THEOREM_UI_CATALOG = {
|
|
|
908
920
|
defaultMessage: 'No spans match',
|
|
909
921
|
description: 'Trace panel, when the search matches nothing.',
|
|
910
922
|
},
|
|
923
|
+
'@theorem.panel.trace.download': {
|
|
924
|
+
defaultMessage: 'Download trace',
|
|
925
|
+
description: "Saves the trace's records as a file.",
|
|
926
|
+
},
|
|
911
927
|
'@theorem.panel.trace.back': {
|
|
912
928
|
defaultMessage: 'Back',
|
|
913
929
|
description: 'Leaves an open span for its turn.',
|