fix: polish tts auto mode + tests (#1667) (thanks @sebslight)
This commit is contained in:
parent
2c1be8af4b
commit
32d370e92e
@ -10,6 +10,7 @@ Docs: https://docs.clawd.bot
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
|
- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
|
||||||
|
- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts
|
||||||
- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
|
- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
|
||||||
- Docs: add verbose installer troubleshooting guidance.
|
- Docs: add verbose installer troubleshooting guidance.
|
||||||
- Docs: update Fly.io guide notes.
|
- Docs: update Fly.io guide notes.
|
||||||
|
|||||||
@ -76,13 +76,16 @@ export const handleTtsCommands: CommandHandler = async (params, allowTextCommand
|
|||||||
action === "on" ? "always" : action === "off" ? "off" : action,
|
action === "on" ? "always" : action === "off" ? "off" : action,
|
||||||
);
|
);
|
||||||
if (requestedAuto) {
|
if (requestedAuto) {
|
||||||
if (params.sessionEntry && params.sessionStore && params.sessionKey) {
|
const entry = params.sessionEntry;
|
||||||
params.sessionEntry.ttsAuto = requestedAuto;
|
const sessionKey = params.sessionKey;
|
||||||
params.sessionEntry.updatedAt = Date.now();
|
const store = params.sessionStore;
|
||||||
params.sessionStore[params.sessionKey] = params.sessionEntry;
|
if (entry && store && sessionKey) {
|
||||||
|
entry.ttsAuto = requestedAuto;
|
||||||
|
entry.updatedAt = Date.now();
|
||||||
|
store[sessionKey] = entry;
|
||||||
if (params.storePath) {
|
if (params.storePath) {
|
||||||
await updateSessionStore(params.storePath, (store) => {
|
await updateSessionStore(params.storePath, (store) => {
|
||||||
store[params.sessionKey] = params.sessionEntry;
|
store[sessionKey] = entry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import path from "node:path";
|
|||||||
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
|
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
|
||||||
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
|
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
|
import type { TtsAutoMode } from "../../config/types.tts.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_RESET_TRIGGERS,
|
DEFAULT_RESET_TRIGGERS,
|
||||||
deriveSessionMetaPatch,
|
deriveSessionMetaPatch,
|
||||||
@ -128,7 +129,7 @@ export async function initSessionState(params: {
|
|||||||
let persistedThinking: string | undefined;
|
let persistedThinking: string | undefined;
|
||||||
let persistedVerbose: string | undefined;
|
let persistedVerbose: string | undefined;
|
||||||
let persistedReasoning: string | undefined;
|
let persistedReasoning: string | undefined;
|
||||||
let persistedTtsAuto: string | undefined;
|
let persistedTtsAuto: TtsAutoMode | undefined;
|
||||||
let persistedModelOverride: string | undefined;
|
let persistedModelOverride: string | undefined;
|
||||||
let persistedProviderOverride: string | undefined;
|
let persistedProviderOverride: string | undefined;
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
|
|||||||
if (typeof tts.enabled !== "boolean") return;
|
if (typeof tts.enabled !== "boolean") return;
|
||||||
tts.auto = tts.enabled ? "always" : "off";
|
tts.auto = tts.enabled ? "always" : "off";
|
||||||
delete tts.enabled;
|
delete tts.enabled;
|
||||||
changes.push(`Moved messages.tts.enabled → messages.tts.auto (${tts.auto}).`);
|
changes.push(`Moved messages.tts.enabled → messages.tts.auto (${String(tts.auto)}).`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -134,8 +134,9 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
threadParentType === ChannelType.GuildForum || threadParentType === ChannelType.GuildMedia;
|
threadParentType === ChannelType.GuildForum || threadParentType === ChannelType.GuildMedia;
|
||||||
const forumParentSlug =
|
const forumParentSlug =
|
||||||
isForumParent && threadParentName ? normalizeDiscordSlug(threadParentName) : "";
|
isForumParent && threadParentName ? normalizeDiscordSlug(threadParentName) : "";
|
||||||
|
const threadChannelId = threadChannel?.id;
|
||||||
const isForumStarter =
|
const isForumStarter =
|
||||||
Boolean(threadChannel && isForumParent && forumParentSlug) && message.id === threadChannel.id;
|
Boolean(threadChannelId && isForumParent && forumParentSlug) && message.id === threadChannelId;
|
||||||
const forumContextLine = isForumStarter ? `[Forum parent: #${forumParentSlug}]` : null;
|
const forumContextLine = isForumStarter ? `[Forum parent: #${forumParentSlug}]` : null;
|
||||||
const groupChannel = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : undefined;
|
const groupChannel = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : undefined;
|
||||||
const groupSubject = isDirectMessage ? undefined : groupChannel;
|
const groupSubject = isDirectMessage ? undefined : groupChannel;
|
||||||
|
|||||||
@ -306,7 +306,7 @@ function resolveTtsAutoModeFromPrefs(prefs: TtsUserPrefs): TtsAutoMode | undefin
|
|||||||
export function resolveTtsAutoMode(params: {
|
export function resolveTtsAutoMode(params: {
|
||||||
config: ResolvedTtsConfig;
|
config: ResolvedTtsConfig;
|
||||||
prefsPath: string;
|
prefsPath: string;
|
||||||
sessionAuto?: TtsAutoMode | string;
|
sessionAuto?: string;
|
||||||
}): TtsAutoMode {
|
}): TtsAutoMode {
|
||||||
const sessionAuto = normalizeTtsAutoMode(params.sessionAuto);
|
const sessionAuto = normalizeTtsAutoMode(params.sessionAuto);
|
||||||
if (sessionAuto) return sessionAuto;
|
if (sessionAuto) return sessionAuto;
|
||||||
@ -372,7 +372,7 @@ function updatePrefs(prefsPath: string, update: (prefs: TtsUserPrefs) => void):
|
|||||||
export function isTtsEnabled(
|
export function isTtsEnabled(
|
||||||
config: ResolvedTtsConfig,
|
config: ResolvedTtsConfig,
|
||||||
prefsPath: string,
|
prefsPath: string,
|
||||||
sessionAuto?: TtsAutoMode | string,
|
sessionAuto?: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
return resolveTtsAutoMode({ config, prefsPath, sessionAuto }) !== "off";
|
return resolveTtsAutoMode({ config, prefsPath, sessionAuto }) !== "off";
|
||||||
}
|
}
|
||||||
@ -1216,7 +1216,7 @@ export async function maybeApplyTtsToPayload(params: {
|
|||||||
channel?: string;
|
channel?: string;
|
||||||
kind?: "tool" | "block" | "final";
|
kind?: "tool" | "block" | "final";
|
||||||
inboundAudio?: boolean;
|
inboundAudio?: boolean;
|
||||||
ttsAuto?: TtsAutoMode | string;
|
ttsAuto?: string;
|
||||||
}): Promise<ReplyPayload> {
|
}): Promise<ReplyPayload> {
|
||||||
const config = resolveTtsConfig(params.cfg);
|
const config = resolveTtsConfig(params.cfg);
|
||||||
const prefsPath = resolveTtsPrefsPath(config);
|
const prefsPath = resolveTtsPrefsPath(config);
|
||||||
|
|||||||
18
src/types/node-edge-tts.d.ts
vendored
Normal file
18
src/types/node-edge-tts.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
declare module "node-edge-tts" {
|
||||||
|
export type EdgeTTSOptions = {
|
||||||
|
voice?: string;
|
||||||
|
lang?: string;
|
||||||
|
outputFormat?: string;
|
||||||
|
saveSubtitles?: boolean;
|
||||||
|
proxy?: string;
|
||||||
|
rate?: string;
|
||||||
|
pitch?: string;
|
||||||
|
volume?: string;
|
||||||
|
timeout?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EdgeTTS {
|
||||||
|
constructor(options?: EdgeTTSOptions);
|
||||||
|
ttsPromise(text: string, outputPath: string): Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -127,9 +127,9 @@ describe("web inbound media saves with extension", () => {
|
|||||||
realSock.ev.emit("messages.upsert", upsert);
|
realSock.ev.emit("messages.upsert", upsert);
|
||||||
|
|
||||||
// Allow a brief window for the async handler to fire on slower hosts.
|
// Allow a brief window for the async handler to fire on slower hosts.
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
if (onMessage.mock.calls.length > 0) break;
|
if (onMessage.mock.calls.length > 0) break;
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(onMessage).toHaveBeenCalledTimes(1);
|
expect(onMessage).toHaveBeenCalledTimes(1);
|
||||||
@ -178,9 +178,9 @@ describe("web inbound media saves with extension", () => {
|
|||||||
|
|
||||||
realSock.ev.emit("messages.upsert", upsert);
|
realSock.ev.emit("messages.upsert", upsert);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
if (onMessage.mock.calls.length > 0) break;
|
if (onMessage.mock.calls.length > 0) break;
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(onMessage).toHaveBeenCalledTimes(1);
|
expect(onMessage).toHaveBeenCalledTimes(1);
|
||||||
@ -218,9 +218,9 @@ describe("web inbound media saves with extension", () => {
|
|||||||
|
|
||||||
realSock.ev.emit("messages.upsert", upsert);
|
realSock.ev.emit("messages.upsert", upsert);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
if (onMessage.mock.calls.length > 0) break;
|
if (onMessage.mock.calls.length > 0) break;
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(onMessage).toHaveBeenCalledTimes(1);
|
expect(onMessage).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user