chowdary1's picture
V2 Full Sync: 4-mode architecture, compiler, RAG, neon theme, round detection fix, camera toggle
7fff83a
Raw History Blame Contribute Delete
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;