pi-voice-input 0.3.0 → 0.3.2

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 CHANGED
@@ -18,9 +18,10 @@ Typing long prompts can slow you down. `pi-voice-input` lets you:
18
18
  - **One-key dictation**: `Ctrl+Shift+R` starts recording; press it again to stop and insert text.
19
19
  - **Editor-safe workflow**: transcription is pasted into the current editor only. It does not auto-submit.
20
20
  - **Chinese/English mixed input**: handles prompts that switch between Chinese, English, product names, and technical terms.
21
+ - **Hotword table support**: can pass a VolcEngine boosting table ID to improve recognition of project terms.
21
22
  - **Works on Linux and macOS**: uses common system recording tools.
22
23
  - **Lowers sound while you speak**: automatically turns down system audio during recording, then restores it afterwards.
23
- - **No hidden rewriting**: inserts the raw ASR transcript, prefixed with a short note that it may contain voice-recognition errors.
24
+ - **No hidden rewriting**: inserts the raw ASR transcript, prefixed with a short note that it may contain voice-recognition errors. If the editor already contains that note, later dictation inserts only the transcript.
24
25
  - **Simple setup commands**: configure from inside pi with `/voice init` and `/voice key`.
25
26
 
26
27
  Current speech provider: **VolcEngine Speech ASR**. A VolcEngine Speech API key is required.
@@ -98,19 +99,22 @@ Useful commands:
98
99
 
99
100
  ## Inserted text format
100
101
 
101
- The extension does not call a model to modify or translate your transcript. It inserts a short caveat telling the downstream model that the following text came from voice input and may contain recognition errors, then appends the raw ASR transcript unchanged.
102
+ The extension does not call a model to modify or translate your transcript. It inserts a concise, location-neutral caveat saying the current conversation may include voice transcription errors, asking the model to correct them from context or ask the user if the meaning is unclear, then appends the raw ASR transcript unchanged. When you dictate multiple times in the same unsent editor draft, the caveat is kept to a single copy.
102
103
 
103
104
  User config keys are:
104
105
 
105
106
  ```json
106
107
  {
107
108
  "volcApiKey": "",
109
+ "boostingTableId": "",
108
110
  "duckSystemVolume": true,
109
111
  "duckSystemVolumeFactor": 0.5,
110
112
  "duckSystemVolumeFadeMs": 300
111
113
  }
112
114
  ```
113
115
 
116
+ Set `boostingTableId` to a VolcEngine hotword/boosting table ID to send it as `boosting_table_id` on ASR requests. Leave it empty to disable hotword-table boosting. Boosting table name is not configured yet.
117
+
114
118
  ## System requirements
115
119
 
116
120
  Linux needs one recording tool:
@@ -135,7 +139,7 @@ On macOS, allow microphone access for your terminal or pi host app when prompted
135
139
  ## Troubleshooting
136
140
 
137
141
  - Run `/voice status` to see whether recording is active.
138
- - Run `/voice config` to confirm the API key is detected.
142
+ - Run `/voice config` to confirm the API key and optional boosting table ID are detected.
139
143
  - Run `/voice key` again if the key was changed or expired.
140
144
  - On macOS, check microphone permission if recording immediately fails.
141
145
  - On Linux, make sure `pw-record` or `arecord` is installed and your microphone works in other apps.
package/ROADMAP.md CHANGED
@@ -7,6 +7,7 @@ This roadmap lists user-visible work planned for pi Voice Input. It is intention
7
7
  - Linux voice input through `pw-record` or `arecord`
8
8
  - macOS voice input through the system `afrecord` command
9
9
  - VolcEngine WebSocket ASR
10
+ - Optional VolcEngine hotword boosting table ID
10
11
  - Raw ASR transcript insertion with a voice-input caveat wrapper
11
12
 
12
13
  ## Planned
@@ -26,11 +26,7 @@ const VOLC_API_KEY_URL = "https://console.volcengine.com/speech/new/setting/apik
26
26
  // the local machine, so force those subprocesses to a known-local cwd.
27
27
  const LOCAL_AUDIO_PROCESS_CWD = homedir();
28
28
  const DEFAULT_SHORTCUT = Key.ctrlShift("r");
