Download client/src/utils/api.js from Mistral-AI-Game-Jam/ai-interactive-comic-book: direct link, hf CLI and curl.
- Browser
- Download file 7.92 kB
-
https://huggingface.co/spaces/Mistral-AI-Game-Jam/ai-interactive-comic-book/resolve/main/client/src/utils/api.js
- Command line
-
hf download hf://spaces/Mistral-AI-Game-Jam/ai-interactive-comic-book/client/src/utils/api.js
-
curl -L -o api.js https://huggingface.co/spaces/Mistral-AI-Game-Jam/ai-interactive-comic-book/resolve/main/client/src/utils/api.js
7.92 kB
| 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; | |