@theoremjs/react 0.0.0-stage → 0.3.0
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 +246 -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-transport.d.ts +48 -0
- package/client/decision-transport.js +102 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -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 +32 -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 +42 -0
- package/client/host-transport.d.ts +68 -0
- package/client/host-transport.js +74 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +20 -0
- package/client/index.js +18 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +69 -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 +23 -0
- package/client/live/live-captions.js +70 -0
- package/client/live/live-mic-forward.d.ts +29 -0
- package/client/live/live-mic-forward.js +37 -0
- package/client/live/live-state.d.ts +11 -0
- package/client/live/live-state.js +20 -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 +17 -0
- package/client/live/run-live-tool-call.js +32 -0
- package/client/live-client.d.ts +141 -0
- package/client/live-client.js +638 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +110 -0
- package/client/live-messages.js +67 -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/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +61 -0
- package/client/run-commit.js +124 -0
- package/client/run-session.d.ts +80 -0
- package/client/run-session.js +215 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +178 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +314 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -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 +275 -0
- package/client/tool-resume.d.ts +33 -0
- package/client/tool-resume.js +11 -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 +416 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +293 -0
- package/client/transcript-groups.d.ts +114 -0
- package/client/transcript-groups.js +167 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +176 -0
- package/client/transport.js +256 -0
- package/client/turn-client.d.ts +35 -0
- package/client/turn-client.js +93 -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 +165 -0
- package/client/walk-away.d.ts +34 -0
- package/client/walk-away.js +48 -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 +10 -0
- package/components/InkWaveform.js +108 -0
- package/components/live/use-live-runner-controls.d.ts +48 -0
- package/components/live/use-live-runner-controls.js +100 -0
- package/components/live/use-live-runner-model.d.ts +45 -0
- package/components/live/use-live-runner-model.js +175 -0
- package/components/live/use-live-runner-ui.d.ts +65 -0
- package/components/live/use-live-runner-ui.js +159 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +123 -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 +16 -0
- package/components/use-voice-playback.d.ts +16 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +8 -0
- package/hooks/use-described.js +23 -0
- package/hooks/use-theorem-chat-actions.d.ts +75 -0
- package/hooks/use-theorem-chat-actions.js +265 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +118 -0
- package/hooks/use-theorem-chat.d.ts +63 -0
- package/hooks/use-theorem-chat.js +340 -0
- package/hooks/use-theorem-decision.d.ts +23 -0
- package/hooks/use-theorem-decision.js +75 -0
- package/hooks/use-theorem-host.d.ts +44 -0
- package/hooks/use-theorem-host.js +117 -0
- package/hooks/use-theorem-interface.d.ts +19 -0
- package/hooks/use-theorem-interface.js +25 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +14 -0
- package/index.js +14 -0
- package/live.d.ts +8 -0
- package/live.js +10 -0
- package/package.json +73 -4
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +34 -0
- package/server/decision-handler.js +55 -0
- package/server/handler.d.ts +114 -0
- package/server/handler.js +568 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +16 -0
- package/server/mod.js +15 -0
- package/server/request-check.d.ts +16 -0
- package/server/request-check.js +34 -0
- package/server/session-store.d.ts +74 -0
- package/server/session-store.js +49 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/walk-away.d.ts +22 -0
- package/server/walk-away.js +41 -0
- package/styles/ink-controls.css +134 -0
- package/ui/ChatComposerBar.d.ts +39 -0
- package/ui/ChatComposerBar.js +251 -0
- package/ui/ChatTranscript.d.ts +30 -0
- package/ui/ChatTranscript.js +474 -0
- package/ui/ConsoleFrame.d.ts +47 -0
- package/ui/ConsoleFrame.js +46 -0
- package/ui/DecisionAnswers.d.ts +15 -0
- package/ui/DecisionAnswers.js +102 -0
- package/ui/LiveRunner.d.ts +28 -0
- package/ui/LiveRunner.js +193 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +122 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +151 -0
- package/ui/SidePanel.d.ts +57 -0
- package/ui/SidePanel.js +50 -0
- package/ui/StateFields.d.ts +36 -0
- package/ui/StateFields.js +96 -0
- package/ui/TheoremChat.d.ts +71 -0
- package/ui/TheoremChat.js +146 -0
- package/ui/TheoremDecision.d.ts +38 -0
- package/ui/TheoremDecision.js +87 -0
- package/ui/TheoremHost.d.ts +38 -0
- package/ui/TheoremHost.js +150 -0
- package/ui/ToolGateCard.d.ts +28 -0
- package/ui/ToolGateCard.js +100 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +60 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +86 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +235 -0
- package/ui/TraceGuardrails.d.ts +10 -0
- package/ui/TraceGuardrails.js +56 -0
- package/ui/TraceInspector.d.ts +6 -0
- package/ui/TraceInspector.js +189 -0
- package/ui/TraceInspectorPanel.d.ts +24 -0
- package/ui/TraceInspectorPanel.js +57 -0
- package/ui/TraceOverview.d.ts +34 -0
- package/ui/TraceOverview.js +96 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +188 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +107 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +113 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +132 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/built/theme.css +774 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +631 -0
- package/ui/built/theorem.variants.d.ts +16 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +163 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +37 -0
- package/ui/index.d.ts +25 -0
- package/ui/index.js +29 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +61 -0
- package/ui/labels.d.ts +1153 -0
- package/ui/labels.js +603 -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 +83 -0
- package/ui/trace-palette.d.ts +24 -0
- package/ui/trace-palette.js +24 -0
- package/ui/trace-patterns.d.ts +32 -0
- package/ui/trace-patterns.js +44 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +39 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LiveProfileInterface } from '@theoremjs/agents/interface';
|
|
2
|
+
import type { LiveCaptionState, LiveCaptionTurn } from '../../client/live/live-captions.ts';
|
|
3
|
+
import type { LiveConnection } from '../../client/live-client.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Owns LiveRunner state, session client, and stage callbacks.
|
|
6
|
+
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
7
|
+
* its id; the playground returns its draft as the relay's open message.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useLiveRunnerModel(iface: LiveProfileInterface, connection: () => LiveConnection | Promise<LiveConnection>): {
|
|
10
|
+
handle: string;
|
|
11
|
+
traces: import("../../index.ts").TraceFeed;
|
|
12
|
+
callStarted: boolean;
|
|
13
|
+
pastCalls: LiveCaptionTurn[][];
|
|
14
|
+
captions: LiveCaptionState;
|
|
15
|
+
failure: import("../../index.ts").ClientFailure | null;
|
|
16
|
+
sessionEnded: string | null;
|
|
17
|
+
inputLevel: number;
|
|
18
|
+
isMuted: boolean;
|
|
19
|
+
isVideoOn: boolean;
|
|
20
|
+
outputLevel: number;
|
|
21
|
+
sessionActive: boolean;
|
|
22
|
+
liveState: import("../../index.ts").LiveState;
|
|
23
|
+
activeTool: string | null;
|
|
24
|
+
status: import("../../index.ts").LiveSessionStatus;
|
|
25
|
+
textAvailable: boolean;
|
|
26
|
+
textDraft: string;
|
|
27
|
+
toolActive: boolean;
|
|
28
|
+
videoAvailable: boolean;
|
|
29
|
+
videoFacingMode: import("../../client/live/live-video.ts").LiveFacingMode;
|
|
30
|
+
videoPreview: HTMLVideoElement | null;
|
|
31
|
+
voiceAvailable: boolean;
|
|
32
|
+
canRestart: boolean;
|
|
33
|
+
gatePrompt: import("../../index.ts").LiveToolGatePrompt | null;
|
|
34
|
+
setTextDraft: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
35
|
+
handleEnd: () => void;
|
|
36
|
+
startCall: (options: {
|
|
37
|
+
video: boolean;
|
|
38
|
+
}) => void;
|
|
39
|
+
handleRestart: () => Promise<void>;
|
|
40
|
+
handleSendText: () => void;
|
|
41
|
+
handleToggleMic: () => void;
|
|
42
|
+
handleFlipCamera: () => Promise<void>;
|
|
43
|
+
handleToggleVideo: () => Promise<void>;
|
|
44
|
+
resolveGateDecision: (resolution: import("../../index.ts").ToolGateResolution) => void;
|
|
45
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { liveIngressEnabledFromSpec } from '@theoremjs/agents';
|
|
3
|
+
import { liveState } from "../../client/live/live-state.js";
|
|
4
|
+
import { createTraceFeed } from "../../client/trace-feed.js";
|
|
5
|
+
import { useLiveRunnerControls } from "./use-live-runner-controls.js";
|
|
6
|
+
import { useLiveRunnerGate, useLiveRunnerUiState } from "./use-live-runner-ui.js";
|
|
7
|
+
import { useLiveSessionClient } from "./use-live-session-client.js";
|
|
8
|
+
/**
|
|
9
|
+
* Calls start from the landing (voice, or video + voice), not on mount. Once
|
|
10
|
+
* started, the call view stays until a reload; ending leaves Start call. Each
|
|
11
|
+
* restart is a new conversation; earlier calls' captions stay, above a divider.
|
|
12
|
+
*/
|
|
13
|
+
function useLiveCallLifecycle(controls, captionsRef, sessionActive, videoAvailable) {
|
|
14
|
+
const [callStarted, setCallStarted] = useState(false);
|
|
15
|
+
const [ended, setEnded] = useState(false);
|
|
16
|
+
const videoOnConnectRef = useRef(false);
|
|
17
|
+
const startCall = useCallback((options) => {
|
|
18
|
+
videoOnConnectRef.current = options.video && videoAvailable;
|
|
19
|
+
setCallStarted(true);
|
|
20
|
+
setEnded(false);
|
|
21
|
+
void controls.startSession();
|
|
22
|
+
}, [controls, videoAvailable]);
|
|
23
|
+
// Camera capture needs a live session, so a video call turns it on once connected.
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!sessionActive || !videoOnConnectRef.current)
|
|
26
|
+
return;
|
|
27
|
+
videoOnConnectRef.current = false;
|
|
28
|
+
void controls.handleToggleVideo();
|
|
29
|
+
}, [controls, sessionActive]);
|
|
30
|
+
const handleEnd = useCallback(() => {
|
|
31
|
+
videoOnConnectRef.current = false;
|
|
32
|
+
controls.teardownSession();
|
|
33
|
+
setEnded(true);
|
|
34
|
+
}, [controls]);
|
|
35
|
+
const [pastCalls, setPastCalls] = useState([]);
|
|
36
|
+
const handleRestart = useCallback(async () => {
|
|
37
|
+
const previous = captionsRef.current.turns;
|
|
38
|
+
if (previous.length > 0)
|
|
39
|
+
setPastCalls((calls) => [...calls, previous]);
|
|
40
|
+
setEnded(false);
|
|
41
|
+
await controls.handleRestart();
|
|
42
|
+
}, [controls, captionsRef]);
|
|
43
|
+
const teardownSessionRef = useRef(controls.teardownSession);
|
|
44
|
+
teardownSessionRef.current = controls.teardownSession;
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return () => {
|
|
47
|
+
teardownSessionRef.current();
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
return { callStarted, ended, pastCalls, startCall, handleEnd, handleRestart };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Owns LiveRunner state, session client, and stage callbacks.
|
|
54
|
+
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
55
|
+
* its id; the playground returns its draft as the relay's open message.
|
|
56
|
+
*/
|
|
57
|
+
export function useLiveRunnerModel(iface, connection) {
|
|
58
|
+
const ui = useLiveRunnerUiState(iface.lexicon);
|
|
59
|
+
const gate = useLiveRunnerGate({
|
|
60
|
+
setCaptions: ui.setCaptions,
|
|
61
|
+
setActiveTool: ui.setActiveTool,
|
|
62
|
+
reportFailure: ui.reportFailure,
|
|
63
|
+
});
|
|
64
|
+
const connectionRef = useRef(connection);
|
|
65
|
+
connectionRef.current = connection;
|
|
66
|
+
const voiceAvailable = liveIngressEnabledFromSpec(iface.live.ingress, 'audio');
|
|
67
|
+
const videoAvailable = liveIngressEnabledFromSpec(iface.live.ingress, 'video');
|
|
68
|
+
const textAvailable = liveIngressEnabledFromSpec(iface.live.ingress, 'text');
|
|
69
|
+
const state = useMemo(() => liveState({
|
|
70
|
+
status: ui.status,
|
|
71
|
+
connectPhase: ui.connectPhase,
|
|
72
|
+
toolName: ui.activeTool,
|
|
73
|
+
isMuted: ui.isMuted,
|
|
74
|
+
voiceEnabled: voiceAvailable,
|
|
75
|
+
}), [ui.activeTool, ui.connectPhase, ui.isMuted, ui.status, voiceAvailable]);
|
|
76
|
+
// One feed per runner: every session it opens adds its records.
|
|
77
|
+
const traces = useMemo(createTraceFeed, []);
|
|
78
|
+
const { clientRef, ensureClient, clearClient } = useLiveSessionClient({
|
|
79
|
+
voiceAvailable,
|
|
80
|
+
traces,
|
|
81
|
+
handleLiveTurnEvent: gate.handleLiveTurnEvent,
|
|
82
|
+
waitForGateDecision: gate.waitForGateDecision,
|
|
83
|
+
captionsRef: ui.captionsRef,
|
|
84
|
+
gatePromptRef: gate.gatePromptRef,
|
|
85
|
+
isMutedRef: ui.isMutedRef,
|
|
86
|
+
sessionPermissionsRef: ui.sessionPermissionsRef,
|
|
87
|
+
videoCaptureRef: ui.videoCaptureRef,
|
|
88
|
+
setConnectPhase: ui.setConnectPhase,
|
|
89
|
+
setStatus: ui.setStatus,
|
|
90
|
+
setSessionActive: ui.setSessionActive,
|
|
91
|
+
reportFailure: ui.reportFailure,
|
|
92
|
+
clearFailure: ui.clearFailure,
|
|
93
|
+
reportSessionEnded: ui.reportSessionEnded,
|
|
94
|
+
clearSessionEnded: ui.clearSessionEnded,
|
|
95
|
+
setCaptions: ui.setCaptions,
|
|
96
|
+
setInputLevel: ui.setInputLevel,
|
|
97
|
+
setOutputLevel: ui.setOutputLevel,
|
|
98
|
+
setActiveTool: ui.setActiveTool,
|
|
99
|
+
setSessionPermissions: ui.setSessionPermissions,
|
|
100
|
+
setVideoPreview: ui.setVideoPreview,
|
|
101
|
+
setVideoFacingMode: ui.setVideoFacingMode,
|
|
102
|
+
setIsVideoOn: ui.setIsVideoOn,
|
|
103
|
+
});
|
|
104
|
+
const controls = useLiveRunnerControls({
|
|
105
|
+
clientRef,
|
|
106
|
+
videoCaptureRef: ui.videoCaptureRef,
|
|
107
|
+
captionsRef: ui.captionsRef,
|
|
108
|
+
connectionRef,
|
|
109
|
+
statusRef: ui.statusRef,
|
|
110
|
+
isMutedRef: ui.isMutedRef,
|
|
111
|
+
sessionPermissionsRef: ui.sessionPermissionsRef,
|
|
112
|
+
ensureClient,
|
|
113
|
+
clearClient,
|
|
114
|
+
cancelGateDecision: gate.cancelGateDecision,
|
|
115
|
+
stopVideo: ui.stopVideo,
|
|
116
|
+
resetCaptions: ui.resetCaptions,
|
|
117
|
+
sessionActive: ui.sessionActive,
|
|
118
|
+
textAvailable,
|
|
119
|
+
videoAvailable,
|
|
120
|
+
voiceAvailable,
|
|
121
|
+
isVideoOn: ui.isVideoOn,
|
|
122
|
+
textDraft: ui.textDraft,
|
|
123
|
+
setTextDraft: ui.setTextDraft,
|
|
124
|
+
setCaptions: ui.setCaptions,
|
|
125
|
+
reportFailure: ui.reportFailure,
|
|
126
|
+
clearFailure: ui.clearFailure,
|
|
127
|
+
setIsMuted: ui.setIsMuted,
|
|
128
|
+
setSessionActive: ui.setSessionActive,
|
|
129
|
+
setSessionPermissions: ui.setSessionPermissions,
|
|
130
|
+
setStatus: ui.setStatus,
|
|
131
|
+
setConnectPhase: ui.setConnectPhase,
|
|
132
|
+
setInputLevel: ui.setInputLevel,
|
|
133
|
+
setOutputLevel: ui.setOutputLevel,
|
|
134
|
+
setVideoPreview: ui.setVideoPreview,
|
|
135
|
+
setVideoFacingMode: ui.setVideoFacingMode,
|
|
136
|
+
setIsVideoOn: ui.setIsVideoOn,
|
|
137
|
+
});
|
|
138
|
+
const { callStarted, ended, pastCalls, startCall, handleEnd, handleRestart } = useLiveCallLifecycle(controls, ui.captionsRef, ui.sessionActive, videoAvailable);
|
|
139
|
+
return {
|
|
140
|
+
handle: iface.identity.handle,
|
|
141
|
+
traces,
|
|
142
|
+
callStarted,
|
|
143
|
+
pastCalls,
|
|
144
|
+
captions: ui.captions,
|
|
145
|
+
failure: ui.failure,
|
|
146
|
+
sessionEnded: ui.sessionEnded,
|
|
147
|
+
inputLevel: ui.inputLevel,
|
|
148
|
+
isMuted: ui.isMuted,
|
|
149
|
+
isVideoOn: ui.isVideoOn,
|
|
150
|
+
outputLevel: ui.outputLevel,
|
|
151
|
+
sessionActive: ui.sessionActive,
|
|
152
|
+
liveState: state,
|
|
153
|
+
activeTool: ui.activeTool,
|
|
154
|
+
status: ui.status,
|
|
155
|
+
textAvailable,
|
|
156
|
+
textDraft: ui.textDraft,
|
|
157
|
+
toolActive: ui.activeTool !== null || gate.gatePrompt !== null,
|
|
158
|
+
videoAvailable,
|
|
159
|
+
videoFacingMode: ui.videoFacingMode,
|
|
160
|
+
videoPreview: ui.videoPreview,
|
|
161
|
+
voiceAvailable,
|
|
162
|
+
// Also after ending (or failing) before the first connect, so the call can't get stuck.
|
|
163
|
+
canRestart: !ui.sessionActive && ui.status !== 'connecting' && (ui.everConnected || ended || ui.failure !== null),
|
|
164
|
+
gatePrompt: gate.gatePrompt,
|
|
165
|
+
setTextDraft: ui.setTextDraft,
|
|
166
|
+
handleEnd,
|
|
167
|
+
startCall,
|
|
168
|
+
handleRestart,
|
|
169
|
+
handleSendText: controls.handleSendText,
|
|
170
|
+
handleToggleMic: controls.handleToggleMic,
|
|
171
|
+
handleFlipCamera: controls.handleFlipCamera,
|
|
172
|
+
handleToggleVideo: controls.handleToggleVideo,
|
|
173
|
+
resolveGateDecision: gate.resolveGateDecision,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
|
+
import { type LexiconOverrides, type SessionEventOf } from '@theoremjs/agents';
|
|
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 { LiveGateAnswer, LiveToolGatePrompt } from '../../client/live/live-tool.ts';
|
|
7
|
+
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
8
|
+
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
9
|
+
import type { ToolGateResolution } from '../../client/tool-resume.ts';
|
|
10
|
+
/** UI + media state bag for the live runner. `lexicon` is the interface's: the profile's wording. */
|
|
11
|
+
export declare function useLiveRunnerUiState(lexicon: LexiconOverrides): {
|
|
12
|
+
status: LiveSessionStatus;
|
|
13
|
+
setStatus: Dispatch<SetStateAction<LiveSessionStatus>>;
|
|
14
|
+
connectPhase: LiveConnectPhase | null;
|
|
15
|
+
setConnectPhase: Dispatch<SetStateAction<LiveConnectPhase | null>>;
|
|
16
|
+
inputLevel: number;
|
|
17
|
+
setInputLevel: Dispatch<SetStateAction<number>>;
|
|
18
|
+
outputLevel: number;
|
|
19
|
+
setOutputLevel: Dispatch<SetStateAction<number>>;
|
|
20
|
+
isMuted: boolean;
|
|
21
|
+
setIsMuted: Dispatch<SetStateAction<boolean>>;
|
|
22
|
+
isVideoOn: boolean;
|
|
23
|
+
setIsVideoOn: Dispatch<SetStateAction<boolean>>;
|
|
24
|
+
activeTool: string | null;
|
|
25
|
+
setActiveTool: Dispatch<SetStateAction<string | null>>;
|
|
26
|
+
captions: LiveCaptionState;
|
|
27
|
+
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
28
|
+
failure: ClientFailure | null;
|
|
29
|
+
reportFailure: (err: unknown) => void;
|
|
30
|
+
clearFailure: () => void;
|
|
31
|
+
sessionEnded: string | null;
|
|
32
|
+
reportSessionEnded: (session: SessionEventOf<"ended">) => void;
|
|
33
|
+
clearSessionEnded: () => void;
|
|
34
|
+
textDraft: string;
|
|
35
|
+
setTextDraft: Dispatch<SetStateAction<string>>;
|
|
36
|
+
sessionActive: boolean;
|
|
37
|
+
setSessionActive: Dispatch<SetStateAction<boolean>>;
|
|
38
|
+
sessionPermissions: string[];
|
|
39
|
+
setSessionPermissions: Dispatch<SetStateAction<string[]>>;
|
|
40
|
+
everConnected: boolean;
|
|
41
|
+
videoPreview: HTMLVideoElement | null;
|
|
42
|
+
setVideoPreview: Dispatch<SetStateAction<HTMLVideoElement | null>>;
|
|
43
|
+
videoFacingMode: LiveFacingMode;
|
|
44
|
+
setVideoFacingMode: Dispatch<SetStateAction<LiveFacingMode>>;
|
|
45
|
+
videoCaptureRef: import("react").RefObject<LiveVideoCapture | null>;
|
|
46
|
+
captionsRef: import("react").RefObject<LiveCaptionState>;
|
|
47
|
+
isMutedRef: import("react").RefObject<boolean>;
|
|
48
|
+
sessionPermissionsRef: import("react").RefObject<string[]>;
|
|
49
|
+
statusRef: import("react").RefObject<LiveSessionStatus>;
|
|
50
|
+
resetCaptions: () => void;
|
|
51
|
+
stopVideo: () => void;
|
|
52
|
+
};
|
|
53
|
+
/** Gate prompt + live turn-tool event wiring. */
|
|
54
|
+
export declare function useLiveRunnerGate(args: {
|
|
55
|
+
setCaptions: Dispatch<SetStateAction<LiveCaptionState>>;
|
|
56
|
+
setActiveTool: Dispatch<SetStateAction<string | null>>;
|
|
57
|
+
reportFailure: (err: unknown) => void;
|
|
58
|
+
}): {
|
|
59
|
+
gatePrompt: LiveToolGatePrompt | null;
|
|
60
|
+
gatePromptRef: import("react").RefObject<LiveToolGatePrompt | null>;
|
|
61
|
+
handleLiveTurnEvent: (event: Parameters<typeof applyLiveTurnToolEvent>[0]) => void;
|
|
62
|
+
waitForGateDecision: (prompt: LiveToolGatePrompt) => Promise<LiveGateAnswer>;
|
|
63
|
+
resolveGateDecision: (resolution: ToolGateResolution) => void;
|
|
64
|
+
cancelGateDecision: () => void;
|
|
65
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { TheoremError } from '@theoremjs/agents';
|
|
3
|
+
import { clientFailure } from "../../client/failure.js";
|
|
4
|
+
import { applyLiveTurnToolEvent } from "../../client/live/apply-live-turn-tool-event.js";
|
|
5
|
+
import { clearLiveCaptionInterim, emptyLiveCaptionState, } from "../../client/live/live-captions.js";
|
|
6
|
+
/** UI + media state bag for the live runner. `lexicon` is the interface's: the profile's wording. */
|
|
7
|
+
export function useLiveRunnerUiState(lexicon) {
|
|
8
|
+
const [status, setStatus] = useState('disconnected');
|
|
9
|
+
const [connectPhase, setConnectPhase] = useState(null);
|
|
10
|
+
const [inputLevel, setInputLevel] = useState(0);
|
|
11
|
+
const [outputLevel, setOutputLevel] = useState(0);
|
|
12
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
13
|
+
const [isVideoOn, setIsVideoOn] = useState(false);
|
|
14
|
+
const [activeTool, setActiveTool] = useState(null);
|
|
15
|
+
const [captions, setCaptions] = useState(emptyLiveCaptionState);
|
|
16
|
+
const [failure, setFailure] = useState(null);
|
|
17
|
+
/** The user's line for a session the provider ended after warning it would. */
|
|
18
|
+
const [sessionEnded, setSessionEnded] = useState(null);
|
|
19
|
+
const [textDraft, setTextDraft] = useState('');
|
|
20
|
+
const [sessionActive, setSessionActive] = useState(false);
|
|
21
|
+
const [sessionPermissions, setSessionPermissions] = useState([]);
|
|
22
|
+
const [everConnected, setEverConnected] = useState(false);
|
|
23
|
+
const [videoPreview, setVideoPreview] = useState(null);
|
|
24
|
+
const [videoFacingMode, setVideoFacingMode] = useState('user');
|
|
25
|
+
const videoCaptureRef = useRef(null);
|
|
26
|
+
const captionsRef = useRef(captions);
|
|
27
|
+
const isMutedRef = useRef(isMuted);
|
|
28
|
+
const sessionPermissionsRef = useRef(sessionPermissions);
|
|
29
|
+
const statusRef = useRef(status);
|
|
30
|
+
captionsRef.current = captions;
|
|
31
|
+
isMutedRef.current = isMuted;
|
|
32
|
+
sessionPermissionsRef.current = sessionPermissions;
|
|
33
|
+
statusRef.current = status;
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (sessionActive)
|
|
36
|
+
setEverConnected(true);
|
|
37
|
+
}, [sessionActive]);
|
|
38
|
+
const reportFailure = useCallback((err) => {
|
|
39
|
+
setFailure(clientFailure(err, lexicon));
|
|
40
|
+
}, [lexicon]);
|
|
41
|
+
const clearFailure = useCallback(() => {
|
|
42
|
+
setFailure(null);
|
|
43
|
+
}, []);
|
|
44
|
+
const reportSessionEnded = useCallback((session) => {
|
|
45
|
+
setSessionEnded(session.message);
|
|
46
|
+
}, []);
|
|
47
|
+
const clearSessionEnded = useCallback(() => {
|
|
48
|
+
setSessionEnded(null);
|
|
49
|
+
}, []);
|
|
50
|
+
const resetCaptions = useCallback(() => {
|
|
51
|
+
setCaptions(emptyLiveCaptionState());
|
|
52
|
+
}, []);
|
|
53
|
+
const stopVideo = useCallback(() => {
|
|
54
|
+
if (videoCaptureRef.current) {
|
|
55
|
+
videoCaptureRef.current.stop();
|
|
56
|
+
videoCaptureRef.current = null;
|
|
57
|
+
}
|
|
58
|
+
setVideoPreview(null);
|
|
59
|
+
setVideoFacingMode('user');
|
|
60
|
+
setIsVideoOn(false);
|
|
61
|
+
}, []);
|
|
62
|
+
return {
|
|
63
|
+
status,
|
|
64
|
+
setStatus,
|
|
65
|
+
connectPhase,
|
|
66
|
+
setConnectPhase,
|
|
67
|
+
inputLevel,
|
|
68
|
+
setInputLevel,
|
|
69
|
+
outputLevel,
|
|
70
|
+
setOutputLevel,
|
|
71
|
+
isMuted,
|
|
72
|
+
setIsMuted,
|
|
73
|
+
isVideoOn,
|
|
74
|
+
setIsVideoOn,
|
|
75
|
+
activeTool,
|
|
76
|
+
setActiveTool,
|
|
77
|
+
captions,
|
|
78
|
+
setCaptions,
|
|
79
|
+
failure,
|
|
80
|
+
reportFailure,
|
|
81
|
+
clearFailure,
|
|
82
|
+
sessionEnded,
|
|
83
|
+
reportSessionEnded,
|
|
84
|
+
clearSessionEnded,
|
|
85
|
+
textDraft,
|
|
86
|
+
setTextDraft,
|
|
87
|
+
sessionActive,
|
|
88
|
+
setSessionActive,
|
|
89
|
+
sessionPermissions,
|
|
90
|
+
setSessionPermissions,
|
|
91
|
+
everConnected,
|
|
92
|
+
videoPreview,
|
|
93
|
+
setVideoPreview,
|
|
94
|
+
videoFacingMode,
|
|
95
|
+
setVideoFacingMode,
|
|
96
|
+
videoCaptureRef,
|
|
97
|
+
captionsRef,
|
|
98
|
+
isMutedRef,
|
|
99
|
+
sessionPermissionsRef,
|
|
100
|
+
statusRef,
|
|
101
|
+
resetCaptions,
|
|
102
|
+
stopVideo,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Gate prompt + live turn-tool event wiring. */
|
|
106
|
+
export function useLiveRunnerGate(args) {
|
|
107
|
+
const [gatePrompt, setGatePrompt] = useState(null);
|
|
108
|
+
const gatePromptRef = useRef(gatePrompt);
|
|
109
|
+
gatePromptRef.current = gatePrompt;
|
|
110
|
+
const gateResolverRef = useRef(null);
|
|
111
|
+
const gateRejectRef = useRef(null);
|
|
112
|
+
const answerGate = useCallback((answer) => {
|
|
113
|
+
gateResolverRef.current?.(answer);
|
|
114
|
+
gateResolverRef.current = null;
|
|
115
|
+
gateRejectRef.current = null;
|
|
116
|
+
setGatePrompt(null);
|
|
117
|
+
}, []);
|
|
118
|
+
const handleLiveTurnEvent = useCallback((event) => {
|
|
119
|
+
applyLiveTurnToolEvent(event, {
|
|
120
|
+
gateCallId: gatePromptRef.current?.callId,
|
|
121
|
+
withdrawGate: () => {
|
|
122
|
+
answerGate('withdrawn');
|
|
123
|
+
},
|
|
124
|
+
clearInterim: () => {
|
|
125
|
+
args.setCaptions((prev) => clearLiveCaptionInterim(prev));
|
|
126
|
+
},
|
|
127
|
+
clearActiveTool: () => {
|
|
128
|
+
args.setActiveTool(null);
|
|
129
|
+
},
|
|
130
|
+
reportFailure: args.reportFailure,
|
|
131
|
+
setActiveTool: args.setActiveTool,
|
|
132
|
+
});
|
|
133
|
+
}, [args, answerGate]);
|
|
134
|
+
const waitForGateDecision = useCallback((prompt) => {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
setGatePrompt(prompt);
|
|
137
|
+
gateResolverRef.current = resolve;
|
|
138
|
+
gateRejectRef.current = reject;
|
|
139
|
+
});
|
|
140
|
+
}, []);
|
|
141
|
+
const resolveGateDecision = useCallback((resolution) => {
|
|
142
|
+
answerGate(resolution);
|
|
143
|
+
}, [answerGate]);
|
|
144
|
+
const cancelGateDecision = useCallback(() => {
|
|
145
|
+
// lexicon-exempt: internal diagnostic; the user reads error.cancelled
|
|
146
|
+
gateRejectRef.current?.(new TheoremError('cancelled', 'live session ended with a gate open'));
|
|
147
|
+
gateResolverRef.current = null;
|
|
148
|
+
gateRejectRef.current = null;
|
|
149
|
+
setGatePrompt(null);
|
|
150
|
+
}, []);
|
|
151
|
+
return {
|
|
152
|
+
gatePrompt,
|
|
153
|
+
gatePromptRef,
|
|
154
|
+
handleLiveTurnEvent,
|
|
155
|
+
waitForGateDecision,
|
|
156
|
+
resolveGateDecision,
|
|
157
|
+
cancelGateDecision,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
|
|
2
|
+
import { type SessionEventOf, type TurnEvent } from '@theoremjs/agents';
|
|
3
|
+
import { type LiveCaptionState } from '../../client/live/live-captions.ts';
|
|
4
|
+
import type { LiveGateAnswer, LiveToolGatePrompt } from '../../client/live/live-tool.ts';
|
|
5
|
+
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
6
|
+
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
7
|
+
import { type LiveConnection, LiveSessionClient } from '../../client/live-client.ts';
|
|
8
|
+
import type { TraceFeed } from '../../client/trace-feed.ts';
|
|
9
|
+
export type LiveClientBindings = {
|
|
10
|
+
voiceAvailable: boolean;
|
|
11
|
+
/** Where the session's trace records go, when the relay delivers them. */
|
|
12
|
+
traces: TraceFeed;
|
|
13
|
+
handleLiveTurnEvent: (event: TurnEvent) => void;
|
|
14
|
+
waitForGateDecision: (prompt: LiveToolGatePrompt) => Promise<LiveGateAnswer>;
|
|
15
|
+
captionsRef: MutableRefObject<LiveCaptionState>;
|
|
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,123 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { TheoremError } from '@theoremjs/agents';
|
|
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
|
+
const next = applyLiveTranscript(bindings.captionsRef.current, text, isUser, meta?.interim);
|
|
27
|
+
bindings.captionsRef.current = next;
|
|
28
|
+
bindings.setCaptions(next);
|
|
29
|
+
}
|
|
30
|
+
async function onLiveToolCall(name, toolArgs, meta, bindings, clientRef) {
|
|
31
|
+
bindings.setActiveTool(name);
|
|
32
|
+
bindings.clearFailure();
|
|
33
|
+
const client = clientRef.current;
|
|
34
|
+
if (!client) {
|
|
35
|
+
// lexicon-exempt: internal diagnostic; the user reads error.request
|
|
36
|
+
bindings.reportFailure(new TheoremError('request', 'live tool call with no session client'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await runLiveToolCall({
|
|
41
|
+
executeToolOnRelay: (call) => client.executeToolOnRelay(call),
|
|
42
|
+
name,
|
|
43
|
+
toolArgs,
|
|
44
|
+
callId: meta.callId,
|
|
45
|
+
sessionPermissions: bindings.sessionPermissionsRef.current,
|
|
46
|
+
setSessionPermissions: (next) => {
|
|
47
|
+
bindings.sessionPermissionsRef.current = next;
|
|
48
|
+
bindings.setSessionPermissions(next);
|
|
49
|
+
},
|
|
50
|
+
waitForGateDecision: bindings.waitForGateDecision,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
bindings.reportFailure(err);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
if (!bindings.gatePromptRef.current)
|
|
58
|
+
bindings.setActiveTool(null);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Owns LiveSessionClient construction + reconnect identity. */
|
|
62
|
+
export function useLiveSessionClient(bindings) {
|
|
63
|
+
const clientRef = useRef(null);
|
|
64
|
+
const bindingsRef = useRef(bindings);
|
|
65
|
+
bindingsRef.current = bindings;
|
|
66
|
+
const ensureClient = useCallback((connection) => {
|
|
67
|
+
if (clientRef.current)
|
|
68
|
+
return clientRef.current;
|
|
69
|
+
const b = bindingsRef.current;
|
|
70
|
+
const client = new LiveSessionClient({
|
|
71
|
+
...connection,
|
|
72
|
+
voiceIngress: b.voiceAvailable,
|
|
73
|
+
onConnectPhase: (phase) => {
|
|
74
|
+
b.setConnectPhase(phase);
|
|
75
|
+
},
|
|
76
|
+
onStatusChange: (next) => {
|
|
77
|
+
onLiveStatusChange(next, bindingsRef.current);
|
|
78
|
+
},
|
|
79
|
+
onTranscript: (text, isUser, meta) => {
|
|
80
|
+
onLiveTranscript(text, isUser, meta, bindingsRef.current);
|
|
81
|
+
},
|
|
82
|
+
onTurnEvent: (event) => {
|
|
83
|
+
// An `unsupported` event is the host's to read; Theorem's UI shows nothing for it.
|
|
84
|
+
if (event.type === 'unsupported')
|
|
85
|
+
return;
|
|
86
|
+
// A `malformed` one was left out and the call goes on; the user reads that part was skipped.
|
|
87
|
+
if (event.type === 'malformed') {
|
|
88
|
+
bindingsRef.current.reportFailure(event.error);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
bindingsRef.current.handleLiveTurnEvent(event);
|
|
92
|
+
},
|
|
93
|
+
onTrace: (record) => {
|
|
94
|
+
bindingsRef.current.traces.push(record);
|
|
95
|
+
},
|
|
96
|
+
onVolumeLevel: (level, isUser) => {
|
|
97
|
+
const current = bindingsRef.current;
|
|
98
|
+
if (isUser) {
|
|
99
|
+
current.setInputLevel(current.isMutedRef.current ? 0 : level);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
current.setOutputLevel(level);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
onError: (err) => {
|
|
106
|
+
bindingsRef.current.reportFailure(err);
|
|
107
|
+
},
|
|
108
|
+
onSessionEnded: (session) => {
|
|
109
|
+
bindingsRef.current.reportSessionEnded(session);
|
|
110
|
+
},
|
|
111
|
+
onToolCall: (name, args, meta) => onLiveToolCall(name, args, meta, bindingsRef.current, clientRef),
|
|
112
|
+
});
|
|
113
|
+
clientRef.current = client;
|
|
114
|
+
return client;
|
|
115
|
+
}, []);
|
|
116
|
+
const clearClient = useCallback(() => {
|
|
117
|
+
if (clientRef.current) {
|
|
118
|
+
clientRef.current.disconnect();
|
|
119
|
+
clientRef.current = null;
|
|
120
|
+
}
|
|
121
|
+
}, []);
|
|
122
|
+
return { clientRef, ensureClient, clearClient };
|
|
123
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type LexiconOverrides } 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 lexicon's `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 `lexicon`: the profile's wording for voice failures. */
|
|
16
|
+
lexicon: LexiconOverrides;
|
|
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
|
+
};
|