// This file handles all communication with your API endpoints. // It is designed to be stateless and does not use any React hooks. import { convertPcmToFloat32 } from "./audioUtils"; // Please replace with your actual endpoints const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; const TTS_ENDPOINT = "http://localhost:8001/speech"; const USERS_LOGIN_ENDPOINT = "http://localhost:8001/users/login"; const USERS_LOGOUT_ENDPOINT = "http://localhost:8001/users/logout"; const USERS_ME_ENDPOINT = "http://localhost:8001/users/me"; /** * A central utility function to get the user ID. * If not found, it redirects to the login page. * @returns {string} The user ID. */ const getUserId = () => { const userId = localStorage.getItem('userId'); if (!userId) { console.error("User not authenticated. Redirecting to login."); // Redirect to the login page window.location.href = '/'; } return userId; }; /** * Initiates the OIDC login flow by redirecting the user to the login endpoint. * This function now sends the frontend's URI to the backend so the backend * knows where to redirect the user after a successful login with the OIDC provider. */ export const login = () => { // Pass the current frontend origin to the backend's login endpoint. // The backend will use this as the `state` parameter for the OIDC provider. const frontendCallbackUri = window.location.origin; const loginUrl = `${USERS_LOGIN_ENDPOINT}?frontend_callback_uri=${encodeURIComponent(frontendCallbackUri)}`; window.location.href = loginUrl; }; /** * Fetches the current user's status from the backend. * @param {string} userId - The unique ID of the current user. * @returns {Promise<Object>} The user status object from the API response. */ export const getUserStatus = async (userId) => { // The backend uses the 'X-User-ID' header to identify the user. const response = await fetch(USERS_ME_ENDPOINT, { method: "GET", headers: { "X-User-ID": userId, }, }); if (!response.ok) { throw new Error(`Failed to get user status. Status: ${response.status}`); } return await response.json(); }; /** * Logs the current user out. * @returns {Promise<Object>} The logout message from the API response. */ export const logout = async () => { const response = await fetch(USERS_LOGOUT_ENDPOINT, { method: "POST", }); if (!response.ok) { throw new Error(`Failed to log out. Status: ${response.status}`); } return await response.json(); }; // --- Updated Existing Functions --- /** * Creates a new chat session. * @returns {Promise<Object>} The session object from the API response. */ export const createSession = async () => { const userId = getUserId(); const response = await fetch(SESSIONS_CREATE_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", "X-User-ID": userId }, // Now we pass the userId to the backend. body: JSON.stringify({ user_id: userId }), }); if (!response.ok) { throw new Error(`Failed to create session. Status: ${response.status}`); } return await response.json(); }; // --- Unchanged Functions --- /** * Sends an audio blob to the STT endpoint for transcription. * @param {Blob} audioBlob - The recorded audio data. * @returns {Promise<string>} The transcribed text. */ export const transcribeAudio = async (audioBlob) => { const userId = getUserId(); const formData = new FormData(); formData.append("audio_file", audioBlob, "audio.wav"); const response = await fetch(STT_ENDPOINT, { method: "POST", body: formData, headers: { "X-User-ID": userId }, }); if (!response.ok) { throw new Error("STT API failed"); } const result = await response.json(); return result.transcript; }; /** * Sends a text prompt to the LLM endpoint and gets a text response. * @param {string} sessionId - The current chat session ID. * @param {string} prompt - The user's text prompt. * @returns {Promise<string>} The AI's text response. */ export const chatWithAI = async (sessionId, prompt) => { const userId = getUserId(); const response = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { method: "POST", headers: { "Content-Type": "application/json", "X-User-ID": userId }, body: JSON.stringify({ prompt: prompt, provider_name: "gemini" }), }); if (!response.ok) { throw new Error("LLM API failed"); } const result = await response.json(); return result.answer; }; /** * Streams speech from the TTS endpoint and processes each chunk. * It uses a callback to pass the processed audio data back to the caller. * @param {string} text - The text to be synthesized. * @param {function(Float32Array): void} onData - Callback for each audio chunk. * @param {function(): void} onDone - Callback to execute when the stream is finished. * @returns {Promise<void>} */ export const streamSpeech = async (text, onData, onDone) => { const userId = getUserId(); try { const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "X-User-ID": userId }, body: JSON.stringify({ text }), }); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const reader = response.body.getReader(); let leftover = new Uint8Array(0); while (true) { const { done, value: chunk } = await reader.read(); if (done) { if (leftover.length > 0) { console.warn("Leftover bytes discarded:", leftover.length); } break; } let combined = new Uint8Array(leftover.length + chunk.length); combined.set(leftover); combined.set(chunk, leftover.length); let length = combined.length; if (length % 2 !== 0) { length -= 1; } const toConvert = combined.slice(0, length); leftover = combined.slice(length); const float32Raw = convertPcmToFloat32(toConvert); // Pass the raw float32 data to the caller for resampling onData(float32Raw); } } catch (error) { console.error("Failed to stream speech:", error); throw error; } finally { // We call the onDone callback to let the hook know the stream has ended. onDone(); } };