Files
realtime_voice_bot/src/local-main.ts

112 lines
3.2 KiB
TypeScript

import { spawn } from "node:child_process";
import process from "node:process";
import ffmpegStatic from "ffmpeg-static";
import type { AssistantRuntimeConfig } from "./config.js";
import { Logger } from "./logger.js";
import { LocalVoiceSession } from "./audio/local-voice-session.js";
import { ElevenLabsSttService } from "./services/elevenlabs-stt.js";
import { ElevenLabsTtsService } from "./services/elevenlabs-tts.js";
import { OpenAiLlmService } from "./services/openai-llm.js";
function resolveFfmpegPath(): string {
const ffmpegPath = ffmpegStatic as unknown as string | null;
if (!ffmpegPath) {
throw new Error("ffmpeg-static 경로를 찾지 못했습니다.");
}
return ffmpegPath;
}
export async function printLocalAudioDevices(): Promise<void> {
if (process.platform === "win32") {
const ffmpegPath = resolveFfmpegPath();
console.log("\n=== ffmpeg dshow audio devices ===");
await new Promise<void>((resolve) => {
const child = spawn(
ffmpegPath,
["-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"],
{
stdio: ["ignore", "ignore", "inherit"],
},
);
child.on("exit", () => resolve());
child.on("error", (error) => {
throw error;
});
});
console.log("\n위 목록의 오디오 장치 이름을 `LOCAL_AUDIO_SOURCE` 에 그대로 넣으면 됩니다.");
console.log("Windows 로컬 모드는 현재 출력 장치 직접 선택 대신 시스템 기본 출력 장치를 사용합니다.");
return;
}
const runs = [
{
label: "wpctl status",
args: ["status"],
},
{
label: "wpctl status -n",
args: ["status", "-n"],
},
] as const;
for (const run of runs) {
console.log(`\n=== ${run.label} ===`);
await new Promise<void>((resolve, reject) => {
const child = spawn("wpctl", run.args, {
stdio: ["ignore", "inherit", "inherit"],
});
child.on("exit", (code) => {
if (code === 0) {
resolve();
return;
}
reject(new Error(`wpctl exited with code ${code ?? "null"}`));
});
child.on("error", reject);
});
}
}
export async function runLocalAssistant(config: AssistantRuntimeConfig, logger: Logger): Promise<void> {
const stt = new ElevenLabsSttService(config);
const tts = new ElevenLabsTtsService(config);
const llm = new OpenAiLlmService(config);
const session = new LocalVoiceSession({
config,
logger,
stt,
tts,
llm,
});
console.log(session.statusSummary());
console.log("로컬 음성 테스트를 시작합니다. Ctrl+C 로 종료합니다.");
if (process.platform === "win32") {
console.log("Windows 로컬 모드는 현재 시스템 기본 출력 장치로 재생됩니다.");
}
if (config.DEBUG_TEXT_EVENTS) {
console.log("텍스트 로그 출력이 켜져 있습니다.");
}
const shutdown = async (exitCode = 0) => {
await session.destroy().catch((error) => {
logger.warn("Local session shutdown failed", error);
});
process.exit(exitCode);
};
process.on("SIGINT", () => {
void shutdown(0);
});
process.on("SIGTERM", () => {
void shutdown(0);
});
await session.start();
}