tfrere's picture
tfrere HF Staff
remove non finished features, add mobile handling, add write your own path feature
ddf672c
import axios from "axios";
import { getDefaultHeaders } from "./session";
// Get API URL from environment or default to localhost in development
const isHFSpace = window.location.hostname.includes("hf.space");
const API_URL = isHFSpace
? "" // URL relative pour HF Spaces
: import.meta.env.VITE_API_URL || "http://localhost:8000";
// Create axios instance with default config
const api = axios.create({
baseURL: API_URL,
...(isHFSpace && {
baseURL: window.location.origin,
}),
});
// Add request interceptor to handle headers
api.interceptors.request.use((config) => {
// Routes qui ne nécessitent pas de session_id
const noSessionRoutes = [
"/api/universe/generate",
"/api/generate-image",
"/api/text-to-speech",
];
if (noSessionRoutes.includes(config.url)) {
return config;
}
// Pour toutes les autres requêtes, s'assurer qu'on a un session_id
if (!config.headers["x-session-id"]) {
throw new Error("Session ID is required for this request");
}
return config;
});
// Error handling middleware
const handleApiError = (error) => {
console.error("API Error:", {
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
config: {
method: error.config?.method,
url: error.config?.url,
data: error.config?.data,
},
});
if (error.response) {
// La requête a été faite et le serveur a répondu avec un code d'erreur
throw new Error(
error.response.data?.message ||
`Erreur ${error.response.status}: ${error.response.statusText}`
);
} else if (error.request) {
// La requête a été faite mais aucune réponse n'a été reçue
throw new Error("Aucune réponse du serveur");
} else {
// Une erreur s'est produite lors de la configuration de la requête
throw new Error(
"Une erreur est survenue lors de la configuration de la requête"
);
}
};
// Audio context for narration
let audioContext = null;
let audioSource = null;
let isSoundEnabled = true;
let hasUserInteraction = false;
// Initialize audio context on user interaction
const initAudioContext = () => {
if (!hasUserInteraction) {
console.warn("Audio context cannot be initialized before user interaction");
return null;
}
if (!audioContext) {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
if (audioContext.state === "suspended") {
audioContext.resume();
}
} catch (error) {
console.error("Failed to initialize audio context:", error);
return null;
}
}
return audioContext;
};
// Function to call when user interacts with the page
const handleUserInteraction = () => {
hasUserInteraction = true;
if (audioContext && audioContext.state === "suspended") {
audioContext.resume();
}
};
// Nouvelle fonction pour gérer l'état du son
const setSoundEnabled = (enabled) => {
isSoundEnabled = enabled;
if (!enabled && audioSource) {
audioSource.stop();
audioSource = null;
}
if (!enabled && audioContext) {
audioContext.suspend();
}
};
// Story related API calls
export const storyApi = {
start: async (sessionId) => {
try {
const response = await api.post(
"/api/chat",
{
message: "restart",
},
{
headers: getDefaultHeaders(sessionId),
}
);
return response.data;
} catch (error) {
return handleApiError(error);
}
},
makeChoice: async (choiceId, sessionId) => {
try {
const response = await api.post(
"/api/chat",
{
message: "choice",
choice_id: choiceId,
},
{
headers: getDefaultHeaders(sessionId),
}
);
return response.data;
} catch (error) {
return handleApiError(error);
}
},
makeCustomChoice: async (customText, sessionId) => {
try {
const response = await api.post(
"/api/chat",
{
message: "custom_choice",
custom_text: customText,
},
{
headers: getDefaultHeaders(sessionId),
}
);
return response.data;
} catch (error) {
return handleApiError(error);
}
},
generateImage: async (
prompt,
width = 512,
height = 512,
sessionId = null
) => {
try {
const config = {
prompt,
width,
height,
};
const options = {};
if (sessionId) {
options.headers = getDefaultHeaders(sessionId);
}
const response = await api.post("/api/generate-image", config, options);
return response.data;
} catch (error) {
return handleApiError(error);
}
},
// Narration related API calls
playNarration: async (text, sessionId) => {
try {
// Ne rien faire si le son est désactivé ou si pas d'interaction utilisateur
if (!isSoundEnabled || !hasUserInteraction) {
return;
}
// Stop any existing narration
if (audioSource) {
audioSource.stop();
audioSource = null;
}
// Initialize audio context if needed
audioContext = initAudioContext();
if (!audioContext) {
return;
}
const response = await api.post(
"/api/text-to-speech",
{
text,
voice_id: "21m00Tcm4TlvDq8ikWAM", // Rachel voice
},
{
headers: getDefaultHeaders(sessionId),
}
);
if (!response.data.success) {
throw new Error("Failed to generate audio");
}
// Ne pas continuer si le son a été désactivé pendant la requête
if (!isSoundEnabled) {
return;
}
// Convert base64 to audio buffer
const audioData = atob(response.data.audio_base64);
const arrayBuffer = new ArrayBuffer(audioData.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < audioData.length; i++) {
view[i] = audioData.charCodeAt(i);
}
// Decode audio data
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// Ne pas continuer si le son a été désactivé pendant le décodage
if (!isSoundEnabled) {
return;
}
// Create and play audio source
audioSource = audioContext.createBufferSource();
audioSource.buffer = audioBuffer;
audioSource.connect(audioContext.destination);
audioSource.start(0);
// Return a promise that resolves when the audio finishes playing
return new Promise((resolve) => {
audioSource.onended = () => {
audioSource = null;
resolve();
};
});
} catch (error) {
console.error("Error playing narration:", error);
throw error;
}
},
stopNarration: () => {
if (audioSource) {
try {
audioSource.stop();
} catch (error) {
console.warn("Error stopping narration:", error);
}
audioSource = null;
}
if (audioContext) {
try {
audioContext.suspend();
} catch (error) {
console.warn("Error suspending audio context:", error);
}
}
},
initAudioContext,
handleUserInteraction,
setSoundEnabled, // Exporter la nouvelle fonction
};
// WebSocket URL
export const WS_URL = import.meta.env.VITE_WS_URL || "ws://localhost:8000/ws";
export const universeApi = {
generate: async () => {
try {
const response = await api.post("/api/universe/generate");
return response.data;
} catch (error) {
return handleApiError(error);
}
},
getStyles: async () => {
try {
const response = await api.get("/api/universe/styles");
return response.data;
} catch (error) {
return handleApiError(error);
}
},
};
// Export the base API instance for other uses
export default api;