copy path and chat about file/folder
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { createRouter } from '../../create-router';
|
||||
import { settingsPath } from './server-settings';
|
||||
|
||||
type TtsConfig = {
|
||||
provider: 'openai' | 'elevenlabs';
|
||||
url: string;
|
||||
apiKey?: string;
|
||||
model: string;
|
||||
voice: string;
|
||||
};
|
||||
|
||||
function maskSecret(value: string | undefined): string | undefined {
|
||||
if (!value || value.length < 8) return value ? '****' : undefined;
|
||||
return value.slice(0, 4) + '****' + value.slice(-4);
|
||||
}
|
||||
|
||||
export async function readTtsConfig(): Promise<TtsConfig | undefined> {
|
||||
const settings = await Bun.file(settingsPath).json().catch(() => ({}));
|
||||
return settings.tts as TtsConfig | undefined;
|
||||
}
|
||||
|
||||
export const ttsRouter = createRouter();
|
||||
|
||||
ttsRouter.get('/', async (ctx) => {
|
||||
const tts = await readTtsConfig();
|
||||
if (!tts) return ctx.json(null);
|
||||
return ctx.json({ ...tts, apiKey: maskSecret(tts.apiKey) });
|
||||
});
|
||||
|
||||
ttsRouter.put('/', async (ctx) => {
|
||||
const body = await ctx.req.json<TtsConfig>();
|
||||
const settings = await Bun.file(settingsPath).json().catch(() => ({}));
|
||||
|
||||
const existing: TtsConfig | undefined = settings.tts;
|
||||
if (existing && body.apiKey?.includes('****')) {
|
||||
body.apiKey = existing.apiKey;
|
||||
}
|
||||
|
||||
settings.tts = body;
|
||||
await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
|
||||
return ctx.json({ success: true });
|
||||
});
|
||||
|
||||
ttsRouter.post('/voices', async (ctx) => {
|
||||
const body = await ctx.req.json<{ provider: string; url?: string; apiKey?: string }>();
|
||||
|
||||
try {
|
||||
if (body.provider === 'elevenlabs') {
|
||||
if (!body.apiKey) return ctx.json({ error: 'API key required' }, 400);
|
||||
const res = await fetch('https://api.elevenlabs.io/v1/voices', {
|
||||
headers: { 'xi-api-key': body.apiKey },
|
||||
});
|
||||
if (!res.ok) return ctx.json({ error: `ElevenLabs error: ${res.status}` }, 500);
|
||||
const json = (await res.json()) as { voices: { voice_id: string; name: string }[] };
|
||||
return ctx.json({ voices: json.voices.map((v) => v.voice_id) });
|
||||
}
|
||||
|
||||
// OpenAI-compatible
|
||||
if (!body.url) return ctx.json({ error: 'URL required' }, 400);
|
||||
const headers: Record<string, string> = {};
|
||||
if (body.apiKey) headers['Authorization'] = `Bearer ${body.apiKey}`;
|
||||
const res = await fetch(`${body.url.replace(/\/+$/, '')}/v1/audio/voices`, { headers });
|
||||
if (!res.ok) return ctx.json({ error: `Voices fetch failed: ${res.status}` }, 500);
|
||||
const json = (await res.json()) as { voices: string[] };
|
||||
return ctx.json({ voices: json.voices });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||
return ctx.json({ error: message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
ttsRouter.post('/test', async (ctx) => {
|
||||
const body = await ctx.req.json<TtsConfig>();
|
||||
|
||||
const settings = await Bun.file(settingsPath).json().catch(() => ({}));
|
||||
const saved: TtsConfig | undefined = settings.tts;
|
||||
if (saved && body.apiKey?.includes('****')) {
|
||||
body.apiKey = saved.apiKey;
|
||||
}
|
||||
|
||||
try {
|
||||
if (body.provider === 'elevenlabs') {
|
||||
if (!body.apiKey) return ctx.json({ error: 'API key required for ElevenLabs' }, 400);
|
||||
const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(body.voice)}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'xi-api-key': body.apiKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: 'This is a test of the text to speech system.',
|
||||
model_id: body.model,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
return ctx.json({ error: `ElevenLabs error: ${res.status} ${text}` }, 500);
|
||||
}
|
||||
const buffer = await res.arrayBuffer();
|
||||
return new Response(buffer, { headers: { 'Content-Type': 'audio/mpeg' } });
|
||||
}
|
||||
|
||||
// OpenAI-compatible (Kokoro, OpenAI, etc.)
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (body.apiKey) headers['Authorization'] = `Bearer ${body.apiKey}`;
|
||||
|
||||
const res = await fetch(`${body.url.replace(/\/+$/, '')}/v1/audio/speech`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: body.model,
|
||||
input: 'This is a test of the text to speech system.',
|
||||
voice: body.voice,
|
||||
response_format: 'mp3',
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => '');
|
||||
return ctx.json({ error: `TTS error: ${res.status} ${text}` }, 500);
|
||||
}
|
||||
const buffer = await res.arrayBuffer();
|
||||
return new Response(buffer, { headers: { 'Content-Type': 'audio/mpeg' } });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||
return ctx.json({ error: message }, 500);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user