-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseOverlayShell.ts
More file actions
148 lines (133 loc) · 4.24 KB
/
useOverlayShell.ts
File metadata and controls
148 lines (133 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { useCallback, useEffect, useState, useRef } from "react";
import {
DEFAULT_SNAPSHOT,
type OverlaySnapshot,
} from "../contracts/overlay";
import {
clearOverlayTranscript,
getOverlaySnapshot,
openSettingsPlaceholder,
setOverlayExpanded,
startDemoDriver,
stopDemoDriver,
subscribeToOverlayEvents,
togglePauseState,
updatePartialTranscript,
updateFinalTranscript,
setOverlayProcessing,
setOverlayListening,
stopOverlayCapture,
} from "../lib/overlayBridge";
function messageFromError(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
return "Unknown bridge failure.";
}
export function useOverlayShell() {
const [snapshot, setSnapshot] = useState<OverlaySnapshot>(DEFAULT_SNAPSHOT);
useEffect(() => {
let active = true;
void getOverlaySnapshot()
.then((initialSnapshot) => {
if (active) {
setSnapshot(initialSnapshot);
}
})
.catch((error: unknown) => {
if (active) {
setSnapshot((current) => ({
...current,
expanded: true,
status: "error",
statusDetail: "Unable to reach the Tauri host shell.",
errorMessage: messageFromError(error),
}));
}
});
const unlistenPromise = subscribeToOverlayEvents((event) => {
if (active) {
setSnapshot(event.snapshot);
}
});
return () => {
active = false;
void unlistenPromise.then((unlisten) => {
unlisten();
});
};
}, []);
const runCommand = useCallback(
async (command: () => Promise<OverlaySnapshot>) => {
try {
const nextSnapshot = await command();
setSnapshot(nextSnapshot);
} catch (error: unknown) {
setSnapshot((current) => ({
...current,
expanded: true,
status: "error",
statusDetail: "The host shell rejected the latest command.",
errorMessage: messageFromError(error),
}));
}
},
[],
);
// Debounce-flush partial transcript updates to avoid flooding the shell on rapid STT output.
const pendingPartialRef = useRef<string | null>(null);
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const flushPartial = useCallback(() => {
const text = pendingPartialRef.current;
if (text === null) return;
pendingPartialRef.current = null;
if (flushTimerRef.current !== null) {
clearTimeout(flushTimerRef.current);
flushTimerRef.current = null;
}
void runCommand(() => updatePartialTranscript(text));
}, [runCommand]);
const queuePartialTranscript = useCallback(
(text: string) => {
pendingPartialRef.current = text;
if (flushTimerRef.current !== null) {
clearTimeout(flushTimerRef.current);
}
// Flush after 80 ms of no new partials — balances latency vs. reduce repaints.
flushTimerRef.current = setTimeout(flushPartial, 80);
},
[flushPartial],
);
const cancelPendingPartial = useCallback(() => {
pendingPartialRef.current = null;
if (flushTimerRef.current !== null) {
clearTimeout(flushTimerRef.current);
flushTimerRef.current = null;
}
}, []);
// Cancel any pending partial flush when the component unmounts.
useEffect(() => {
return () => {
cancelPendingPartial();
};
}, [cancelPendingPartial]);
return {
snapshot,
setExpanded: (expanded: boolean) => runCommand(() => setOverlayExpanded(expanded)),
startDemo: () => runCommand(startDemoDriver),
togglePause: () => runCommand(togglePauseState),
stopDemo: () => runCommand(stopDemoDriver),
clearTranscript: () => runCommand(clearOverlayTranscript),
openSettings: () => runCommand(openSettingsPlaceholder),
// Pipeline wiring — for real STT integration.
// queuePartialTranscript debounces rapid partials; flushPartial sends immediately.
queuePartialTranscript,
updateFinalTranscript: (text: string) => runCommand(() => updateFinalTranscript(text)),
setOverlayProcessing: () => runCommand(setOverlayProcessing),
setOverlayListening: () => runCommand(setOverlayListening),
stopOverlayCapture: () => runCommand(stopOverlayCapture),
};
}