221 lines
8.8 KiB
JavaScript
221 lines
8.8 KiB
JavaScript
// 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<Array<Object>>} 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<Object>} 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<Object>} 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<Object>} 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<Object>} 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()
|
|
};
|
|
}
|