增加语音对话功能

This commit is contained in:
yhy
2026-08-29 22:13:55 +08:00
parent 1ffa069151
commit 3a2438f566
51 changed files with 6207 additions and 3 deletions
@@ -0,0 +1,93 @@
import { joinPath } from "../../platform/paths";
import {
defaultVoiceForLang,
removeIfExists,
resolveExecutable,
writeTempTextFile,
} from "../../platform/executable";
import { runCommand } from "../../platform/subprocess";
import type { TtsProvider, TtsSynthesizeOptions, TtsResult } from "../types";
import { TtsError } from "../types";
import { getPref } from "../../../../../utils/prefs";
async function estimateWavDurationMs(path: string): Promise<number> {
try {
const stat = await IOUtils.stat(path);
return Math.max(1000, Math.floor((stat.size / 32000) * 1000));
} catch {
return 0;
}
}
export function createWindowsSapiProvider(): TtsProvider {
return {
id: "system-win",
label: "Windows SAPI",
kind: "local",
supportsTimestamps: false,
async synthesize(options: TtsSynthesizeOptions): Promise<TtsResult> {
const powershell = await resolveExecutable("powershell");
const voice =
options.voice ||
(getPref("ttsVoice") as string) ||
defaultVoiceForLang(options.lang);
const outWav = options.outputPath.endsWith(".wav")
? options.outputPath
: options.outputPath.replace(/\.\w+$/, ".wav");
const textPath = await writeTempTextFile(options.text, "sapi-text");
const scriptPath = joinPath(
PathUtils.tempDir,
`chatpapers-sapi-${Date.now()}.ps1`,
);
const psScript = [
"Add-Type -AssemblyName System.Speech",
"$text = Get-Content -LiteralPath $args[0] -Raw -Encoding UTF8",
"$out = $args[1]",
"$voice = $args[2]",
"$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer",
"if ($voice) { try { $synth.SelectVoice($voice) } catch {} }",
"$synth.SetOutputToWaveFile($out)",
"$synth.Speak($text)",
"$synth.Dispose()",
].join("\n");
await IOUtils.writeUTF8(scriptPath, psScript);
try {
const result = await runCommand(powershell, [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
scriptPath,
textPath,
outWav,
voice,
]);
if (result.exitCode !== 0) {
throw new TtsError(
`PowerShell SAPI 退出码 ${result.exitCode}`,
"runtime",
);
}
if (!(await IOUtils.exists(outWav))) {
throw new TtsError("SAPI 未生成音频文件", "runtime");
}
return {
audioPath: outWav,
timestamps: [],
durationMs: await estimateWavDurationMs(outWav),
format: "wav",
};
} finally {
await removeIfExists(textPath);
await removeIfExists(scriptPath);
}
},
};
}