Spaces:
Sleeping
Sleeping
Download frontend/src/services/api.js from chowdary1/ai-interviewer-version-2: direct link, hf CLI and curl.
- Browser
- Download file 48.6 kB
-
https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/services/api.js
- Command line
-
hf download hf://spaces/chowdary1/ai-interviewer-version-2/frontend/src/services/api.js
-
curl -L -o api.js https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/services/api.js
48.6 kB
| /** | |
| * API Service | |
| * | |
| * Centralized API client for backend communication. | |
| * All API calls go through this service. | |
| * | |
| * IMPORTANT: Frontend ONLY talks to backend. | |
| * NO direct AI API calls from frontend. | |
| */ | |
| const API_BASE_URL = '/api'; | |
| // Token key for localStorage | |
| const TOKEN_KEY = 'ai_interviewer_token'; | |
| const USER_KEY = 'ai_interviewer_user'; | |
| // =========================================== | |
| // TOKEN MANAGEMENT | |
| // =========================================== | |
| /** | |
| * Get auth token from storage | |
| */ | |
| export const getToken = () => localStorage.getItem(TOKEN_KEY); | |
| /** | |
| * Set auth token in storage | |
| */ | |
| export const setToken = (token) => { | |
| localStorage.setItem(TOKEN_KEY, token); | |
| }; | |
| /** | |
| * Remove auth token from storage | |
| */ | |
| export const removeToken = () => { | |
| localStorage.removeItem(TOKEN_KEY); | |
| localStorage.removeItem(USER_KEY); | |
| }; | |
| /** | |
| * Get stored user data | |
| */ | |
| export const getStoredUser = () => { | |
| const userData = localStorage.getItem(USER_KEY); | |
| return userData ? JSON.parse(userData) : null; | |
| }; | |
| /** | |
| * Store user data | |
| */ | |
| export const setStoredUser = (user) => { | |
| localStorage.setItem(USER_KEY, JSON.stringify(user)); | |
| }; | |
| /** | |
| * Clear all app-specific cached data (NOT auth tokens) | |
| * Use this on app reload to ensure fresh state | |
| */ | |
| export const clearSessionData = () => { | |
| // Clear sessionStorage completely | |
| sessionStorage.clear(); | |
| // Clear any interview/session related data from localStorage | |
| // BUT PRESERVE auth tokens (ai_interviewer_token, ai_interviewer_user) | |
| const keysToRemove = []; | |
| for (let i = 0; i < localStorage.length; i++) { | |
| const key = localStorage.key(i); | |
| // CRITICAL: Skip auth keys - never clear these! | |
| if (key === TOKEN_KEY || key === USER_KEY) { | |
| continue; | |
| } | |
| // Also skip theme preference | |
| if (key === 'ai_interviewer_theme') { | |
| continue; | |
| } | |
| // Clear interview/session related cached data | |
| if (key && ( | |
| key.includes('interview_session') || | |
| key.includes('current_interview') || | |
| key.includes('ats_') || | |
| key.includes('plan_') || | |
| key.includes('analytics_cache') || | |
| key.includes('resume_cache') || | |
| key.includes('evaluation_cache') | |
| )) { | |
| keysToRemove.push(key); | |
| } | |
| } | |
| keysToRemove.forEach(key => localStorage.removeItem(key)); | |
| }; | |
| /** | |
| * Clear ALL app data including auth (for logout) | |
| * Preserves only theme preference | |
| */ | |
| export const clearAllAppData = () => { | |
| // Save theme preference before clearing | |
| const theme = localStorage.getItem('ai_interviewer_theme'); | |
| // Clear everything | |
| localStorage.clear(); | |
| sessionStorage.clear(); | |
| // Restore theme preference | |
| if (theme) { | |
| localStorage.setItem('ai_interviewer_theme', theme); | |
| } | |
| }; | |
| /** | |
| * Check if user is authenticated | |
| */ | |
| export const isAuthenticated = () => !!getToken(); | |
| // =========================================== | |
| // HTTP REQUEST HELPER | |
| // =========================================== | |
| // Flag to track if we've already detected a connection error | |
| // This prevents retry spam on ECONNREFUSED | |
| let connectionErrorDetected = false; | |
| /** | |
| * Reset connection error flag (call when app starts fresh) | |
| */ | |
| export const resetConnectionState = () => { | |
| connectionErrorDetected = false; | |
| }; | |
| /** | |
| * Check if backend is reachable | |
| */ | |
| export const checkBackendHealth = async () => { | |
| try { | |
| const response = await fetch(`${API_BASE_URL}/health`, { | |
| method: 'GET', | |
| headers: { 'Content-Type': 'application/json' }, | |
| }); | |
| if (response.ok) { | |
| connectionErrorDetected = false; | |
| return true; | |
| } | |
| return false; | |
| } catch { | |
| return false; | |
| } | |
| }; | |
| /** | |
| * Make authenticated API request | |
| * SAFETY: Handles ECONNREFUSED gracefully, no retry spam | |
| */ | |
| async function request(endpoint, options = {}) { | |
| // If we've already detected a connection error, don't spam requests | |
| if (connectionErrorDetected && endpoint !== '/health') { | |
| throw new Error('Backend is unreachable. Please ensure the server is running.'); | |
| } | |
| const token = getToken(); | |
| const headers = { | |
| 'Content-Type': 'application/json', | |
| ...(token && { Authorization: `Bearer ${token}` }), | |
| ...options.headers, | |
| }; | |
| try { | |
| const response = await fetch(`${API_BASE_URL}${endpoint}`, { | |
| ...options, | |
| headers, | |
| }); | |
| // Connection succeeded, reset error flag | |
| connectionErrorDetected = false; | |
| // Handle 401 - Unauthorized | |
| if (response.status === 401) { | |
| removeToken(); | |
| window.location.href = '/login'; | |
| throw new Error('Session expired. Please login again.'); | |
| } | |
| // Parse response | |
| const data = await response.json().catch(() => ({})); | |
| if (!response.ok) { | |
| throw new Error(data.detail || data.message || `Request failed: ${response.status}`); | |
| } | |
| return data; | |
| } catch (error) { | |
| // Check for connection errors (ECONNREFUSED, network failure, etc.) | |
| const isConnectionError = | |
| error.message === 'Failed to fetch' || | |
| error.message.includes('NetworkError') || | |
| error.message.includes('ECONNREFUSED') || | |
| error.name === 'TypeError' && !error.message.includes('401'); | |
| if (isConnectionError) { | |
| // Mark connection as failed to prevent retry spam | |
| connectionErrorDetected = true; | |
| // DON'T clear auth state on connection errors - the server might just be temporarily down | |
| // The user's token might still be valid | |
| // removeToken(); // REMOVED - was causing logout on refresh | |
| // Don't redirect to login on connection error - show error instead | |
| // if (window.location.pathname !== '/login') { | |
| // console.error('[API] Backend unreachable - redirecting to login'); | |
| // window.location.href = '/login'; | |
| // } | |
| throw new Error('Unable to connect to server. Please check if the backend is running.'); | |
| } | |
| // For auth-related errors, don't retry | |
| if (error.message.includes('Session expired')) { | |
| throw error; | |
| } | |
| throw error; | |
| } | |
| } | |
| // =========================================== | |
| // AUTH API | |
| // =========================================== | |
| export const authApi = { | |
| /** | |
| * User signup | |
| */ | |
| async signup(name, email, password) { | |
| const response = await request('/auth/signup', { | |
| method: 'POST', | |
| body: JSON.stringify({ name, email, password }), | |
| }); | |
| if (response.success && response.token) { | |
| setToken(response.token.access_token); | |
| setStoredUser(response.user); | |
| } | |
| return response; | |
| }, | |
| /** | |
| * User login | |
| */ | |
| async login(email, password) { | |
| const response = await request('/auth/login', { | |
| method: 'POST', | |
| body: JSON.stringify({ email, password }), | |
| }); | |
| if (response.success && response.token) { | |
| setToken(response.token.access_token); | |
| setStoredUser(response.user); | |
| } | |
| return response; | |
| }, | |
| /** | |
| * User logout | |
| */ | |
| async logout() { | |
| try { | |
| await request('/auth/logout', { method: 'POST' }); | |
| } catch (error) { | |
| // Continue with local logout even if server fails | |
| console.warn('Logout API call failed:', error); | |
| } finally { | |
| removeToken(); | |
| } | |
| }, | |
| /** | |
| * Get current user | |
| */ | |
| async getCurrentUser() { | |
| return request('/auth/me'); | |
| }, | |
| /** | |
| * Verify token is valid | |
| * SAFETY: Only calls backend if token exists in localStorage | |
| * Returns: true (valid), false (invalid), or throws on connection error | |
| */ | |
| async verifyToken() { | |
| // Check if token exists BEFORE making any API call | |
| const token = getToken(); | |
| if (!token) { | |
| console.log('[Auth] No token in storage, skipping verify-token call'); | |
| return false; | |
| } | |
| try { | |
| await request('/auth/verify-token', { method: 'POST' }); | |
| return true; | |
| } catch (error) { | |
| // Check if this is a connection error vs actual auth failure | |
| const isConnectionError = | |
| error.message.includes('Unable to connect') || | |
| error.message.includes('Backend is unreachable') || | |
| error.message.includes('Failed to fetch') || | |
| error.message.includes('NetworkError'); | |
| if (isConnectionError) { | |
| // Re-throw connection errors so AuthContext can handle them differently | |
| console.warn('[Auth] Connection error during token verification'); | |
| throw error; | |
| } | |
| // Actual auth failure (401, invalid token, etc.) | |
| console.warn('[Auth] Token verification failed:', error.message); | |
| return false; | |
| } | |
| }, | |
| }; | |
| // =========================================== | |
| // USER API | |
| // =========================================== | |
| export const userApi = { | |
| /** | |
| * Get user profile | |
| */ | |
| async getProfile() { | |
| return request('/users/profile'); | |
| }, | |
| /** | |
| * Update user profile | |
| */ | |
| async updateProfile(data) { | |
| const params = new URLSearchParams(); | |
| if (data.name) params.append('name', data.name); | |
| return request(`/users/profile?${params.toString()}`, { | |
| method: 'PUT', | |
| }); | |
| }, | |
| /** | |
| * Get user analytics | |
| */ | |
| async getAnalytics() { | |
| return request('/users/analytics'); | |
| }, | |
| /** | |
| * Get interview history | |
| */ | |
| async getHistory(limit = 10, offset = 0) { | |
| return request(`/users/history?limit=${limit}&offset=${offset}`); | |
| }, | |
| /** | |
| * Get user resumes | |
| */ | |
| async getResumes() { | |
| return request('/resumes/me'); | |
| }, | |
| }; | |
| // =========================================== | |
| // BRANCH & ROLE CONFIG API | |
| // =========================================== | |
| export const branchApi = { | |
| /** | |
| * Get all engineering branches | |
| * Returns 6 branches: CSE, ECE, EEE, Mechanical, Civil, Architecture | |
| */ | |
| async getAllBranches() { | |
| return request('/interviews/branches'); | |
| }, | |
| /** | |
| * Get all roles within a specific branch | |
| * @param {string} branchId - Branch ID (e.g., 'computer_science', 'mechanical') | |
| */ | |
| async getBranchRoles(branchId) { | |
| return request(`/interviews/branches/${branchId}/roles`); | |
| }, | |
| /** | |
| * Get detailed configuration for a specific role | |
| * Returns interview rounds, topics, and duration | |
| * @param {string} branchId - Branch ID | |
| * @param {string} roleId - Role ID within the branch | |
| */ | |
| async getRoleConfig(branchId, roleId) { | |
| return request(`/interviews/branches/${branchId}/roles/${roleId}`); | |
| }, | |
| /** | |
| * Get interview questions for a specific role | |
| * @param {string} branchId - Branch ID | |
| * @param {string} roleId - Role ID | |
| */ | |
| async getRoleQuestions(branchId, roleId) { | |
| return request(`/interviews/branches/${branchId}/roles/${roleId}/questions`); | |
| }, | |
| }; | |
| // =========================================== | |
| // INTERVIEW API | |
| // =========================================== | |
| export const interviewApi = { | |
| /** | |
| * Create interview session | |
| */ | |
| async createSession(config) { | |
| const params = new URLSearchParams(); | |
| if (config.sessionType) params.append('session_type', config.sessionType); | |
| if (config.difficulty) params.append('difficulty', config.difficulty); | |
| if (config.questionCount) params.append('question_count', config.questionCount); | |
| if (config.resumeId) params.append('resume_id', config.resumeId); | |
| if (config.jobRoleId) params.append('job_role_id', config.jobRoleId); | |
| return request(`/interviews/sessions?${params.toString()}`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get session details | |
| */ | |
| async getSession(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}`); | |
| }, | |
| /** | |
| * Start question planning | |
| */ | |
| async startPlanning(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/plan`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Start interview | |
| */ | |
| async startInterview(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/start`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get current question | |
| */ | |
| async getCurrentQuestion(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/question`); | |
| }, | |
| /** | |
| * Submit answer | |
| */ | |
| async submitAnswer(sessionId, questionId, answerText, responseTimeSeconds) { | |
| const params = new URLSearchParams(); | |
| params.append('question_id', questionId); | |
| params.append('answer_text', answerText); | |
| params.append('response_time_seconds', responseTimeSeconds); | |
| return request(`/interviews/sessions/${sessionId}/answer?${params.toString()}`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Pause interview | |
| */ | |
| async pauseInterview(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/pause`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Resume interview | |
| */ | |
| async resumeInterview(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/resume`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * End interview | |
| */ | |
| async endInterview(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/end`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get session results | |
| */ | |
| async getResults(sessionId) { | |
| return request(`/interviews/sessions/${sessionId}/results`); | |
| }, | |
| /** | |
| * Get available job roles | |
| */ | |
| async getJobRoles() { | |
| return request('/interviews/roles'); | |
| }, | |
| }; | |
| // =========================================== | |
| // RESUME API | |
| // =========================================== | |
| export const resumeApi = { | |
| /** | |
| * Upload a resume file | |
| * @param {File} file - The resume file (PDF or DOCX) | |
| */ | |
| async upload(file) { | |
| const token = getToken(); | |
| if (!token) { | |
| removeToken(); | |
| window.location.href = '/login'; | |
| throw new Error('Authentication required. Please login first.'); | |
| } | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| const response = await fetch(`${API_BASE_URL}/resumes/upload`, { | |
| method: 'POST', | |
| headers: { | |
| ...(token && { Authorization: `Bearer ${token}` }), | |
| }, | |
| body: formData, | |
| }); | |
| // Handle 401 - Unauthorized | |
| if (response.status === 401) { | |
| removeToken(); | |
| window.location.href = '/login'; | |
| throw new Error('Session expired. Please login again.'); | |
| } | |
| const data = await response.json().catch(() => ({})); | |
| if (!response.ok) { | |
| throw new Error(data.detail || data.message || 'Failed to upload resume'); | |
| } | |
| return data; | |
| }, | |
| /** | |
| * Get all resumes for current user | |
| */ | |
| async getAll() { | |
| return request('/resumes/me'); | |
| }, | |
| /** | |
| * Get a specific resume by ID | |
| */ | |
| async getById(resumeId) { | |
| return request(`/resumes/${resumeId}`); | |
| }, | |
| /** | |
| * Delete a resume | |
| */ | |
| async delete(resumeId) { | |
| return request(`/resumes/${resumeId}`, { | |
| method: 'DELETE', | |
| }); | |
| }, | |
| /** | |
| * Validate user-selected domain against resume classification | |
| * MANDATORY: Must be called before starting interview/prep | |
| * @param {string} resumeId - Resume ID to validate | |
| * @param {string} selectedBranch - User-selected branch (e.g., 'computer_science') | |
| * @param {string} selectedRole - User-selected role (e.g., 'software_engineer') | |
| */ | |
| async validateDomain(resumeId, selectedBranch, selectedRole) { | |
| return request('/resumes/validate-domain', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| resume_id: resumeId, | |
| selected_branch: selectedBranch, | |
| selected_role: selectedRole, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Confirm domain selection despite mismatch | |
| * For advanced users who want to override the warning | |
| * @param {string} resumeId - Resume ID | |
| * @param {string} selectedBranch - User-selected branch | |
| * @param {string} selectedRole - User-selected role | |
| */ | |
| async confirmDomain(resumeId, selectedBranch, selectedRole) { | |
| return request('/resumes/confirm-domain', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| resume_id: resumeId, | |
| selected_branch: selectedBranch, | |
| selected_role: selectedRole, | |
| user_confirmed: true, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get domain classification for a resume | |
| * @param {string} resumeId - Resume ID | |
| */ | |
| async getClassification(resumeId) { | |
| return request(`/resumes/${resumeId}/classification`); | |
| }, | |
| }; | |
| // =========================================== | |
| // ATS API | |
| // =========================================== | |
| export const atsApi = { | |
| /** | |
| * Analyze resume for ATS compatibility | |
| * @param {string} resumeId - Resume ID to analyze | |
| * @param {string} targetRole - Target job role | |
| * @param {string} [targetDescription] - Optional job description | |
| */ | |
| async analyze(resumeId, targetRole, targetDescription = null) { | |
| return request(`/ats/analyze/${resumeId}`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| target_role: targetRole, | |
| target_role_description: targetDescription, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get ATS analysis result by ID | |
| */ | |
| async getResult(analysisId) { | |
| return request(`/ats/result/${analysisId}`); | |
| }, | |
| /** | |
| * Get latest ATS analysis for a resume | |
| */ | |
| async getResumeAnalysis(resumeId) { | |
| return request(`/ats/resume/${resumeId}`); | |
| }, | |
| /** | |
| * Get user's ATS analysis history | |
| */ | |
| async getHistory(limit = 10) { | |
| return request(`/ats/history?limit=${limit}`); | |
| }, | |
| /** | |
| * Get ATS API status (OpenAI configured or mock mode) | |
| */ | |
| async getStatus() { | |
| return request('/ats/status'); | |
| }, | |
| }; | |
| // =========================================== | |
| // INTERVIEW PLAN API | |
| // =========================================== | |
| export const planApi = { | |
| /** | |
| * Generate interview plan for a resume | |
| * @param {string} resumeId - Resume ID | |
| * @param {string} targetRole - Target job role | |
| * @param {object} options - Optional settings including roundConfig | |
| */ | |
| async generate(resumeId, targetRole, options = {}) { | |
| return request(`/interviews/plan/${resumeId}`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| target_role: targetRole, | |
| target_role_description: options.targetDescription || null, | |
| session_type: options.sessionType || 'mixed', | |
| difficulty_level: options.difficultyLevel || 'medium', | |
| question_count: options.questionCount || 10, | |
| company_mode: options.companyMode || null, | |
| // NEW: 4-mode architecture | |
| interview_mode: options.interviewMode || null, | |
| personality: options.personality || null, | |
| // CRITICAL: Pass per-round question configuration | |
| round_config: options.roundConfig || null, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get plan by ID | |
| */ | |
| async getById(planId) { | |
| return request(`/interviews/plan/${planId}`); | |
| }, | |
| /** | |
| * Get latest plan for a resume | |
| */ | |
| async getByResume(resumeId) { | |
| return request(`/interviews/plans/resume/${resumeId}`); | |
| }, | |
| /** | |
| * Get all user's plans | |
| */ | |
| async getAll(limit = 10) { | |
| return request(`/interviews/plans/me?limit=${limit}`); | |
| }, | |
| /** | |
| * Get questions from a plan | |
| */ | |
| async getQuestions(planId) { | |
| return request(`/interviews/plan/${planId}/questions`); | |
| }, | |
| }; | |
| // =========================================== | |
| // INTERVIEW FLOW API (NEW - MANDATORY FLOW CONTROL) | |
| // =========================================== | |
| export const interviewFlowApi = { | |
| /** | |
| * Start the complete interview preparation flow | |
| * MANDATORY: Executes all validation steps before plan can be generated | |
| * | |
| * Steps executed: | |
| * 1. Resume parsing check | |
| * 2. Domain classification | |
| * 3. Internet grounding (fetches live interview data) | |
| * 4. Domain validation | |
| * | |
| * @param {string} resumeId - Resume ID | |
| * @param {string} selectedBranch - User-selected branch | |
| * @param {string} selectedRole - User-selected role | |
| */ | |
| async startFlow(resumeId, selectedBranch, selectedRole) { | |
| return request('/interviews/flow/start', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| resume_id: resumeId, | |
| selected_branch: selectedBranch, | |
| selected_role: selectedRole, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Confirm domain selection despite mismatch | |
| * For advanced users who want to override the warning | |
| */ | |
| async confirmDomain(resumeId, selectedBranch, selectedRole) { | |
| return request('/interviews/flow/confirm-domain', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| resume_id: resumeId, | |
| selected_branch: selectedBranch, | |
| selected_role: selectedRole, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Fetch internet grounding for a domain | |
| * Returns current (2024-2026) interview data | |
| */ | |
| async fetchGrounding(domain, role, detectedSkills = []) { | |
| return request('/interviews/grounding/fetch', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| domain: domain, | |
| role: role, | |
| detected_skills: detectedSkills, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get grounding service status | |
| */ | |
| async getGroundingStatus() { | |
| return request('/interviews/grounding/status'); | |
| }, | |
| /** | |
| * Get flow requirements | |
| */ | |
| async getRequirements() { | |
| return request('/interviews/flow/requirements'); | |
| }, | |
| }; | |
| // =========================================== | |
| // USER MEMORY & INSIGHTS API | |
| // =========================================== | |
| export const memoryApi = { | |
| /** | |
| * Get intelligent dashboard comment | |
| * Analyzes user's interview history and generates actionable insight | |
| * | |
| * @param {string} mode - Interview mode for tone (friendly, balanced, strict, final_boss) | |
| */ | |
| async getDashboardComment(mode = 'balanced') { | |
| return request(`/memory/dashboard-comment?mode=${mode}`); | |
| }, | |
| /** | |
| * Get profile defining quote | |
| * One sentence that defines the user's interview personality | |
| */ | |
| async getProfileQuote() { | |
| return request('/memory/profile-quote'); | |
| }, | |
| /** | |
| * Get full insight summary | |
| * Complete insight including performance, behavior, and recommendations | |
| */ | |
| async getInsightSummary() { | |
| return request('/memory/insight-summary'); | |
| }, | |
| /** | |
| * Get performance history | |
| * @param {number} limit - Number of records to fetch | |
| */ | |
| async getPerformanceHistory(limit = 10) { | |
| return request(`/memory/performance-history?limit=${limit}`); | |
| }, | |
| /** | |
| * Record performance after interview completion | |
| * @param {Object} data - Performance data | |
| */ | |
| async recordPerformance(data) { | |
| return request('/memory/record-performance', { | |
| method: 'POST', | |
| body: JSON.stringify(data), | |
| }); | |
| }, | |
| /** | |
| * Get behavior history (video-based analysis) | |
| * @param {number} limit - Number of records to fetch | |
| */ | |
| async getBehaviorHistory(limit = 10) { | |
| return request(`/memory/behavior-history?limit=${limit}`); | |
| }, | |
| /** | |
| * Get confidence timeline graph data for an interview | |
| * ONLY available for interviews with video (Balanced, Strict, Final Boss modes) | |
| * | |
| * @param {string} interviewId - Interview ID | |
| */ | |
| async getConfidenceGraph(interviewId) { | |
| return request(`/memory/confidence-graph/${interviewId}`); | |
| }, | |
| /** | |
| * Trigger video analysis for an interview | |
| * @param {string} interviewId - Interview ID | |
| * @param {number} durationSeconds - Video duration in seconds | |
| */ | |
| async analyzeVideo(interviewId, durationSeconds = 600) { | |
| return request(`/memory/analyze-video/${interviewId}?video_duration_seconds=${durationSeconds}`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get consistency metrics | |
| * Interview seriousness and commitment tracking | |
| */ | |
| async getConsistency() { | |
| return request('/memory/consistency'); | |
| }, | |
| /** | |
| * Record interview start for consistency tracking | |
| */ | |
| async recordInterviewStart() { | |
| return request('/memory/consistency/interview-start', { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Record interview completion for consistency tracking | |
| * @param {number} questionsAsked - Total questions asked | |
| * @param {number} questionsAnswered - Questions answered | |
| * @param {number} questionsSkipped - Questions skipped | |
| */ | |
| async recordInterviewComplete(questionsAsked, questionsAnswered, questionsSkipped) { | |
| return request(`/memory/consistency/interview-complete?questions_asked=${questionsAsked}&questions_answered=${questionsAnswered}&questions_skipped=${questionsSkipped}`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| }; | |
| // =========================================== | |
| // LIVE INTERVIEW API | |
| // =========================================== | |
| export const liveInterviewApi = { | |
| /** | |
| * Start a live interview session | |
| * @param {string} planId - Interview plan ID | |
| * @param {string} persona - Interviewer persona (professional, friendly, stress) | |
| */ | |
| async start(planId, persona = 'professional') { | |
| return request('/interviews/live/start', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| plan_id: planId, | |
| persona: persona, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get session state (for resuming after refresh) | |
| */ | |
| async getState(sessionId) { | |
| return request(`/interviews/live/${sessionId}/state`); | |
| }, | |
| /** | |
| * Submit an answer | |
| */ | |
| async submitAnswer(sessionId, answerText, responseTimeSeconds = null) { | |
| return request(`/interviews/live/${sessionId}/answer`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| answer_text: answerText, | |
| response_time_seconds: responseTimeSeconds, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Skip current question | |
| */ | |
| async skip(sessionId) { | |
| return request(`/interviews/live/${sessionId}/skip`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Pause interview | |
| */ | |
| async pause(sessionId) { | |
| return request(`/interviews/live/${sessionId}/pause`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Resume interview | |
| */ | |
| async resume(sessionId) { | |
| return request(`/interviews/live/${sessionId}/resume`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * End interview early | |
| */ | |
| async end(sessionId) { | |
| return request(`/interviews/live/${sessionId}/end`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Confirm user consent to start interview | |
| * Called after user says "yes" to the greeting | |
| */ | |
| async confirmConsent(sessionId) { | |
| return request(`/interviews/live/${sessionId}/consent`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Process a message through the Interview Brain | |
| * | |
| * This is the recommended endpoint for conversational interviews. | |
| * It handles: | |
| * - Off-topic detection and redirection | |
| * - Repeat/clarify requests | |
| * - Pause/skip commands | |
| * - Answer evaluation | |
| * | |
| * @param {string} sessionId - Interview session ID | |
| * @param {string} messageText - User's message | |
| * @returns {Promise<Object>} Response with: | |
| * - response: Interviewer's response text | |
| * - intent: Detected intent (answer, off_topic, repeat, skip, pause) | |
| * - next_action: Next action (continue, next_question, repeated, redirected, paused, completed) | |
| * - next_question: Next question object (if advancing) | |
| * - progress: Updated progress | |
| */ | |
| async processMessage(sessionId, messageText) { | |
| return request(`/interviews/live/${sessionId}/message`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| message_text: messageText, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get API status | |
| */ | |
| async getStatus() { | |
| return request('/interviews/live/status'); | |
| }, | |
| }; | |
| // =========================================== | |
| // EVALUATION API | |
| // =========================================== | |
| export const evaluationApi = { | |
| /** | |
| * Quick evaluation (OpenAI) - Layer 1 | |
| */ | |
| async quick(sessionId, questionId, questionText, answerText, questionType = null) { | |
| return request('/evaluations/quick', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| session_id: sessionId, | |
| question_id: questionId, | |
| question_text: questionText, | |
| answer_text: answerText, | |
| question_type: questionType, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Deep evaluation (OpenAI) - Layer 2 | |
| */ | |
| async deep(sessionId, questionId, questionText, answerText, options = {}) { | |
| return request('/evaluations/deep', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| session_id: sessionId, | |
| question_id: questionId, | |
| question_text: questionText, | |
| answer_text: answerText, | |
| question_type: options.questionType, | |
| resume_context: options.resumeContext, | |
| expected_topics: options.expectedTopics, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Batch deep evaluation for session | |
| */ | |
| async batch(sessionId) { | |
| return request('/evaluations/batch', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| session_id: sessionId, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get session evaluations | |
| */ | |
| async getSession(sessionId) { | |
| return request(`/evaluations/${sessionId}`); | |
| }, | |
| /** | |
| * Get single evaluation | |
| */ | |
| async getSingle(evaluationId) { | |
| return request(`/evaluations/single/${evaluationId}`); | |
| }, | |
| /** | |
| * Finalize session evaluations | |
| */ | |
| async finalize(sessionId) { | |
| return request(`/evaluations/finalize/${sessionId}`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get evaluation API status | |
| */ | |
| async getStatus() { | |
| return request('/evaluations/status'); | |
| }, | |
| }; | |
| // =========================================== | |
| // SIMULATION API (Behavioral Insights) | |
| // =========================================== | |
| export const simulationApi = { | |
| /** | |
| * Analyze answer for behavioral patterns | |
| * Text-based inference only | |
| */ | |
| async analyze(sessionId, questionId, answerText, options = {}) { | |
| return request('/simulation/analyze', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| session_id: sessionId, | |
| question_id: questionId, | |
| answer_text: answerText, | |
| question_text: options.questionText, | |
| question_type: options.questionType, | |
| response_time_seconds: options.responseTime, | |
| evaluation_id: options.evaluationId, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Generate session behavioral summary | |
| */ | |
| async generateSummary(sessionId) { | |
| return request('/simulation/session/summary', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| session_id: sessionId, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get session behavioral insights | |
| */ | |
| async getSession(sessionId) { | |
| return request(`/simulation/session/${sessionId}`); | |
| }, | |
| /** | |
| * Get single insight | |
| */ | |
| async getInsight(insightId) { | |
| return request(`/simulation/insight/${insightId}`); | |
| }, | |
| /** | |
| * Get disclaimer | |
| */ | |
| async getDisclaimer() { | |
| return request('/simulation/disclaimer'); | |
| }, | |
| }; | |
| // =========================================== | |
| // REPORT API | |
| // =========================================== | |
| export const reportApi = { | |
| /** | |
| * Generate interview report | |
| */ | |
| async generate(sessionId, options = {}) { | |
| return request(`/reports/generate/${sessionId}`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| include_behavioral: options.includeBehavioral ?? true, | |
| include_skills: options.includeSkills ?? true, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get interview report | |
| */ | |
| async get(sessionId) { | |
| return request(`/reports/${sessionId}`); | |
| }, | |
| /** | |
| * List user reports | |
| */ | |
| async list(limit = 10) { | |
| return request(`/reports/?limit=${limit}`); | |
| }, | |
| }; | |
| // =========================================== | |
| // ROADMAP API | |
| // =========================================== | |
| export const roadmapApi = { | |
| /** | |
| * Generate career roadmap for session | |
| */ | |
| async generate(sessionId, options = {}) { | |
| return request(`/roadmap/generate/${sessionId}`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| target_role: options.targetRole, | |
| current_level: options.currentLevel, | |
| target_level: options.targetLevel, | |
| focus_areas: options.focusAreas, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Generate standalone roadmap | |
| */ | |
| async generateStandalone(options = {}) { | |
| return request('/roadmap/generate', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| target_role: options.targetRole || 'Software Developer', | |
| current_level: options.currentLevel, | |
| target_level: options.targetLevel, | |
| focus_areas: options.focusAreas, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Get roadmap for session | |
| */ | |
| async get(sessionId) { | |
| return request(`/roadmap/${sessionId}`); | |
| }, | |
| /** | |
| * List user roadmaps | |
| */ | |
| async list(limit = 10) { | |
| return request(`/roadmap/?limit=${limit}`); | |
| }, | |
| }; | |
| // =========================================== | |
| // COMPANY MODES API | |
| // =========================================== | |
| export const companyApi = { | |
| /** | |
| * Get all company modes | |
| */ | |
| async getModes() { | |
| return request('/companies/modes'); | |
| }, | |
| /** | |
| * Get specific company mode | |
| */ | |
| async getMode(modeId) { | |
| return request(`/companies/modes/${modeId}`); | |
| }, | |
| }; | |
| // =========================================== | |
| // PERSONALITY MODES API | |
| // =========================================== | |
| export const personalityApi = { | |
| /** | |
| * Get all personality modes | |
| */ | |
| async getAll() { | |
| return request('/personalities/'); | |
| }, | |
| /** | |
| * Get specific personality mode | |
| */ | |
| async getById(personalityId) { | |
| return request(`/personalities/${personalityId}`); | |
| }, | |
| }; | |
| // =========================================== | |
| // ANALYTICS API | |
| // =========================================== | |
| export const analyticsApi = { | |
| /** | |
| * Get full dashboard data | |
| */ | |
| async getDashboard(days = 30) { | |
| return request(`/analytics/dashboard?days=${days}`); | |
| }, | |
| /** | |
| * Get overview statistics | |
| */ | |
| async getOverview(days = 30) { | |
| return request(`/analytics/overview?days=${days}`); | |
| }, | |
| /** | |
| * Get skill performance | |
| */ | |
| async getSkills() { | |
| return request('/analytics/skills'); | |
| }, | |
| /** | |
| * Get progress over time | |
| */ | |
| async getProgress(days = 90, interval = 'week') { | |
| return request(`/analytics/progress?days=${days}&interval=${interval}`); | |
| }, | |
| /** | |
| * Get question type breakdown | |
| */ | |
| async getQuestionTypes() { | |
| return request('/analytics/question-types'); | |
| }, | |
| /** | |
| * Get recent interviews | |
| */ | |
| async getRecent(limit = 5) { | |
| return request(`/analytics/recent?limit=${limit}`); | |
| }, | |
| }; | |
| // =========================================== | |
| // VISION API (Confidence Graph & Video Analysis) | |
| // =========================================== | |
| export const visionApi = { | |
| /** | |
| * Get confidence graph data for an interview | |
| * Returns time-series confidence data for rendering a graph | |
| * | |
| * @param {string} interviewId - Interview ID | |
| */ | |
| async getConfidenceGraph(interviewId) { | |
| return request(`/vision/interviews/${interviewId}/confidence-graph`); | |
| }, | |
| /** | |
| * Submit real-time confidence data during interview | |
| * Called periodically to store eye contact and confidence metrics | |
| * | |
| * @param {string} interviewId - Interview ID | |
| * @param {Array} dataPoints - Array of {timestamp, confidence, eye_contact, face_detected, gaze_direction} | |
| * @param {string} interviewMode - 'strict' or 'final_boss' | |
| */ | |
| async submitConfidenceData(interviewId, dataPoints, interviewMode = 'strict') { | |
| return request(`/vision/interviews/${interviewId}/confidence-data`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| interview_id: interviewId, | |
| data_points: dataPoints, | |
| interview_mode: interviewMode, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Finalize confidence data after interview ends | |
| * Computes summary statistics and caches graph data | |
| * | |
| * @param {string} interviewId - Interview ID | |
| */ | |
| async finalizeConfidenceData(interviewId) { | |
| return request(`/vision/interviews/${interviewId}/finalize-confidence`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get detailed per-second confidence metrics | |
| * More detailed than graph data, includes all detection info | |
| * | |
| * @param {string} interviewId - Interview ID | |
| */ | |
| async getConfidenceMetrics(interviewId) { | |
| return request(`/vision/interviews/${interviewId}/confidence-metrics`); | |
| }, | |
| /** | |
| * Get confidence analysis summary | |
| * | |
| * @param {string} interviewId - Interview ID | |
| */ | |
| async getConfidenceSummary(interviewId) { | |
| return request(`/vision/interviews/${interviewId}/confidence-summary`); | |
| }, | |
| /** | |
| * Trigger video analysis for an interview (background processing) | |
| * | |
| * @param {string} interviewId - Interview ID | |
| * @param {string} videoPath - Optional video path override | |
| * @param {boolean} force - Force reprocessing | |
| */ | |
| async analyzeVideo(interviewId, videoPath = null, force = false) { | |
| const params = new URLSearchParams(); | |
| if (videoPath) params.append('video_path', videoPath); | |
| if (force) params.append('force', 'true'); | |
| return request(`/vision/interviews/${interviewId}/analyze-video?${params.toString()}`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get user's confidence history across all interviews | |
| * | |
| * @param {string} userId - User ID | |
| * @param {number} limit - Number of records | |
| */ | |
| async getConfidenceHistory(userId, limit = 10) { | |
| return request(`/vision/users/${userId}/confidence-history?limit=${limit}`); | |
| }, | |
| }; | |
| // =========================================== | |
| // IDENTITY API (Profile Quotes & Career Identity) | |
| // =========================================== | |
| export const identityApi = { | |
| /** | |
| * Get user's full profile identity | |
| * Returns complete identity including quote, domain scores, trend | |
| * | |
| * @param {string} userId - User ID | |
| */ | |
| async getProfileIdentity(userId) { | |
| return request(`/identity/users/${userId}/profile`); | |
| }, | |
| /** | |
| * Get user's profile quote (minimal) | |
| * | |
| * @param {string} userId - User ID | |
| */ | |
| async getQuote(userId) { | |
| return request(`/identity/users/${userId}/quote`); | |
| }, | |
| /** | |
| * Get user's quote history | |
| * Shows evolution of profile identity over time | |
| * | |
| * @param {string} userId - User ID | |
| * @param {number} limit - Number of records | |
| */ | |
| async getQuoteHistory(userId, limit = 10) { | |
| return request(`/identity/users/${userId}/quote-history?limit=${limit}`); | |
| }, | |
| /** | |
| * Manually refresh user's identity (trigger recomputation) | |
| * | |
| * @param {string} userId - User ID | |
| */ | |
| async refreshIdentity(userId) { | |
| return request(`/identity/users/${userId}/refresh`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Generate AI-powered profile quote | |
| * Creates a personalized quote based on interview performance | |
| * | |
| * @param {string} userId - User ID | |
| */ | |
| async generateAIQuote(userId) { | |
| return request(`/identity/users/${userId}/generate-ai-quote`, { | |
| method: 'POST', | |
| }); | |
| }, | |
| /** | |
| * Get user's domain scores | |
| * | |
| * @param {string} userId - User ID | |
| */ | |
| async getDomainScores(userId) { | |
| return request(`/identity/users/${userId}/domain-scores`); | |
| }, | |
| }; | |
| // =========================================== | |
| // PROFILE API (User Profile Page) | |
| // =========================================== | |
| export const profileApi = { | |
| /** | |
| * Get user profile (same as auth/me) | |
| */ | |
| async getProfile() { | |
| return request('/users/profile'); | |
| }, | |
| /** | |
| * Get interview history | |
| */ | |
| async getInterviewHistory(limit = 20, offset = 0) { | |
| return request(`/users/history?limit=${limit}&offset=${offset}`); | |
| }, | |
| /** | |
| * Get user statistics | |
| */ | |
| async getUserStats() { | |
| try { | |
| const response = await request('/analytics/overview?days=90'); | |
| // Transform to expected shape | |
| return { | |
| success: true, | |
| stats: { | |
| total_interviews: response.total_interviews || 0, | |
| completed_interviews: response.completed_interviews || 0, | |
| average_score: response.average_score || 0, | |
| total_resumes: 0, // Will be filled from resume API | |
| }, | |
| }; | |
| } catch (error) { | |
| return { | |
| success: false, | |
| stats: { | |
| total_interviews: 0, | |
| completed_interviews: 0, | |
| average_score: 0, | |
| total_resumes: 0, | |
| }, | |
| }; | |
| } | |
| }, | |
| /** | |
| * Update user profile | |
| */ | |
| async updateProfile(data) { | |
| const params = new URLSearchParams(); | |
| if (data.name) params.append('name', data.name); | |
| return request(`/users/profile?${params.toString()}`, { | |
| method: 'PUT', | |
| }); | |
| }, | |
| }; | |
| // =========================================== | |
| // COMPILER API - Code Execution for Coding Rounds | |
| // =========================================== | |
| export const compilerApi = { | |
| /** | |
| * Get available programming languages | |
| */ | |
| async getLanguages() { | |
| return request('/compiler/languages'); | |
| }, | |
| /** | |
| * Execute code | |
| * @param {string} code - Source code to execute | |
| * @param {string} language - Programming language (python, javascript, java, etc.) | |
| * @param {string} stdin - Optional standard input | |
| * @param {number} timeout - Execution timeout in seconds (default: 10) | |
| */ | |
| async execute(code, language, stdin = '', timeout = 10) { | |
| return request('/compiler/execute', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| code, | |
| language, | |
| stdin: stdin || undefined, | |
| timeout, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Test compiler with Hello World | |
| */ | |
| async test() { | |
| return request('/compiler/test', { method: 'POST' }); | |
| }, | |
| }; | |
| // =========================================== | |
| // FINAL BOSS REPORT API | |
| // =========================================== | |
| export const finalBossApi = { | |
| /** | |
| * Generate Final Boss Report | |
| * Returns comprehensive battle plan with weakness heatmap and 14-day war plan | |
| * @param {string} sessionId - Interview session ID | |
| * @param {string} candidateName - Optional display name for candidate | |
| */ | |
| async generate(sessionId, candidateName = null) { | |
| return request(`/final-boss/generate/${sessionId}`, { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| candidate_name: candidateName, | |
| }), | |
| }); | |
| }, | |
| /** | |
| * Download Final Boss Report as PDF | |
| * @param {string} sessionId - Interview session ID | |
| * @param {string} candidateName - Optional display name for candidate | |
| * @returns {Promise<Blob>} PDF blob for download | |
| */ | |
| async downloadPdf(sessionId, candidateName = null) { | |
| const token = getToken(); | |
| const params = candidateName ? `?candidate_name=${encodeURIComponent(candidateName)}` : ''; | |
| const response = await fetch(`${API_BASE_URL}/final-boss/pdf/${sessionId}${params}`, { | |
| method: 'GET', | |
| headers: { | |
| 'Authorization': token ? `Bearer ${token}` : '', | |
| }, | |
| }); | |
| if (!response.ok) { | |
| const error = await response.json().catch(() => ({ message: 'Failed to download PDF' })); | |
| throw new Error(error.detail || error.message || 'Failed to download PDF'); | |
| } | |
| return response.blob(); | |
| }, | |
| /** | |
| * Get Final Boss Report as HTML | |
| * Useful for client-side rendering or PDF generation | |
| * @param {string} sessionId - Interview session ID | |
| * @param {string} candidateName - Optional display name for candidate | |
| */ | |
| async getHtml(sessionId, candidateName = null) { | |
| const token = getToken(); | |
| const params = candidateName ? `?candidate_name=${encodeURIComponent(candidateName)}` : ''; | |
| const response = await fetch(`${API_BASE_URL}/final-boss/html/${sessionId}${params}`, { | |
| method: 'GET', | |
| headers: { | |
| 'Authorization': token ? `Bearer ${token}` : '', | |
| }, | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Failed to get HTML report'); | |
| } | |
| return response.text(); | |
| }, | |
| }; | |
| // =========================================== | |
| // COMBINED API EXPORT | |
| // =========================================== | |
| export const api = { | |
| auth: authApi, | |
| user: userApi, | |
| interview: interviewApi, | |
| resume: resumeApi, | |
| ats: atsApi, | |
| plan: planApi, | |
| live: liveInterviewApi, | |
| evaluation: evaluationApi, | |
| simulation: simulationApi, | |
| report: reportApi, | |
| roadmap: roadmapApi, | |
| company: companyApi, | |
| personality: personalityApi, | |
| analytics: analyticsApi, | |
| profile: profileApi, | |
| vision: visionApi, // Confidence graph & video analysis | |
| identity: identityApi, // Profile quotes & career identity | |
| compiler: compilerApi, // Code compiler for coding rounds | |
| finalBoss: finalBossApi, // Final Boss Report - comprehensive PDF battle plan | |
| // Legacy compatibility | |
| login: authApi.login, | |
| signup: authApi.signup, | |
| logout: authApi.logout, | |
| getCurrentUser: authApi.getCurrentUser, | |
| }; | |
| export default api; | |