/** * 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} 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} 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;