@theoremjs/react 0.3.0 → 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/README.md +728 -202
- package/client/composer-hints.js +1 -1
- package/client/decision-images.d.ts +76 -0
- package/client/decision-images.js +139 -0
- package/client/decision-transport.d.ts +26 -4
- package/client/decision-transport.js +79 -6
- 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/failure.d.ts +7 -10
- package/client/failure.js +9 -9
- package/client/host-call.d.ts +1 -1
- package/client/host-call.js +7 -2
- package/client/host-transport.d.ts +2 -0
- package/client/host-transport.js +3 -2
- package/client/index.d.ts +19 -11
- package/client/index.js +14 -10
- package/client/ink-waveform.d.ts +1 -1
- package/client/ink-waveform.js +1 -2
- package/client/live/apply-live-turn-tool-event.js +1 -1
- package/client/live/live-captions.d.ts +30 -0
- package/client/live/live-captions.js +105 -1
- package/client/live/live-mic-forward.d.ts +1 -0
- package/client/live/live-mic-forward.js +11 -2
- 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 +4 -2
- package/client/live/live-state.js +2 -0
- package/client/live/live-video.js +1 -1
- package/client/live/run-live-tool-call.d.ts +7 -2
- package/client/live/run-live-tool-call.js +48 -10
- 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 +52 -4
- package/client/live-client.js +250 -59
- package/client/live-messages.d.ts +51 -13
- package/client/live-messages.js +42 -9
- package/client/page-dispatch.d.ts +15 -0
- package/client/page-dispatch.js +63 -0
- package/client/run-commit.d.ts +16 -43
- package/client/run-commit.js +26 -101
- package/client/run-session.d.ts +40 -41
- package/client/run-session.js +104 -112
- package/client/schema-fields.js +27 -6
- package/client/shaped-data.js +37 -8
- package/client/source-citations.js +1 -1
- package/client/space-geometry.d.ts +39 -0
- package/client/space-geometry.js +183 -0
- package/client/state-fields.js +1 -1
- package/client/tool-result.js +30 -12
- package/client/tool-resume.d.ts +18 -1
- package/client/tool-resume.js +4 -1
- package/client/trace-feed.d.ts +1 -1
- package/client/trace-story.d.ts +2 -2
- package/client/trace-story.js +43 -19
- package/client/trace-view.d.ts +2 -2
- package/client/trace-view.js +56 -16
- package/client/transcript-groups.d.ts +28 -2
- package/client/transcript-groups.js +63 -13
- package/client/transcript-schema.d.ts +10 -0
- package/client/transcript-schema.js +120 -0
- package/client/transport.d.ts +114 -26
- package/client/transport.js +106 -32
- package/client/turn-client.d.ts +17 -8
- package/client/turn-client.js +26 -31
- package/client/voice-recorder.d.ts +1 -1
- package/client/voice-recorder.js +2 -5
- package/client/wire-line.js +5 -5
- package/client/worklets/mic-capture.js +9 -9
- package/components/InkWaveform.d.ts +12 -1
- package/components/InkWaveform.js +65 -42
- package/components/live/use-live-runner-controls.d.ts +3 -4
- package/components/live/use-live-runner-controls.js +6 -10
- package/components/live/use-live-runner-model.d.ts +10 -6
- package/components/live/use-live-runner-model.js +29 -14
- package/components/live/use-live-runner-ui.d.ts +10 -8
- package/components/live/use-live-runner-ui.js +19 -11
- package/components/live/use-live-session-client.d.ts +4 -4
- package/components/live/use-live-session-client.js +11 -8
- package/components/use-composer-voice.d.ts +4 -4
- package/components/use-composer-voice.js +8 -8
- package/components/use-editor-selection.js +4 -1
- package/components/use-voice-playback.d.ts +2 -1
- package/components/use-voice-playback.js +4 -4
- package/hooks/use-described.d.ts +5 -1
- package/hooks/use-described.js +11 -9
- 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 +25 -21
- package/hooks/use-theorem-chat-actions.js +67 -45
- package/hooks/use-theorem-chat-state.d.ts +6 -6
- package/hooks/use-theorem-chat-state.js +13 -9
- package/hooks/use-theorem-chat.d.ts +48 -14
- package/hooks/use-theorem-chat.js +329 -141
- package/hooks/use-theorem-decision.d.ts +5 -10
- package/hooks/use-theorem-decision.js +4 -67
- package/hooks/use-theorem-embedding.d.ts +18 -0
- package/hooks/use-theorem-embedding.js +12 -0
- package/hooks/use-theorem-host.d.ts +5 -4
- package/hooks/use-theorem-host.js +26 -8
- package/hooks/use-theorem-interface.d.ts +6 -3
- package/hooks/use-theorem-interface.js +12 -22
- package/index.d.ts +6 -3
- package/index.js +5 -2
- package/live.d.ts +1 -0
- package/live.js +1 -1
- package/package.json +6 -5
- 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/decision-handler.d.ts +12 -4
- package/server/decision-handler.js +29 -38
- package/server/embedding-handler.d.ts +33 -0
- package/server/embedding-handler.js +45 -0
- package/server/handler.d.ts +58 -20
- package/server/handler.js +441 -238
- package/server/mod.d.ts +15 -6
- package/server/mod.js +13 -5
- 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 +6 -1
- package/server/request-check.js +27 -12
- 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 +69 -20
- package/server/session-store.js +220 -5
- package/server/single-action.d.ts +21 -0
- package/server/single-action.js +30 -0
- package/server/steer-inbox.js +1 -1
- 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 +78 -78
- package/ui/CardWall.d.ts +10 -0
- package/ui/CardWall.js +61 -0
- package/ui/ChatComposerBar.d.ts +28 -5
- package/ui/ChatComposerBar.js +85 -30
- package/ui/ChatTranscript.d.ts +14 -1
- package/ui/ChatTranscript.js +110 -57
- package/ui/ConsoleFrame.d.ts +61 -4
- package/ui/ConsoleFrame.js +109 -9
- package/ui/DecisionAnswers.d.ts +40 -2
- package/ui/DecisionAnswers.js +166 -35
- 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 +6 -4
- package/ui/LiveRunner.js +42 -55
- package/ui/PaneState.d.ts +33 -0
- package/ui/PaneState.js +50 -0
- package/ui/SchemaFields.d.ts +1 -1
- package/ui/SchemaFields.js +54 -26
- package/ui/ShapedData.js +73 -28
- package/ui/SidePanel.d.ts +74 -5
- package/ui/SidePanel.js +82 -9
- package/ui/StateFields.d.ts +10 -2
- package/ui/StateFields.js +56 -13
- package/ui/TheoremChat.d.ts +65 -14
- package/ui/TheoremChat.js +66 -78
- package/ui/TheoremDecision.d.ts +12 -20
- package/ui/TheoremDecision.js +78 -35
- package/ui/TheoremEmbedding.d.ts +27 -0
- package/ui/TheoremEmbedding.js +122 -0
- package/ui/TheoremHost.d.ts +22 -5
- package/ui/TheoremHost.js +127 -37
- package/ui/ToolGateCard.d.ts +5 -1
- package/ui/ToolGateCard.js +30 -23
- package/ui/ToolResult.js +12 -10
- package/ui/ToolResultCharts.js +29 -12
- package/ui/TraceCharts.d.ts +2 -2
- package/ui/TraceCharts.js +48 -17
- package/ui/TraceGuardrails.d.ts +3 -2
- package/ui/TraceGuardrails.js +24 -6
- package/ui/TraceInspector.d.ts +11 -1
- package/ui/TraceInspector.js +45 -28
- package/ui/TraceInspectorPanel.d.ts +31 -9
- package/ui/TraceInspectorPanel.js +77 -39
- package/ui/TraceOverview.d.ts +7 -3
- package/ui/TraceOverview.js +44 -14
- package/ui/TraceSpanDetail.js +24 -22
- package/ui/TraceStory.js +9 -8
- package/ui/TraceValues.js +19 -9
- package/ui/TraceWaterfall.js +12 -9
- package/ui/VoiceNote.js +4 -4
- 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 +86 -68
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem.d.ts +3 -3
- package/ui/built/theorem.js +34 -5
- package/ui/built/theorem.variants.d.ts +8 -2
- package/ui/code-language.d.ts +12 -0
- package/ui/code-language.js +50 -0
- package/ui/disclosure-motion.js +53 -9
- 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.js +2 -1
- package/ui/index.d.ts +20 -9
- package/ui/index.js +20 -10
- package/ui/labels-provider.js +5 -2
- package/ui/labels.d.ts +374 -85
- package/ui/labels.js +1028 -188
- 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 +1 -1
- package/ui/theme.js +2 -2
- package/ui/theorem-theme.js +32 -10
- package/ui/trace-palette.d.ts +2 -16
- package/ui/trace-palette.js +2 -16
- package/ui/trace-patterns.d.ts +11 -3
- package/ui/trace-patterns.js +16 -3
- package/ui/transcript-copy-text.js +1 -0
- package/ui/wall-columns.d.ts +8 -0
- package/ui/wall-columns.js +19 -0
- package/client/walk-away.d.ts +0 -34
- package/client/walk-away.js +0 -48
- package/server/walk-away.d.ts +0 -22
- package/server/walk-away.js +0 -41
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Why recording failed; the user reads
|
|
1
|
+
/** Why recording failed; the user reads labels `voice.<code>`. */
|
|
2
2
|
export type VoiceRecorderFailureCode = 'unsupported' | 'permission' | 'unavailable' | 'failed' | 'empty';
|
|
3
3
|
/** Headless MediaRecorder session for turn-composer voice notes. */
|
|
4
4
|
export declare class ComposerVoiceRecorder {
|
package/client/voice-recorder.js
CHANGED
|
@@ -41,7 +41,6 @@ export class ComposerVoiceRecorder {
|
|
|
41
41
|
this.accept = accept;
|
|
42
42
|
this.onLevel = onLevel;
|
|
43
43
|
}
|
|
44
|
-
// fallow-ignore-next-line unused-class-member -- called from useComposerVoice via recorder refs
|
|
45
44
|
async start() {
|
|
46
45
|
this.disposeTracks();
|
|
47
46
|
const mime = pickMediaRecorderMime(this.accept);
|
|
@@ -72,10 +71,9 @@ export class ComposerVoiceRecorder {
|
|
|
72
71
|
this.recording = true;
|
|
73
72
|
this.startMeter();
|
|
74
73
|
}
|
|
75
|
-
// fallow-ignore-next-line unused-class-member -- called from useComposerVoice via recorder refs
|
|
76
74
|
async stop() {
|
|
77
75
|
if (!this.recorder || !this.recording) {
|
|
78
|
-
//
|
|
76
|
+
// labels-exempt: internal diagnostic; the user reads voice.empty
|
|
79
77
|
throw new VoiceRecorderFailure('empty', 'stop without a recording');
|
|
80
78
|
}
|
|
81
79
|
const recorder = this.recorder;
|
|
@@ -110,14 +108,13 @@ export class ComposerVoiceRecorder {
|
|
|
110
108
|
this.recorder.stop();
|
|
111
109
|
}
|
|
112
110
|
catch {
|
|
113
|
-
//
|
|
111
|
+
// why: stop() throws when the recorder stopped between the check and the call; it is stopped either way.
|
|
114
112
|
}
|
|
115
113
|
}
|
|
116
114
|
this.disposeTracks();
|
|
117
115
|
this.chunks = [];
|
|
118
116
|
this.recorder = null;
|
|
119
117
|
}
|
|
120
|
-
// fallow-ignore-next-line unused-class-member -- called from useComposerVoice cleanup
|
|
121
118
|
dispose() {
|
|
122
119
|
this.cancel();
|
|
123
120
|
}
|
package/client/wire-line.js
CHANGED
|
@@ -32,7 +32,7 @@ export function issueSummary(error) {
|
|
|
32
32
|
return issuesSummary(error.issues, []);
|
|
33
33
|
}
|
|
34
34
|
function badResponse(what, error) {
|
|
35
|
-
//
|
|
35
|
+
// labels-exempt: internal diagnostic; the user reads error.bad_response
|
|
36
36
|
return new TheoremError('bad_response', `${what} failed its wire check: ${issueSummary(error)}`);
|
|
37
37
|
}
|
|
38
38
|
/** `raw` as `schema` reads it; one that fails is `bad_response`, naming `what`. */
|
|
@@ -51,7 +51,7 @@ function malformed(message) {
|
|
|
51
51
|
/** One wire value: a known kind that passed its schema, `unsupported`, or `malformed`. */
|
|
52
52
|
export function readWireValue(lines, raw) {
|
|
53
53
|
const kind = wireKind.safeParse(raw);
|
|
54
|
-
//
|
|
54
|
+
// labels-exempt: internal diagnostic; the user reads session.part_skipped
|
|
55
55
|
if (!kind.success)
|
|
56
56
|
return malformed(`a line without a kind failed its wire check: ${issueSummary(kind.error)}`);
|
|
57
57
|
const { type } = kind.data;
|
|
@@ -60,7 +60,7 @@ export function readWireValue(lines, raw) {
|
|
|
60
60
|
const line = lines[type].safeParse(raw);
|
|
61
61
|
if (line.success)
|
|
62
62
|
return line.data;
|
|
63
|
-
//
|
|
63
|
+
// labels-exempt: internal diagnostic; the user reads session.part_skipped
|
|
64
64
|
return malformed(`a '${type}' line failed its wire check: ${issueSummary(line.error)}`);
|
|
65
65
|
}
|
|
66
66
|
/** JSON text as a value, or undefined when it is not JSON. The parser's own error quotes the text, so it is not kept. */
|
|
@@ -77,13 +77,13 @@ function jsonValue(text) {
|
|
|
77
77
|
/** One wire line's text: {@link readWireValue} of its JSON, or `malformed` when it is not JSON. */
|
|
78
78
|
export function readWireLine(lines, text) {
|
|
79
79
|
const json = jsonValue(text);
|
|
80
|
-
//
|
|
80
|
+
// labels-exempt: internal diagnostic; the user reads session.part_skipped
|
|
81
81
|
return json ? readWireValue(lines, json.value) : malformed('a line is not JSON');
|
|
82
82
|
}
|
|
83
83
|
/** A whole reply's JSON text as a value; text that is not JSON is `bad_response`. */
|
|
84
84
|
export function parseWireJson(text) {
|
|
85
85
|
const json = jsonValue(text);
|
|
86
|
-
//
|
|
86
|
+
// labels-exempt: internal diagnostic; the user reads error.bad_response
|
|
87
87
|
if (!json)
|
|
88
88
|
throw new TheoremError('bad_response', 'a line is not JSON');
|
|
89
89
|
return json.value;
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* by URL (see `../mic-capture.ts`), so it imports nothing and ships as is.
|
|
5
5
|
*/
|
|
6
6
|
registerProcessor(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
'mic-capture-processor',
|
|
8
|
+
class MicCaptureProcessor extends AudioWorkletProcessor {
|
|
9
|
+
process(inputs) {
|
|
10
|
+
const channel = inputs[0][0];
|
|
11
|
+
const copy = new Float32Array(channel);
|
|
12
|
+
this.port.postMessage(copy, [copy.buffer]);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
16
|
);
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
1
2
|
import { type InkWaveStatus } from '../client/ink-waveform.ts';
|
|
3
|
+
/** Mic and speaker levels. A host on the audio clock writes this; the bars read it each frame. */
|
|
4
|
+
export type InkWaveLevels = {
|
|
5
|
+
input: number;
|
|
6
|
+
output: number;
|
|
7
|
+
};
|
|
2
8
|
export type InkWaveformProps = {
|
|
3
9
|
status?: InkWaveStatus;
|
|
4
10
|
inputLevel?: number;
|
|
5
11
|
outputLevel?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Latest levels, read each frame. When set, it wins over `inputLevel` and
|
|
14
|
+
* `outputLevel`, so a host can move the bars without rendering.
|
|
15
|
+
*/
|
|
16
|
+
levelsRef?: RefObject<InkWaveLevels>;
|
|
6
17
|
toolActive?: boolean;
|
|
7
18
|
frozen?: boolean;
|
|
8
19
|
variant?: 'default' | 'hero' | 'pill' | 'strip';
|
|
9
20
|
};
|
|
10
|
-
export declare function InkWaveform({ status, inputLevel, outputLevel, toolActive, frozen, variant, }: InkWaveformProps): import("react").JSX.Element;
|
|
21
|
+
export declare function InkWaveform({ status, inputLevel, outputLevel, levelsRef, toolActive, frozen, variant, }: InkWaveformProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useRef
|
|
3
|
-
import { computeInkBarTargets, INK_WAVE_BAR_COUNT, INK_WAVE_HERO_BAR_COUNT, inkWaveDriver, inkWavePhases,
|
|
2
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { computeInkBarTargets, INK_WAVE_BAR_COUNT, INK_WAVE_HERO_BAR_COUNT, inkWaveDriver, inkWavePhases, } from "../client/ink-waveform.js";
|
|
4
|
+
const REST_HEIGHT = 0.06;
|
|
4
5
|
const WAVE_CONFIG = {
|
|
5
6
|
hero: {
|
|
6
7
|
viewWidth: 960,
|
|
@@ -16,7 +17,6 @@ const WAVE_CONFIG = {
|
|
|
16
17
|
strokeWidth: 3,
|
|
17
18
|
barCount: INK_WAVE_BAR_COUNT,
|
|
18
19
|
},
|
|
19
|
-
// A long, low band (a call bar along the foot of a page): many fine bars.
|
|
20
20
|
strip: {
|
|
21
21
|
viewWidth: 1600,
|
|
22
22
|
viewHeight: 48,
|
|
@@ -37,62 +37,84 @@ function resolveWaveDimensions(variant) {
|
|
|
37
37
|
const gap = (cfg.viewWidth - cfg.strokeWidth * cfg.barCount) / (cfg.barCount + 1);
|
|
38
38
|
return { ...cfg, gap };
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
/** One frame's bar targets. The live ref's levels win over the last render's; a frozen wave moves to none. */
|
|
41
|
+
function frameTargets(snap, phases, timeMs) {
|
|
42
|
+
const { input, output } = snap.levelsRef?.current ?? {
|
|
43
|
+
input: snap.inputLevel,
|
|
44
|
+
output: snap.outputLevel,
|
|
45
|
+
};
|
|
46
|
+
const heard = snap.frozen ? { input: 0, output: 0 } : { input, output };
|
|
47
|
+
return computeInkBarTargets({
|
|
48
|
+
phases,
|
|
49
|
+
timeMs,
|
|
50
|
+
driver: inkWaveDriver(snap.status, snap.toolActive, input, output, snap.frozen),
|
|
51
|
+
inputLevel: heard.input,
|
|
52
|
+
outputLevel: heard.output,
|
|
53
|
+
frozen: snap.frozen,
|
|
49
54
|
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Paints bar heights onto the mounted lines. The motion is the same as
|
|
58
|
+
* `computeInkBarTargets` plus the per-frame ease; React never sees a frame,
|
|
59
|
+
* so a strip of bars does not reconcile on the audio clock.
|
|
60
|
+
*/
|
|
61
|
+
function usePaintedWave(svgRef, snapRef, phases, barCount, viewHeight) {
|
|
50
62
|
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
const svg = svgRef.current;
|
|
64
|
+
if (!svg)
|
|
65
|
+
return;
|
|
66
|
+
const lines = svg.children;
|
|
67
|
+
const count = Math.min(barCount, lines.length);
|
|
68
|
+
const heights = new Float64Array(count);
|
|
69
|
+
heights.fill(REST_HEIGHT);
|
|
70
|
+
const restY = viewHeight - REST_HEIGHT * (viewHeight - 4);
|
|
71
|
+
for (let index = 0; index < count; index++) {
|
|
72
|
+
lines[index].y2.baseVal.value = restY;
|
|
73
|
+
}
|
|
54
74
|
let frame = 0;
|
|
55
|
-
setHeights(Array.from({ length: barCount }, () => 0.06));
|
|
56
75
|
const tick = (time) => {
|
|
57
76
|
const snap = snapRef.current;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
outputLevel: snap.frozen ? 0 : snap.outputLevel,
|
|
66
|
-
frozen: snap.frozen,
|
|
67
|
-
});
|
|
68
|
-
return stepInkBarHeights(current, targets, snap.frozen ? 0.2 : 0.14);
|
|
69
|
-
});
|
|
77
|
+
const targets = frameTargets(snap, phases, time);
|
|
78
|
+
const alpha = snap.frozen ? 0.2 : 0.14;
|
|
79
|
+
for (let index = 0; index < count; index++) {
|
|
80
|
+
const height = heights[index] + (targets[index] - heights[index]) * alpha;
|
|
81
|
+
heights[index] = height;
|
|
82
|
+
lines[index].y2.baseVal.value = viewHeight - height * (viewHeight - 4);
|
|
83
|
+
}
|
|
70
84
|
frame = requestAnimationFrame(tick);
|
|
71
85
|
};
|
|
72
86
|
frame = requestAnimationFrame(tick);
|
|
73
87
|
return () => {
|
|
74
88
|
cancelAnimationFrame(frame);
|
|
75
89
|
};
|
|
76
|
-
}, [barCount,
|
|
77
|
-
return heights;
|
|
90
|
+
}, [svgRef, snapRef, phases, barCount, viewHeight]);
|
|
78
91
|
}
|
|
79
|
-
export function InkWaveform({ status = 'disconnected', inputLevel = 0, outputLevel = 0, toolActive = false, frozen = false, variant = 'default', }) {
|
|
92
|
+
export function InkWaveform({ status = 'disconnected', inputLevel = 0, outputLevel = 0, levelsRef, toolActive = false, frozen = false, variant = 'default', }) {
|
|
80
93
|
const { viewWidth, viewHeight, preserveAspect, strokeWidth, barCount, gap } = resolveWaveDimensions(variant);
|
|
81
94
|
const phases = useMemo(() => inkWavePhases(barCount), [barCount]);
|
|
82
|
-
const
|
|
83
|
-
barCount
|
|
84
|
-
|
|
95
|
+
const xs = useMemo(() => {
|
|
96
|
+
const next = new Array(barCount);
|
|
97
|
+
for (let index = 0; index < barCount; index++) {
|
|
98
|
+
next[index] = gap + index * (strokeWidth + gap) + strokeWidth / 2;
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
}, [barCount, gap, strokeWidth]);
|
|
102
|
+
const svgRef = useRef(null);
|
|
103
|
+
const snapRef = useRef({
|
|
85
104
|
status,
|
|
86
105
|
inputLevel,
|
|
87
106
|
outputLevel,
|
|
107
|
+
levelsRef,
|
|
88
108
|
toolActive,
|
|
89
109
|
frozen,
|
|
90
110
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
snapRef.current.status = status;
|
|
112
|
+
snapRef.current.inputLevel = inputLevel;
|
|
113
|
+
snapRef.current.outputLevel = outputLevel;
|
|
114
|
+
snapRef.current.levelsRef = levelsRef;
|
|
115
|
+
snapRef.current.toolActive = toolActive;
|
|
116
|
+
snapRef.current.frozen = frozen;
|
|
117
|
+
usePaintedWave(svgRef, snapRef, phases, barCount, viewHeight);
|
|
96
118
|
const className = [
|
|
97
119
|
'ink-wave',
|
|
98
120
|
variant === 'hero' ? 'ink-wave--hero' : '',
|
|
@@ -101,8 +123,9 @@ export function InkWaveform({ status = 'disconnected', inputLevel = 0, outputLev
|
|
|
101
123
|
]
|
|
102
124
|
.filter(Boolean)
|
|
103
125
|
.join(' ');
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
const restY = viewHeight - REST_HEIGHT * (viewHeight - 4);
|
|
127
|
+
return (_jsx("svg", { ref: svgRef, className: className, "aria-hidden": "true", viewBox: `0 0 ${String(viewWidth)} ${String(viewHeight)}`, preserveAspectRatio: preserveAspect,
|
|
128
|
+
// why: Attribute defaults for hosts without ink-controls.css (the Astryx UI);
|
|
106
129
|
// the live stylesheet overrides them where it loads.
|
|
107
|
-
width: "100%", height: "100%", children:
|
|
130
|
+
width: "100%", height: "100%", children: xs.map((x) => (_jsx("line", { className: "ink-wave__bar", x1: x, x2: x, y1: viewHeight, y2: restY, strokeWidth: strokeWidth, stroke: "currentColor", strokeLinecap: "square" }, x))) }));
|
|
108
131
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
|
|
2
2
|
import { type LiveCaptionState } from '../../client/live/live-captions.ts';
|
|
3
|
-
import { type LiveFacingMode, type LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
4
3
|
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
4
|
+
import { type LiveFacingMode, type LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
5
5
|
import type { LiveConnection, LiveSessionClient } from '../../client/live-client.ts';
|
|
6
6
|
/** Media + session lifecycle handlers for LiveRunner. */
|
|
7
7
|
export declare function useLiveRunnerControls(args: {
|
|
8
8
|
clientRef: MutableRefObject<LiveSessionClient | null>;
|
|
9
9
|
videoCaptureRef: MutableRefObject<LiveVideoCapture | null>;
|
|
10
|
-
captionsRef: MutableRefObject<LiveCaptionState>;
|
|
11
10
|
connectionRef: MutableRefObject<() => LiveConnection | Promise<LiveConnection>>;
|
|
12
11
|
statusRef: MutableRefObject<LiveSessionStatus>;
|
|
13
12
|
isMutedRef: MutableRefObject<boolean>;
|
|
@@ -40,7 +39,7 @@ export declare function useLiveRunnerControls(args: {
|
|
|
40
39
|
}): {
|
|
41
40
|
teardownSession: () => void;
|
|
42
41
|
startSession: () => Promise<void>;
|
|
43
|
-
handleSendText: () => void;
|
|
42
|
+
handleSendText: (text?: string) => void;
|
|
44
43
|
handleToggleVideo: () => Promise<void>;
|
|
45
44
|
handleFlipCamera: () => Promise<void>;
|
|
46
45
|
handleToggleMic: () => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
-
import { applyLiveTranscript
|
|
2
|
+
import { applyLiveTranscript } from "../../client/live/live-captions.js";
|
|
3
3
|
import { startLiveVideoCapture, } from "../../client/live/live-video.js";
|
|
4
4
|
/** Media + session lifecycle handlers for LiveRunner. */
|
|
5
5
|
export function useLiveRunnerControls(args) {
|
|
@@ -31,17 +31,13 @@ export function useLiveRunnerControls(args) {
|
|
|
31
31
|
args.reportFailure(err);
|
|
32
32
|
}
|
|
33
33
|
}, [args]);
|
|
34
|
-
const handleSendText = useCallback(() => {
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
34
|
+
const handleSendText = useCallback((text) => {
|
|
35
|
+
const message = (typeof text === 'string' ? text : args.textDraft).trim();
|
|
36
|
+
if (!message || !args.clientRef.current || !args.sessionActive || !args.textAvailable)
|
|
37
37
|
return;
|
|
38
|
-
args.clientRef.current.sendText(
|
|
38
|
+
args.clientRef.current.sendText(message);
|
|
39
39
|
args.setTextDraft('');
|
|
40
|
-
|
|
41
|
-
forceNew: true,
|
|
42
|
-
});
|
|
43
|
-
args.captionsRef.current = next;
|
|
44
|
-
args.setCaptions(next);
|
|
40
|
+
args.setCaptions((prev) => applyLiveTranscript(prev, message, true, false, { forceNew: true }));
|
|
45
41
|
}, [args]);
|
|
46
42
|
const handleToggleVideo = useCallback(async () => {
|
|
47
43
|
if (!args.clientRef.current || !args.sessionActive || !args.videoAvailable)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { LiveProfileInterface } from '@theoremjs/agents/interface';
|
|
2
|
-
import type
|
|
2
|
+
import { type LiveCaptionState, type LiveCaptionTurn } from '../../client/live/live-captions.ts';
|
|
3
|
+
import type { LiveCallOptions } from '../../client/live/live-page-tool.ts';
|
|
3
4
|
import type { LiveConnection } from '../../client/live-client.ts';
|
|
4
5
|
/**
|
|
5
6
|
* Owns LiveRunner state, session client, and stage callbacks.
|
|
6
7
|
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
7
|
-
* its id; the
|
|
8
|
+
* its id; the studio returns its draft as the relay's open message. `options`
|
|
9
|
+
* are the host's part in the call: its slots, the page's context, and its page tools.
|
|
8
10
|
*/
|
|
9
|
-
export declare function useLiveRunnerModel(iface: LiveProfileInterface, connection: () => LiveConnection | Promise<LiveConnection
|
|
11
|
+
export declare function useLiveRunnerModel(iface: LiveProfileInterface, connection: () => LiveConnection | Promise<LiveConnection>, options?: LiveCallOptions): {
|
|
10
12
|
handle: string;
|
|
11
13
|
traces: import("../../index.ts").TraceFeed;
|
|
12
14
|
callStarted: boolean;
|
|
@@ -14,10 +16,12 @@ export declare function useLiveRunnerModel(iface: LiveProfileInterface, connecti
|
|
|
14
16
|
captions: LiveCaptionState;
|
|
15
17
|
failure: import("../../index.ts").ClientFailure | null;
|
|
16
18
|
sessionEnded: string | null;
|
|
17
|
-
|
|
19
|
+
levelsRef: import("react").RefObject<{
|
|
20
|
+
input: number;
|
|
21
|
+
output: number;
|
|
22
|
+
}>;
|
|
18
23
|
isMuted: boolean;
|
|
19
24
|
isVideoOn: boolean;
|
|
20
|
-
outputLevel: number;
|
|
21
25
|
sessionActive: boolean;
|
|
22
26
|
liveState: import("../../index.ts").LiveState;
|
|
23
27
|
activeTool: string | null;
|
|
@@ -37,7 +41,7 @@ export declare function useLiveRunnerModel(iface: LiveProfileInterface, connecti
|
|
|
37
41
|
video: boolean;
|
|
38
42
|
}) => void;
|
|
39
43
|
handleRestart: () => Promise<void>;
|
|
40
|
-
handleSendText: () => void;
|
|
44
|
+
handleSendText: (text?: string) => void;
|
|
41
45
|
handleToggleMic: () => void;
|
|
42
46
|
handleFlipCamera: () => Promise<void>;
|
|
43
47
|
handleToggleVideo: () => Promise<void>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
1
|
import { liveIngressEnabledFromSpec } from '@theoremjs/agents';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { stashLiveCaptionCall, } from "../../client/live/live-captions.js";
|
|
3
4
|
import { liveState } from "../../client/live/live-state.js";
|
|
4
5
|
import { createTraceFeed } from "../../client/trace-feed.js";
|
|
6
|
+
import { usePageToolWarnings } from "../../hooks/use-page-tool-warnings.js";
|
|
5
7
|
import { useLiveRunnerControls } from "./use-live-runner-controls.js";
|
|
6
8
|
import { useLiveRunnerGate, useLiveRunnerUiState } from "./use-live-runner-ui.js";
|
|
7
9
|
import { useLiveSessionClient } from "./use-live-session-client.js";
|
|
@@ -20,7 +22,7 @@ function useLiveCallLifecycle(controls, captionsRef, sessionActive, videoAvailab
|
|
|
20
22
|
setEnded(false);
|
|
21
23
|
void controls.startSession();
|
|
22
24
|
}, [controls, videoAvailable]);
|
|
23
|
-
// Camera capture needs a live session, so a video call turns it on once connected.
|
|
25
|
+
// why: Camera capture needs a live session, so a video call turns it on once connected.
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
if (!sessionActive || !videoOnConnectRef.current)
|
|
26
28
|
return;
|
|
@@ -35,8 +37,7 @@ function useLiveCallLifecycle(controls, captionsRef, sessionActive, videoAvailab
|
|
|
35
37
|
const [pastCalls, setPastCalls] = useState([]);
|
|
36
38
|
const handleRestart = useCallback(async () => {
|
|
37
39
|
const previous = captionsRef.current.turns;
|
|
38
|
-
|
|
39
|
-
setPastCalls((calls) => [...calls, previous]);
|
|
40
|
+
setPastCalls((calls) => stashLiveCaptionCall(calls, previous));
|
|
40
41
|
setEnded(false);
|
|
41
42
|
await controls.handleRestart();
|
|
42
43
|
}, [controls, captionsRef]);
|
|
@@ -52,10 +53,11 @@ function useLiveCallLifecycle(controls, captionsRef, sessionActive, videoAvailab
|
|
|
52
53
|
/**
|
|
53
54
|
* Owns LiveRunner state, session client, and stage callbacks.
|
|
54
55
|
* `connection` resolves what the call opens: a host with a fixed profile returns
|
|
55
|
-
* its id; the
|
|
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.
|
|
56
58
|
*/
|
|
57
|
-
export function useLiveRunnerModel(iface, connection) {
|
|
58
|
-
const ui = useLiveRunnerUiState(iface.
|
|
59
|
+
export function useLiveRunnerModel(iface, connection, options = {}) {
|
|
60
|
+
const ui = useLiveRunnerUiState(iface.labels);
|
|
59
61
|
const gate = useLiveRunnerGate({
|
|
60
62
|
setCaptions: ui.setCaptions,
|
|
61
63
|
setActiveTool: ui.setActiveTool,
|
|
@@ -73,14 +75,16 @@ export function useLiveRunnerModel(iface, connection) {
|
|
|
73
75
|
isMuted: ui.isMuted,
|
|
74
76
|
voiceEnabled: voiceAvailable,
|
|
75
77
|
}), [ui.activeTool, ui.connectPhase, ui.isMuted, ui.status, voiceAvailable]);
|
|
76
|
-
// One feed per runner: every session it opens adds its records.
|
|
78
|
+
// why: One feed per runner: every session it opens adds its records.
|
|
77
79
|
const traces = useMemo(createTraceFeed, []);
|
|
78
80
|
const { clientRef, ensureClient, clearClient } = useLiveSessionClient({
|
|
81
|
+
slots: options.slots,
|
|
82
|
+
context: options.context,
|
|
83
|
+
pageTools: options.pageTools,
|
|
79
84
|
voiceAvailable,
|
|
80
85
|
traces,
|
|
81
86
|
handleLiveTurnEvent: gate.handleLiveTurnEvent,
|
|
82
87
|
waitForGateDecision: gate.waitForGateDecision,
|
|
83
|
-
captionsRef: ui.captionsRef,
|
|
84
88
|
gatePromptRef: gate.gatePromptRef,
|
|
85
89
|
isMutedRef: ui.isMutedRef,
|
|
86
90
|
sessionPermissionsRef: ui.sessionPermissionsRef,
|
|
@@ -104,7 +108,6 @@ export function useLiveRunnerModel(iface, connection) {
|
|
|
104
108
|
const controls = useLiveRunnerControls({
|
|
105
109
|
clientRef,
|
|
106
110
|
videoCaptureRef: ui.videoCaptureRef,
|
|
107
|
-
captionsRef: ui.captionsRef,
|
|
108
111
|
connectionRef,
|
|
109
112
|
statusRef: ui.statusRef,
|
|
110
113
|
isMutedRef: ui.isMutedRef,
|
|
@@ -136,6 +139,17 @@ export function useLiveRunnerModel(iface, connection) {
|
|
|
136
139
|
setIsVideoOn: ui.setIsVideoOn,
|
|
137
140
|
});
|
|
138
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);
|
|
139
153
|
return {
|
|
140
154
|
handle: iface.identity.handle,
|
|
141
155
|
traces,
|
|
@@ -144,10 +158,9 @@ export function useLiveRunnerModel(iface, connection) {
|
|
|
144
158
|
captions: ui.captions,
|
|
145
159
|
failure: ui.failure,
|
|
146
160
|
sessionEnded: ui.sessionEnded,
|
|
147
|
-
|
|
161
|
+
levelsRef: ui.levelsRef,
|
|
148
162
|
isMuted: ui.isMuted,
|
|
149
163
|
isVideoOn: ui.isVideoOn,
|
|
150
|
-
outputLevel: ui.outputLevel,
|
|
151
164
|
sessionActive: ui.sessionActive,
|
|
152
165
|
liveState: state,
|
|
153
166
|
activeTool: ui.activeTool,
|
|
@@ -159,8 +172,10 @@ export function useLiveRunnerModel(iface, connection) {
|
|
|
159
172
|
videoFacingMode: ui.videoFacingMode,
|
|
160
173
|
videoPreview: ui.videoPreview,
|
|
161
174
|
voiceAvailable,
|
|
162
|
-
// Also after ending (or failing) before the first connect, so the call can't get stuck.
|
|
163
|
-
canRestart: !ui.sessionActive &&
|
|
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),
|
|
164
179
|
gatePrompt: gate.gatePrompt,
|
|
165
180
|
setTextDraft: ui.setTextDraft,
|
|
166
181
|
handleEnd,
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
+
import { type LabelOverrides, type SessionEventOf } from '@theoremjs/agents';
|
|
1
2
|
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
|
-
import { type LexiconOverrides, type SessionEventOf } from '@theoremjs/agents';
|
|
3
3
|
import { type ClientFailure } from '../../client/failure.ts';
|
|
4
4
|
import { applyLiveTurnToolEvent } from '../../client/live/apply-live-turn-tool-event.ts';
|
|
5
5
|
import { type LiveCaptionState } from '../../client/live/live-captions.ts';
|
|
6
|
+
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
6
7
|
import type { LiveGateAnswer, LiveToolGatePrompt } from '../../client/live/live-tool.ts';
|
|
7
8
|
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
8
|
-
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
9
9
|
import type { ToolGateResolution } from '../../client/tool-resume.ts';
|
|
10
|
-
/** UI + media state bag for the live runner. `
|
|
11
|
-
export declare function useLiveRunnerUiState(
|
|
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
12
|
status: LiveSessionStatus;
|
|
13
13
|
setStatus: Dispatch<SetStateAction<LiveSessionStatus>>;
|
|
14
14
|
connectPhase: LiveConnectPhase | null;
|
|
15
15
|
setConnectPhase: Dispatch<SetStateAction<LiveConnectPhase | null>>;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
levelsRef: import("react").RefObject<{
|
|
17
|
+
input: number;
|
|
18
|
+
output: number;
|
|
19
|
+
}>;
|
|
20
|
+
setInputLevel: (value: SetStateAction<number>) => void;
|
|
21
|
+
setOutputLevel: (value: SetStateAction<number>) => void;
|
|
20
22
|
isMuted: boolean;
|
|
21
23
|
setIsMuted: Dispatch<SetStateAction<boolean>>;
|
|
22
24
|
isVideoOn: boolean;
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
1
|
import { TheoremError } from '@theoremjs/agents';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
3
3
|
import { clientFailure } from "../../client/failure.js";
|
|
4
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. `
|
|
7
|
-
export function useLiveRunnerUiState(
|
|
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
8
|
const [status, setStatus] = useState('disconnected');
|
|
9
9
|
const [connectPhase, setConnectPhase] = useState(null);
|
|
10
|
-
|
|
11
|
-
const
|
|
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
|
+
}, []);
|
|
12
20
|
const [isMuted, setIsMuted] = useState(false);
|
|
13
21
|
const [isVideoOn, setIsVideoOn] = useState(false);
|
|
14
22
|
const [activeTool, setActiveTool] = useState(null);
|
|
@@ -36,8 +44,8 @@ export function useLiveRunnerUiState(lexicon) {
|
|
|
36
44
|
setEverConnected(true);
|
|
37
45
|
}, [sessionActive]);
|
|
38
46
|
const reportFailure = useCallback((err) => {
|
|
39
|
-
setFailure(clientFailure(err,
|
|
40
|
-
}, [
|
|
47
|
+
setFailure(clientFailure(err, labels));
|
|
48
|
+
}, [labels]);
|
|
41
49
|
const clearFailure = useCallback(() => {
|
|
42
50
|
setFailure(null);
|
|
43
51
|
}, []);
|
|
@@ -64,9 +72,8 @@ export function useLiveRunnerUiState(lexicon) {
|
|
|
64
72
|
setStatus,
|
|
65
73
|
connectPhase,
|
|
66
74
|
setConnectPhase,
|
|
67
|
-
|
|
75
|
+
levelsRef,
|
|
68
76
|
setInputLevel,
|
|
69
|
-
outputLevel,
|
|
70
77
|
setOutputLevel,
|
|
71
78
|
isMuted,
|
|
72
79
|
setIsMuted,
|
|
@@ -116,6 +123,7 @@ export function useLiveRunnerGate(args) {
|
|
|
116
123
|
setGatePrompt(null);
|
|
117
124
|
}, []);
|
|
118
125
|
const handleLiveTurnEvent = useCallback((event) => {
|
|
126
|
+
args.setCaptions((prev) => applyLiveTurnEvent(prev, event));
|
|
119
127
|
applyLiveTurnToolEvent(event, {
|
|
120
128
|
gateCallId: gatePromptRef.current?.callId,
|
|
121
129
|
withdrawGate: () => {
|
|
@@ -142,7 +150,7 @@ export function useLiveRunnerGate(args) {
|
|
|
142
150
|
answerGate(resolution);
|
|
143
151
|
}, [answerGate]);
|
|
144
152
|
const cancelGateDecision = useCallback(() => {
|
|
145
|
-
//
|
|
153
|
+
// labels-exempt: internal diagnostic; the user reads error.cancelled
|
|
146
154
|
gateRejectRef.current?.(new TheoremError('cancelled', 'live session ended with a gate open'));
|
|
147
155
|
gateResolverRef.current = null;
|
|
148
156
|
gateRejectRef.current = null;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
|
|
2
1
|
import { type SessionEventOf, type TurnEvent } from '@theoremjs/agents';
|
|
2
|
+
import { type Dispatch, type MutableRefObject, type SetStateAction } from 'react';
|
|
3
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';
|
|
4
6
|
import type { LiveGateAnswer, LiveToolGatePrompt } from '../../client/live/live-tool.ts';
|
|
5
7
|
import type { LiveFacingMode, LiveVideoCapture } from '../../client/live/live-video.ts';
|
|
6
|
-
import type { LiveConnectPhase, LiveSessionStatus } from '../../client/live/live-state.ts';
|
|
7
8
|
import { type LiveConnection, LiveSessionClient } from '../../client/live-client.ts';
|
|
8
9
|
import type { TraceFeed } from '../../client/trace-feed.ts';
|
|
9
|
-
export type LiveClientBindings = {
|
|
10
|
+
export type LiveClientBindings = LiveCallOptions & {
|
|
10
11
|
voiceAvailable: boolean;
|
|
11
12
|
/** Where the session's trace records go, when the relay delivers them. */
|
|
12
13
|
traces: TraceFeed;
|
|
13
14
|
handleLiveTurnEvent: (event: TurnEvent) => void;
|
|
14
15
|
waitForGateDecision: (prompt: LiveToolGatePrompt) => Promise<LiveGateAnswer>;
|
|
15
|
-
captionsRef: MutableRefObject<LiveCaptionState>;
|
|
16
16
|
gatePromptRef: MutableRefObject<LiveToolGatePrompt | null>;
|
|
17
17
|
isMutedRef: MutableRefObject<boolean>;
|
|
18
18
|
sessionPermissionsRef: MutableRefObject<string[]>;
|