@theoremjs/react 0.3.4-next.845.g08954d1 → 0.3.4-next.849.g2dfd688
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/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 +30 -0
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';
|
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.849.g2dfd688",
|
|
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.849.g2dfd688",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.849.g2dfd688"
|
|
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,30 @@
|
|
|
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
|
+
/** Room around the gate card for its shadow. */
|
|
6
|
+
const GATE_DIALOG_PADDING = 4;
|
|
7
|
+
const GATE_DIALOG_WIDTH = GATE_CARD_WIDTH + 2 * GATE_DIALOG_PADDING * 4;
|
|
8
|
+
/** The dialog draws no surface of its own: the card is the one chat shows, alone. */
|
|
9
|
+
const GATE_DIALOG_BARE = { background: 'transparent', boxShadow: 'none' };
|
|
10
|
+
/**
|
|
11
|
+
* Tool approvals and credential prompts, as a dialog that must be answered. It shows the card
|
|
12
|
+
* chat shows inline and nothing else, and closes on the answer. `LiveRunner` and
|
|
13
|
+
* `LiveCallTile` both show it, so a gate is answered wherever the call is drawn.
|
|
14
|
+
*/
|
|
15
|
+
export function LiveToolGateDialog({ call }) {
|
|
16
|
+
const t = useLabels();
|
|
17
|
+
const prompt = call.gatePrompt;
|
|
18
|
+
const onResolve = call.resolveGateDecision;
|
|
19
|
+
const agent = t('@theorem.agent.handle', { handle: call.handle });
|
|
20
|
+
return (_jsx(Dialog, { isOpen: prompt !== null, purpose: "required", width: GATE_DIALOG_WIDTH, padding: GATE_DIALOG_PADDING, style: GATE_DIALOG_BARE,
|
|
21
|
+
// why: "required" disables dismissal; should one slip through, it denies.
|
|
22
|
+
onOpenChange: (open) => {
|
|
23
|
+
if (!open)
|
|
24
|
+
onResolve({ action: 'deny' });
|
|
25
|
+
}, children: prompt ? (prompt.gate.kind === 'auth' ? (_jsx(AuthChallengeCard, { gate: prompt.gate, toolName: prompt.gate.tool, onAuthenticated: (secret) => {
|
|
26
|
+
onResolve({ action: 'auth', secret });
|
|
27
|
+
} })) : (_jsx(ApprovalCard, { gate: prompt.gate, toolName: prompt.gate.tool, agent: agent, input: prompt.input, onDecision: (action) => {
|
|
28
|
+
onResolve({ action });
|
|
29
|
+
} }))) : null }));
|
|
30
|
+
}
|