@theoremjs/react 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +772 -2
- package/client/attachment-hover-preview.d.ts +11 -0
- package/client/attachment-hover-preview.js +36 -0
- package/client/audio-level.d.ts +5 -0
- package/client/audio-level.js +24 -0
- package/client/composer-attachments.d.ts +30 -0
- package/client/composer-attachments.js +33 -0
- package/client/composer-drawer.d.ts +19 -0
- package/client/composer-drawer.js +15 -0
- package/client/composer-hints.d.ts +25 -0
- package/client/composer-hints.js +20 -0
- package/client/composer-primary.d.ts +16 -0
- package/client/composer-primary.js +25 -0
- package/client/decision-images.d.ts +76 -0
- package/client/decision-images.js +139 -0
- package/client/decision-transport.d.ts +70 -0
- package/client/decision-transport.js +175 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -0
- package/client/embedding-input.d.ts +84 -0
- package/client/embedding-input.js +194 -0
- package/client/embedding-space.d.ts +82 -0
- package/client/embedding-space.js +389 -0
- package/client/embedding-transport.d.ts +40 -0
- package/client/embedding-transport.js +71 -0
- package/client/encode-composer-draft.d.ts +9 -0
- package/client/encode-composer-draft.js +34 -0
- package/client/encode-files.d.ts +18 -0
- package/client/encode-files.js +54 -0
- package/client/failure.d.ts +29 -0
- package/client/failure.js +34 -0
- package/client/generation-selection.d.ts +15 -0
- package/client/generation-selection.js +24 -0
- package/client/host-call.d.ts +51 -0
- package/client/host-call.js +47 -0
- package/client/host-transport.d.ts +70 -0
- package/client/host-transport.js +75 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +28 -0
- package/client/index.js +22 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +68 -0
- package/client/live/apply-live-turn-tool-event.d.ts +14 -0
- package/client/live/apply-live-turn-tool-event.js +39 -0
- package/client/live/live-captions.d.ts +53 -0
- package/client/live/live-captions.js +174 -0
- package/client/live/live-mic-forward.d.ts +30 -0
- package/client/live/live-mic-forward.js +46 -0
- package/client/live/live-page-tool.d.ts +34 -0
- package/client/live/live-page-tool.js +11 -0
- package/client/live/live-state.d.ts +13 -0
- package/client/live/live-state.js +22 -0
- package/client/live/live-tool.d.ts +11 -0
- package/client/live/live-tool.js +1 -0
- package/client/live/live-video.d.ts +10 -0
- package/client/live/live-video.js +71 -0
- package/client/live/run-live-tool-call.d.ts +22 -0
- package/client/live/run-live-tool-call.js +70 -0
- package/client/live/use-live-caption-log.d.ts +17 -0
- package/client/live/use-live-caption-log.js +51 -0
- package/client/live-client.d.ts +189 -0
- package/client/live-client.js +829 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +148 -0
- package/client/live-messages.js +100 -0
- package/client/mic-capture.d.ts +4 -0
- package/client/mic-capture.js +6 -0
- package/client/oauth-popup.d.ts +26 -0
- package/client/oauth-popup.js +30 -0
- package/client/page-dispatch.d.ts +15 -0
- package/client/page-dispatch.js +63 -0
- package/client/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +34 -0
- package/client/run-commit.js +49 -0
- package/client/run-session.d.ts +79 -0
- package/client/run-session.js +207 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +199 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +343 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -0
- package/client/space-geometry.d.ts +39 -0
- package/client/space-geometry.js +183 -0
- package/client/state-fields.d.ts +16 -0
- package/client/state-fields.js +44 -0
- package/client/tool-result.d.ts +78 -0
- package/client/tool-result.js +293 -0
- package/client/tool-resume.d.ts +50 -0
- package/client/tool-resume.js +14 -0
- package/client/trace-feed.d.ts +13 -0
- package/client/trace-feed.js +18 -0
- package/client/trace-story.d.ts +163 -0
- package/client/trace-story.js +440 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +333 -0
- package/client/transcript-groups.d.ts +140 -0
- package/client/transcript-groups.js +217 -0
- package/client/transcript-schema.d.ts +10 -0
- package/client/transcript-schema.js +120 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +264 -0
- package/client/transport.js +330 -0
- package/client/turn-client.d.ts +44 -0
- package/client/turn-client.js +88 -0
- package/client/voice-label.d.ts +2 -0
- package/client/voice-label.js +15 -0
- package/client/voice-recorder.d.ts +27 -0
- package/client/voice-recorder.js +162 -0
- package/client/wire-line.d.ts +56 -0
- package/client/wire-line.js +90 -0
- package/client/worklets/mic-capture.js +16 -0
- package/components/InkWaveform.d.ts +21 -0
- package/components/InkWaveform.js +131 -0
- package/components/live/use-live-runner-controls.d.ts +47 -0
- package/components/live/use-live-runner-controls.js +96 -0
- package/components/live/use-live-runner-model.d.ts +49 -0
- package/components/live/use-live-runner-model.js +190 -0
- package/components/live/use-live-runner-ui.d.ts +67 -0
- package/components/live/use-live-runner-ui.js +167 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +126 -0
- package/components/use-composer-voice.d.ts +28 -0
- package/components/use-composer-voice.js +90 -0
- package/components/use-editor-selection.d.ts +3 -0
- package/components/use-editor-selection.js +19 -0
- package/components/use-voice-playback.d.ts +17 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +12 -0
- package/hooks/use-described.js +25 -0
- package/hooks/use-embedding-index.d.ts +27 -0
- package/hooks/use-embedding-index.js +43 -0
- package/hooks/use-one-call.d.ts +31 -0
- package/hooks/use-one-call.js +76 -0
- package/hooks/use-page-tool-warnings.d.ts +6 -0
- package/hooks/use-page-tool-warnings.js +25 -0
- package/hooks/use-second-ticker.d.ts +2 -0
- package/hooks/use-second-ticker.js +13 -0
- package/hooks/use-theorem-chat-actions.d.ts +79 -0
- package/hooks/use-theorem-chat-actions.js +287 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +122 -0
- package/hooks/use-theorem-chat.d.ts +97 -0
- package/hooks/use-theorem-chat.js +528 -0
- package/hooks/use-theorem-decision.d.ts +18 -0
- package/hooks/use-theorem-decision.js +12 -0
- package/hooks/use-theorem-embedding.d.ts +18 -0
- package/hooks/use-theorem-embedding.js +12 -0
- package/hooks/use-theorem-host.d.ts +45 -0
- package/hooks/use-theorem-host.js +135 -0
- package/hooks/use-theorem-interface.d.ts +22 -0
- package/hooks/use-theorem-interface.js +15 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +17 -0
- package/index.js +17 -0
- package/live.d.ts +9 -0
- package/live.js +10 -0
- package/package.json +74 -4
- package/server/checkpoint-ownership.d.ts +6 -0
- package/server/checkpoint-ownership.js +42 -0
- package/server/conversation-store.d.ts +92 -0
- package/server/conversation-store.js +131 -0
- package/server/conversation.d.ts +193 -0
- package/server/conversation.js +552 -0
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +42 -0
- package/server/decision-handler.js +46 -0
- package/server/embedding-handler.d.ts +33 -0
- package/server/embedding-handler.js +45 -0
- package/server/handler.d.ts +152 -0
- package/server/handler.js +771 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +25 -0
- package/server/mod.js +23 -0
- package/server/page-dispatch.d.ts +22 -0
- package/server/page-dispatch.js +127 -0
- package/server/request-body.d.ts +2 -0
- package/server/request-body.js +42 -0
- package/server/request-check.d.ts +21 -0
- package/server/request-check.js +49 -0
- package/server/session-authority.d.ts +8 -0
- package/server/session-authority.js +78 -0
- package/server/session-boundary.d.ts +26 -0
- package/server/session-boundary.js +64 -0
- package/server/session-identity.d.ts +16 -0
- package/server/session-identity.js +41 -0
- package/server/session-limits.d.ts +12 -0
- package/server/session-limits.js +59 -0
- package/server/session-store.d.ts +123 -0
- package/server/session-store.js +264 -0
- package/server/single-action.d.ts +21 -0
- package/server/single-action.js +30 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/tool-operations.d.ts +6 -0
- package/server/tool-operations.js +132 -0
- package/server/turn-input.d.ts +10 -0
- package/server/turn-input.js +29 -0
- package/styles/ink-controls.css +134 -0
- package/ui/CardWall.d.ts +10 -0
- package/ui/CardWall.js +61 -0
- package/ui/ChatComposerBar.d.ts +62 -0
- package/ui/ChatComposerBar.js +306 -0
- package/ui/ChatTranscript.d.ts +43 -0
- package/ui/ChatTranscript.js +527 -0
- package/ui/ConsoleFrame.d.ts +104 -0
- package/ui/ConsoleFrame.js +146 -0
- package/ui/DecisionAnswers.d.ts +53 -0
- package/ui/DecisionAnswers.js +233 -0
- package/ui/DecisionImages.d.ts +16 -0
- package/ui/DecisionImages.js +46 -0
- package/ui/EmbeddingDocuments.d.ts +78 -0
- package/ui/EmbeddingDocuments.js +278 -0
- package/ui/EmbeddingQuery.d.ts +27 -0
- package/ui/EmbeddingQuery.js +95 -0
- package/ui/EmbeddingResults.d.ts +41 -0
- package/ui/EmbeddingResults.js +165 -0
- package/ui/EmbeddingSpace.d.ts +26 -0
- package/ui/EmbeddingSpace.js +106 -0
- package/ui/LiveCaptionsPanel.d.ts +22 -0
- package/ui/LiveCaptionsPanel.js +27 -0
- package/ui/LiveRunner.d.ts +30 -0
- package/ui/LiveRunner.js +180 -0
- package/ui/PaneState.d.ts +33 -0
- package/ui/PaneState.js +50 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +150 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +196 -0
- package/ui/SidePanel.d.ts +126 -0
- package/ui/SidePanel.js +123 -0
- package/ui/StateFields.d.ts +44 -0
- package/ui/StateFields.js +139 -0
- package/ui/TheoremChat.d.ts +122 -0
- package/ui/TheoremChat.js +134 -0
- package/ui/TheoremDecision.d.ts +30 -0
- package/ui/TheoremDecision.js +130 -0
- package/ui/TheoremEmbedding.d.ts +27 -0
- package/ui/TheoremEmbedding.js +122 -0
- package/ui/TheoremHost.d.ts +55 -0
- package/ui/TheoremHost.js +240 -0
- package/ui/ToolGateCard.d.ts +32 -0
- package/ui/ToolGateCard.js +107 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +62 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +103 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +266 -0
- package/ui/TraceGuardrails.d.ts +11 -0
- package/ui/TraceGuardrails.js +74 -0
- package/ui/TraceInspector.d.ts +16 -0
- package/ui/TraceInspector.js +206 -0
- package/ui/TraceInspectorPanel.d.ts +46 -0
- package/ui/TraceInspectorPanel.js +95 -0
- package/ui/TraceOverview.d.ts +38 -0
- package/ui/TraceOverview.js +126 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +190 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +108 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +123 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +135 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/approval-heading.d.ts +4 -0
- package/ui/approval-heading.js +9 -0
- package/ui/arrive.d.ts +18 -0
- package/ui/arrive.js +46 -0
- package/ui/built/theme.css +792 -0
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +660 -0
- package/ui/built/theorem.variants.d.ts +22 -0
- package/ui/code-language.d.ts +12 -0
- package/ui/code-language.js +50 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +207 -0
- package/ui/embedding-space-gl.d.ts +37 -0
- package/ui/embedding-space-gl.js +663 -0
- package/ui/embedding-space-view.d.ts +8 -0
- package/ui/embedding-space-view.js +8 -0
- package/ui/glide.d.ts +7 -0
- package/ui/glide.js +44 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +38 -0
- package/ui/index.d.ts +36 -0
- package/ui/index.js +39 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +64 -0
- package/ui/labels.d.ts +1442 -0
- package/ui/labels.js +1443 -0
- package/ui/markdown-plugins.d.ts +9 -0
- package/ui/markdown-plugins.js +13 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/row-keys.d.ts +19 -0
- package/ui/row-keys.js +33 -0
- package/ui/theme.d.ts +10 -0
- package/ui/theme.js +13 -0
- package/ui/theorem-theme.d.ts +10 -0
- package/ui/theorem-theme.js +105 -0
- package/ui/trace-palette.d.ts +10 -0
- package/ui/trace-palette.js +10 -0
- package/ui/trace-patterns.d.ts +40 -0
- package/ui/trace-patterns.js +57 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +40 -0
- package/ui/wall-columns.d.ts +8 -0
- package/ui/wall-columns.js +19 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { liveIngressEnabledFromSpec } from '@theoremjs/agents';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { stashLiveCaptionCall, } from "../../client/live/live-captions.js";
|
|
4
|
+
import { liveState } from "../../client/live/live-state.js";
|
|
5
|
+
import { createTraceFeed } from "../../client/trace-feed.js";
|
|
6
|
+
import { usePageToolWarnings } from "../../hooks/use-page-tool-warnings.js";
|
|
7
|
+
import { useLiveRunnerControls } from "./use-live-runner-controls.js";
|
|
8
|
+
import { useLiveRunnerGate, useLiveRunnerUiState } from "./use-live-runner-ui.js";
|
|
9
|
+
import { useLiveSessionClient } from "./use-live-session-client.js";
|
|
10
|
+
/**
|
|
11
|
+
* Calls start from the landing (voice, or video + voice), not on mount. Once
|
|
12
|
+
* started, the call view stays until a reload; ending leaves Start call. Each
|
|
13
|
+
* restart is a new conversation; earlier calls' captions stay, above a divider.
|
|
14
|
+
*/
|
|
15
|
+
function useLiveCallLifecycle(controls, captionsRef, sessionActive, videoAvailable) {
|
|
16
|
+
const [callStarted, setCallStarted] = useState(false);
|
|
17
|
+
const [ended, setEnded] = useState(false);
|
|
18
|
+
const videoOnConnectRef = useRef(false);
|
|
19
|
+
const startCall = useCallback((options) => {
|
|
20
|
+
videoOnConnectRef.current = options.video && videoAvailable;
|
|
21
|
+
setCallStarted(true);
|
|
22
|
+
setEnded(false);
|
|
23
|
+
void controls.startSession();
|
|
24
|
+
}, [controls, videoAvailable]);
|
|
25
|
+
// why: Camera capture needs a live session, so a video call turns it on once connected.
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!sessionActive || !videoOnConnectRef.current)
|
|
28
|
+
return;
|
|
29
|
+
videoOnConnectRef.current = false;
|
|
30
|
+
void controls.handleToggleVideo();
|
|
31
|
+
}, [controls, sessionActive]);
|
|
32
|
+
const handleEnd = useCallback(() => {
|
|
33
|
+
videoOnConnectRef.current = false;
|
|
34
|
+
controls.teardownSession();
|
|
35
|
+
setEnded(true);
|
|
36
|
+
}, [controls]);
|
|
37
|
+
const [pastCalls, setPastCalls] = useState([]);
|
|
38
|
+
const handleRestart = useCallback(async () => {
|
|
39
|
+
const previous = captionsRef.current.turns;
|
|
40
|
+
setPastCalls((calls) => stashLiveCaptionCall(calls, previous));
|
|
41
|
+
setEnded(false);
|
|
42
|
+
await controls.handleRestart();
|
|
43
|
+
}, [controls, captionsRef]);
|
|
44
|
+
const teardownSessionRef = useRef(controls.teardownSession);
|
|
45
|
+
teardownSessionRef.current = controls.teardownSession;
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
return () => {
|
|
48
|
+
teardownSessionRef.current();
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
return { callStarted, ended, pastCalls, startCall, handleEnd, handleRestart };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Owns LiveRunner state, session client, and stage callbacks.
|
|
55
|
+
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
56
|
+
* its id; the studio returns its draft as the relay's open message. `options`
|
|
57
|
+
* are the host's part in the call: its slots, the page's context, and its page tools.
|
|
58
|
+
*/
|
|
59
|
+
export function useLiveRunnerModel(iface, connection, options = {}) {
|
|
60
|
+
const ui = useLiveRunnerUiState(iface.labels);
|
|
61
|
+
const gate = useLiveRunnerGate({
|
|
62
|
+
setCaptions: ui.setCaptions,
|
|
63
|
+
setActiveTool: ui.setActiveTool,
|
|
64
|
+
reportFailure: ui.reportFailure,
|
|
65
|
+
});
|
|
66
|
+
const connectionRef = useRef(connection);
|
|
67
|
+
connectionRef.current = connection;
|
|
68
|
+
const voiceAvailable = liveIngressEnabledFromSpec(iface.live.ingress, 'audio');
|
|
69
|
+
const videoAvailable = liveIngressEnabledFromSpec(iface.live.ingress, 'video');
|
|
70
|
+
const textAvailable = liveIngressEnabledFromSpec(iface.live.ingress, 'text');
|
|
71
|
+
const state = useMemo(() => liveState({
|
|
72
|
+
status: ui.status,
|
|
73
|
+
connectPhase: ui.connectPhase,
|
|
74
|
+
toolName: ui.activeTool,
|
|
75
|
+
isMuted: ui.isMuted,
|
|
76
|
+
voiceEnabled: voiceAvailable,
|
|
77
|
+
}), [ui.activeTool, ui.connectPhase, ui.isMuted, ui.status, voiceAvailable]);
|
|
78
|
+
// why: One feed per runner: every session it opens adds its records.
|
|
79
|
+
const traces = useMemo(createTraceFeed, []);
|
|
80
|
+
const { clientRef, ensureClient, clearClient } = useLiveSessionClient({
|
|
81
|
+
slots: options.slots,
|
|
82
|
+
context: options.context,
|
|
83
|
+
pageTools: options.pageTools,
|
|
84
|
+
voiceAvailable,
|
|
85
|
+
traces,
|
|
86
|
+
handleLiveTurnEvent: gate.handleLiveTurnEvent,
|
|
87
|
+
waitForGateDecision: gate.waitForGateDecision,
|
|
88
|
+
gatePromptRef: gate.gatePromptRef,
|
|
89
|
+
isMutedRef: ui.isMutedRef,
|
|
90
|
+
sessionPermissionsRef: ui.sessionPermissionsRef,
|
|
91
|
+
videoCaptureRef: ui.videoCaptureRef,
|
|
92
|
+
setConnectPhase: ui.setConnectPhase,
|
|
93
|
+
setStatus: ui.setStatus,
|
|
94
|
+
setSessionActive: ui.setSessionActive,
|
|
95
|
+
reportFailure: ui.reportFailure,
|
|
96
|
+
clearFailure: ui.clearFailure,
|
|
97
|
+
reportSessionEnded: ui.reportSessionEnded,
|
|
98
|
+
clearSessionEnded: ui.clearSessionEnded,
|
|
99
|
+
setCaptions: ui.setCaptions,
|
|
100
|
+
setInputLevel: ui.setInputLevel,
|
|
101
|
+
setOutputLevel: ui.setOutputLevel,
|
|
102
|
+
setActiveTool: ui.setActiveTool,
|
|
103
|
+
setSessionPermissions: ui.setSessionPermissions,
|
|
104
|
+
setVideoPreview: ui.setVideoPreview,
|
|
105
|
+
setVideoFacingMode: ui.setVideoFacingMode,
|
|
106
|
+
setIsVideoOn: ui.setIsVideoOn,
|
|
107
|
+
});
|
|
108
|
+
const controls = useLiveRunnerControls({
|
|
109
|
+
clientRef,
|
|
110
|
+
videoCaptureRef: ui.videoCaptureRef,
|
|
111
|
+
connectionRef,
|
|
112
|
+
statusRef: ui.statusRef,
|
|
113
|
+
isMutedRef: ui.isMutedRef,
|
|
114
|
+
sessionPermissionsRef: ui.sessionPermissionsRef,
|
|
115
|
+
ensureClient,
|
|
116
|
+
clearClient,
|
|
117
|
+
cancelGateDecision: gate.cancelGateDecision,
|
|
118
|
+
stopVideo: ui.stopVideo,
|
|
119
|
+
resetCaptions: ui.resetCaptions,
|
|
120
|
+
sessionActive: ui.sessionActive,
|
|
121
|
+
textAvailable,
|
|
122
|
+
videoAvailable,
|
|
123
|
+
voiceAvailable,
|
|
124
|
+
isVideoOn: ui.isVideoOn,
|
|
125
|
+
textDraft: ui.textDraft,
|
|
126
|
+
setTextDraft: ui.setTextDraft,
|
|
127
|
+
setCaptions: ui.setCaptions,
|
|
128
|
+
reportFailure: ui.reportFailure,
|
|
129
|
+
clearFailure: ui.clearFailure,
|
|
130
|
+
setIsMuted: ui.setIsMuted,
|
|
131
|
+
setSessionActive: ui.setSessionActive,
|
|
132
|
+
setSessionPermissions: ui.setSessionPermissions,
|
|
133
|
+
setStatus: ui.setStatus,
|
|
134
|
+
setConnectPhase: ui.setConnectPhase,
|
|
135
|
+
setInputLevel: ui.setInputLevel,
|
|
136
|
+
setOutputLevel: ui.setOutputLevel,
|
|
137
|
+
setVideoPreview: ui.setVideoPreview,
|
|
138
|
+
setVideoFacingMode: ui.setVideoFacingMode,
|
|
139
|
+
setIsVideoOn: ui.setIsVideoOn,
|
|
140
|
+
});
|
|
141
|
+
const { callStarted, ended, pastCalls, startCall, handleEnd, handleRestart } = useLiveCallLifecycle(controls, ui.captionsRef, ui.sessionActive, videoAvailable);
|
|
142
|
+
// why: The page's context is compared by value, so a host may build it inline on each render.
|
|
143
|
+
const contextJson = JSON.stringify(options.context);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
clientRef.current?.setContext(contextJson === undefined ? undefined : JSON.parse(contextJson));
|
|
146
|
+
}, [clientRef, contextJson]);
|
|
147
|
+
// why: Slots are compared by value too; a changed value takes an open call up again with it.
|
|
148
|
+
const slotsJson = JSON.stringify(options.slots);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
clientRef.current?.setSlots(slotsJson === undefined ? undefined : JSON.parse(slotsJson));
|
|
151
|
+
}, [clientRef, slotsJson]);
|
|
152
|
+
usePageToolWarnings(iface.tools.page, options.pageTools);
|
|
153
|
+
return {
|
|
154
|
+
handle: iface.identity.handle,
|
|
155
|
+
traces,
|
|
156
|
+
callStarted,
|
|
157
|
+
pastCalls,
|
|
158
|
+
captions: ui.captions,
|
|
159
|
+
failure: ui.failure,
|
|
160
|
+
sessionEnded: ui.sessionEnded,
|
|
161
|
+
levelsRef: ui.levelsRef,
|
|
162
|
+
isMuted: ui.isMuted,
|
|
163
|
+
isVideoOn: ui.isVideoOn,
|
|
164
|
+
sessionActive: ui.sessionActive,
|
|
165
|
+
liveState: state,
|
|
166
|
+
activeTool: ui.activeTool,
|
|
167
|
+
status: ui.status,
|
|
168
|
+
textAvailable,
|
|
169
|
+
textDraft: ui.textDraft,
|
|
170
|
+
toolActive: ui.activeTool !== null || gate.gatePrompt !== null,
|
|
171
|
+
videoAvailable,
|
|
172
|
+
videoFacingMode: ui.videoFacingMode,
|
|
173
|
+
videoPreview: ui.videoPreview,
|
|
174
|
+
voiceAvailable,
|
|
175
|
+
// why: Also after ending (or failing) before the first connect, so the call can't get stuck.
|
|
176
|
+
canRestart: !ui.sessionActive &&
|
|
177
|
+
ui.status !== 'connecting' &&
|
|
178
|
+
(ui.everConnected || ended || ui.failure !== null),
|
|
179
|
+
gatePrompt: gate.gatePrompt,
|
|
180
|
+
setTextDraft: ui.setTextDraft,
|
|
181
|
+
handleEnd,
|
|
182
|
+
startCall,
|
|
183
|
+
handleRestart,
|
|
184
|
+
handleSendText: controls.handleSendText,
|
|
185
|
+
handleToggleMic: controls.handleToggleMic,
|
|
186
|
+
handleFlipCamera: controls.handleFlipCamera,
|
|
187
|
+
handleToggleVideo: controls.handleToggleVideo,
|
|
188
|
+
resolveGateDecision: gate.resolveGateDecision,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type LabelOverrides, type SessionEventOf } from '@theoremjs/agents';
|
|
2
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
3
|
+
import { type ClientFailure } from '../../client/failure.ts';
|
|
4
|
+
import { applyLiveTurnToolEvent } from '../../client/live/apply-live-turn-tool-event.ts';
|
|
5
|
+
import { type LiveCaptionState } from '../../client/live/live-captions.ts';
|
|
6
|
+
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
7
|
+
import type { LiveGateAnswer, LiveToolGatePrompt } from '../../client/live/live-tool.ts';
|
|
8
|
+
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
9
|
+
import type { ToolGateResolution } from '../../client/tool-resume.ts';
|
|
10
|
+
/** UI + media state bag for the live runner. `labels` is the interface's: the profile's wording. */
|
|
11
|
+
export declare function useLiveRunnerUiState(labels: LabelOverrides): {
|
|
12
|
+
status: LiveSessionStatus;
|
|
13
|
+
setStatus: Dispatch<SetStateAction<LiveSessionStatus>>;
|
|
14
|
+
connectPhase: LiveConnectPhase | null;
|
|
15
|
+
setConnectPhase: Dispatch<SetStateAction<LiveConnectPhase | null>>;
|
|
16
|
+
levelsRef: import("react").RefObject<{
|
|
17
|
+
input: number;
|
|
18
|
+
output: number;
|
|
19
|
+
}>;
|
|
20
|
+
setInputLevel: (value: SetStateAction<number>) => void;
|
|
21
|
+
setOutputLevel: (value: SetStateAction<number>) => void;
|
|
22
|
+
isMuted: boolean;
|
|
23
|
+
setIsMuted: Dispatch<SetStateAction<boolean>>;
|
|
24
|
+
isVideoOn: boolean;
|
|
25
|
+
setIsVideoOn: Dispatch<SetStateAction<boolean>>;
|
|
26
|
+
activeTool: string | null;
|
|
27
|
+
setActiveTool: Dispatch<SetStateAction<string | null>>;
|
|
28
|
+
captions: LiveCaptionState;
|
|
29
|
+
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
30
|
+
failure: ClientFailure | null;
|
|
31
|
+
reportFailure: (err: unknown) => void;
|
|
32
|
+
clearFailure: () => void;
|
|
33
|
+
sessionEnded: string | null;
|
|
34
|
+
reportSessionEnded: (session: SessionEventOf<"ended">) => void;
|
|
35
|
+
clearSessionEnded: () => void;
|
|
36
|
+
textDraft: string;
|
|
37
|
+
setTextDraft: Dispatch<SetStateAction<string>>;
|
|
38
|
+
sessionActive: boolean;
|
|
39
|
+
setSessionActive: Dispatch<SetStateAction<boolean>>;
|
|
40
|
+
sessionPermissions: string[];
|
|
41
|
+
setSessionPermissions: Dispatch<SetStateAction<string[]>>;
|
|
42
|
+
everConnected: boolean;
|
|
43
|
+
videoPreview: HTMLVideoElement | null;
|
|
44
|
+
setVideoPreview: Dispatch<SetStateAction<HTMLVideoElement | null>>;
|
|
45
|
+
videoFacingMode: LiveFacingMode;
|
|
46
|
+
setVideoFacingMode: Dispatch<SetStateAction<LiveFacingMode>>;
|
|
47
|
+
videoCaptureRef: import("react").RefObject<LiveVideoCapture | null>;
|
|
48
|
+
captionsRef: import("react").RefObject<LiveCaptionState>;
|
|
49
|
+
isMutedRef: import("react").RefObject<boolean>;
|
|
50
|
+
sessionPermissionsRef: import("react").RefObject<string[]>;
|
|
51
|
+
statusRef: import("react").RefObject<LiveSessionStatus>;
|
|
52
|
+
resetCaptions: () => void;
|
|
53
|
+
stopVideo: () => void;
|
|
54
|
+
};
|
|
55
|
+
/** Gate prompt + live turn-tool event wiring. */
|
|
56
|
+
export declare function useLiveRunnerGate(args: {
|
|
57
|
+
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
58
|
+
setActiveTool: Dispatch<SetStateAction<string | null>>;
|
|
59
|
+
reportFailure: (err: unknown) => void;
|
|
60
|
+
}): {
|
|
61
|
+
gatePrompt: LiveToolGatePrompt | null;
|
|
62
|
+
gatePromptRef: import("react").RefObject<LiveToolGatePrompt | null>;
|
|
63
|
+
handleLiveTurnEvent: (event: Parameters<typeof applyLiveTurnToolEvent>[0]) => void;
|
|
64
|
+
waitForGateDecision: (prompt: LiveToolGatePrompt) => Promise<LiveGateAnswer>;
|
|
65
|
+
resolveGateDecision: (resolution: ToolGateResolution) => void;
|
|
66
|
+
cancelGateDecision: () => void;
|
|
67
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { TheoremError } from '@theoremjs/agents';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { clientFailure } from "../../client/failure.js";
|
|
4
|
+
import { applyLiveTurnToolEvent } from "../../client/live/apply-live-turn-tool-event.js";
|
|
5
|
+
import { applyLiveTurnEvent, clearLiveCaptionInterim, emptyLiveCaptionState, } from "../../client/live/live-captions.js";
|
|
6
|
+
/** UI + media state bag for the live runner. `labels` is the interface's: the profile's wording. */
|
|
7
|
+
export function useLiveRunnerUiState(labels) {
|
|
8
|
+
const [status, setStatus] = useState('disconnected');
|
|
9
|
+
const [connectPhase, setConnectPhase] = useState(null);
|
|
10
|
+
// why: Levels move the waveform only. Writing them here keeps a sample off the React tree.
|
|
11
|
+
const levelsRef = useRef({ input: 0, output: 0 });
|
|
12
|
+
const setInputLevel = useCallback((value) => {
|
|
13
|
+
const levels = levelsRef.current;
|
|
14
|
+
levels.input = typeof value === 'function' ? value(levels.input) : value;
|
|
15
|
+
}, []);
|
|
16
|
+
const setOutputLevel = useCallback((value) => {
|
|
17
|
+
const levels = levelsRef.current;
|
|
18
|
+
levels.output = typeof value === 'function' ? value(levels.output) : value;
|
|
19
|
+
}, []);
|
|
20
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
21
|
+
const [isVideoOn, setIsVideoOn] = useState(false);
|
|
22
|
+
const [activeTool, setActiveTool] = useState(null);
|
|
23
|
+
const [captions, setCaptions] = useState(emptyLiveCaptionState);
|
|
24
|
+
const [failure, setFailure] = useState(null);
|
|
25
|
+
/** The user's line for a session the provider ended after warning it would. */
|
|
26
|
+
const [sessionEnded, setSessionEnded] = useState(null);
|
|
27
|
+
const [textDraft, setTextDraft] = useState('');
|
|
28
|
+
const [sessionActive, setSessionActive] = useState(false);
|
|
29
|
+
const [sessionPermissions, setSessionPermissions] = useState([]);
|
|
30
|
+
const [everConnected, setEverConnected] = useState(false);
|
|
31
|
+
const [videoPreview, setVideoPreview] = useState(null);
|
|
32
|
+
const [videoFacingMode, setVideoFacingMode] = useState('user');
|
|
33
|
+
const videoCaptureRef = useRef(null);
|
|
34
|
+
const captionsRef = useRef(captions);
|
|
35
|
+
const isMutedRef = useRef(isMuted);
|
|
36
|
+
const sessionPermissionsRef = useRef(sessionPermissions);
|
|
37
|
+
const statusRef = useRef(status);
|
|
38
|
+
captionsRef.current = captions;
|
|
39
|
+
isMutedRef.current = isMuted;
|
|
40
|
+
sessionPermissionsRef.current = sessionPermissions;
|
|
41
|
+
statusRef.current = status;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (sessionActive)
|
|
44
|
+
setEverConnected(true);
|
|
45
|
+
}, [sessionActive]);
|
|
46
|
+
const reportFailure = useCallback((err) => {
|
|
47
|
+
setFailure(clientFailure(err, labels));
|
|
48
|
+
}, [labels]);
|
|
49
|
+
const clearFailure = useCallback(() => {
|
|
50
|
+
setFailure(null);
|
|
51
|
+
}, []);
|
|
52
|
+
const reportSessionEnded = useCallback((session) => {
|
|
53
|
+
setSessionEnded(session.message);
|
|
54
|
+
}, []);
|
|
55
|
+
const clearSessionEnded = useCallback(() => {
|
|
56
|
+
setSessionEnded(null);
|
|
57
|
+
}, []);
|
|
58
|
+
const resetCaptions = useCallback(() => {
|
|
59
|
+
setCaptions(emptyLiveCaptionState());
|
|
60
|
+
}, []);
|
|
61
|
+
const stopVideo = useCallback(() => {
|
|
62
|
+
if (videoCaptureRef.current) {
|
|
63
|
+
videoCaptureRef.current.stop();
|
|
64
|
+
videoCaptureRef.current = null;
|
|
65
|
+
}
|
|
66
|
+
setVideoPreview(null);
|
|
67
|
+
setVideoFacingMode('user');
|
|
68
|
+
setIsVideoOn(false);
|
|
69
|
+
}, []);
|
|
70
|
+
return {
|
|
71
|
+
status,
|
|
72
|
+
setStatus,
|
|
73
|
+
connectPhase,
|
|
74
|
+
setConnectPhase,
|
|
75
|
+
levelsRef,
|
|
76
|
+
setInputLevel,
|
|
77
|
+
setOutputLevel,
|
|
78
|
+
isMuted,
|
|
79
|
+
setIsMuted,
|
|
80
|
+
isVideoOn,
|
|
81
|
+
setIsVideoOn,
|
|
82
|
+
activeTool,
|
|
83
|
+
setActiveTool,
|
|
84
|
+
captions,
|
|
85
|
+
setCaptions,
|
|
86
|
+
failure,
|
|
87
|
+
reportFailure,
|
|
88
|
+
clearFailure,
|
|
89
|
+
sessionEnded,
|
|
90
|
+
reportSessionEnded,
|
|
91
|
+
clearSessionEnded,
|
|
92
|
+
textDraft,
|
|
93
|
+
setTextDraft,
|
|
94
|
+
sessionActive,
|
|
95
|
+
setSessionActive,
|
|
96
|
+
sessionPermissions,
|
|
97
|
+
setSessionPermissions,
|
|
98
|
+
everConnected,
|
|
99
|
+
videoPreview,
|
|
100
|
+
setVideoPreview,
|
|
101
|
+
videoFacingMode,
|
|
102
|
+
setVideoFacingMode,
|
|
103
|
+
videoCaptureRef,
|
|
104
|
+
captionsRef,
|
|
105
|
+
isMutedRef,
|
|
106
|
+
sessionPermissionsRef,
|
|
107
|
+
statusRef,
|
|
108
|
+
resetCaptions,
|
|
109
|
+
stopVideo,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Gate prompt + live turn-tool event wiring. */
|
|
113
|
+
export function useLiveRunnerGate(args) {
|
|
114
|
+
const [gatePrompt, setGatePrompt] = useState(null);
|
|
115
|
+
const gatePromptRef = useRef(gatePrompt);
|
|
116
|
+
gatePromptRef.current = gatePrompt;
|
|
117
|
+
const gateResolverRef = useRef(null);
|
|
118
|
+
const gateRejectRef = useRef(null);
|
|
119
|
+
const answerGate = useCallback((answer) => {
|
|
120
|
+
gateResolverRef.current?.(answer);
|
|
121
|
+
gateResolverRef.current = null;
|
|
122
|
+
gateRejectRef.current = null;
|
|
123
|
+
setGatePrompt(null);
|
|
124
|
+
}, []);
|
|
125
|
+
const handleLiveTurnEvent = useCallback((event) => {
|
|
126
|
+
args.setCaptions((prev) => applyLiveTurnEvent(prev, event));
|
|
127
|
+
applyLiveTurnToolEvent(event, {
|
|
128
|
+
gateCallId: gatePromptRef.current?.callId,
|
|
129
|
+
withdrawGate: () => {
|
|
130
|
+
answerGate('withdrawn');
|
|
131
|
+
},
|
|
132
|
+
clearInterim: () => {
|
|
133
|
+
args.setCaptions((prev) => clearLiveCaptionInterim(prev));
|
|
134
|
+
},
|
|
135
|
+
clearActiveTool: () => {
|
|
136
|
+
args.setActiveTool(null);
|
|
137
|
+
},
|
|
138
|
+
reportFailure: args.reportFailure,
|
|
139
|
+
setActiveTool: args.setActiveTool,
|
|
140
|
+
});
|
|
141
|
+
}, [args, answerGate]);
|
|
142
|
+
const waitForGateDecision = useCallback((prompt) => {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
setGatePrompt(prompt);
|
|
145
|
+
gateResolverRef.current = resolve;
|
|
146
|
+
gateRejectRef.current = reject;
|
|
147
|
+
});
|
|
148
|
+
}, []);
|
|
149
|
+
const resolveGateDecision = useCallback((resolution) => {
|
|
150
|
+
answerGate(resolution);
|
|
151
|
+
}, [answerGate]);
|
|
152
|
+
const cancelGateDecision = useCallback(() => {
|
|
153
|
+
// labels-exempt: internal diagnostic; the user reads error.cancelled
|
|
154
|
+
gateRejectRef.current?.(new TheoremError('cancelled', 'live session ended with a gate open'));
|
|
155
|
+
gateResolverRef.current = null;
|
|
156
|
+
gateRejectRef.current = null;
|
|
157
|
+
setGatePrompt(null);
|
|
158
|
+
}, []);
|
|
159
|
+
return {
|
|
160
|
+
gatePrompt,
|
|
161
|
+
gatePromptRef,
|
|
162
|
+
handleLiveTurnEvent,
|
|
163
|
+
waitForGateDecision,
|
|
164
|
+
resolveGateDecision,
|
|
165
|
+
cancelGateDecision,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type SessionEventOf, type TurnEvent } from '@theoremjs/agents';
|
|
2
|
+
import { type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
|
|
3
|
+
import { type LiveCaptionState } from '../../client/live/live-captions.ts';
|
|
4
|
+
import type { LiveCallOptions } from '../../client/live/live-page-tool.ts';
|
|
5
|
+
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
6
|
+
import type { LiveGateAnswer, LiveToolGatePrompt } from '../../client/live/live-tool.ts';
|
|
7
|
+
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
8
|
+
import { type LiveConnection, LiveSessionClient } from '../../client/live-client.ts';
|
|
9
|
+
import type { TraceFeed } from '../../client/trace-feed.ts';
|
|
10
|
+
export type LiveClientBindings = LiveCallOptions & {
|
|
11
|
+
voiceAvailable: boolean;
|
|
12
|
+
/** Where the session's trace records go, when the relay delivers them. */
|
|
13
|
+
traces: TraceFeed;
|
|
14
|
+
handleLiveTurnEvent: (event: TurnEvent) => void;
|
|
15
|
+
waitForGateDecision: (prompt: LiveToolGatePrompt) => Promise<LiveGateAnswer>;
|
|
16
|
+
gatePromptRef: MutableRefObject<LiveToolGatePrompt | null>;
|
|
17
|
+
isMutedRef: MutableRefObject<boolean>;
|
|
18
|
+
sessionPermissionsRef: MutableRefObject<string[]>;
|
|
19
|
+
videoCaptureRef: MutableRefObject<LiveVideoCapture | null>;
|
|
20
|
+
setConnectPhase: Dispatch<SetStateAction<LiveConnectPhase | null>>;
|
|
21
|
+
setStatus: Dispatch<SetStateAction<LiveSessionStatus>>;
|
|
22
|
+
setSessionActive: Dispatch<SetStateAction<boolean>>;
|
|
23
|
+
reportFailure: (err: unknown) => void;
|
|
24
|
+
clearFailure: () => void;
|
|
25
|
+
reportSessionEnded: (session: SessionEventOf<'ended'>) => void;
|
|
26
|
+
clearSessionEnded: () => void;
|
|
27
|
+
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
28
|
+
setInputLevel: Dispatch<SetStateAction<number>>;
|
|
29
|
+
setOutputLevel: Dispatch<SetStateAction<number>>;
|
|
30
|
+
setActiveTool: Dispatch<SetStateAction<string | null>>;
|
|
31
|
+
setSessionPermissions: Dispatch<SetStateAction<string[]>>;
|
|
32
|
+
setVideoPreview: Dispatch<SetStateAction<HTMLVideoElement | null>>;
|
|
33
|
+
setVideoFacingMode: Dispatch<SetStateAction<LiveFacingMode>>;
|
|
34
|
+
setIsVideoOn: Dispatch<SetStateAction<boolean>>;
|
|
35
|
+
};
|
|
36
|
+
/** Owns LiveSessionClient construction + reconnect identity. */
|
|
37
|
+
export declare function useLiveSessionClient(bindings: LiveClientBindings): {
|
|
38
|
+
clientRef: import("react").RefObject<LiveSessionClient | null>;
|
|
39
|
+
ensureClient: (connection: LiveConnection) => LiveSessionClient;
|
|
40
|
+
clearClient: () => void;
|
|
41
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { TheoremError } from '@theoremjs/agents';
|
|
2
|
+
import { useCallback, useRef, } from 'react';
|
|
3
|
+
import { applyLiveTranscript } from "../../client/live/live-captions.js";
|
|
4
|
+
import { runLiveToolCall } from "../../client/live/run-live-tool-call.js";
|
|
5
|
+
import { LiveSessionClient } from "../../client/live-client.js";
|
|
6
|
+
function onLiveStatusChange(next, bindings) {
|
|
7
|
+
bindings.setStatus(next);
|
|
8
|
+
if (next === 'listening' || next === 'ready') {
|
|
9
|
+
bindings.setSessionActive(true);
|
|
10
|
+
bindings.clearFailure();
|
|
11
|
+
bindings.clearSessionEnded();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (next !== 'disconnected' && next !== 'error')
|
|
15
|
+
return;
|
|
16
|
+
bindings.setSessionActive(false);
|
|
17
|
+
if (bindings.videoCaptureRef.current) {
|
|
18
|
+
bindings.videoCaptureRef.current.stop();
|
|
19
|
+
bindings.videoCaptureRef.current = null;
|
|
20
|
+
}
|
|
21
|
+
bindings.setVideoPreview(null);
|
|
22
|
+
bindings.setVideoFacingMode('user');
|
|
23
|
+
bindings.setIsVideoOn(false);
|
|
24
|
+
}
|
|
25
|
+
function onLiveTranscript(text, isUser, meta, bindings) {
|
|
26
|
+
// why: A thought or a tool call in the same batch is already queued; each folds onto the last.
|
|
27
|
+
bindings.setCaptions((prev) => applyLiveTranscript(prev, text, isUser, meta?.interim));
|
|
28
|
+
}
|
|
29
|
+
async function onLiveToolCall(name, toolArgs, meta, bindings, clientRef) {
|
|
30
|
+
bindings.setActiveTool(name);
|
|
31
|
+
bindings.clearFailure();
|
|
32
|
+
const client = clientRef.current;
|
|
33
|
+
if (!client) {
|
|
34
|
+
// labels-exempt: internal diagnostic; the user reads error.request
|
|
35
|
+
bindings.reportFailure(new TheoremError('request', 'live tool call with no session client'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await runLiveToolCall({
|
|
40
|
+
pageTools: bindings.pageTools,
|
|
41
|
+
executeToolOnRelay: (call) => client.executeToolOnRelay(call),
|
|
42
|
+
name,
|
|
43
|
+
toolArgs,
|
|
44
|
+
callId: meta.callId,
|
|
45
|
+
operationId: meta.operationId,
|
|
46
|
+
sessionPermissions: bindings.sessionPermissionsRef.current,
|
|
47
|
+
setSessionPermissions: (next) => {
|
|
48
|
+
bindings.sessionPermissionsRef.current = next;
|
|
49
|
+
bindings.setSessionPermissions(next);
|
|
50
|
+
},
|
|
51
|
+
waitForGateDecision: bindings.waitForGateDecision,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
bindings.reportFailure(err);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (!bindings.gatePromptRef.current)
|
|
59
|
+
bindings.setActiveTool(null);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Owns LiveSessionClient construction + reconnect identity. */
|
|
63
|
+
export function useLiveSessionClient(bindings) {
|
|
64
|
+
const clientRef = useRef(null);
|
|
65
|
+
const bindingsRef = useRef(bindings);
|
|
66
|
+
bindingsRef.current = bindings;
|
|
67
|
+
const ensureClient = useCallback((connection) => {
|
|
68
|
+
if (clientRef.current)
|
|
69
|
+
return clientRef.current;
|
|
70
|
+
const b = bindingsRef.current;
|
|
71
|
+
const client = new LiveSessionClient({
|
|
72
|
+
...connection,
|
|
73
|
+
slots: b.slots,
|
|
74
|
+
context: b.context,
|
|
75
|
+
voiceIngress: b.voiceAvailable,
|
|
76
|
+
onConnectPhase: (phase) => {
|
|
77
|
+
b.setConnectPhase(phase);
|
|
78
|
+
},
|
|
79
|
+
onStatusChange: (next) => {
|
|
80
|
+
onLiveStatusChange(next, bindingsRef.current);
|
|
81
|
+
},
|
|
82
|
+
onTranscript: (text, isUser, meta) => {
|
|
83
|
+
onLiveTranscript(text, isUser, meta, bindingsRef.current);
|
|
84
|
+
},
|
|
85
|
+
onTurnEvent: (event) => {
|
|
86
|
+
// why: An `unsupported` event is the host's to read; Theorem's UI shows nothing for it.
|
|
87
|
+
if (event.type === 'unsupported')
|
|
88
|
+
return;
|
|
89
|
+
// why: A `malformed` one was left out and the call goes on; the user reads that part was skipped.
|
|
90
|
+
if (event.type === 'malformed') {
|
|
91
|
+
bindingsRef.current.reportFailure(event.error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
bindingsRef.current.handleLiveTurnEvent(event);
|
|
95
|
+
},
|
|
96
|
+
onTrace: (record) => {
|
|
97
|
+
bindingsRef.current.traces.push(record);
|
|
98
|
+
},
|
|
99
|
+
onVolumeLevel: (level, isUser) => {
|
|
100
|
+
const current = bindingsRef.current;
|
|
101
|
+
if (isUser) {
|
|
102
|
+
current.setInputLevel(current.isMutedRef.current ? 0 : level);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
current.setOutputLevel(level);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
onError: (err) => {
|
|
109
|
+
bindingsRef.current.reportFailure(err);
|
|
110
|
+
},
|
|
111
|
+
onSessionEnded: (session) => {
|
|
112
|
+
bindingsRef.current.reportSessionEnded(session);
|
|
113
|
+
},
|
|
114
|
+
onToolCall: (name, args, meta) => onLiveToolCall(name, args, meta, bindingsRef.current, clientRef),
|
|
115
|
+
});
|
|
116
|
+
clientRef.current = client;
|
|
117
|
+
return client;
|
|
118
|
+
}, []);
|
|
119
|
+
const clearClient = useCallback(() => {
|
|
120
|
+
if (clientRef.current) {
|
|
121
|
+
clientRef.current.disconnect();
|
|
122
|
+
clientRef.current = null;
|
|
123
|
+
}
|
|
124
|
+
}, []);
|
|
125
|
+
return { clientRef, ensureClient, clearClient };
|
|
126
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type LabelOverrides } from '@theoremjs/agents';
|
|
2
|
+
import type { ProfileInputsInterface } from '@theoremjs/agents/interface';
|
|
3
|
+
import { type VoiceRecorderFailureCode } from '../client/voice-recorder.ts';
|
|
4
|
+
/** A voice note that could not be recorded or staged. */
|
|
5
|
+
export type VoiceFailure = {
|
|
6
|
+
/** A recorder failure, or `too_many_files` when the message is already full. */
|
|
7
|
+
code: VoiceRecorderFailureCode | 'too_many_files';
|
|
8
|
+
/** What the user reads: the profile labels' `voice.<code>` (or the file-limit line). */
|
|
9
|
+
error: string;
|
|
10
|
+
/** Raw detail for the builder; never shown to the user. */
|
|
11
|
+
errorInternal?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function useComposerVoice(args: {
|
|
14
|
+
inputs: ProfileInputsInterface;
|
|
15
|
+
/** The interface's `labels`: the profile's wording for voice failures. */
|
|
16
|
+
labels: LabelOverrides;
|
|
17
|
+
pendingFiles: readonly File[];
|
|
18
|
+
onVoiceStaged?: (file: File) => void;
|
|
19
|
+
onVoiceClear?: () => void;
|
|
20
|
+
}): {
|
|
21
|
+
recording: boolean;
|
|
22
|
+
inputLevel: number;
|
|
23
|
+
failure: VoiceFailure | null;
|
|
24
|
+
setFailure: import("react").Dispatch<import("react").SetStateAction<VoiceFailure | null>>;
|
|
25
|
+
toggleRecording: () => Promise<void>;
|
|
26
|
+
discardRecordingOrVoice: () => void;
|
|
27
|
+
disposeRecorder: () => void;
|
|
28
|
+
};
|