mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 18:34:33 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
200
src/tts/tts.ts
200
src/tts/tts.ts
@@ -208,7 +208,9 @@ type TtsStatusEntry = {
|
||||
let lastTtsAttempt: TtsStatusEntry | undefined;
|
||||
|
||||
export function normalizeTtsAutoMode(value: unknown): TtsAutoMode | undefined {
|
||||
if (typeof value !== "string") return undefined;
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (TTS_AUTO_MODES.has(normalized as TtsAutoMode)) {
|
||||
return normalized as TtsAutoMode;
|
||||
@@ -303,15 +305,21 @@ export function resolveTtsConfig(cfg: OpenClawConfig): ResolvedTtsConfig {
|
||||
}
|
||||
|
||||
export function resolveTtsPrefsPath(config: ResolvedTtsConfig): string {
|
||||
if (config.prefsPath?.trim()) return resolveUserPath(config.prefsPath.trim());
|
||||
if (config.prefsPath?.trim()) {
|
||||
return resolveUserPath(config.prefsPath.trim());
|
||||
}
|
||||
const envPath = process.env.OPENCLAW_TTS_PREFS?.trim();
|
||||
if (envPath) return resolveUserPath(envPath);
|
||||
if (envPath) {
|
||||
return resolveUserPath(envPath);
|
||||
}
|
||||
return path.join(CONFIG_DIR, "settings", "tts.json");
|
||||
}
|
||||
|
||||
function resolveTtsAutoModeFromPrefs(prefs: TtsUserPrefs): TtsAutoMode | undefined {
|
||||
const auto = normalizeTtsAutoMode(prefs.tts?.auto);
|
||||
if (auto) return auto;
|
||||
if (auto) {
|
||||
return auto;
|
||||
}
|
||||
if (typeof prefs.tts?.enabled === "boolean") {
|
||||
return prefs.tts.enabled ? "always" : "off";
|
||||
}
|
||||
@@ -324,9 +332,13 @@ export function resolveTtsAutoMode(params: {
|
||||
sessionAuto?: string;
|
||||
}): TtsAutoMode {
|
||||
const sessionAuto = normalizeTtsAutoMode(params.sessionAuto);
|
||||
if (sessionAuto) return sessionAuto;
|
||||
if (sessionAuto) {
|
||||
return sessionAuto;
|
||||
}
|
||||
const prefsAuto = resolveTtsAutoModeFromPrefs(readPrefs(params.prefsPath));
|
||||
if (prefsAuto) return prefsAuto;
|
||||
if (prefsAuto) {
|
||||
return prefsAuto;
|
||||
}
|
||||
return params.config.auto;
|
||||
}
|
||||
|
||||
@@ -334,7 +346,9 @@ export function buildTtsSystemPromptHint(cfg: OpenClawConfig): string | undefine
|
||||
const config = resolveTtsConfig(cfg);
|
||||
const prefsPath = resolveTtsPrefsPath(config);
|
||||
const autoMode = resolveTtsAutoMode({ config, prefsPath });
|
||||
if (autoMode === "off") return undefined;
|
||||
if (autoMode === "off") {
|
||||
return undefined;
|
||||
}
|
||||
const maxLength = getTtsMaxLength(prefsPath);
|
||||
const summarize = isSummarizationEnabled(prefsPath) ? "on" : "off";
|
||||
const autoHint =
|
||||
@@ -355,7 +369,9 @@ export function buildTtsSystemPromptHint(cfg: OpenClawConfig): string | undefine
|
||||
|
||||
function readPrefs(prefsPath: string): TtsUserPrefs {
|
||||
try {
|
||||
if (!existsSync(prefsPath)) return {};
|
||||
if (!existsSync(prefsPath)) {
|
||||
return {};
|
||||
}
|
||||
return JSON.parse(readFileSync(prefsPath, "utf8")) as TtsUserPrefs;
|
||||
} catch {
|
||||
return {};
|
||||
@@ -407,11 +423,19 @@ export function setTtsEnabled(prefsPath: string, enabled: boolean): void {
|
||||
|
||||
export function getTtsProvider(config: ResolvedTtsConfig, prefsPath: string): TtsProvider {
|
||||
const prefs = readPrefs(prefsPath);
|
||||
if (prefs.tts?.provider) return prefs.tts.provider;
|
||||
if (config.providerSource === "config") return config.provider;
|
||||
if (prefs.tts?.provider) {
|
||||
return prefs.tts.provider;
|
||||
}
|
||||
if (config.providerSource === "config") {
|
||||
return config.provider;
|
||||
}
|
||||
|
||||
if (resolveTtsApiKey(config, "openai")) return "openai";
|
||||
if (resolveTtsApiKey(config, "elevenlabs")) return "elevenlabs";
|
||||
if (resolveTtsApiKey(config, "openai")) {
|
||||
return "openai";
|
||||
}
|
||||
if (resolveTtsApiKey(config, "elevenlabs")) {
|
||||
return "elevenlabs";
|
||||
}
|
||||
return "edge";
|
||||
}
|
||||
|
||||
@@ -452,7 +476,9 @@ export function setLastTtsAttempt(entry: TtsStatusEntry | undefined): void {
|
||||
}
|
||||
|
||||
function resolveOutputFormat(channelId?: string | null) {
|
||||
if (channelId === "telegram") return TELEGRAM_OUTPUT;
|
||||
if (channelId === "telegram") {
|
||||
return TELEGRAM_OUTPUT;
|
||||
}
|
||||
return DEFAULT_OUTPUT;
|
||||
}
|
||||
|
||||
@@ -484,7 +510,9 @@ export function resolveTtsProviderOrder(primary: TtsProvider): TtsProvider[] {
|
||||
}
|
||||
|
||||
export function isTtsProviderConfigured(config: ResolvedTtsConfig, provider: TtsProvider): boolean {
|
||||
if (provider === "edge") return config.edge.enabled;
|
||||
if (provider === "edge") {
|
||||
return config.edge.enabled;
|
||||
}
|
||||
return Boolean(resolveTtsApiKey(config, provider));
|
||||
}
|
||||
|
||||
@@ -494,7 +522,9 @@ function isValidVoiceId(voiceId: string): boolean {
|
||||
|
||||
function normalizeElevenLabsBaseUrl(baseUrl: string): string {
|
||||
const trimmed = baseUrl.trim();
|
||||
if (!trimmed) return DEFAULT_ELEVENLABS_BASE_URL;
|
||||
if (!trimmed) {
|
||||
return DEFAULT_ELEVENLABS_BASE_URL;
|
||||
}
|
||||
return trimmed.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
@@ -513,7 +543,9 @@ function assertElevenLabsVoiceSettings(settings: ResolvedTtsConfig["elevenlabs"]
|
||||
|
||||
function normalizeLanguageCode(code?: string): string | undefined {
|
||||
const trimmed = code?.trim();
|
||||
if (!trimmed) return undefined;
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (!/^[a-z]{2}$/.test(normalized)) {
|
||||
throw new Error("languageCode must be a 2-letter ISO 639-1 code (e.g. en, de, fr)");
|
||||
@@ -523,14 +555,20 @@ function normalizeLanguageCode(code?: string): string | undefined {
|
||||
|
||||
function normalizeApplyTextNormalization(mode?: string): "auto" | "on" | "off" | undefined {
|
||||
const trimmed = mode?.trim();
|
||||
if (!trimmed) return undefined;
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (normalized === "auto" || normalized === "on" || normalized === "off") return normalized;
|
||||
if (normalized === "auto" || normalized === "on" || normalized === "off") {
|
||||
return normalized;
|
||||
}
|
||||
throw new Error("applyTextNormalization must be one of: auto, on, off");
|
||||
}
|
||||
|
||||
function normalizeSeed(seed?: number): number | undefined {
|
||||
if (seed == null) return undefined;
|
||||
if (seed == null) {
|
||||
return undefined;
|
||||
}
|
||||
const next = Math.floor(seed);
|
||||
if (!Number.isFinite(next) || next < 0 || next > 4_294_967_295) {
|
||||
throw new Error("seed must be between 0 and 4294967295");
|
||||
@@ -540,8 +578,12 @@ function normalizeSeed(seed?: number): number | undefined {
|
||||
|
||||
function parseBooleanValue(value: string): boolean | undefined {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
||||
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
||||
if (["true", "1", "yes", "on"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (["false", "0", "no", "off"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -578,15 +620,21 @@ function parseTtsDirectives(
|
||||
const tokens = body.split(/\s+/).filter(Boolean);
|
||||
for (const token of tokens) {
|
||||
const eqIndex = token.indexOf("=");
|
||||
if (eqIndex === -1) continue;
|
||||
if (eqIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
const rawKey = token.slice(0, eqIndex).trim();
|
||||
const rawValue = token.slice(eqIndex + 1).trim();
|
||||
if (!rawKey || !rawValue) continue;
|
||||
if (!rawKey || !rawValue) {
|
||||
continue;
|
||||
}
|
||||
const key = rawKey.toLowerCase();
|
||||
try {
|
||||
switch (key) {
|
||||
case "provider":
|
||||
if (!policy.allowProvider) break;
|
||||
if (!policy.allowProvider) {
|
||||
break;
|
||||
}
|
||||
if (rawValue === "openai" || rawValue === "elevenlabs" || rawValue === "edge") {
|
||||
overrides.provider = rawValue;
|
||||
} else {
|
||||
@@ -596,7 +644,9 @@ function parseTtsDirectives(
|
||||
case "voice":
|
||||
case "openai_voice":
|
||||
case "openaivoice":
|
||||
if (!policy.allowVoice) break;
|
||||
if (!policy.allowVoice) {
|
||||
break;
|
||||
}
|
||||
if (isValidOpenAIVoice(rawValue)) {
|
||||
overrides.openai = { ...overrides.openai, voice: rawValue };
|
||||
} else {
|
||||
@@ -607,7 +657,9 @@ function parseTtsDirectives(
|
||||
case "voice_id":
|
||||
case "elevenlabs_voice":
|
||||
case "elevenlabsvoice":
|
||||
if (!policy.allowVoice) break;
|
||||
if (!policy.allowVoice) {
|
||||
break;
|
||||
}
|
||||
if (isValidVoiceId(rawValue)) {
|
||||
overrides.elevenlabs = { ...overrides.elevenlabs, voiceId: rawValue };
|
||||
} else {
|
||||
@@ -621,7 +673,9 @@ function parseTtsDirectives(
|
||||
case "elevenlabsmodel":
|
||||
case "openai_model":
|
||||
case "openaimodel":
|
||||
if (!policy.allowModelId) break;
|
||||
if (!policy.allowModelId) {
|
||||
break;
|
||||
}
|
||||
if (isValidOpenAIModel(rawValue)) {
|
||||
overrides.openai = { ...overrides.openai, model: rawValue };
|
||||
} else {
|
||||
@@ -629,7 +683,9 @@ function parseTtsDirectives(
|
||||
}
|
||||
break;
|
||||
case "stability":
|
||||
if (!policy.allowVoiceSettings) break;
|
||||
if (!policy.allowVoiceSettings) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
const value = parseNumberValue(rawValue);
|
||||
if (value == null) {
|
||||
@@ -646,7 +702,9 @@ function parseTtsDirectives(
|
||||
case "similarity":
|
||||
case "similarityboost":
|
||||
case "similarity_boost":
|
||||
if (!policy.allowVoiceSettings) break;
|
||||
if (!policy.allowVoiceSettings) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
const value = parseNumberValue(rawValue);
|
||||
if (value == null) {
|
||||
@@ -661,7 +719,9 @@ function parseTtsDirectives(
|
||||
}
|
||||
break;
|
||||
case "style":
|
||||
if (!policy.allowVoiceSettings) break;
|
||||
if (!policy.allowVoiceSettings) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
const value = parseNumberValue(rawValue);
|
||||
if (value == null) {
|
||||
@@ -676,7 +736,9 @@ function parseTtsDirectives(
|
||||
}
|
||||
break;
|
||||
case "speed":
|
||||
if (!policy.allowVoiceSettings) break;
|
||||
if (!policy.allowVoiceSettings) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
const value = parseNumberValue(rawValue);
|
||||
if (value == null) {
|
||||
@@ -694,7 +756,9 @@ function parseTtsDirectives(
|
||||
case "speaker_boost":
|
||||
case "usespeakerboost":
|
||||
case "use_speaker_boost":
|
||||
if (!policy.allowVoiceSettings) break;
|
||||
if (!policy.allowVoiceSettings) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
const value = parseBooleanValue(rawValue);
|
||||
if (value == null) {
|
||||
@@ -710,7 +774,9 @@ function parseTtsDirectives(
|
||||
case "normalize":
|
||||
case "applytextnormalization":
|
||||
case "apply_text_normalization":
|
||||
if (!policy.allowNormalization) break;
|
||||
if (!policy.allowNormalization) {
|
||||
break;
|
||||
}
|
||||
overrides.elevenlabs = {
|
||||
...overrides.elevenlabs,
|
||||
applyTextNormalization: normalizeApplyTextNormalization(rawValue),
|
||||
@@ -719,14 +785,18 @@ function parseTtsDirectives(
|
||||
case "language":
|
||||
case "languagecode":
|
||||
case "language_code":
|
||||
if (!policy.allowNormalization) break;
|
||||
if (!policy.allowNormalization) {
|
||||
break;
|
||||
}
|
||||
overrides.elevenlabs = {
|
||||
...overrides.elevenlabs,
|
||||
languageCode: normalizeLanguageCode(rawValue),
|
||||
};
|
||||
break;
|
||||
case "seed":
|
||||
if (!policy.allowSeed) break;
|
||||
if (!policy.allowSeed) {
|
||||
break;
|
||||
}
|
||||
overrides.elevenlabs = {
|
||||
...overrides.elevenlabs,
|
||||
seed: normalizeSeed(Number.parseInt(rawValue, 10)),
|
||||
@@ -786,13 +856,17 @@ type OpenAiTtsVoice = (typeof OPENAI_TTS_VOICES)[number];
|
||||
|
||||
function isValidOpenAIModel(model: string): boolean {
|
||||
// Allow any model when using custom endpoint (e.g., Kokoro, LocalAI)
|
||||
if (isCustomOpenAIEndpoint()) return true;
|
||||
if (isCustomOpenAIEndpoint()) {
|
||||
return true;
|
||||
}
|
||||
return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]);
|
||||
}
|
||||
|
||||
function isValidOpenAIVoice(voice: string): voice is OpenAiTtsVoice {
|
||||
// Allow any voice when using custom endpoint (e.g., Kokoro Chinese voices)
|
||||
if (isCustomOpenAIEndpoint()) return true;
|
||||
if (isCustomOpenAIEndpoint()) {
|
||||
return true;
|
||||
}
|
||||
return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice);
|
||||
}
|
||||
|
||||
@@ -814,7 +888,9 @@ function resolveSummaryModelRef(
|
||||
): SummaryModelSelection {
|
||||
const defaultRef = resolveDefaultModelForAgent({ cfg });
|
||||
const override = config.summaryModel?.trim();
|
||||
if (!override) return { ref: defaultRef, source: "default" };
|
||||
if (!override) {
|
||||
return { ref: defaultRef, source: "default" };
|
||||
}
|
||||
|
||||
const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: defaultRef.provider });
|
||||
const resolved = resolveModelRefFromString({
|
||||
@@ -822,7 +898,9 @@ function resolveSummaryModelRef(
|
||||
defaultProvider: defaultRef.provider,
|
||||
aliasIndex,
|
||||
});
|
||||
if (!resolved) return { ref: defaultRef, source: "default" };
|
||||
if (!resolved) {
|
||||
return { ref: defaultRef, source: "default" };
|
||||
}
|
||||
return { ref: resolved.ref, source: "summaryModel" };
|
||||
}
|
||||
|
||||
@@ -1046,9 +1124,15 @@ async function openaiTTS(params: {
|
||||
|
||||
function inferEdgeExtension(outputFormat: string): string {
|
||||
const normalized = outputFormat.toLowerCase();
|
||||
if (normalized.includes("webm")) return ".webm";
|
||||
if (normalized.includes("ogg")) return ".ogg";
|
||||
if (normalized.includes("opus")) return ".opus";
|
||||
if (normalized.includes("webm")) {
|
||||
return ".webm";
|
||||
}
|
||||
if (normalized.includes("ogg")) {
|
||||
return ".ogg";
|
||||
}
|
||||
if (normalized.includes("opus")) {
|
||||
return ".opus";
|
||||
}
|
||||
if (normalized.includes("wav") || normalized.includes("riff") || normalized.includes("pcm")) {
|
||||
return ".wav";
|
||||
}
|
||||
@@ -1356,7 +1440,9 @@ export async function maybeApplyTtsToPayload(params: {
|
||||
prefsPath,
|
||||
sessionAuto: params.ttsAuto,
|
||||
});
|
||||
if (autoMode === "off") return params.payload;
|
||||
if (autoMode === "off") {
|
||||
return params.payload;
|
||||
}
|
||||
|
||||
const text = params.payload.text ?? "";
|
||||
const directives = parseTtsDirectives(text, config.modelOverrides);
|
||||
@@ -1377,16 +1463,30 @@ export async function maybeApplyTtsToPayload(params: {
|
||||
text: visibleText.length > 0 ? visibleText : undefined,
|
||||
};
|
||||
|
||||
if (autoMode === "tagged" && !directives.hasDirective) return nextPayload;
|
||||
if (autoMode === "inbound" && params.inboundAudio !== true) return nextPayload;
|
||||
if (autoMode === "tagged" && !directives.hasDirective) {
|
||||
return nextPayload;
|
||||
}
|
||||
if (autoMode === "inbound" && params.inboundAudio !== true) {
|
||||
return nextPayload;
|
||||
}
|
||||
|
||||
const mode = config.mode ?? "final";
|
||||
if (mode === "final" && params.kind && params.kind !== "final") return nextPayload;
|
||||
if (mode === "final" && params.kind && params.kind !== "final") {
|
||||
return nextPayload;
|
||||
}
|
||||
|
||||
if (!ttsText.trim()) return nextPayload;
|
||||
if (params.payload.mediaUrl || (params.payload.mediaUrls?.length ?? 0) > 0) return nextPayload;
|
||||
if (text.includes("MEDIA:")) return nextPayload;
|
||||
if (ttsText.trim().length < 10) return nextPayload;
|
||||
if (!ttsText.trim()) {
|
||||
return nextPayload;
|
||||
}
|
||||
if (params.payload.mediaUrl || (params.payload.mediaUrls?.length ?? 0) > 0) {
|
||||
return nextPayload;
|
||||
}
|
||||
if (text.includes("MEDIA:")) {
|
||||
return nextPayload;
|
||||
}
|
||||
if (ttsText.trim().length < 10) {
|
||||
return nextPayload;
|
||||
}
|
||||
|
||||
const maxLength = getTtsMaxLength(prefsPath);
|
||||
let textForAudio = ttsText.trim();
|
||||
|
||||
Reference in New Issue
Block a user