pi-web-voice 0.1.3 → 0.1.4
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/package.json +1 -1
- package/public/inject.js +25 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-voice",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Voice input for pi-web. A NODE_OPTIONS hook that injects a microphone button into the chat composer and transcribes speech with Azure AI Speech, Azure OpenAI, or any OpenAI-compatible endpoint. No fork, no patching, no rebuild.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
package/public/inject.js
CHANGED
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
denied: "麦克风权限被拒绝",
|
|
98
98
|
empty: "没有识别到语音",
|
|
99
99
|
failed: "转写失败",
|
|
100
|
+
noComposer: "找不到输入框,转写结果",
|
|
100
101
|
}
|
|
101
102
|
: {
|
|
102
103
|
idle: "Voice input — click, or press and hold",
|
|
@@ -107,6 +108,7 @@
|
|
|
107
108
|
denied: "Microphone permission denied",
|
|
108
109
|
empty: "No speech detected",
|
|
109
110
|
failed: "Transcription failed",
|
|
111
|
+
noComposer: "No composer found; transcript",
|
|
110
112
|
};
|
|
111
113
|
|
|
112
114
|
// ── audio ────────────────────────────────────────────────────────────────
|
|
@@ -224,10 +226,26 @@
|
|
|
224
226
|
|
|
225
227
|
// ── composer ─────────────────────────────────────────────────────────────
|
|
226
228
|
|
|
229
|
+
// "The last visible textarea" is not good enough: pi-web's workspace
|
|
230
|
+
// terminal is xterm.js, which keeps a hidden IME helper textarea mounted
|
|
231
|
+
// after the composer. Writing there drops the transcript at best, and at
|
|
232
|
+
// worst hands it to the shell.
|
|
233
|
+
function isComposerCandidate(area) {
|
|
234
|
+
if (area.offsetParent === null) return false;
|
|
235
|
+
if (area.classList.contains("xterm-helper-textarea")) return false;
|
|
236
|
+
return !area.closest(".xterm");
|
|
237
|
+
}
|
|
238
|
+
|
|
227
239
|
function findComposer() {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
);
|
|
240
|
+
// The button was mounted next to the composer's own toolbar, so walking up
|
|
241
|
+
// from it finds the right textarea even when the page holds several.
|
|
242
|
+
const button = document.getElementById(BUTTON_ID);
|
|
243
|
+
for (let node = button?.parentElement; node; node = node.parentElement) {
|
|
244
|
+
const nearby = Array.from(node.querySelectorAll("textarea")).filter(isComposerCandidate);
|
|
245
|
+
if (nearby.length) return nearby[nearby.length - 1];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const areas = Array.from(document.querySelectorAll("textarea")).filter(isComposerCandidate);
|
|
231
249
|
return areas[areas.length - 1] || null;
|
|
232
250
|
}
|
|
233
251
|
|
|
@@ -453,7 +471,10 @@
|
|
|
453
471
|
// Always inserted, never sent: a wrong term is one keystroke from
|
|
454
472
|
// being fixed, and Enter is right there when it is correct.
|
|
455
473
|
const textarea = findComposer();
|
|
474
|
+
// Never fail silently: a transcript with nowhere to go is shown
|
|
475
|
+
// rather than dropped, so it can still be copied by hand.
|
|
456
476
|
if (textarea) insertAtCaret(textarea, text);
|
|
477
|
+
else this.toast(`${T.noComposer}: ${text}`);
|
|
457
478
|
}
|
|
458
479
|
} catch (error) {
|
|
459
480
|
this.toast(`${T.failed}: ${error.message}`);
|
|
@@ -516,6 +537,7 @@
|
|
|
516
537
|
ui,
|
|
517
538
|
recorder,
|
|
518
539
|
config: CONFIG,
|
|
540
|
+
findComposer,
|
|
519
541
|
get sessionId() {
|
|
520
542
|
return sessionId;
|
|
521
543
|
},
|