pi-web-voice 0.1.4 → 0.1.5
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 +4 -4
- package/package.json +1 -1
- package/public/inject.js +11 -30
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Voice input for [pi-web](https://github.com/agegr/pi-web), added from the outside.
|
|
4
4
|
|
|
5
|
-
A microphone button appears in the chat composer.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
A microphone button appears in the chat composer. Click it to start, click again to stop.
|
|
6
|
+
The transcript lands at your caret; you review it, then send. Keyboard, VoiceOver and the
|
|
7
|
+
accessibility API can press it too. pi-web is never modified: the
|
|
8
8
|
whole thing is one `--require` hook that injects a single `<script>` tag into HTML
|
|
9
9
|
responses and serves two routes of its own.
|
|
10
10
|
|
|
@@ -87,7 +87,7 @@ file, loaded by Node itself — no dependency, no parser of ours:
|
|
|
87
87
|
```sh
|
|
88
88
|
AZURE_SPEECH_ENDPOINT=https://my-resource.cognitiveservices.azure.com
|
|
89
89
|
AZURE_SPEECH_KEY=abc123...
|
|
90
|
-
|
|
90
|
+
PI_VOICE_PROVIDER=azure-speech
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
Anything already exported wins over the file, so `AZURE_SPEECH_KEY=other pi-web-voice`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-voice",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
window.__PI_WEB_VOICE__ || {},
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
// A press shorter than this latches recording on, so a tap toggles and a
|
|
21
|
-
// long press is walkie-talkie. No setting needed: the gesture says which.
|
|
22
|
-
const HOLD_THRESHOLD_MS = 400;
|
|
23
20
|
const MAX_SECONDS = 180;
|
|
24
21
|
const SHORTCUT = "mod+shift+v";
|
|
25
22
|
|
|
@@ -89,9 +86,8 @@
|
|
|
89
86
|
const zh = (navigator.language || "").toLowerCase().startsWith("zh");
|
|
90
87
|
const T = zh
|
|
91
88
|
? {
|
|
92
|
-
idle: "语音输入 —
|
|
89
|
+
idle: "语音输入 — 点击开始,再点一次结束",
|
|
93
90
|
recording: "正在录音 — 点击停止",
|
|
94
|
-
holding: "松开结束录音",
|
|
95
91
|
working: "转写中…",
|
|
96
92
|
insecure: "浏览器只在 HTTPS 或 localhost 下允许使用麦克风",
|
|
97
93
|
denied: "麦克风权限被拒绝",
|
|
@@ -100,9 +96,8 @@
|
|
|
100
96
|
noComposer: "找不到输入框,转写结果",
|
|
101
97
|
}
|
|
102
98
|
: {
|
|
103
|
-
idle: "Voice input — click,
|
|
99
|
+
idle: "Voice input — click to start, click again to stop",
|
|
104
100
|
recording: "Recording — click to stop",
|
|
105
|
-
holding: "Release to stop",
|
|
106
101
|
working: "Transcribing…",
|
|
107
102
|
insecure: "Microphone needs HTTPS or localhost",
|
|
108
103
|
denied: "Microphone permission denied",
|
|
@@ -343,30 +338,16 @@
|
|
|
343
338
|
"transition:color .15s,background .15s,transform .1s",
|
|
344
339
|
].join(";");
|
|
345
340
|
|
|
346
|
-
// One button,
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
button.addEventListener("pointerdown", (event) => {
|
|
352
|
-
event.preventDefault();
|
|
353
|
-
if (this.state === "recording") {
|
|
354
|
-
latched = false;
|
|
355
|
-
this.stop();
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
pressedAt = Date.now();
|
|
359
|
-
latched = false;
|
|
360
|
-
this.start();
|
|
361
|
-
});
|
|
341
|
+
// One button, one gesture: click to start, click again to stop. Going
|
|
342
|
+
// through `click` rather than pointer events is what makes the keyboard,
|
|
343
|
+
// VoiceOver and the accessibility API able to press it at all — none of
|
|
344
|
+
// them produce pointer events.
|
|
345
|
+
button.addEventListener("click", () => this.toggle());
|
|
362
346
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
};
|
|
368
|
-
button.addEventListener("pointerup", release);
|
|
369
|
-
button.addEventListener("pointercancel", release);
|
|
347
|
+
// A button steals focus from the composer on mousedown. Refusing that
|
|
348
|
+
// default keeps the caret where it was, and on a phone keeps the
|
|
349
|
+
// on-screen keyboard from collapsing under the composer.
|
|
350
|
+
button.addEventListener("mousedown", (event) => event.preventDefault());
|
|
370
351
|
|
|
371
352
|
anchor.parentElement.insertBefore(button, anchor);
|
|
372
353
|
this.button = button;
|