import axios, { AxiosError } from "axios"; import type { Job, SearchFilters, GeneratedCV } from "@/types"; const API_BASE = import.meta.env.VITE_API_URL || ""; const api = axios.create({ baseURL: API_BASE, headers: { "Content-Type": "application/json", }, }); // Request interceptor to add auth headers if needed api.interceptors.request.use((config) => { return config; }); // Response interceptor for error handling api.interceptors.response.use( (response) => response, (error: AxiosError) => { console.error("API Error:", error.response?.data || error.message); return Promise.reject(error); } ); export const jobsAPI = { // Load runtime app config/settings async getConfig(): Promise<{ tailoring_mode: "local" | "api"; llm_provider: "ollama" | "groq" | "grok" | "openai"; enable_professional_summary: boolean; include_cover_letters: boolean; }> { const response = await api.get("/api/config"); return response.data; }, // Save user profile for the Auto-Apply Extension async saveUserProfile(profileData: { first_name: string; last_name: string; email: string; phone: string; linkedin: string; github: string; }): Promise<{ success: boolean; message?: string }> { const response = await api.post("/api/user-profile", profileData); return response.data; }, // Run deep semantic analysis for a specific job evaluateJobFit: async ( jobId: string ): Promise<{ success: boolean; match_score?: number; matched_skills?: string[]; missing_skills?: string[]; error?: string; }> => { const response = await api.post("/api/evaluate-fit", { job_id: jobId }); return response.data; }, // Save runtime app settings async saveSettings(payload: { tailoring_mode: "local" | "api"; llm_provider: "ollama" | "groq" | "grok" | "openai"; enable_professional_summary: boolean; include_cover_letters: boolean; }): Promise<{ success: boolean; settings: unknown }> { const response = await api.post("/api/settings", payload); return response.data; }, // Search for jobs async searchJobs( filters: SearchFilters ): Promise<{ jobs: Job[]; excel_file: string }> { const response = await api.post("/api/search", { keyword: filters.keyword, location: filters.location, max_jobs: filters.maxJobs, max_days_old: filters.maxDaysOld || 30, }); return { jobs: response.data.jobs || [], excel_file: response.data.excel_file || "", }; }, // Fast Search using Apify async searchJobsApify( filters: SearchFilters ): Promise<{ jobs: Job[]; json_file: string }> { const response = await api.post("/api/apify-search", { keyword: filters.keyword, location: filters.location, max_jobs: filters.maxJobs, max_days_old: filters.maxDaysOld || 30, }); return { jobs: response.data.jobs || [], json_file: response.data.json_file || "", }; }, // Upload CV template async uploadCV(file: File): Promise<{ success: boolean; message: string }> { const formData = new FormData(); formData.append("file", file); const response = await api.post("/api/upload-cv", formData, { headers: { "Content-Type": "multipart/form-data", }, }); return response.data; }, // Get jobs list async getJobsList(): Promise { const response = await api.get("/api/jobs"); return response.data.jobs || []; }, // Generate CV for single job async generateCV(jobId: string): Promise<{ success: boolean; filename: string; url: string; job?: Job; // <--- Temporary cover_letter_filename?: string; cover_letter_url?: string; }> { const response = await api.post(`/api/generate-cv/${jobId}`); const coverLetterFilename = response.data.cover_letter_filename || undefined; return { success: response.data.success, filename: response.data.filename, url: `/api/download/${encodeURIComponent(response.data.filename)}`, job: response.data.job, // <--- Temporary cover_letter_filename: coverLetterFilename, cover_letter_url: coverLetterFilename ? `/api/download/${encodeURIComponent(coverLetterFilename)}` : undefined, }; }, // Generate CVs for all selected jobs async generateAllCVs(jobIds?: string[]): Promise<{ success: boolean; successful: GeneratedCV[]; failed: Array<{ jobId: string; error: string }>; zip_filename: string; }> { // Updated the IDs of each job to represent actual IDs and avoid duplicated scrapes and Stop converting UUID strings into Numbers! const jobIndices = jobIds || []; const response = await api.post("/api/generate-all-cvs", { job_indices: jobIndices, }); const successful: GeneratedCV[] = (response.data.successful || []).map( (cv: any) => ({ jobId: String(cv.job_index), jobTitle: cv.job_title || "", company: cv.company || "", filename: cv.filename, url: `/api/download/${encodeURIComponent(cv.filename)}`, coverLetterFilename: cv.cover_letter_filename || undefined, coverLetterUrl: cv.cover_letter_filename ? `/api/download/${encodeURIComponent(cv.cover_letter_filename)}` : undefined, generatedAt: new Date().toISOString(), status: "success", }) ); const failed = (response.data.failed || []).map((item: any) => ({ jobId: String(item.job_index), error: item.error || "Unknown error", })); return { success: response.data.success, successful, failed, zip_filename: response.data.zip_filename, }; }, // Download file async downloadFile(filename: string): Promise { const encodedFilename = encodeURIComponent(filename); const response = await api.get(`/api/download/${encodedFilename}`, { responseType: "blob", }); return response.data; }, // Get batch progress (for real-time updates) async getBatchProgress(): Promise<{ completed: number; total: number; current_job?: string; failed: number; }> { return { completed: 0, total: 0, failed: 0, current_job: undefined, }; }, // Health check async healthCheck(): Promise<{ status: string }> { const response = await api.get("/api/health"); return response.data; }, }; export default api;