// frontend/js/api.js import { API_BASE_URL_WITH_PREFIX } from './config.js'; const API_BASE_URL = API_BASE_URL_WITH_PREFIX; /** * Fetches the list of available speakers. * @returns {Promise>} A promise that resolves to an array of speaker objects. * @throws {Error} If the network response is not ok. */ export async function getSpeakers() { const url = `${API_BASE_URL}/speakers/`; const response = await fetch(url); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Failed to fetch speakers: ${errorData.detail || errorData.message || response.statusText}`); } return response.json(); } // We will add more functions here: addSpeaker, deleteSpeaker, generateDialog // ... (keep API_BASE_URL and getSpeakers) /** * Adds a new speaker (Higgs TTS only). * @param {Object} speakerData - The speaker data object * @param {string} speakerData.name - Speaker name * @param {File} speakerData.audioFile - Audio file * @param {string} speakerData.referenceText - Reference text (required for Higgs TTS) * @returns {Promise} A promise that resolves to the new speaker object. * @throws {Error} If the network response is not ok. */ export async function addSpeaker(speakerData) { // Create FormData from speakerData object const formData = new FormData(); formData.append('name', speakerData.name); formData.append('audio_file', speakerData.audioFile); formData.append('reference_text', speakerData.referenceText); const response = await fetch(`${API_BASE_URL}/speakers/`, { method: 'POST', body: formData, // FormData sets Content-Type to multipart/form-data automatically }); if (!response.ok) { console.log('API_JS_ADD_SPEAKER: Entered !response.ok block. Status:', response.status, 'StatusText:', response.statusText); let errorPayload = { detail: `Request failed with status ${response.status}` }; // Default payload try { console.log('API_JS_ADD_SPEAKER: Attempting to parse error response as JSON...'); errorPayload = await response.json(); console.log('API_JS_ADD_SPEAKER: Successfully parsed error JSON:', errorPayload); } catch (e) { console.warn('API_JS_ADD_SPEAKER: Failed to parse error response as JSON. Error:', e); // Use statusText if JSON parsing fails errorPayload = { detail: response.statusText || `Request failed with status ${response.status} and no JSON body.`, parseError: e.toString() }; } console.error('--- BEGIN SERVER ERROR PAYLOAD (addSpeaker) ---'); console.error('Status:', response.status); console.error('Status Text:', response.statusText); console.error('Parsed Payload:', errorPayload); console.error('--- END SERVER ERROR PAYLOAD (addSpeaker) ---'); let detailedMessage = "Unknown error"; if (errorPayload && errorPayload.detail) { if (typeof errorPayload.detail === 'string') { detailedMessage = errorPayload.detail; } else { // If detail is an array (FastAPI validation errors) or object, stringify it. detailedMessage = JSON.stringify(errorPayload.detail); } } else if (errorPayload && errorPayload.message) { detailedMessage = errorPayload.message; } else if (response.statusText) { detailedMessage = response.statusText; } else { detailedMessage = `HTTP error ${response.status}`; } console.log(`API_JS_ADD_SPEAKER: Constructed detailedMessage: "${detailedMessage}"`); console.log(`API_JS_ADD_SPEAKER: Throwing error with message: "Failed to add speaker: ${detailedMessage}"`); throw new Error(`Failed to add speaker: ${detailedMessage}`); } return response.json(); } // ... (keep API_BASE_URL, getSpeakers, addSpeaker) /** * Deletes a speaker by their ID. * @param {string} speakerId - The ID of the speaker to delete. * @returns {Promise} A promise that resolves to the response data (e.g., success message). * @throws {Error} If the network response is not ok. */ export async function deleteSpeaker(speakerId) { const response = await fetch(`${API_BASE_URL}/speakers/${speakerId}/`, { method: 'DELETE', }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Failed to delete speaker ${speakerId}: ${errorData.detail || errorData.message || response.statusText}`); } // Handle 204 No Content specifically, as .json() would fail if (response.status === 204) { return { message: `Speaker ${speakerId} deleted successfully.` }; } return response.json(); } // ... (keep API_BASE_URL, getSpeakers, addSpeaker, deleteSpeaker) /** * Generates audio for a single dialog line (speech or silence). * @param {Object} line - The dialog line object (type: 'speech' or 'silence'). * @returns {Promise} Resolves with { audio_url } on success. * @throws {Error} If the network response is not ok. */ export async function generateLine(line) { console.log('generateLine called with:', line); const response = await fetch(`${API_BASE_URL}/dialog/generate_line`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(line), }); console.log('Response status:', response.status); console.log('Response headers:', [...response.headers.entries()]); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Failed to generate line audio: ${errorData.detail || errorData.message || response.statusText}`); } const responseText = await response.text(); console.log('Raw response text:', responseText); try { const jsonData = JSON.parse(responseText); console.log('Parsed JSON:', jsonData); return jsonData; } catch (parseError) { console.error('JSON parse error:', parseError); throw new Error(`Invalid JSON response: ${responseText}`); } } /** * Generates a dialog by sending a payload to the backend. * @param {Object} dialogPayload - The payload for dialog generation. * Example: * { * output_base_name: "my_dialog", * dialog_items: [ * { type: "speech", speaker_id: "speaker1", text: "Hello world.", exaggeration: 1.0, cfg_weight: 2.0, temperature: 0.7 }, * { type: "silence", duration_ms: 500 }, * { type: "speech", speaker_id: "speaker2", text: "How are you?" } * ] * } * @returns {Promise} A promise that resolves to the dialog generation response (log, file URLs). * @throws {Error} If the network response is not ok. */ export async function generateDialog(dialogPayload) { const response = await fetch(`${API_BASE_URL}/dialog/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(dialogPayload), }); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`Failed to generate dialog: ${errorData.detail || errorData.message || response.statusText}`); } return response.json(); } /** * Validates speaker data for Higgs TTS. * @param {Object} speakerData - Speaker data to validate * @param {string} speakerData.name - Speaker name * @param {string} speakerData.referenceText - Reference text * @returns {Object} Validation result with errors if any */ export function validateSpeakerData(speakerData) { const errors = {}; // Validate name if (!speakerData.name || speakerData.name.trim().length === 0) { errors.name = 'Speaker name is required'; } // Validate reference text (required for Higgs TTS) if (!speakerData.referenceText || speakerData.referenceText.trim().length === 0) { errors.referenceText = 'Reference text is required for Higgs TTS'; } else if (speakerData.referenceText.trim().length > 500) { errors.referenceText = 'Reference text should be under 500 characters'; } return { isValid: Object.keys(errors).length === 0, errors: errors }; } /** * Creates a speaker data object for Higgs TTS. * @param {string} name - Speaker name * @param {File} audioFile - Audio file * @param {string} referenceText - Reference text (required for Higgs TTS) * @returns {Object} Properly formatted speaker data object */ export function createSpeakerData(name, audioFile, referenceText) { return { name: name.trim(), audioFile: audioFile, referenceText: referenceText.trim() }; }