feat: implement tier-a4 tts voice output replies
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import type { OutboundAttachment } from '../channels/types.js';
|
||||
|
||||
export type TtsOutputFormat = 'mp3' | 'wav' | 'opus';
|
||||
|
||||
export interface TtsSynthesisConfig {
|
||||
endpoint?: string;
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
voice?: string;
|
||||
format?: TtsOutputFormat;
|
||||
}
|
||||
|
||||
function outputFormatToMimeType(format: TtsOutputFormat): string {
|
||||
switch (format) {
|
||||
case 'wav':
|
||||
return 'audio/wav';
|
||||
case 'opus':
|
||||
return 'audio/ogg';
|
||||
case 'mp3':
|
||||
default:
|
||||
return 'audio/mpeg';
|
||||
}
|
||||
}
|
||||
|
||||
function outputFormatToExtension(format: TtsOutputFormat): string {
|
||||
switch (format) {
|
||||
case 'wav':
|
||||
return 'wav';
|
||||
case 'opus':
|
||||
return 'ogg';
|
||||
case 'mp3':
|
||||
default:
|
||||
return 'mp3';
|
||||
}
|
||||
}
|
||||
|
||||
/** Synthesize speech via an OpenAI-compatible /v1/audio/speech endpoint. */
|
||||
export async function synthesizeSpeechAttachment(
|
||||
text: string,
|
||||
config: TtsSynthesisConfig,
|
||||
): Promise<OutboundAttachment | null> {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (!config.endpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const format = config.format ?? 'mp3';
|
||||
const model = config.model ?? 'gpt-4o-mini-tts';
|
||||
const voice = config.voice ?? 'alloy';
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (config.apiKey) {
|
||||
headers.Authorization = `Bearer ${config.apiKey}`;
|
||||
}
|
||||
|
||||
const response = await fetch(config.endpoint, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
voice,
|
||||
input: trimmed,
|
||||
response_format: format,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text().catch(() => '');
|
||||
throw new Error(
|
||||
`TTS request failed: ${response.status} ${response.statusText}${detail ? ` - ${detail.slice(0, 200)}` : ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
const audioBytes = await response.arrayBuffer();
|
||||
const data = Buffer.from(audioBytes).toString('base64');
|
||||
const extension = outputFormatToExtension(format);
|
||||
|
||||
return {
|
||||
mimeType: outputFormatToMimeType(format),
|
||||
data,
|
||||
filename: `flynn-reply-${Date.now()}.${extension}`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user