read alout chat messages

This commit is contained in:
2026-02-25 05:11:09 +00:00
parent 0720f25ed5
commit 7bdeecd7f0
7 changed files with 113 additions and 13 deletions
+45
View File
@@ -414,6 +414,51 @@ router.post('/tts', async (ctx) => {
return ctx.json({ audioPath: cacheRel, audioRoot: 'user-data' });
});
// Text-to-speech from raw text with caching
router.post('/tts-text', async (ctx) => {
const user = ctx.get('user');
const { text, id } = ctx.get('body') as { text: string; id: string };
if (!text) throw errors.BAD_REQUEST('text is required');
if (!id) throw errors.BAD_REQUEST('id is required');
const userDataDir = getUserDataDir(user.email);
const cacheRel = `cache/tts/chat/${id}.mp3`;
const cacheAbs = resolve(userDataDir, cacheRel);
if (existsSync(cacheAbs)) {
return ctx.json({ audioPath: cacheRel, audioRoot: 'user-data' });
}
const ttsConfig = await readTtsConfig();
if (!ttsConfig) throw errors.BAD_REQUEST('TTS not configured — set it up in Settings → Text to Speech');
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
let res: Response;
if (ttsConfig.provider === 'elevenlabs') {
if (!ttsConfig.apiKey) throw errors.BAD_REQUEST('ElevenLabs API key not configured');
res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(ttsConfig.voice)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'xi-api-key': ttsConfig.apiKey },
body: JSON.stringify({ text, model_id: ttsConfig.model }),
});
} else {
if (ttsConfig.apiKey) headers['Authorization'] = `Bearer ${ttsConfig.apiKey}`;
res = await fetch(`${ttsConfig.url.replace(/\/+$/, '')}/v1/audio/speech`, {
method: 'POST',
headers,
body: JSON.stringify({ model: ttsConfig.model, input: text, voice: ttsConfig.voice, response_format: 'mp3' }),
});
}
if (!res.ok) throw errors.BAD_REQUEST('TTS request failed');
await mkdir(dirname(cacheAbs), { recursive: true });
const buffer = await res.arrayBuffer();
await Bun.write(cacheAbs, buffer);
return ctx.json({ audioPath: cacheRel, audioRoot: 'user-data' });
});
// OCR image via vision model with caching
router.post('/ocr', async (ctx) => {
const user = ctx.get('user');