pi-voice-input 0.2.14 → 0.2.15

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.
@@ -1308,22 +1308,46 @@ function cleanPostprocessOutput(output: string): string {
1308
1308
  return text;
1309
1309
  }
1310
1310
 
1311
+ const EXPLICIT_ENGLISH_MULTILINE_PATTERN =
1312
+ /\b(?:new\s*line|newline|line break|next line|new paragraph|paragraph break|carriage return|press enter|separate lines?|multi[- ]line|multiple lines)\b/i;
1313
+ const EXPLICIT_CHINESE_MULTILINE_PATTERN = /(?:换行|新的一行|另起一行|下一行|回车|分行|多行|逐行|每行|空一行|新段落|另起一段|分段)/u;
1314
+ const CJK_LIKE_PATTERN = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
1315
+ const CJK_PUNCTUATION_PATTERN = /[,。!?、;:()《》「」『』“”‘’]/u;
1316
+ const CLOSING_PUNCTUATION_PATTERN = /^[,.;:!?,。!?、;:))\]}》」』”’]/u;
1317
+ const OPENING_PUNCTUATION_PATTERN = /[(([{\[《「『“‘]$/u;
1318
+
1311
1319
  function rawTextRequestsMultiline(rawText: string): boolean {
1312
- return (
1313
- /\r|\n/.test(rawText) ||
1314
- /\b(?:new\s*line|newline|line break|next line|new paragraph|paragraph break|carriage return|press enter|separate lines?|multi[- ]line|multiple lines)\b/i.test(rawText) ||
1315
- /(?:换行|新的一行|另起一行|下一行|回车|分行|多行|逐行|每行|空一行|新段落|另起一段|分段)/u.test(rawText)
1316
- );
1320
+ // Existing newlines in raw ASR are not reliable user intent: providers can
1321
+ // insert segment or sentence breaks on their own. Treat only spoken layout
1322
+ // commands as intentional multiline input.
1323
+ return EXPLICIT_ENGLISH_MULTILINE_PATTERN.test(rawText) || EXPLICIT_CHINESE_MULTILINE_PATTERN.test(rawText);
1324
+ }
1325
+
1326
+ function lineBreakJoiner(left: string, right: string): string {
1327
+ if (!left || !right) return "";
1328
+ if (CLOSING_PUNCTUATION_PATTERN.test(right) || OPENING_PUNCTUATION_PATTERN.test(left)) return "";
1329
+ if (CJK_PUNCTUATION_PATTERN.test(left) || CJK_PUNCTUATION_PATTERN.test(right)) return "";
1330
+ if (CJK_LIKE_PATTERN.test(left) && CJK_LIKE_PATTERN.test(right)) return "";
1331
+ return " ";
1317
1332
  }
1318
1333
 
1319
1334
  function collapseUnexpectedLineBreaks(text: string): string {
1320
- return text
1321
- .replace(/\r\n?/g, "\n")
1322
- .replace(/[ \t\f\v]*\n+[ \t\f\v]*/g, " ")
1335
+ const normalized = text.replace(/\r\n?/g, "\n");
1336
+ return normalized
1337
+ .replace(/[ \t\f\v]*\n+[ \t\f\v]*/g, (match, offset: number, source: string) => {
1338
+ const left = source.slice(0, offset).replace(/[ \t\f\v]+$/g, "").at(-1) ?? "";
1339
+ const right = source.slice(offset + match.length).replace(/^[ \t\f\v]+/g, "").at(0) ?? "";
1340
+ return lineBreakJoiner(left, right);
1341
+ })
1323
1342
  .replace(/[ \t\f\v]{2,}/g, " ")
1324
1343
  .trim();
1325
1344
  }
1326
1345
 
1346
+ function normalizeRawTextForPostprocess(rawText: string): string {
1347
+ const raw = rawText.trim();
1348
+ return rawTextRequestsMultiline(raw) ? raw : collapseUnexpectedLineBreaks(raw);
1349
+ }
1350
+
1327
1351
  function preserveExpectedPostprocessLayout(rawText: string, output: string): string {
1328
1352
  if (rawTextRequestsMultiline(rawText)) return output.trim();
1329
1353
  return collapseUnexpectedLineBreaks(output);
@@ -1418,7 +1442,7 @@ async function postprocessTranscript(ctx: ExtensionContext, rawText: string, con
1418
1442
  messages: [
1419
1443
  {
1420
1444
  role: "user",
1421
- content: buildPostprocessPrompt(ctx, raw, config),
1445
+ content: buildPostprocessPrompt(ctx, normalizeRawTextForPostprocess(raw), config),
1422
1446
  timestamp: Date.now(),
1423
1447
  },
1424
1448
  ],
@@ -1595,6 +1619,7 @@ async function stopRecording(ctx: ExtensionContext, transcribe = true) {
1595
1619
 
1596
1620
  let finalText = result.text;
1597
1621
  let postprocessMs = 0;
1622
+ let postprocessSucceeded = false;
1598
1623
  let postprocessUsed = false;
1599
1624
  if (config.postprocessEnabled) {
1600
1625
  ctx.ui.setStatus("voice-input", ctx.ui.theme.fg("warning", "● polishing"));
@@ -1602,7 +1627,7 @@ async function stopRecording(ctx: ExtensionContext, transcribe = true) {
1602
1627
  try {
1603
1628
  finalText = await postprocessTranscript(ctx, result.text, config);
1604
1629
  postprocessMs = Date.now() - postprocessStart;
1605
- postprocessUsed = finalText.trim() !== result.text.trim();
1630
+ postprocessSucceeded = true;
1606
1631
  } catch (error) {
1607
1632
  postprocessMs = Date.now() - postprocessStart;
1608
1633
  ctx.ui.notify(
@@ -1612,6 +1637,9 @@ async function stopRecording(ctx: ExtensionContext, transcribe = true) {
1612
1637
  }
1613
1638
  }
1614
1639
 
1640
+ finalText = preserveExpectedPostprocessLayout(result.text, finalText);
1641
+ postprocessUsed = postprocessSucceeded && finalText.trim() !== result.text.trim();
1642
+
1615
1643
  ctx.ui.setStatus("voice-input", undefined);
1616
1644
  insertIntoEditor(ctx, finalText);
1617
1645
  ctx.ui.notify(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-voice-input",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": "Press Ctrl+Shift+R to dictate prompts into Pi using VolcEngine ASR",
5
5
  "type": "module",
6
6
  "keywords": [