ugly-app 0.1.991 → 0.1.993
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/dist/cli/version.d.ts +1 -1
- package/dist/cli/version.js +1 -1
- package/dist/client/audio/useTTS.d.ts.map +1 -1
- package/dist/client/audio/useTTS.js +54 -2
- package/dist/client/audio/useTTS.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/version.ts +1 -1
- package/src/client/audio/useTTS.ts +82 -1
package/dist/cli/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.1.
|
|
1
|
+
export declare const CLI_VERSION = "0.1.993";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/cli/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTTS.d.ts","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;
|
|
1
|
+
{"version":3,"file":"useTTS.d.ts","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAoCD,wBAAgB,MAAM,CAAC,MAAM,EAAE,aAAa;iBAoE3B,MAAM,YAAY,UAAU;;;;;;;;;;;;;;;;;EA8P5C"}
|
|
@@ -8,6 +8,26 @@ let streamCounter = 0;
|
|
|
8
8
|
// silently or the server hung. Fail loudly instead of leaving `playing` true
|
|
9
9
|
// forever with the rAF loop spinning and the socket held.
|
|
10
10
|
const WATCHDOG_MS = 30_000;
|
|
11
|
+
/**
|
|
12
|
+
* Report a failed or silent read-aloud on the framework's UX audit channel.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ THIS EXISTS BECAUSE "TTS does not play" WAS UNDIAGNOSABLE. The hook set its
|
|
15
|
+
* local `error` state and stopped there, so a failure was visible only to
|
|
16
|
+
* whatever the app chose to render — and apps mostly render nothing, or abort
|
|
17
|
+
* quietly. Production reports therefore said "TTS does not play" with no page,
|
|
18
|
+
* no route and no reason, and answering them meant asking the user which screen
|
|
19
|
+
* they were on. `[ugly.ux] …` is the same channel the image/keyboard/safe-area
|
|
20
|
+
* audits use: the Logger ships it to the project's error log with the page
|
|
21
|
+
* attached, so every app gets this for free and none has to build its own.
|
|
22
|
+
*/
|
|
23
|
+
function reportAudioFailure(reason, detail, extra) {
|
|
24
|
+
const page = typeof window === 'undefined' ? '' : window.location.pathname;
|
|
25
|
+
console.error(`[ugly.ux] audio: read-aloud ${detail}`, {
|
|
26
|
+
reason,
|
|
27
|
+
page,
|
|
28
|
+
...extra,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
11
31
|
export function useTTS(socket) {
|
|
12
32
|
const [playing, setPlaying] = useState(false);
|
|
13
33
|
const [currentWord, setCurrentWord] = useState('');
|
|
@@ -71,10 +91,19 @@ export function useTTS(socket) {
|
|
|
71
91
|
setAnalyser(playerRef.current.getAnalyser());
|
|
72
92
|
}, []);
|
|
73
93
|
const play = useCallback(async (text, options) => {
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
// Unlock BEFORE the first await. When `play` is called straight from a
|
|
95
|
+
// tap/click — the common case for a "Listen" button — this synchronous
|
|
96
|
+
// prologue is the only part of the call that still runs inside the user
|
|
97
|
+
// gesture, and Safari/iOS only grants audio to a resume() that happens
|
|
98
|
+
// there. `init()` below awaits `addModule`, so its own resume lands too
|
|
99
|
+
// late: the context stays suspended, the worklet never pumps, and
|
|
100
|
+
// nothing is ever heard. Callers that must await something first (a
|
|
101
|
+
// voice lookup, a chat reply) still need `warmup()` in their handler.
|
|
76
102
|
if (!playerRef.current)
|
|
77
103
|
playerRef.current = new AudioPlayer();
|
|
104
|
+
playerRef.current.unlock();
|
|
105
|
+
const streamId = `tts-${++streamCounter}`;
|
|
106
|
+
streamIdRef.current = streamId;
|
|
78
107
|
const player = playerRef.current;
|
|
79
108
|
await player.init();
|
|
80
109
|
player.reset();
|
|
@@ -107,6 +136,17 @@ export function useTTS(socket) {
|
|
|
107
136
|
// Flush any remaining caption indices so the text fully reveals.
|
|
108
137
|
while (wi < words.length)
|
|
109
138
|
options?.onSubtitleIndex?.(words[wi++].charEnd);
|
|
139
|
+
// Audio was delivered and the stream is over, but the worklet's own
|
|
140
|
+
// sample clock never moved: whatever the cause (a context that says
|
|
141
|
+
// "running" and isn't, a worklet that failed to load, output routed
|
|
142
|
+
// nowhere), the user heard nothing. Belt to the first-chunk check's
|
|
143
|
+
// braces — that one names the common cause, this one catches the rest.
|
|
144
|
+
if (statsRef.current.chunks > 0 && player.getPlaybackTimeMs() === 0)
|
|
145
|
+
reportAudioFailure('no-playback', 'delivered audio but the playback clock never advanced — the user heard nothing', {
|
|
146
|
+
voice: options?.voice,
|
|
147
|
+
ctx: player.contextState(),
|
|
148
|
+
...statsRef.current,
|
|
149
|
+
});
|
|
110
150
|
setPlaying(false);
|
|
111
151
|
setCurrentWord('');
|
|
112
152
|
cleanupListeners();
|
|
@@ -121,6 +161,7 @@ export function useTTS(socket) {
|
|
|
121
161
|
if (streamIdRef.current !== streamId)
|
|
122
162
|
return;
|
|
123
163
|
setError('TTS stream timed out — no response from server');
|
|
164
|
+
reportAudioFailure('stream-timeout', `received no audio for ${WATCHDOG_MS / 1000}s and gave up`, { voice: options?.voice, ...statsRef.current });
|
|
124
165
|
finish();
|
|
125
166
|
}, WATCHDOG_MS);
|
|
126
167
|
};
|
|
@@ -170,6 +211,7 @@ export function useTTS(socket) {
|
|
|
170
211
|
if (streamIdRef.current !== streamId || serverDone)
|
|
171
212
|
return;
|
|
172
213
|
setError('connection lost');
|
|
214
|
+
reportAudioFailure('socket-closed', 'lost its connection to ugly.bot mid-stream', { voice: options?.voice, ...statsRef.current });
|
|
173
215
|
finish();
|
|
174
216
|
});
|
|
175
217
|
unsubsRef.current.push(socket.on('tts:chunk', (data) => {
|
|
@@ -177,6 +219,15 @@ export function useTTS(socket) {
|
|
|
177
219
|
return;
|
|
178
220
|
armWatchdog();
|
|
179
221
|
statsRef.current.chunks++;
|
|
222
|
+
// THE SILENT FAILURE, caught at the one moment it is unambiguous.
|
|
223
|
+
// Audio has arrived, so the server and the socket are fine — but a
|
|
224
|
+
// context the browser never unlocked will not play a sample of it,
|
|
225
|
+
// and nothing downstream will ever raise an error. This is what
|
|
226
|
+
// "TTS does not play" has meant every time so far, and it was
|
|
227
|
+
// invisible: `playing` stays true, no error fires, the app shows a
|
|
228
|
+
// spinner or nothing. Report it on the first chunk, once per stream.
|
|
229
|
+
if (statsRef.current.chunks === 1 && player.contextState() !== 'running')
|
|
230
|
+
reportAudioFailure('context-not-running', `arrived but the AudioContext is "${player.contextState()}" — the browser never unlocked it, so nothing will be heard. Call warmup() inside the tap that starts playback.`, { voice: options?.voice });
|
|
180
231
|
void player.feedChunk(data.audio);
|
|
181
232
|
if (data.visemes) {
|
|
182
233
|
for (const v of data.visemes) {
|
|
@@ -216,6 +267,7 @@ export function useTTS(socket) {
|
|
|
216
267
|
if (data.streamId !== streamId)
|
|
217
268
|
return;
|
|
218
269
|
setError(data.message);
|
|
270
|
+
reportAudioFailure('server-error', `was refused by ugly.bot — ${data.message}`, { voice: options?.voice, ...statsRef.current });
|
|
219
271
|
finish();
|
|
220
272
|
}), ...(unsubClose ? [unsubClose] : []));
|
|
221
273
|
rafRef.current = requestAnimationFrame(tick);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTTS.js","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"useTTS.js","sourceRoot":"","sources":["../../../src/client/audio/useTTS.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ugly-app",
|
|
3
3
|
"packageManager": "pnpm@10.34.5",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.993",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"comment:files": "Allowlist what ships to npm. dist = runtime; src = sourcemap targets (dist/*.js.map reference ../../src/); templates = CLI scaffold. Everything else at repo root (.pgdata local Postgres, coverage, assets/icons sources, test/, test-results/) is excluded by omission. The !negations strip the scaffold's installed deps + cruft (templates/node_modules is 200MB+ and must never ship). package.json/README/LICENSE ship automatically.",
|
|
7
7
|
"files": [
|
package/src/cli/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by prebuild — do not edit manually
|
|
2
|
-
export const CLI_VERSION = "0.1.
|
|
2
|
+
export const CLI_VERSION = "0.1.993";
|
|
@@ -35,6 +35,31 @@ let streamCounter = 0;
|
|
|
35
35
|
// forever with the rAF loop spinning and the socket held.
|
|
36
36
|
const WATCHDOG_MS = 30_000;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Report a failed or silent read-aloud on the framework's UX audit channel.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ THIS EXISTS BECAUSE "TTS does not play" WAS UNDIAGNOSABLE. The hook set its
|
|
42
|
+
* local `error` state and stopped there, so a failure was visible only to
|
|
43
|
+
* whatever the app chose to render — and apps mostly render nothing, or abort
|
|
44
|
+
* quietly. Production reports therefore said "TTS does not play" with no page,
|
|
45
|
+
* no route and no reason, and answering them meant asking the user which screen
|
|
46
|
+
* they were on. `[ugly.ux] …` is the same channel the image/keyboard/safe-area
|
|
47
|
+
* audits use: the Logger ships it to the project's error log with the page
|
|
48
|
+
* attached, so every app gets this for free and none has to build its own.
|
|
49
|
+
*/
|
|
50
|
+
function reportAudioFailure(
|
|
51
|
+
reason: string,
|
|
52
|
+
detail: string,
|
|
53
|
+
extra: Record<string, unknown>,
|
|
54
|
+
): void {
|
|
55
|
+
const page = typeof window === 'undefined' ? '' : window.location.pathname;
|
|
56
|
+
console.error(`[ugly.ux] audio: read-aloud ${detail}`, {
|
|
57
|
+
reason,
|
|
58
|
+
page,
|
|
59
|
+
...extra,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
export function useTTS(socket: UglyBotSocket) {
|
|
39
64
|
const [playing, setPlaying] = useState(false);
|
|
40
65
|
const [currentWord, setCurrentWord] = useState('');
|
|
@@ -104,10 +129,20 @@ export function useTTS(socket: UglyBotSocket) {
|
|
|
104
129
|
|
|
105
130
|
const play = useCallback(
|
|
106
131
|
async (text: string, options?: TTSOptions) => {
|
|
132
|
+
// Unlock BEFORE the first await. When `play` is called straight from a
|
|
133
|
+
// tap/click — the common case for a "Listen" button — this synchronous
|
|
134
|
+
// prologue is the only part of the call that still runs inside the user
|
|
135
|
+
// gesture, and Safari/iOS only grants audio to a resume() that happens
|
|
136
|
+
// there. `init()` below awaits `addModule`, so its own resume lands too
|
|
137
|
+
// late: the context stays suspended, the worklet never pumps, and
|
|
138
|
+
// nothing is ever heard. Callers that must await something first (a
|
|
139
|
+
// voice lookup, a chat reply) still need `warmup()` in their handler.
|
|
140
|
+
if (!playerRef.current) playerRef.current = new AudioPlayer();
|
|
141
|
+
playerRef.current.unlock();
|
|
142
|
+
|
|
107
143
|
const streamId = `tts-${++streamCounter}`;
|
|
108
144
|
streamIdRef.current = streamId;
|
|
109
145
|
|
|
110
|
-
if (!playerRef.current) playerRef.current = new AudioPlayer();
|
|
111
146
|
const player = playerRef.current;
|
|
112
147
|
await player.init();
|
|
113
148
|
player.reset();
|
|
@@ -142,6 +177,21 @@ export function useTTS(socket: UglyBotSocket) {
|
|
|
142
177
|
// Flush any remaining caption indices so the text fully reveals.
|
|
143
178
|
while (wi < words.length)
|
|
144
179
|
options?.onSubtitleIndex?.(words[wi++]!.charEnd);
|
|
180
|
+
// Audio was delivered and the stream is over, but the worklet's own
|
|
181
|
+
// sample clock never moved: whatever the cause (a context that says
|
|
182
|
+
// "running" and isn't, a worklet that failed to load, output routed
|
|
183
|
+
// nowhere), the user heard nothing. Belt to the first-chunk check's
|
|
184
|
+
// braces — that one names the common cause, this one catches the rest.
|
|
185
|
+
if (statsRef.current.chunks > 0 && player.getPlaybackTimeMs() === 0)
|
|
186
|
+
reportAudioFailure(
|
|
187
|
+
'no-playback',
|
|
188
|
+
'delivered audio but the playback clock never advanced — the user heard nothing',
|
|
189
|
+
{
|
|
190
|
+
voice: options?.voice,
|
|
191
|
+
ctx: player.contextState(),
|
|
192
|
+
...statsRef.current,
|
|
193
|
+
},
|
|
194
|
+
);
|
|
145
195
|
setPlaying(false);
|
|
146
196
|
setCurrentWord('');
|
|
147
197
|
cleanupListeners();
|
|
@@ -156,6 +206,11 @@ export function useTTS(socket: UglyBotSocket) {
|
|
|
156
206
|
watchdogRef.current = null;
|
|
157
207
|
if (streamIdRef.current !== streamId) return;
|
|
158
208
|
setError('TTS stream timed out — no response from server');
|
|
209
|
+
reportAudioFailure(
|
|
210
|
+
'stream-timeout',
|
|
211
|
+
`received no audio for ${WATCHDOG_MS / 1000}s and gave up`,
|
|
212
|
+
{ voice: options?.voice, ...statsRef.current },
|
|
213
|
+
);
|
|
159
214
|
finish();
|
|
160
215
|
}, WATCHDOG_MS);
|
|
161
216
|
};
|
|
@@ -205,6 +260,11 @@ export function useTTS(socket: UglyBotSocket) {
|
|
|
205
260
|
// let the rAF loop finish it normally.
|
|
206
261
|
if (streamIdRef.current !== streamId || serverDone) return;
|
|
207
262
|
setError('connection lost');
|
|
263
|
+
reportAudioFailure(
|
|
264
|
+
'socket-closed',
|
|
265
|
+
'lost its connection to ugly.bot mid-stream',
|
|
266
|
+
{ voice: options?.voice, ...statsRef.current },
|
|
267
|
+
);
|
|
208
268
|
finish();
|
|
209
269
|
});
|
|
210
270
|
unsubsRef.current.push(
|
|
@@ -212,6 +272,22 @@ export function useTTS(socket: UglyBotSocket) {
|
|
|
212
272
|
if (data.streamId !== streamId) return;
|
|
213
273
|
armWatchdog();
|
|
214
274
|
statsRef.current.chunks++;
|
|
275
|
+
// THE SILENT FAILURE, caught at the one moment it is unambiguous.
|
|
276
|
+
// Audio has arrived, so the server and the socket are fine — but a
|
|
277
|
+
// context the browser never unlocked will not play a sample of it,
|
|
278
|
+
// and nothing downstream will ever raise an error. This is what
|
|
279
|
+
// "TTS does not play" has meant every time so far, and it was
|
|
280
|
+
// invisible: `playing` stays true, no error fires, the app shows a
|
|
281
|
+
// spinner or nothing. Report it on the first chunk, once per stream.
|
|
282
|
+
if (
|
|
283
|
+
statsRef.current.chunks === 1 &&
|
|
284
|
+
player.contextState() !== 'running'
|
|
285
|
+
)
|
|
286
|
+
reportAudioFailure(
|
|
287
|
+
'context-not-running',
|
|
288
|
+
`arrived but the AudioContext is "${player.contextState()}" — the browser never unlocked it, so nothing will be heard. Call warmup() inside the tap that starts playback.`,
|
|
289
|
+
{ voice: options?.voice },
|
|
290
|
+
);
|
|
215
291
|
void player.feedChunk(data.audio);
|
|
216
292
|
if (data.visemes) {
|
|
217
293
|
for (const v of data.visemes) {
|
|
@@ -251,6 +327,11 @@ export function useTTS(socket: UglyBotSocket) {
|
|
|
251
327
|
socket.on('tts:error', (data) => {
|
|
252
328
|
if (data.streamId !== streamId) return;
|
|
253
329
|
setError(data.message);
|
|
330
|
+
reportAudioFailure(
|
|
331
|
+
'server-error',
|
|
332
|
+
`was refused by ugly.bot — ${data.message}`,
|
|
333
|
+
{ voice: options?.voice, ...statsRef.current },
|
|
334
|
+
);
|
|
254
335
|
finish();
|
|
255
336
|
}),
|
|
256
337
|
...(unsubClose ? [unsubClose] : []),
|