mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(tts): split complex command handlers (#105005)
This commit is contained in:
@@ -249,6 +249,22 @@ describe("handleTtsCommands status fallback reporting", () => {
|
||||
expect(reply.text).toContain("TTS status");
|
||||
});
|
||||
|
||||
it("keeps base status fields in display order", async () => {
|
||||
const reply = expectReply(await handleTtsCommands(buildTtsParams("/tts status"), true));
|
||||
|
||||
expect(reply.text).toBe(
|
||||
[
|
||||
"📊 TTS status",
|
||||
"State: ✅ enabled",
|
||||
"Chat override: default",
|
||||
`Provider: ${PRIMARY_TTS_PROVIDER} (✅ configured)`,
|
||||
"Persona: none",
|
||||
"Text limit: 1500 chars",
|
||||
"Auto-summary: on",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves status config for the active agent", async () => {
|
||||
const cfg = {
|
||||
agents: { list: [{ id: "reader", tts: { provider: "elevenlabs" } }] },
|
||||
@@ -364,7 +380,7 @@ describe("handleTtsCommands status fallback reporting", () => {
|
||||
|
||||
const beforeTtsRead = Date.now();
|
||||
const result = await handleTtsCommands(
|
||||
buildTtsParams("/tts latest", {}, undefined, { sessionEntry, sessionStore }),
|
||||
buildTtsParams("/tts read latest", {}, undefined, { sessionEntry, sessionStore }),
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
persistSessionEntry,
|
||||
sessionEntryPersistenceConflictReply,
|
||||
} from "./commands-session-store.js";
|
||||
import type { CommandHandler } from "./commands-types.js";
|
||||
import type { CommandHandler, CommandHandlerResult } from "./commands-types.js";
|
||||
|
||||
type ParsedTtsCommand = {
|
||||
action: string;
|
||||
@@ -48,6 +48,8 @@ type TtsAttemptDetail = NonNullable<
|
||||
NonNullable<ReturnType<typeof getLastTtsAttempt>>["attempts"]
|
||||
>[number];
|
||||
|
||||
type TtsCommandParams = Parameters<CommandHandler>[0];
|
||||
|
||||
function parseTtsCommand(normalized: string): ParsedTtsCommand | null {
|
||||
// Accept `/tts` and `/tts <action> [args]` as a single control surface.
|
||||
if (normalized === "/tts") {
|
||||
@@ -84,6 +86,10 @@ function formatAttemptDetails(attempts: TtsAttemptDetail[] | undefined): string
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
function stopWithText(text: string): CommandHandlerResult {
|
||||
return { shouldContinue: false, reply: { text } };
|
||||
}
|
||||
|
||||
function ttsUsage(): ReplyPayload {
|
||||
// Keep usage in one place so help/validation stays consistent.
|
||||
return {
|
||||
@@ -126,7 +132,7 @@ async function buildTtsAudioReply(params: {
|
||||
accountId?: string;
|
||||
prefsPath: string;
|
||||
agentId?: string;
|
||||
}): Promise<{ reply: ReplyPayload; provider?: string; hash?: string } | { error: string }> {
|
||||
}): Promise<{ reply: ReplyPayload } | { error: string }> {
|
||||
const start = Date.now();
|
||||
const result = await textToSpeech({
|
||||
text: params.text,
|
||||
@@ -151,7 +157,6 @@ async function buildTtsAudioReply(params: {
|
||||
latencyMs: result.latencyMs,
|
||||
});
|
||||
return {
|
||||
provider: result.provider,
|
||||
reply: {
|
||||
mediaUrl: result.audioPath,
|
||||
audioAsVoice: result.audioAsVoice === true || result.voiceCompatible === true,
|
||||
@@ -175,6 +180,139 @@ async function buildTtsAudioReply(params: {
|
||||
return { error: result.error ?? "unknown error" };
|
||||
}
|
||||
|
||||
async function handleTtsChatAction(
|
||||
params: TtsCommandParams,
|
||||
args: string,
|
||||
): Promise<CommandHandlerResult> {
|
||||
const requested = args.toLowerCase();
|
||||
if (!params.sessionEntry || !params.sessionStore || !params.sessionKey) {
|
||||
return stopWithText("🔇 No active chat session is available for a chat-scoped TTS override.");
|
||||
}
|
||||
if (!requested || requested === "status") {
|
||||
return stopWithText(`🔊 Chat TTS override: ${params.sessionEntry.ttsAuto ?? "default"}.`);
|
||||
}
|
||||
|
||||
let replyText: string;
|
||||
if (requested === "on") {
|
||||
params.sessionEntry.ttsAuto = "always";
|
||||
replyText = "🔊 TTS enabled for this chat.";
|
||||
} else if (requested === "off") {
|
||||
params.sessionEntry.ttsAuto = "off";
|
||||
replyText = "🔇 TTS disabled for this chat.";
|
||||
} else if (requested === "default" || requested === "inherit" || requested === "clear") {
|
||||
delete params.sessionEntry.ttsAuto;
|
||||
replyText = "🔊 TTS chat override cleared.";
|
||||
} else {
|
||||
return { shouldContinue: false, reply: ttsUsage() };
|
||||
}
|
||||
|
||||
if (!(await persistSessionEntry({ ...params, touchedFields: ["ttsAuto"] }))) {
|
||||
return sessionEntryPersistenceConflictReply();
|
||||
}
|
||||
return stopWithText(replyText);
|
||||
}
|
||||
|
||||
async function handleTtsLatestAction(
|
||||
params: TtsCommandParams,
|
||||
accountId: string | undefined,
|
||||
prefsPath: string,
|
||||
): Promise<CommandHandlerResult> {
|
||||
if (!params.sessionEntry || !params.sessionStore || !params.sessionKey) {
|
||||
return stopWithText("🎤 No active chat session is available for `/tts latest`.");
|
||||
}
|
||||
const latest = await readLatestAssistantTextFromSessionTranscript(
|
||||
params.sessionEntry.sessionFile,
|
||||
);
|
||||
const latestText = latest?.text.trim();
|
||||
if (!latestText || isSilentReplyPayloadText(latestText)) {
|
||||
return stopWithText("🎤 No readable assistant reply was found in this chat yet.");
|
||||
}
|
||||
const hash = hashTtsReadLatestText(latestText);
|
||||
if (params.sessionEntry.lastTtsReadLatestHash === hash) {
|
||||
return stopWithText("🔊 Latest assistant reply was already sent as audio.");
|
||||
}
|
||||
|
||||
const audio = await buildTtsAudioReply({
|
||||
text: latestText,
|
||||
cfg: params.cfg,
|
||||
channel: params.command.channel,
|
||||
accountId,
|
||||
prefsPath,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
if ("error" in audio) {
|
||||
return stopWithText(`❌ Error generating audio: ${audio.error}`);
|
||||
}
|
||||
|
||||
params.sessionEntry.lastTtsReadLatestHash = hash;
|
||||
params.sessionEntry.lastTtsReadLatestAt = Date.now();
|
||||
if (
|
||||
!(await persistSessionEntry({
|
||||
...params,
|
||||
touchedFields: ["lastTtsReadLatestHash", "lastTtsReadLatestAt"],
|
||||
}))
|
||||
) {
|
||||
return sessionEntryPersistenceConflictReply();
|
||||
}
|
||||
return { shouldContinue: false, reply: audio.reply };
|
||||
}
|
||||
|
||||
function handleTtsStatusAction(
|
||||
params: TtsCommandParams,
|
||||
config: ReturnType<typeof resolveTtsConfig>,
|
||||
prefsPath: string,
|
||||
): CommandHandlerResult {
|
||||
const enabled = isTtsEnabled(config, prefsPath);
|
||||
const provider = getTtsProvider(config, prefsPath);
|
||||
const persona = getTtsPersona(config, prefsPath);
|
||||
const hasKey = isTtsProviderConfigured(config, provider, params.cfg);
|
||||
const maxLength = getTtsMaxLength(prefsPath);
|
||||
const summarize = isSummarizationEnabled(prefsPath);
|
||||
const last = getLastTtsAttempt();
|
||||
const lines = [
|
||||
"📊 TTS status",
|
||||
`State: ${enabled ? "✅ enabled" : "❌ disabled"}`,
|
||||
`Chat override: ${params.sessionEntry?.ttsAuto ?? "default"}`,
|
||||
`Provider: ${provider} (${hasKey ? "✅ configured" : "❌ not configured"})`,
|
||||
`Persona: ${persona?.id ?? "none"}`,
|
||||
`Text limit: ${maxLength} chars`,
|
||||
`Auto-summary: ${summarize ? "on" : "off"}`,
|
||||
];
|
||||
if (last) {
|
||||
const timeAgo = Math.round((Date.now() - last.timestamp) / 1000);
|
||||
lines.push("");
|
||||
lines.push(`Last attempt (${timeAgo}s ago): ${last.success ? "✅" : "❌"}`);
|
||||
lines.push(`Text: ${last.textLength} chars${last.summarized ? " (summarized)" : ""}`);
|
||||
if (last.success) {
|
||||
lines.push(`Provider: ${last.provider ?? "unknown"}`);
|
||||
if (last.persona) {
|
||||
lines.push(`Persona: ${last.persona}`);
|
||||
}
|
||||
if (last.fallbackFrom && last.provider && last.fallbackFrom !== last.provider) {
|
||||
lines.push(`Fallback: ${last.fallbackFrom} -> ${last.provider}`);
|
||||
}
|
||||
if (last.attemptedProviders && last.attemptedProviders.length > 1) {
|
||||
lines.push(`Attempts: ${last.attemptedProviders.join(" -> ")}`);
|
||||
}
|
||||
const details = formatAttemptDetails(last.attempts);
|
||||
if (details) {
|
||||
lines.push(`Attempt details: ${details}`);
|
||||
}
|
||||
lines.push(`Latency: ${last.latencyMs ?? 0}ms`);
|
||||
} else if (last.error) {
|
||||
lines.push(`Error: ${last.error}`);
|
||||
if (last.attemptedProviders && last.attemptedProviders.length > 0) {
|
||||
lines.push(`Attempts: ${last.attemptedProviders.join(" -> ")}`);
|
||||
}
|
||||
const details = formatAttemptDetails(last.attempts);
|
||||
if (details) {
|
||||
lines.push(`Attempt details: ${details}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return stopWithText(lines.join("\n"));
|
||||
}
|
||||
|
||||
export const handleTtsCommands: CommandHandler = async (params, allowTextCommands) => {
|
||||
if (!allowTextCommands) {
|
||||
return null;
|
||||
@@ -207,119 +345,29 @@ export const handleTtsCommands: CommandHandler = async (params, allowTextCommand
|
||||
|
||||
if (action === "on") {
|
||||
setTtsEnabled(prefsPath, true);
|
||||
return { shouldContinue: false, reply: { text: "🔊 TTS enabled." } };
|
||||
return stopWithText("🔊 TTS enabled.");
|
||||
}
|
||||
|
||||
if (action === "off") {
|
||||
setTtsEnabled(prefsPath, false);
|
||||
return { shouldContinue: false, reply: { text: "🔇 TTS disabled." } };
|
||||
return stopWithText("🔇 TTS disabled.");
|
||||
}
|
||||
|
||||
if (action === "chat") {
|
||||
const requested = normalizeOptionalLowercaseString(args) ?? "";
|
||||
if (!params.sessionEntry || !params.sessionStore || !params.sessionKey) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: "🔇 No active chat session is available for a chat-scoped TTS override." },
|
||||
};
|
||||
}
|
||||
if (!requested || requested === "status") {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: `🔊 Chat TTS override: ${params.sessionEntry.ttsAuto ?? "default"}.` },
|
||||
};
|
||||
}
|
||||
if (requested === "on") {
|
||||
params.sessionEntry.ttsAuto = "always";
|
||||
if (!(await persistSessionEntry({ ...params, touchedFields: ["ttsAuto"] }))) {
|
||||
return sessionEntryPersistenceConflictReply();
|
||||
}
|
||||
return { shouldContinue: false, reply: { text: "🔊 TTS enabled for this chat." } };
|
||||
}
|
||||
if (requested === "off") {
|
||||
params.sessionEntry.ttsAuto = "off";
|
||||
if (!(await persistSessionEntry({ ...params, touchedFields: ["ttsAuto"] }))) {
|
||||
return sessionEntryPersistenceConflictReply();
|
||||
}
|
||||
return { shouldContinue: false, reply: { text: "🔇 TTS disabled for this chat." } };
|
||||
}
|
||||
if (requested === "default" || requested === "inherit" || requested === "clear") {
|
||||
delete params.sessionEntry.ttsAuto;
|
||||
if (!(await persistSessionEntry({ ...params, touchedFields: ["ttsAuto"] }))) {
|
||||
return sessionEntryPersistenceConflictReply();
|
||||
}
|
||||
return { shouldContinue: false, reply: { text: "🔊 TTS chat override cleared." } };
|
||||
}
|
||||
return { shouldContinue: false, reply: ttsUsage() };
|
||||
return handleTtsChatAction(params, args);
|
||||
}
|
||||
|
||||
if (
|
||||
action === "latest" ||
|
||||
(action === "read" && normalizeOptionalLowercaseString(args) === "latest")
|
||||
) {
|
||||
if (!params.sessionEntry || !params.sessionStore || !params.sessionKey) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: "🎤 No active chat session is available for `/tts latest`." },
|
||||
};
|
||||
}
|
||||
const latest = await readLatestAssistantTextFromSessionTranscript(
|
||||
params.sessionEntry.sessionFile,
|
||||
);
|
||||
const latestText = latest?.text.trim();
|
||||
if (!latestText || isSilentReplyPayloadText(latestText)) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: "🎤 No readable assistant reply was found in this chat yet." },
|
||||
};
|
||||
}
|
||||
const hash = hashTtsReadLatestText(latestText);
|
||||
if (params.sessionEntry.lastTtsReadLatestHash === hash) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: "🔊 Latest assistant reply was already sent as audio." },
|
||||
};
|
||||
}
|
||||
|
||||
const audio = await buildTtsAudioReply({
|
||||
text: latestText,
|
||||
cfg: params.cfg,
|
||||
channel: params.command.channel,
|
||||
accountId,
|
||||
prefsPath,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
if ("error" in audio) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: `❌ Error generating audio: ${audio.error}` },
|
||||
};
|
||||
}
|
||||
|
||||
params.sessionEntry.lastTtsReadLatestHash = hash;
|
||||
params.sessionEntry.lastTtsReadLatestAt = Date.now();
|
||||
if (
|
||||
!(await persistSessionEntry({
|
||||
...params,
|
||||
touchedFields: ["lastTtsReadLatestHash", "lastTtsReadLatestAt"],
|
||||
}))
|
||||
) {
|
||||
return sessionEntryPersistenceConflictReply();
|
||||
}
|
||||
return { shouldContinue: false, reply: audio.reply };
|
||||
if (action === "latest" || (action === "read" && args.toLowerCase() === "latest")) {
|
||||
return handleTtsLatestAction(params, accountId, prefsPath);
|
||||
}
|
||||
|
||||
if (action === "audio") {
|
||||
if (!args.trim()) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text:
|
||||
`🎤 Generate audio from text.\n\n` +
|
||||
`Usage: /tts audio <text>\n` +
|
||||
`Example: /tts audio Hello, this is a test!`,
|
||||
},
|
||||
};
|
||||
if (!args) {
|
||||
return stopWithText(
|
||||
`🎤 Generate audio from text.\n\n` +
|
||||
`Usage: /tts audio <text>\n` +
|
||||
`Example: /tts audio Hello, this is a test!`,
|
||||
);
|
||||
}
|
||||
|
||||
const audio = await buildTtsAudioReply({
|
||||
@@ -333,46 +381,39 @@ export const handleTtsCommands: CommandHandler = async (params, allowTextCommand
|
||||
if (!("error" in audio)) {
|
||||
return { shouldContinue: false, reply: audio.reply };
|
||||
}
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: `❌ Error generating audio: ${audio.error}` },
|
||||
};
|
||||
return stopWithText(`❌ Error generating audio: ${audio.error}`);
|
||||
}
|
||||
|
||||
if (action === "provider") {
|
||||
const currentProvider = getTtsProvider(config, prefsPath);
|
||||
if (!args.trim()) {
|
||||
if (!args) {
|
||||
const providers = listSpeechProviders(params.cfg);
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text:
|
||||
`🎙️ TTS provider\n` +
|
||||
`Primary: ${currentProvider}\n` +
|
||||
providers
|
||||
.map(
|
||||
(provider) =>
|
||||
`${provider.label}: ${
|
||||
provider.isConfigured({
|
||||
cfg: params.cfg,
|
||||
providerConfig: getResolvedSpeechProviderConfig(
|
||||
config,
|
||||
provider.id,
|
||||
params.cfg,
|
||||
),
|
||||
timeoutMs: config.timeoutMs,
|
||||
})
|
||||
? "✅"
|
||||
: "❌"
|
||||
}`,
|
||||
)
|
||||
.join("\n") +
|
||||
`\nUsage: /tts provider <id>`,
|
||||
},
|
||||
};
|
||||
return stopWithText(
|
||||
`🎙️ TTS provider\n` +
|
||||
`Primary: ${currentProvider}\n` +
|
||||
providers
|
||||
.map(
|
||||
(provider) =>
|
||||
`${provider.label}: ${
|
||||
provider.isConfigured({
|
||||
cfg: params.cfg,
|
||||
providerConfig: getResolvedSpeechProviderConfig(
|
||||
config,
|
||||
provider.id,
|
||||
params.cfg,
|
||||
),
|
||||
timeoutMs: config.timeoutMs,
|
||||
})
|
||||
? "✅"
|
||||
: "❌"
|
||||
}`,
|
||||
)
|
||||
.join("\n") +
|
||||
`\nUsage: /tts provider <id>`,
|
||||
);
|
||||
}
|
||||
|
||||
const requested = normalizeOptionalLowercaseString(args) ?? "";
|
||||
const requested = args.toLowerCase();
|
||||
const resolvedProvider = getSpeechProvider(requested, params.cfg);
|
||||
if (!resolvedProvider) {
|
||||
return { shouldContinue: false, reply: ttsUsage() };
|
||||
@@ -380,16 +421,13 @@ export const handleTtsCommands: CommandHandler = async (params, allowTextCommand
|
||||
|
||||
const nextProvider = canonicalizeSpeechProviderId(requested, params.cfg) ?? resolvedProvider.id;
|
||||
setTtsProvider(prefsPath, nextProvider);
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: `✅ TTS provider set to ${nextProvider}.` },
|
||||
};
|
||||
return stopWithText(`✅ TTS provider set to ${nextProvider}.`);
|
||||
}
|
||||
|
||||
if (action === "persona") {
|
||||
const personas = listTtsPersonas(config);
|
||||
const activePersona = getTtsPersona(config, prefsPath);
|
||||
if (!args.trim()) {
|
||||
if (!args) {
|
||||
const lines = [
|
||||
"🎭 TTS persona",
|
||||
`Active: ${activePersona?.id ?? "none"}`,
|
||||
@@ -404,141 +442,68 @@ export const handleTtsCommands: CommandHandler = async (params, allowTextCommand
|
||||
: "No personas configured.",
|
||||
"Usage: /tts persona <id> | off",
|
||||
];
|
||||
return { shouldContinue: false, reply: { text: lines.join("\n") } };
|
||||
return stopWithText(lines.join("\n"));
|
||||
}
|
||||
|
||||
const requested = normalizeOptionalLowercaseString(args) ?? "";
|
||||
const requested = args.toLowerCase();
|
||||
if (requested === "off" || requested === "none" || requested === "default") {
|
||||
setTtsPersona(prefsPath, null);
|
||||
return { shouldContinue: false, reply: { text: "✅ TTS persona disabled." } };
|
||||
return stopWithText("✅ TTS persona disabled.");
|
||||
}
|
||||
const persona = personas.find((entry) => entry.id === requested);
|
||||
if (!persona) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text:
|
||||
`❌ Unknown TTS persona: ${requested || args}.\n` +
|
||||
`Use /tts persona to list configured personas.`,
|
||||
},
|
||||
};
|
||||
return stopWithText(
|
||||
`❌ Unknown TTS persona: ${requested || args}.\n` +
|
||||
`Use /tts persona to list configured personas.`,
|
||||
);
|
||||
}
|
||||
setTtsPersona(prefsPath, persona.id);
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: `✅ TTS persona set to ${persona.id}.` },
|
||||
};
|
||||
return stopWithText(`✅ TTS persona set to ${persona.id}.`);
|
||||
}
|
||||
|
||||
if (action === "limit") {
|
||||
if (!args.trim()) {
|
||||
if (!args) {
|
||||
const currentLimit = getTtsMaxLength(prefsPath);
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text:
|
||||
`📏 TTS limit: ${currentLimit} characters.\n\n` +
|
||||
`Text longer than this triggers summary (if enabled).\n` +
|
||||
`Range: 100-4096 chars (Telegram max).\n\n` +
|
||||
`To change: /tts limit <number>\n` +
|
||||
`Example: /tts limit 2000`,
|
||||
},
|
||||
};
|
||||
return stopWithText(
|
||||
`📏 TTS limit: ${currentLimit} characters.\n\n` +
|
||||
`Text longer than this triggers summary (if enabled).\n` +
|
||||
`Range: 100-4096 chars (Telegram max).\n\n` +
|
||||
`To change: /tts limit <number>\n` +
|
||||
`Example: /tts limit 2000`,
|
||||
);
|
||||
}
|
||||
const trimmedLimit = args.trim();
|
||||
const next = /^\d+$/.test(trimmedLimit) ? Number(trimmedLimit) : Number.NaN;
|
||||
const next = /^\d+$/.test(args) ? Number(args) : Number.NaN;
|
||||
if (!Number.isSafeInteger(next) || next < 100 || next > 4096) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: "❌ Limit must be between 100 and 4096 characters." },
|
||||
};
|
||||
return stopWithText("❌ Limit must be between 100 and 4096 characters.");
|
||||
}
|
||||
setTtsMaxLength(prefsPath, next);
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: { text: `✅ TTS limit set to ${next} characters.` },
|
||||
};
|
||||
return stopWithText(`✅ TTS limit set to ${next} characters.`);
|
||||
}
|
||||
|
||||
if (action === "summary") {
|
||||
if (!args.trim()) {
|
||||
if (!args) {
|
||||
const enabled = isSummarizationEnabled(prefsPath);
|
||||
const maxLen = getTtsMaxLength(prefsPath);
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text:
|
||||
`📝 TTS auto-summary: ${enabled ? "on" : "off"}.\n\n` +
|
||||
`When text exceeds ${maxLen} chars:\n` +
|
||||
`• ON: summarizes text, then generates audio\n` +
|
||||
`• OFF: truncates text, then generates audio\n\n` +
|
||||
`To change: /tts summary on | off`,
|
||||
},
|
||||
};
|
||||
return stopWithText(
|
||||
`📝 TTS auto-summary: ${enabled ? "on" : "off"}.\n\n` +
|
||||
`When text exceeds ${maxLen} chars:\n` +
|
||||
`• ON: summarizes text, then generates audio\n` +
|
||||
`• OFF: truncates text, then generates audio\n\n` +
|
||||
`To change: /tts summary on | off`,
|
||||
);
|
||||
}
|
||||
const requested = normalizeOptionalLowercaseString(args) ?? "";
|
||||
const requested = args.toLowerCase();
|
||||
if (requested !== "on" && requested !== "off") {
|
||||
return { shouldContinue: false, reply: ttsUsage() };
|
||||
}
|
||||
setSummarizationEnabled(prefsPath, requested === "on");
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text: requested === "on" ? "✅ TTS auto-summary enabled." : "❌ TTS auto-summary disabled.",
|
||||
},
|
||||
};
|
||||
return stopWithText(
|
||||
requested === "on" ? "✅ TTS auto-summary enabled." : "❌ TTS auto-summary disabled.",
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "status") {
|
||||
const enabled = isTtsEnabled(config, prefsPath);
|
||||
const provider = getTtsProvider(config, prefsPath);
|
||||
const persona = getTtsPersona(config, prefsPath);
|
||||
const hasKey = isTtsProviderConfigured(config, provider, params.cfg);
|
||||
const maxLength = getTtsMaxLength(prefsPath);
|
||||
const summarize = isSummarizationEnabled(prefsPath);
|
||||
const last = getLastTtsAttempt();
|
||||
const lines = [
|
||||
"📊 TTS status",
|
||||
`State: ${enabled ? "✅ enabled" : "❌ disabled"}`,
|
||||
`Chat override: ${params.sessionEntry?.ttsAuto ?? "default"}`,
|
||||
`Provider: ${provider} (${hasKey ? "✅ configured" : "❌ not configured"})`,
|
||||
`Persona: ${persona?.id ?? "none"}`,
|
||||
`Text limit: ${maxLength} chars`,
|
||||
`Auto-summary: ${summarize ? "on" : "off"}`,
|
||||
];
|
||||
if (last) {
|
||||
const timeAgo = Math.round((Date.now() - last.timestamp) / 1000);
|
||||
lines.push("");
|
||||
lines.push(`Last attempt (${timeAgo}s ago): ${last.success ? "✅" : "❌"}`);
|
||||
lines.push(`Text: ${last.textLength} chars${last.summarized ? " (summarized)" : ""}`);
|
||||
if (last.success) {
|
||||
lines.push(`Provider: ${last.provider ?? "unknown"}`);
|
||||
if (last.persona) {
|
||||
lines.push(`Persona: ${last.persona}`);
|
||||
}
|
||||
if (last.fallbackFrom && last.provider && last.fallbackFrom !== last.provider) {
|
||||
lines.push(`Fallback: ${last.fallbackFrom} -> ${last.provider}`);
|
||||
}
|
||||
if (last.attemptedProviders && last.attemptedProviders.length > 1) {
|
||||
lines.push(`Attempts: ${last.attemptedProviders.join(" -> ")}`);
|
||||
}
|
||||
const details = formatAttemptDetails(last.attempts);
|
||||
if (details) {
|
||||
lines.push(`Attempt details: ${details}`);
|
||||
}
|
||||
lines.push(`Latency: ${last.latencyMs ?? 0}ms`);
|
||||
} else if (last.error) {
|
||||
lines.push(`Error: ${last.error}`);
|
||||
if (last.attemptedProviders && last.attemptedProviders.length > 0) {
|
||||
lines.push(`Attempts: ${last.attemptedProviders.join(" -> ")}`);
|
||||
}
|
||||
const details = formatAttemptDetails(last.attempts);
|
||||
if (details) {
|
||||
lines.push(`Attempt details: ${details}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { shouldContinue: false, reply: { text: lines.join("\n") } };
|
||||
return handleTtsStatusAction(params, config, prefsPath);
|
||||
}
|
||||
|
||||
return { shouldContinue: false, reply: ttsUsage() };
|
||||
|
||||
Reference in New Issue
Block a user