@theoremjs/react 0.3.4-next.847.g6513d7e → 0.3.4-next.851.g9ebce61
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 +1 -1
- package/client/live/apply-live-turn-tool-event.d.ts +8 -1
- package/client/live/apply-live-turn-tool-event.js +7 -1
- package/components/live/use-live-call.d.ts +1 -1
- package/components/live/use-live-call.js +1 -2
- package/components/live/use-live-runner-ui.d.ts +4 -4
- package/components/live/use-live-runner-ui.js +4 -2
- package/components/live/use-live-session-client.d.ts +0 -1
- package/package.json +3 -3
- package/ui/LiveCallTile.d.ts +4 -2
- package/ui/LiveCallTile.js +13 -6
- package/ui/LiveRunner.js +2 -24
- package/ui/LiveToolGateDialog.d.ts +9 -0
- package/ui/LiveToolGateDialog.js +32 -0
- package/ui/ToolGateCard.js +3 -1
- package/ui/labels.d.ts +7 -3
- package/ui/labels.js +10 -4
package/README.md
CHANGED
|
@@ -367,7 +367,7 @@ The call lasts as long as the component that calls `useLiveCall`. Call it above
|
|
|
367
367
|
|
|
368
368
|
Give `LiveRunner` an `onMinimize` to show a minimize control in the call. Theorem calls it and does not navigate: your code decides where to go, and the call goes on.
|
|
369
369
|
|
|
370
|
-
`LiveCallTile` draws the same call small: the camera preview, or the ink waveform when the camera is off, then the call time, mute, end, and expand. It fills the box you give it, so your code places it and sizes it. A press on the tile or its expand control calls `onExpand`.
|
|
370
|
+
`LiveCallTile` draws the same call small: the camera preview, or the ink waveform when the camera is off, then the call time, mute, end, and expand. While a tool runs, its name shows in place of the time, and a tool gate opens the same dialog `LiveRunner` shows, so the person can answer it on any page. It fills the box you give it, so your code places it and sizes it. A press on the tile or its expand control calls `onExpand`.
|
|
371
371
|
|
|
372
372
|
```tsx
|
|
373
373
|
import { LiveCallTile } from '@theoremjs/react/live';
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { TurnEvent } from '@theoremjs/agents';
|
|
2
|
+
/** The tool a live call is running: its name, and its activity label once the call runs. */
|
|
3
|
+
export type LiveActiveTool = {
|
|
4
|
+
name: string;
|
|
5
|
+
activity?: string;
|
|
6
|
+
};
|
|
7
|
+
/** The tool to show after `next`: only the running phase carries the activity, so a later phase of that tool keeps it. */
|
|
8
|
+
export declare function nextActiveTool(prev: LiveActiveTool | null, next: LiveActiveTool): LiveActiveTool;
|
|
2
9
|
type LiveToolEventArgs = {
|
|
3
10
|
/** The call whose gate the user is looking at, if one is open. */
|
|
4
11
|
gateCallId: string | undefined;
|
|
@@ -7,7 +14,7 @@ type LiveToolEventArgs = {
|
|
|
7
14
|
clearInterim: () => void;
|
|
8
15
|
clearActiveTool: () => void;
|
|
9
16
|
reportFailure: (err: unknown) => void;
|
|
10
|
-
setActiveTool: (
|
|
17
|
+
setActiveTool: (tool: LiveActiveTool) => void;
|
|
11
18
|
};
|
|
12
19
|
export type { LiveToolEventArgs };
|
|
13
20
|
/** Apply a live turn event to active-tool / caption / error state. */
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { TheoremStreamError } from '@theoremjs/web/client';
|
|
2
|
+
/** The tool to show after `next`: only the running phase carries the activity, so a later phase of that tool keeps it. */
|
|
3
|
+
export function nextActiveTool(prev, next) {
|
|
4
|
+
return next.activity === undefined && prev?.name === next.name ? prev : next;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* A failed tool step as the host reported it: the user reads its wording (else
|
|
4
8
|
* the labels' for its kind); the model's `message` stays builder detail.
|
|
@@ -25,7 +29,9 @@ function applyToolPhase(tool, args) {
|
|
|
25
29
|
clearToolUnlessGated(args);
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
28
|
-
args.setActiveTool(tool.
|
|
32
|
+
args.setActiveTool(tool.phase === 'running' && tool.activity
|
|
33
|
+
? { name: tool.name, activity: tool.activity }
|
|
34
|
+
: { name: tool.name });
|
|
29
35
|
}
|
|
30
36
|
/** Apply a live turn event to active-tool / caption / error state. */
|
|
31
37
|
export function applyLiveTurnToolEvent(event, args) {
|
|
@@ -35,7 +35,7 @@ export declare function useLiveCall({ iface, connection, ...options }: LiveCallI
|
|
|
35
35
|
isVideoOn: boolean;
|
|
36
36
|
sessionActive: boolean;
|
|
37
37
|
liveState: import("@theoremjs/web/client").LiveState;
|
|
38
|
-
activeTool:
|
|
38
|
+
activeTool: import("../../client/live/apply-live-turn-tool-event.ts").LiveActiveTool | null;
|
|
39
39
|
status: import("@theoremjs/web/client").LiveSessionStatus;
|
|
40
40
|
textAvailable: boolean;
|
|
41
41
|
textDraft: string;
|
|
@@ -77,7 +77,7 @@ export function useLiveCall({ iface, connection, ...options }) {
|
|
|
77
77
|
const state = useMemo(() => liveState({
|
|
78
78
|
status: ui.status,
|
|
79
79
|
connectPhase: ui.connectPhase,
|
|
80
|
-
toolName: ui.activeTool,
|
|
80
|
+
toolName: ui.activeTool?.name ?? null,
|
|
81
81
|
isMuted: ui.isMuted,
|
|
82
82
|
voiceEnabled: voiceAvailable,
|
|
83
83
|
}), [ui.activeTool, ui.connectPhase, ui.isMuted, ui.status, voiceAvailable]);
|
|
@@ -108,7 +108,6 @@ export function useLiveCall({ iface, connection, ...options }) {
|
|
|
108
108
|
setCaptions: ui.setCaptions,
|
|
109
109
|
setInputLevel: ui.setInputLevel,
|
|
110
110
|
setOutputLevel: ui.setOutputLevel,
|
|
111
|
-
setActiveTool: ui.setActiveTool,
|
|
112
111
|
setSessionPermissions: ui.setSessionPermissions,
|
|
113
112
|
setVideoPreview: ui.setVideoPreview,
|
|
114
113
|
setVideoFacingMode: ui.setVideoFacingMode,
|
|
@@ -2,7 +2,7 @@ import { type LabelOverrides, type SessionEventOf } from '@theoremjs/agents';
|
|
|
2
2
|
import type { LiveConnectPhase, LiveGateAnswer, LiveSessionStatus, LiveToolGatePrompt, ToolGateResolution } from '@theoremjs/web/client';
|
|
3
3
|
import { type ClientFailure } from '@theoremjs/web/client';
|
|
4
4
|
import { type Dispatch, type SetStateAction } from 'react';
|
|
5
|
-
import { applyLiveTurnToolEvent } from '../../client/live/apply-live-turn-tool-event.ts';
|
|
5
|
+
import { applyLiveTurnToolEvent, type LiveActiveTool } from '../../client/live/apply-live-turn-tool-event.ts';
|
|
6
6
|
import { type LiveCaptionState } from '../../client/live/live-captions.ts';
|
|
7
7
|
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
8
8
|
/** UI + media state bag for the live runner. `labels` is the interface's: the profile's wording. */
|
|
@@ -21,8 +21,8 @@ export declare function useLiveRunnerUiState(labels: LabelOverrides): {
|
|
|
21
21
|
setIsMuted: Dispatch<SetStateAction<boolean>>;
|
|
22
22
|
isVideoOn: boolean;
|
|
23
23
|
setIsVideoOn: Dispatch<SetStateAction<boolean>>;
|
|
24
|
-
activeTool:
|
|
25
|
-
setActiveTool: Dispatch<SetStateAction<
|
|
24
|
+
activeTool: LiveActiveTool | null;
|
|
25
|
+
setActiveTool: Dispatch<SetStateAction<LiveActiveTool | null>>;
|
|
26
26
|
captions: LiveCaptionState;
|
|
27
27
|
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
28
28
|
failure: ClientFailure | null;
|
|
@@ -53,7 +53,7 @@ export declare function useLiveRunnerUiState(labels: LabelOverrides): {
|
|
|
53
53
|
/** Gate prompt + live turn-tool event wiring. */
|
|
54
54
|
export declare function useLiveRunnerGate(args: {
|
|
55
55
|
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
56
|
-
setActiveTool: Dispatch<SetStateAction<
|
|
56
|
+
setActiveTool: Dispatch<SetStateAction<LiveActiveTool | null>>;
|
|
57
57
|
reportFailure: (err: unknown) => void;
|
|
58
58
|
}): {
|
|
59
59
|
gatePrompt: LiveToolGatePrompt | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TheoremError } from '@theoremjs/agents';
|
|
2
2
|
import { clientFailure } from '@theoremjs/web/client';
|
|
3
3
|
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
4
|
-
import { applyLiveTurnToolEvent } from "../../client/live/apply-live-turn-tool-event.js";
|
|
4
|
+
import { applyLiveTurnToolEvent, nextActiveTool, } from "../../client/live/apply-live-turn-tool-event.js";
|
|
5
5
|
import { applyLiveTurnEvent, clearLiveCaptionInterim, emptyLiveCaptionState, } from "../../client/live/live-captions.js";
|
|
6
6
|
/** UI + media state bag for the live runner. `labels` is the interface's: the profile's wording. */
|
|
7
7
|
export function useLiveRunnerUiState(labels) {
|
|
@@ -136,7 +136,9 @@ export function useLiveRunnerGate(args) {
|
|
|
136
136
|
args.setActiveTool(null);
|
|
137
137
|
},
|
|
138
138
|
reportFailure: args.reportFailure,
|
|
139
|
-
setActiveTool:
|
|
139
|
+
setActiveTool: (tool) => {
|
|
140
|
+
args.setActiveTool((prev) => nextActiveTool(prev, tool));
|
|
141
|
+
},
|
|
140
142
|
});
|
|
141
143
|
}, [args, answerGate]);
|
|
142
144
|
const waitForGateDecision = useCallback((prompt) => {
|
|
@@ -24,7 +24,6 @@ export type LiveClientBindings = LiveCallOptions & {
|
|
|
24
24
|
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
25
25
|
setInputLevel: Dispatch<SetStateAction<number>>;
|
|
26
26
|
setOutputLevel: Dispatch<SetStateAction<number>>;
|
|
27
|
-
setActiveTool: Dispatch<SetStateAction<string | null>>;
|
|
28
27
|
setSessionPermissions: Dispatch<SetStateAction<string[]>>;
|
|
29
28
|
setVideoPreview: Dispatch<SetStateAction<HTMLVideoElement | null>>;
|
|
30
29
|
setVideoFacingMode: Dispatch<SetStateAction<LiveFacingMode>>;
|
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.851.g9ebce61",
|
|
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.851.g9ebce61",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.851.g9ebce61"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/LiveCallTile.d.ts
CHANGED
|
@@ -18,7 +18,9 @@ export type LiveCallTileProps = {
|
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* A small view of a running call: the camera preview, or the ink waveform
|
|
21
|
-
* when the camera is off, then the call time, mute, end, and expand.
|
|
22
|
-
*
|
|
21
|
+
* when the camera is off, then the call time, mute, end, and expand. While a
|
|
22
|
+
* tool runs, its name takes the time's place, and a tool gate opens the same
|
|
23
|
+
* dialog `LiveRunner` shows. It fills the box the host gives it; the host
|
|
24
|
+
* places it.
|
|
23
25
|
*/
|
|
24
26
|
export declare function LiveCallTile({ theme, mode, labels, ...props }: LiveCallTileProps): import("react").JSX.Element;
|
package/ui/LiveCallTile.js
CHANGED
|
@@ -11,33 +11,40 @@ import { VStack } from '@astryxdesign/core/VStack';
|
|
|
11
11
|
import { IconArrowsDiagonal, IconMicrophone, IconMicrophoneOff, IconPhoneOff, } from '@tabler/icons-react';
|
|
12
12
|
import { InkWaveform } from "../components/InkWaveform.js";
|
|
13
13
|
import { useSecondTicker } from "../hooks/use-second-ticker.js";
|
|
14
|
+
import { LiveToolGateDialog } from "./LiveToolGateDialog.js";
|
|
14
15
|
import { LiveVideoPreview } from "./LiveVideoPreview.js";
|
|
15
16
|
import { liveDuration, liveStateLabel } from "./labels.js";
|
|
16
17
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
17
18
|
import { TheoremThemeProvider } from "./theme.js";
|
|
18
19
|
/**
|
|
19
20
|
* A small view of a running call: the camera preview, or the ink waveform
|
|
20
|
-
* when the camera is off, then the call time, mute, end, and expand.
|
|
21
|
-
*
|
|
21
|
+
* when the camera is off, then the call time, mute, end, and expand. While a
|
|
22
|
+
* tool runs, its name takes the time's place, and a tool gate opens the same
|
|
23
|
+
* dialog `LiveRunner` shows. It fills the box the host gives it; the host
|
|
24
|
+
* places it.
|
|
22
25
|
*/
|
|
23
26
|
export function LiveCallTile({ theme, mode, labels, ...props }) {
|
|
24
27
|
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(LiveCallTileBody, { ...props }) }) }));
|
|
25
28
|
}
|
|
29
|
+
/** why: The tile floats on its shadow; Astryx's default card has no borderless variant with its surface. */
|
|
30
|
+
const TILE_BORDERLESS = { borderWidth: 0 };
|
|
31
|
+
/** The bars in the icon colour, as on `LiveRunner`'s stage. */
|
|
32
|
+
const WAVE_COLOR = { color: 'var(--color-icon-primary)' };
|
|
26
33
|
function LiveCallTileBody({ call, onExpand, }) {
|
|
27
34
|
const t = useLabels();
|
|
28
|
-
return (_jsx(ClickableCard, { label: t('@theorem.live.expand'), onClick: onExpand, padding: 0, elevation: "high", width: "100%", children: _jsxs(VStack, { children: [_jsx(TileMedia, { call: call }), _jsxs(HStack, { gap: 1, vAlign: "center", padding: 2, children: [_jsx(StackItem, { size: "fill", children: _jsx(TileTime, { call: call }) }), _jsx(TileControls, { call: call, onExpand: onExpand })] })] }) }));
|
|
35
|
+
return (_jsxs(_Fragment, { children: [_jsx(ClickableCard, { label: t('@theorem.live.expand'), onClick: onExpand, padding: 0, elevation: "high", width: "100%", style: TILE_BORDERLESS, children: _jsxs(VStack, { children: [_jsx(TileMedia, { call: call }), _jsxs(HStack, { gap: 1, vAlign: "center", padding: 2, children: [_jsx(StackItem, { size: "fill", children: _jsx(TileTime, { call: call }) }), _jsx(TileControls, { call: call, onExpand: onExpand })] })] }) }), _jsx(LiveToolGateDialog, { call: call })] }));
|
|
29
36
|
}
|
|
30
37
|
/** The camera preview while the camera is on; otherwise the ink waveform. */
|
|
31
38
|
function TileMedia({ call }) {
|
|
32
39
|
if (call.isVideoOn && call.videoPreview) {
|
|
33
40
|
return _jsx(LiveVideoPreview, { video: call.videoPreview, facingMode: call.videoFacingMode });
|
|
34
41
|
}
|
|
35
|
-
return (_jsx(AspectRatio, { ratio: 4 / 3, children: _jsx(VStack, { height: "100%", vAlign: "center",
|
|
42
|
+
return (_jsx(AspectRatio, { ratio: 4 / 3, children: _jsx(VStack, { height: "100%", vAlign: "center", paddingBlock: 4, style: WAVE_COLOR, "aria-hidden": "true", children: _jsx(InkWaveform, { status: call.status, levelsRef: call.levelsRef, toolActive: call.toolActive, variant: "pill" }) }) }));
|
|
36
43
|
}
|
|
37
|
-
/** How long the call has run, or its state before it connects
|
|
44
|
+
/** How long the call has run, or its state: before it connects, after it ends, and while a tool runs. */
|
|
38
45
|
function TileTime({ call }) {
|
|
39
46
|
const t = useLabels();
|
|
40
|
-
const since = call.sessionActive ? call.connectedAt : null;
|
|
47
|
+
const since = call.sessionActive && call.liveState !== 'calling_tool' ? call.connectedAt : null;
|
|
41
48
|
const now = useSecondTicker(since !== null);
|
|
42
49
|
return (_jsx(Text, { type: "supporting", hasTabularNumbers: true, maxLines: 1, children: since === null
|
|
43
50
|
? liveStateLabel(t, call.liveState, call.activeTool)
|
package/ui/LiveRunner.js
CHANGED
|
@@ -2,7 +2,6 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Banner } from '@astryxdesign/core/Banner';
|
|
3
3
|
import { Button } from '@astryxdesign/core/Button';
|
|
4
4
|
import { Center } from '@astryxdesign/core/Center';
|
|
5
|
-
import { Dialog } from '@astryxdesign/core/Dialog';
|
|
6
5
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
7
6
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
8
7
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
@@ -16,12 +15,12 @@ import { IconArrowsDiagonalMinimize2, IconCameraRotate, IconMessage, IconMicroph
|
|
|
16
15
|
import { Fragment, useLayoutEffect, useRef } from 'react';
|
|
17
16
|
import { InkWaveform } from "../components/InkWaveform.js";
|
|
18
17
|
import { LiveCaptionsPanel } from "./LiveCaptionsPanel.js";
|
|
18
|
+
import { LiveToolGateDialog } from "./LiveToolGateDialog.js";
|
|
19
19
|
import { LiveVideoPreview } from "./LiveVideoPreview.js";
|
|
20
20
|
import { liveStateLabel } from "./labels.js";
|
|
21
21
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
22
22
|
import { PANEL_EDGE, SidePanel, SidePanelHeader, SidePanelToggle, useSidePanel, } from "./SidePanel.js";
|
|
23
23
|
import { DEFAULT_CHAT_MAX_WIDTH } from "./TheoremChat.js";
|
|
24
|
-
import { ApprovalCard, AuthChallengeCard, GATE_CARD_WIDTH } from "./ToolGateCard.js";
|
|
25
24
|
import { useTraceInspector, WithTrace } from "./TraceInspectorPanel.js";
|
|
26
25
|
import { TheoremThemeProvider } from "./theme.js";
|
|
27
26
|
/** The waveform's box: it rests on the stage floor rather than filling the stage. */
|
|
@@ -53,7 +52,7 @@ function LiveRunnerBody({ call, trace, onMinimize, }) {
|
|
|
53
52
|
if (!call.callStarted) {
|
|
54
53
|
return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", header: _jsx(SidePanelHeader, { children: inspector.toggle }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(LiveLanding, { call: call }) }) }) }));
|
|
55
54
|
}
|
|
56
|
-
return (_jsxs(_Fragment, { children: [_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", header: _jsx(SidePanelHeader, { children: inspector.toggle }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(Layout, { height: "fill", end: _jsx(SidePanel, { id: captions.id, labels: captionLabels, resizable: captions.resizable, open: captions.open, children: _jsx(LiveCaptions, { call: call }) }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(VStack, { height: "100%", padding: PANEL_EDGE, children: _jsx(LiveStage, { call: call, captionsToggle: captionsToggle, onMinimize: onMinimize }) }) }) }) }) }) }), _jsx(LiveToolGateDialog, {
|
|
55
|
+
return (_jsxs(_Fragment, { children: [_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", header: _jsx(SidePanelHeader, { children: inspector.toggle }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(Layout, { height: "fill", end: _jsx(SidePanel, { id: captions.id, labels: captionLabels, resizable: captions.resizable, open: captions.open, children: _jsx(LiveCaptions, { call: call }) }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(VStack, { height: "100%", padding: PANEL_EDGE, children: _jsx(LiveStage, { call: call, captionsToggle: captionsToggle, onMinimize: onMinimize }) }) }) }) }) }) }), _jsx(LiveToolGateDialog, { call: call })] }));
|
|
57
56
|
}
|
|
58
57
|
/**
|
|
59
58
|
* Before a call: the chat landing's greeting, with call buttons where the
|
|
@@ -134,24 +133,3 @@ function LiveCallButton({ call }) {
|
|
|
134
133
|
function LiveCaptions({ call }) {
|
|
135
134
|
return (_jsx(LiveCaptionsPanel, { handle: call.handle, captions: call.captions, pastCalls: call.pastCalls, draftText: call.textDraft, onDraftTextChange: call.setTextDraft, onSubmit: call.handleSendText, isDisabled: !call.sessionActive, showComposer: call.textAvailable, leading: call.videoPreview ? (_jsx(LiveVideoPreview, { video: call.videoPreview, facingMode: call.videoFacingMode })) : null }));
|
|
136
135
|
}
|
|
137
|
-
/** Room around the gate card for its shadow. */
|
|
138
|
-
const GATE_DIALOG_PADDING = 4;
|
|
139
|
-
const GATE_DIALOG_WIDTH = GATE_CARD_WIDTH + 2 * GATE_DIALOG_PADDING * 4;
|
|
140
|
-
/** The dialog draws no surface of its own: the card is the one chat shows, alone. */
|
|
141
|
-
const GATE_DIALOG_BARE = { background: 'transparent', boxShadow: 'none' };
|
|
142
|
-
/**
|
|
143
|
-
* Tool approvals and credential prompts, as a dialog that must be answered. It shows the card
|
|
144
|
-
* chat shows inline and nothing else, and closes on the answer.
|
|
145
|
-
*/
|
|
146
|
-
function LiveToolGateDialog({ prompt, agent, onResolve, }) {
|
|
147
|
-
return (_jsx(Dialog, { isOpen: prompt !== null, purpose: "required", width: GATE_DIALOG_WIDTH, padding: GATE_DIALOG_PADDING, style: GATE_DIALOG_BARE,
|
|
148
|
-
// why: "required" disables dismissal; should one slip through, it denies.
|
|
149
|
-
onOpenChange: (open) => {
|
|
150
|
-
if (!open)
|
|
151
|
-
onResolve({ action: 'deny' });
|
|
152
|
-
}, children: prompt ? (prompt.gate.kind === 'auth' ? (_jsx(AuthChallengeCard, { gate: prompt.gate, toolName: prompt.gate.tool, onAuthenticated: (secret) => {
|
|
153
|
-
onResolve({ action: 'auth', secret });
|
|
154
|
-
} })) : (_jsx(ApprovalCard, { gate: prompt.gate, toolName: prompt.gate.tool, agent: agent, input: prompt.input, onDecision: (action) => {
|
|
155
|
-
onResolve({ action });
|
|
156
|
-
} }))) : null }));
|
|
157
|
-
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LiveCall } from '../components/live/use-live-call.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Tool approvals and credential prompts, as a dialog that must be answered. It shows the card
|
|
4
|
+
* chat shows inline and nothing else, and closes on the answer. `LiveRunner` and
|
|
5
|
+
* `LiveCallTile` both show it, so a gate is answered wherever the call is drawn.
|
|
6
|
+
*/
|
|
7
|
+
export declare function LiveToolGateDialog({ call }: {
|
|
8
|
+
call: LiveCall;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Dialog } from '@astryxdesign/core/Dialog';
|
|
3
|
+
import { useLabels } from "./labels-provider.js";
|
|
4
|
+
import { ApprovalCard, AuthChallengeCard, GATE_CARD_WIDTH } from "./ToolGateCard.js";
|
|
5
|
+
/** The dialog takes the card's corner (the bubble's --radius-chat), so none of its surface shows past the card. */
|
|
6
|
+
const GATE_DIALOG_CORNER = {
|
|
7
|
+
borderRadius: 'var(--radius-chat, var(--radius-container))',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Tool approvals and credential prompts, as a dialog that must be answered. It shows the card
|
|
11
|
+
* chat shows inline and nothing else, and closes on the answer. `LiveRunner` and
|
|
12
|
+
* `LiveCallTile` both show it, so a gate is answered wherever the call is drawn.
|
|
13
|
+
*/
|
|
14
|
+
export function LiveToolGateDialog({ call }) {
|
|
15
|
+
const t = useLabels();
|
|
16
|
+
const prompt = call.gatePrompt;
|
|
17
|
+
const onResolve = call.resolveGateDecision;
|
|
18
|
+
const agent = t('@theorem.agent.handle', { handle: call.handle });
|
|
19
|
+
return (_jsx(Dialog, { isOpen: prompt !== null, purpose: "required",
|
|
20
|
+
// why: The card is the one chat shows, a tint made for a page. The dialog's own surface,
|
|
21
|
+
// the card's size and radius, is that page; over the bare backdrop the tint goes grey.
|
|
22
|
+
width: GATE_CARD_WIDTH, padding: 0, style: GATE_DIALOG_CORNER,
|
|
23
|
+
// why: "required" disables dismissal; should one slip through, it denies.
|
|
24
|
+
onOpenChange: (open) => {
|
|
25
|
+
if (!open)
|
|
26
|
+
onResolve({ action: 'deny' });
|
|
27
|
+
}, children: prompt ? (prompt.gate.kind === 'auth' ? (_jsx(AuthChallengeCard, { gate: prompt.gate, toolName: prompt.gate.tool, onAuthenticated: (secret) => {
|
|
28
|
+
onResolve({ action: 'auth', secret });
|
|
29
|
+
} })) : (_jsx(ApprovalCard, { gate: prompt.gate, toolName: prompt.gate.tool, agent: agent, input: prompt.input, onDecision: (action) => {
|
|
30
|
+
onResolve({ action });
|
|
31
|
+
} }))) : null }));
|
|
32
|
+
}
|
package/ui/ToolGateCard.js
CHANGED
|
@@ -17,6 +17,8 @@ import { approvalHeading } from "./approval-heading.js";
|
|
|
17
17
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
18
18
|
/** The widest a gate card gets, in pixels. */
|
|
19
19
|
export const GATE_CARD_WIDTH = 400;
|
|
20
|
+
/** why: A focused title shows no ring; the focus is for screen readers, as on Astryx's DialogHeader. */
|
|
21
|
+
const TITLE_FOCUS = { outline: 'none' };
|
|
20
22
|
function formatInput(value) {
|
|
21
23
|
if (typeof value === 'string')
|
|
22
24
|
return value;
|
|
@@ -26,7 +28,7 @@ function formatInput(value) {
|
|
|
26
28
|
}
|
|
27
29
|
/** The one frame of every gate card: what is asked, a tag beside it, and what answers it. */
|
|
28
30
|
function GateCard(props) {
|
|
29
|
-
return (_jsx(Card, { variant: "bubble", elevation: "med", maxWidth: GATE_CARD_WIDTH, children: _jsxs(VStack, { gap: 3, children: [_jsxs(HStack, { justify: "between", align: "start", gap: 3, children: [_jsx(Text, { type: "large", weight: "semibold", children: props.heading }), props.tag] }), props.children] }) }));
|
|
31
|
+
return (_jsx(Card, { variant: "bubble", elevation: "med", maxWidth: GATE_CARD_WIDTH, children: _jsxs(VStack, { gap: 3, children: [_jsxs(HStack, { justify: "between", align: "start", gap: 3, children: [_jsx(Text, { type: "large", weight: "semibold", tabIndex: -1, "data-autofocus": true, style: TITLE_FOCUS, children: props.heading }), props.tag] }), props.children] }) }));
|
|
30
32
|
}
|
|
31
33
|
const ACCESS_ICON = {
|
|
32
34
|
'read-only': IconEye,
|
package/ui/labels.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
import type
|
|
10
|
+
import { type ComposerDrawerSummary, type LiveState } from '@theoremjs/web/client';
|
|
11
|
+
import type { LiveActiveTool } from '../client/live/apply-live-turn-tool-event.ts';
|
|
11
12
|
import type { TurnUsage, WorkStatus } from '../client/transcript-groups.ts';
|
|
12
13
|
export declare const THEOREM_UI_CATALOG: {
|
|
13
14
|
readonly '@theorem.agent.handle': {
|
|
@@ -1469,8 +1470,11 @@ export declare function resultMeta(t: LabelText, usd: Intl.NumberFormat, result:
|
|
|
1469
1470
|
export declare function liveDuration(t: LabelText, durationMs: number): string;
|
|
1470
1471
|
/** "Working for 12s" while streaming (when the start is known), "Worked for 3.2s" after. */
|
|
1471
1472
|
export declare function workStatusLabel(t: LabelText, status: WorkStatus | null): string;
|
|
1472
|
-
/**
|
|
1473
|
-
|
|
1473
|
+
/**
|
|
1474
|
+
* A live call's status line. While a tool runs it is the tool's activity label, as chat shows
|
|
1475
|
+
* it, else `calling_tool` with the tool's name in words, as its gate names it.
|
|
1476
|
+
*/
|
|
1477
|
+
export declare function liveStateLabel(t: LabelText, state: LiveState, tool: LiveActiveTool | null): string;
|
|
1474
1478
|
/** A voice note's name from its audio format; the unnamed line when the format is unknown. */
|
|
1475
1479
|
export declare function voiceNoteName(t: LabelText, format: string | undefined): string;
|
|
1476
1480
|
/**
|
package/ui/labels.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
+
import { humanize } from '@theoremjs/web/client';
|
|
10
11
|
import { IntlMessageFormat } from 'intl-messageformat';
|
|
11
12
|
export const THEOREM_UI_CATALOG = {
|
|
12
13
|
'@theorem.agent.handle': {
|
|
@@ -1411,10 +1412,15 @@ export function workStatusLabel(t, status) {
|
|
|
1411
1412
|
? t('@theorem.transcript.worked')
|
|
1412
1413
|
: t('@theorem.transcript.worked_for', { duration: workDuration(t, status.elapsedMs) });
|
|
1413
1414
|
}
|
|
1414
|
-
/**
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1415
|
+
/**
|
|
1416
|
+
* A live call's status line. While a tool runs it is the tool's activity label, as chat shows
|
|
1417
|
+
* it, else `calling_tool` with the tool's name in words, as its gate names it.
|
|
1418
|
+
*/
|
|
1419
|
+
export function liveStateLabel(t, state, tool) {
|
|
1420
|
+
if (state === 'calling_tool') {
|
|
1421
|
+
return (tool?.activity ??
|
|
1422
|
+
t('@theorem.live.state.calling_tool', { tool: humanize(tool?.name ?? '').toLowerCase() }));
|
|
1423
|
+
}
|
|
1418
1424
|
return t(`@theorem.live.state.${state}`);
|
|
1419
1425
|
}
|
|
1420
1426
|
/** A voice note's name from its audio format; the unnamed line when the format is unknown. */
|