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.
- package/extensions/voice-input.ts +38 -10
- package/package.json +1 -1
|
@@ -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
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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
|
-
|
|
1321
|
-
|
|
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
|
-
|
|
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(
|