realtime-minutes/server/config.ts
2026-04-17 16:11:31 +09:00

156 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from "node:fs";
import { parse as parseYaml } from "yaml";
import { getEnv } from "./env.js";
import { logger } from "./logger.js";
export interface Replacement {
find: string;
replace: string;
}
export interface Config {
server: {
port: number;
};
deepgram: {
model: string;
language: string;
diarize: boolean;
interimResults: boolean;
endpointing: number | false;
utteranceEndMs: number;
smartFormat: boolean;
punctuate: boolean;
keyterms: string[];
replacements: Replacement[];
};
keepAlive: {
intervalMs: number;
idleThresholdMs: number;
};
taskExtraction: {
model: string;
intervalMs: number;
llmMaxRetries: number;
maxConsecutiveFailures: number;
maxBackoffMs: number;
};
reconnect: {
maxAttempts: number;
maxBufferBytes: number;
};
output: {
dir: string;
};
participants: string[];
}
// --- config.yaml 読み込み ---
const CONFIG_YAML_PATH = "config.yaml";
interface MeetingConfig {
participants: string[];
keyterms: string[];
replacements: Replacement[];
}
const DEFAULT_MEETING_CONFIG: MeetingConfig = {
participants: [],
keyterms: [],
replacements: [],
};
function filterStrings(value: unknown): string[] {
return Array.isArray(value)
? value.filter((v): v is string => typeof v === "string")
: [];
}
function isReplacement(v: unknown): v is Replacement {
if (typeof v !== "object" || v === null) return false;
const r = v as Record<string, unknown>;
return typeof r.find === "string" && typeof r.replace === "string";
}
function loadMeetingConfig(): MeetingConfig {
if (!fs.existsSync(CONFIG_YAML_PATH)) {
logger.info("config.yaml が見つかりません。デフォルト設定で起動します");
return DEFAULT_MEETING_CONFIG;
}
try {
const raw = fs.readFileSync(CONFIG_YAML_PATH, "utf-8");
const parsed = parseYaml(raw) as Record<string, unknown>;
const config: MeetingConfig = {
participants: filterStrings(parsed.participants),
keyterms: filterStrings(parsed.keyterms),
replacements: Array.isArray(parsed.replacements)
? parsed.replacements.filter(isReplacement)
: [],
};
logger.info(
`config.yaml: 参加者 ${config.participants.length} 名, キーターム ${config.keyterms.length} 件, 置換ルール ${config.replacements.length}`,
);
return config;
} catch (e) {
logger.warn(
"config.yaml の読み込みに失敗しました。デフォルト設定で起動します",
e,
);
return DEFAULT_MEETING_CONFIG;
}
}
// プロセスライフタイムで1回だけ初期化される設定値録音セッション状態とは無関係
let _config: Config | null = null;
export function getConfig(): Config {
if (!_config) {
const env = getEnv();
const meeting = loadMeetingConfig();
_config = {
server: {
port: env.PORT,
},
deepgram: {
model: "nova-3",
language: "ja",
diarize: false,
interimResults: true,
endpointing: 500,
utteranceEndMs: 1000,
smartFormat: true,
punctuate: true,
keyterms: [
...new Set([...meeting.participants, ...meeting.keyterms]),
],
replacements: meeting.replacements,
},
keepAlive: {
intervalMs: 3_000,
idleThresholdMs: 5_000,
},
taskExtraction: {
model: "gemini-3-flash-preview",
intervalMs: 30_000,
llmMaxRetries: 0,
maxConsecutiveFailures: 3,
maxBackoffMs: 180_000,
},
reconnect: {
maxAttempts: 3,
maxBufferBytes: 2 * 1024 * 1024,
},
output: {
dir: "output",
},
participants: meeting.participants,
};
}
return _config;
}