| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- import { TTSBackend } from "@/types/backend";
- import { EvaluationResult } from "./diagnosisScript";
- const additionalUrls = {
- elevenlabs: "?optimize_streaming_latency=0&output_format=mp3_44100_128",
- openai_tts: "/v1/audio/speech",
- localXTTSTTS: "/api/tts-generate",
- piper: "/api/v1/generate",
- coquiLocal: "/api/tts",
- };
- const message = "Hello World";
- const TIME_OUT = 8000;
- const MIN_DURATION = 2000;
- // Utility to safely call fetch
- async function safeFetch(
- fullUrl: string,
- options?: RequestInit,
- timeoutMs = TIME_OUT
- ): Promise<EvaluationResult> {
- const controller = new AbortController();
- const id = setTimeout(() => controller.abort(), timeoutMs);
- const start = performance.now();
- try {
- if (!options) {
- const res = await fetch(fullUrl, {
- signal: controller.signal,
- });
- const end = performance.now();
- clearTimeout(id);
- const duration = end - start;
- const status = res.ok ? "pass" : "fail";
- const score = calculateScore({ status, duration });
- return { status, score };
- } else {
- const res = await fetch(fullUrl, {
- ...options,
- signal: controller.signal,
- });
- const end = performance.now();
- clearTimeout(id);
- const duration = end - start;
- const status = res.ok ? "pass" : "fail";
- const score = calculateScore({ status, duration });
- return { status, score };
- }
- } catch (err:any) {
- const end = performance.now();
- clearTimeout(id);
- const duration = end - start;
- const isAbort = err.name === "AbortError";
- return { status: "fail", score: calculateScore({ status: "fail", duration, timeout: isAbort }) };
- }
- }
- // Score calculation logic
- function calculateScore({
- status,
- duration,
- timeout = false,
- }: {
- status: "pass" | "fail";
- duration: number;
- timeout?: boolean;
- }): number {
- if (timeout) return 0;
- let score = 0;
- if (status === "pass") score += 50;
- if (duration < MIN_DURATION) score += 50 * ((MIN_DURATION - duration) / MIN_DURATION);
- return Math.round(score);
- }
- // Individual backend handlers
- const backendHandlers: Record<
- string,
- (params: TTSBackend) => Promise<EvaluationResult>
- > = {
- elevenlabs: async (params) => {
- const { elevenlabs_apikey, elevenlabs_voiceid, elevenlabs_model } = params.elevenlabs || {};
- if (!elevenlabs_apikey || !elevenlabs_voiceid || !elevenlabs_model) return {status: "fail", score: 0};
- const url = `https://api.elevenlabs.io/v1/text-to-speech/${elevenlabs_voiceid}`;
- return await safeFetch(`${url}${additionalUrls.elevenlabs}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Accept: "audio/mpeg",
- "xi-api-key": elevenlabs_apikey,
- },
- body: JSON.stringify({
- text: message,
- model_id: elevenlabs_model,
- voice_settings: {
- stability: 0,
- similarity_boost: 0,
- style: 0,
- use_speaker_boost: true,
- },
- }),
- });
- },
- openai_tts: async (params) => {
- const { openai_tts_apikey, openai_tts_url, openai_tts_model, openai_tts_voice } = params.openai_tts || {};
- if (!openai_tts_model || !openai_tts_apikey || !openai_tts_url || !openai_tts_voice) return {status: "fail", score: 0};
- return await safeFetch(`${openai_tts_url}${additionalUrls.openai_tts}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${openai_tts_apikey}`,
- },
- body: JSON.stringify({
- model: openai_tts_model,
- input: message,
- voice: openai_tts_voice,
- }),
- });
- },
- localXTTSTTS: async (params) => {
- const { localXTTS_url, alltalk_version, alltalk_rvc_voice, alltalk_language, alltalk_rvc_pitch, alltalk_voice } =
- params.localXTTS || {};
- const baseUrl = localXTTS_url?.replace(/\/+$/, "").replace("/api/tts-generate", "");
- const formData = new URLSearchParams({
- text_input: message,
- text_filtering: "standard",
- character_voice_gen: alltalk_voice || "female_01.wav",
- narrator_enabled: "false",
- narrator_voice_gen: alltalk_voice || "female_01.wav",
- text_not_inside: "character",
- language: alltalk_language || "en",
- output_file_name: "amica_output",
- output_file_timestamp: "true",
- autoplay: "false",
- autoplay_volume: "0.8",
- });
- if (alltalk_version === "v2") {
- if (alltalk_rvc_voice && alltalk_rvc_voice !== "Disabled") {
- formData.append("rvccharacter_voice_gen", alltalk_rvc_voice);
- formData.append("rvccharacter_pitch", alltalk_rvc_pitch || "0");
- }
- }
- return await safeFetch(`${baseUrl}${additionalUrls.localXTTSTTS}`, {
- method: "POST",
- body: formData,
- });
- },
- piper: async (params) => {
- const { piper_url } = params.piper || {};
- if (!piper_url) return {status: "fail", score: 0};
- const newUrl = new URL(piper_url);
- newUrl.searchParams.append('text', message);
- return await safeFetch(newUrl.toString());
- },
- coquiLocal: async (params) => {
- const { coquiLocal_url, coquiLocal_voiceid } = params.coquiLocal || {};
- if (!coquiLocal_url || !coquiLocal_voiceid) return {status: "fail", score: 0};
- return await safeFetch(`${coquiLocal_url}${additionalUrls.coquiLocal}`, {
- method: "POST",
- headers: {
- 'text': message,
- 'speaker-id': coquiLocal_voiceid,
- }
- });
- },
- speecht5: async () => { return {status: "pass", score: 100}; },
- };
- // Dispatcher function
- export async function ttsDiagnosis(
- backend: string,
- params: TTSBackend,
- ): Promise<EvaluationResult> {
- const handler = backendHandlers[backend];
- if (!handler) return {status: "fail", score: 0};
- return await handler(params);
- }
|