29
- const VOICE_TRANSCRIPT_NOTICE = [
30
- "以下内容来自用户通过语音输入的原始转写,可能包含语音识别错误、错别字、同音词、断句或标点不准确,以及中英文、术语、代码名、文件名误识别等问题。",
31
- "请明确注意:这段转写不一定完全准确。请结合上下文理解用户意图;必要时可以先澄清、翻译或推断,再继续处理。",
32
- "原始语音转写如下:",
33
- ].join("\n");
29
+ const VOICE_TRANSCRIPT_NOTICE = "当前会话中的内容包含语音转写,可能存在识别错误;请结合上下文纠正,若不确定或明显偏离主题无法理解,请先询问用户。";
34
30
 
35
31
  const MSG_TYPE_CLIENT_FULL_REQUEST = 0b0001;
36
32
  const MSG_TYPE_CLIENT_AUDIO_ONLY_REQUEST = 0b0010;
@@ -46,6 +42,7 @@ type JsonObject = Record<string, unknown>;
46
42
 
47
43
  type VoiceInputConfigFile = {
48
44
  volcApiKey: string;
45
+ boostingTableId: string;
49
46
  duckSystemVolume: boolean;
50
47
  duckSystemVolumeFactor: number;
51
48
  duckSystemVolumeFadeMs: number;
@@ -59,6 +56,7 @@ type VoiceConfig = {
59
56
  language: string;
60
57
  uid: string;
61
58
  prompt: string;
59
+ boostingTableId: string;
62
60
  segmentMs: number;
63
61
  requestTimeoutMs: number;
64
62
  finalizeDelayMs: number;
@@ -118,6 +116,7 @@ function ensureDir(dir: string) {
118
116
  function defaultConfigFile(): VoiceInputConfigFile {
119
117
  return {
120
118
  volcApiKey: "",
119
+ boostingTableId: "",
121
120
  duckSystemVolume: true,
122
121
  duckSystemVolumeFactor: 0.5,
123
122
  duckSystemVolumeFadeMs: 300,
@@ -152,6 +151,11 @@ function normalizeConfigFile(input: unknown): VoiceInputConfigFile {
152
151
  const root = isObject(input) ? input : {};
153
152
  return {
154
153
  volcApiKey: stringField(root, "volcApiKey", defaults.volcApiKey).trim(),
154
+ boostingTableId: stringField(
155
+ root,
156
+ "boostingTableId",
157
+ stringField(root, "boosting_table_id", defaults.boostingTableId),
158
+ ).trim(),
155
159
  duckSystemVolume: booleanField(root, "duckSystemVolume", defaults.duckSystemVolume),
156
160
  duckSystemVolumeFactor: clamp(numberField(root, "duckSystemVolumeFactor", defaults.duckSystemVolumeFactor), 0, 1),
157
161
  duckSystemVolumeFadeMs: Math.round(clamp(numberField(root, "duckSystemVolumeFadeMs", defaults.duckSystemVolumeFadeMs), 0, 3000)),
@@ -185,6 +189,7 @@ function getConfig(): VoiceConfig {
185
189
  language: "",
186
190
  uid: "pi-voice-input",
187
191
  prompt: "",
192
+ boostingTableId: fileConfig.boostingTableId,
188
193
  segmentMs: 5000,
189
194
  requestTimeoutMs: 90000,
190
195
  finalizeDelayMs: 100,
@@ -794,6 +799,7 @@ async function transcribePcm(pcm: Buffer, durationMs: number, config: VoiceConfi
794
799
  "X-Api-Resource-Id": config.resourceId,
795
800
  "X-Api-Connect-Id": connectId,
796
801
  "X-Api-Request-Id": connectId,
802
+ "X-Api-Sequence": "-1",
797
803
  },
798
804
  handshakeTimeout: 15_000,
799
805
  });
@@ -871,6 +877,10 @@ async function transcribePcm(pcm: Buffer, durationMs: number, config: VoiceConfi
871
877
  audioPayload.language = config.language;
872
878
  }
873
879
 
880
+ const corpusPayload: Record<string, unknown> = {};
881
+ if (config.boostingTableId) corpusPayload.boosting_table_id = config.boostingTableId;
882
+ if (config.prompt) corpusPayload.context = config.prompt;
883
+
874
884
  const requestPayload: Record<string, unknown> = {
875
885
  user: { uid: config.uid || "pi-voice-input" },
876
886
  audio: audioPayload,
@@ -881,7 +891,7 @@ async function transcribePcm(pcm: Buffer, durationMs: number, config: VoiceConfi
881
891
  enable_ddc: config.enableDdc,
882
892
  show_utterances: config.showUtterances,
883
893
  result_type: "full",
884
- ...(config.prompt ? { corpus: { context: config.prompt } } : {}),
894
+ ...(Object.keys(corpusPayload).length ? { corpus: corpusPayload } : {}),
885
895
  },
886
896
  };
887
897
 
@@ -928,9 +938,14 @@ async function transcribePcm(pcm: Buffer, durationMs: number, config: VoiceConfi
928
938
  };
929
939
  }
930
940
 
931
- function wrapVoiceTranscript(rawText: string): string {
941
+ function editorHasVoiceTranscriptNotice(editorText: string): boolean {
942
+ return editorText.replace(/\r\n/g, "\n").includes(VOICE_TRANSCRIPT_NOTICE);
943
+ }
944
+
945
+ function wrapVoiceTranscript(rawText: string, currentEditorText = ""): string {
932
946
  const trimmed = rawText.trim();
933
947
  if (!trimmed) return "";
948
+ if (editorHasVoiceTranscriptNotice(currentEditorText)) return trimmed;
934
949
  return `${VOICE_TRANSCRIPT_NOTICE}
935
950
 
936
951
  ${trimmed}`;
@@ -1084,7 +1099,7 @@ async function stopRecording(ctx: ExtensionContext, transcribe = true) {
1084
1099
  return;
1085
1100
  }
1086
1101
 
1087
- const finalText = wrapVoiceTranscript(result.text);
1102
+ const finalText = wrapVoiceTranscript(result.text, ctx.ui.getEditorText());
1088
1103
 
1089
1104
  ctx.ui.setStatus("voice-input", undefined);
1090
1105
  insertIntoEditor(ctx, finalText);
@@ -1107,6 +1122,7 @@ function setupHelp(config = getConfig()): string {
1107
1122
  "- Current provider: VolcEngine WebSocket ASR",
1108
1123
  `- Config file: ${config.configPath}`,
1109
1124
  `- API key: ${config.apiKey ? "set" : "missing"}`,
1125
+ `- Hotword boosting table ID: ${config.boostingTableId ? "set" : "not set"}`,
1110
1126
  "- To create/update the JSON config file, run: /voice init",
1111
1127
  "- To save/update the key, run: /voice key",
1112
1128
  "- Output: raw ASR transcript wrapped with a short voice-input caveat",
@@ -1146,12 +1162,13 @@ function configSummary(config: VoiceConfig): string {
1146
1162
  "Voice input config:",
1147
1163
  `- config file: ${config.configPath}${existsSync(config.configPath) ? "" : " (missing; run /voice init to create it)"}`,
1148
1164
  `- volcApiKey: ${config.apiKey ? "set" : "missing"} (update with /voice key)`,
1165
+ `- boostingTableId: ${config.boostingTableId ? "set" : "not set"}`,
1149
1166
  "- outputMode: raw transcript with voice-input caveat wrapper",
1150
1167
  `- duckSystemVolume: ${config.duckSystemVolume ? "enabled" : "disabled"}`,
1151
1168
  `- duckSystemVolumeFactor: ${config.duckSystemVolumeFactor}`,
1152
1169
  `- duckSystemVolumeFadeMs: ${config.duckSystemVolumeFadeMs}`,
1153
1170
  `- current recording device: ${currentDevice}`,
1154
- "Config keys: volcApiKey, duckSystemVolume, duckSystemVolumeFactor, duckSystemVolumeFadeMs.",
1171
+ "Config keys: volcApiKey, boostingTableId, duckSystemVolume, duckSystemVolumeFactor, duckSystemVolumeFadeMs.",
1155
1172
  `VolcEngine API key URL: ${VOLC_API_KEY_URL}`,
1156
1173
  ].join("\n");
1157
1174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-voice-input",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Press Ctrl+Shift+R to dictate prompts into Pi using VolcEngine ASR",
5
5
  "type": "module",
6
6
  "keywords": [