/** Typed fetch wrapper for the FastAPI backend. All requests include the Supabase JWT. */ const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; interface ApiError { detail: string; errors?: Array<{ field: string; message: string }>; } class ApiClient { private token: string | null = null; setToken(token: string | null) { this.token = token; } private async request( method: string, path: string, body?: unknown ): Promise { const headers: Record = { "Content-Type": "application/json", }; if (this.token) { headers["Authorization"] = `Bearer ${this.token}`; } const res = await fetch(`${API_BASE}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) { const error: ApiError = await res.json().catch(() => ({ detail: `Request failed with status ${res.status}`, })); throw new Error(error.detail || "An unknown error occurred"); } return res.json(); } async analyzeJD(data: { jd_text: string; role: string; company_type: string; geography?: string; }) { return this.request("POST", "/api/v1/jd/analyze", data); } async getAnalysis(analysisId: string) { return this.request("GET", `/api/v1/jd/${analysisId}`); } async listAnalyses(limit = 20, offset = 0) { return this.request( "GET", `/api/v1/jd/?limit=${limit}&offset=${offset}` ); } async deleteAnalysis(analysisId: string) { return this.request<{ deleted: boolean; analysis_id: string }>( "DELETE", `/api/v1/jd/${analysisId}` ); } async generateCapstone(data: { analysis_id: string; num_projects?: number; preferred_stack?: string[]; }) { return this.request( "POST", "/api/v1/capstone/generate", data ); } async getCapstoneProjects(analysisId: string) { return this.request<{ analysis_id: string; projects: CapstoneProject[] }>( "GET", `/api/v1/capstone/${analysisId}` ); } async toggleProjectSelection(projectId: string, selected: boolean) { return this.request<{ id: string; is_selected: boolean }>( "PUT", `/api/v1/capstone/${projectId}/select?selected=${selected}` ); } async analyzeRepo(data: { github_url: string }) { return this.request( "POST", "/api/v1/repo/analyze", data ); } async getRepoAnalysis(analysisId: string) { return this.request( "GET", `/api/v1/repo/${analysisId}` ); } async listRepoAnalyses(limit = 20, offset = 0) { return this.request( "GET", `/api/v1/repo/?limit=${limit}&offset=${offset}` ); } async generateScaffold(data: { project_title: string; project_description: string; tech_stack: string[]; include_docker?: boolean; include_ci?: boolean; include_tests?: boolean; analysis_id?: string; project_id?: string; }) { return this.request( "POST", "/api/v1/scaffold/generate", data ); } async getScaffold(scaffoldId: string) { return this.request( "GET", `/api/v1/scaffold/${scaffoldId}` ); } async listScaffolds(limit = 20, offset = 0) { return this.request( "GET", `/api/v1/scaffold/?limit=${limit}&offset=${offset}` ); } async optimizePortfolio(data: { project_title: string; project_description: string; tech_stack: string[]; key_features?: string[]; repo_score?: number; target_role?: string; analysis_id?: string; }) { return this.request( "POST", "/api/v1/portfolio/optimize", data ); } async getPortfolio(portfolioId: string) { return this.request( "GET", `/api/v1/portfolio/${portfolioId}` ); } async listPortfolios(limit = 20, offset = 0) { return this.request( "GET", `/api/v1/portfolio/?limit=${limit}&offset=${offset}` ); } async scoreFitness(data: { analysis_id: string; resume_text: string }) { return this.request( "POST", "/api/v1/fitness/score", data ); } async getFitness(fitnessId: string) { return this.request( "GET", `/api/v1/fitness/${fitnessId}` ); } async listFitnessScores(limit = 20, offset = 0) { return this.request( "GET", `/api/v1/fitness/?limit=${limit}&offset=${offset}` ); } } export interface Skill { name: string; category: string; weight: number; source: string; } export interface EngineeringExpectation { dimension: string; importance: number; description: string; } export interface SkillProfile { skills: Skill[]; experience_level: string; domain: string; engineering_expectations: EngineeringExpectation[]; key_responsibilities: string[]; summary: string; } export interface CompanyModifiers { company_type: string; emphasis_areas: string[]; weight_adjustments: Record; portfolio_focus: string; } export interface JDAnalysisResponse { analysis_id: string; skill_profile: SkillProfile; company_modifiers: CompanyModifiers; raw_role: string; raw_company_type: string; raw_geography?: string; } export interface AnalysisListItem { id: string; role: string; company_type: string; geography?: string; status: string; processing_time_ms?: number; created_at: string; updated_at: string; } export interface AnalysisListResponse { analyses: AnalysisListItem[]; total: number; limit: number; offset: number; } export interface ArchitectureOverview { description: string; components: string[]; data_flow: string; diagram_mermaid?: string; } export interface CapstoneProject { title: string; problem_statement: string; recruiter_match_reasoning: string; architecture: ArchitectureOverview; tech_stack: string[]; complexity_level: number; estimated_days: number; resume_bullet: string; key_features: string[]; differentiator: string; } export interface CapstoneResponse { analysis_id: string; projects: CapstoneProject[]; generation_metadata: { processing_time_ms: number; projects_generated: number; projects_persisted: number; }; } export interface ScoreDimension { name: string; score: number; details: string; suggestions: string[]; } export interface RepoScoreCard { repo_url: string; repo_name: string; primary_language?: string; total_files: number; total_lines: number; code_quality: ScoreDimension; test_coverage: ScoreDimension; complexity: ScoreDimension; structure: ScoreDimension; deployment_readiness: ScoreDimension; overall_score: number; summary: string; top_improvements: string[]; } export interface RepoAnalysisResponse { analysis_id: string; scorecard: RepoScoreCard; analysis_metadata: Record; } export interface RepoAnalysisListItem { id: string; github_url: string; repo_name?: string; primary_language?: string; overall_score?: number; status: string; processing_time_ms?: number; created_at: string; updated_at: string; } export interface RepoAnalysisListResponse { analyses: RepoAnalysisListItem[]; total: number; limit: number; offset: number; } export interface GeneratedFile { path: string; content: string; language: string; description: string; } export interface ScaffoldResponse { project_name: string; files: GeneratedFile[]; file_tree: string; download_url?: string; generation_metadata: Record; } export interface ScaffoldListItem { id: string; project_id?: string; project_title: string; tech_stack: string[]; status: string; processing_time_ms?: number; created_at: string; updated_at: string; } export interface ScaffoldListResponse { scaffolds: ScaffoldListItem[]; total: number; limit: number; offset: number; } export interface ResumeBullet { bullet: string; keywords: string[]; impact_type: "quantitative" | "qualitative" | "technical"; } export interface DemoStep { timestamp: string; action: string; narration: string; } export interface DemoScript { total_duration_seconds: number; opening_hook: string; steps: DemoStep[]; closing_cta: string; } export interface LinkedInPost { hook: string; body: string; hashtags: string[]; call_to_action: string; } export interface PortfolioResponse { readme_markdown: string; resume_bullets: ResumeBullet[]; demo_script: DemoScript; linkedin_post: LinkedInPost; generation_metadata: { portfolio_id: string; processing_time_ms: number; model: string; }; } export interface PortfolioRecord { id: string; project_title: string; project_description: string; tech_stack: string[]; target_role?: string; readme_markdown?: string; resume_bullets?: ResumeBullet[]; demo_script?: DemoScript; linkedin_post?: LinkedInPost; status: string; processing_time_ms?: number; created_at: string; updated_at: string; } export interface PortfolioListItem { id: string; project_title: string; tech_stack: string[]; target_role?: string; status: string; processing_time_ms?: number; created_at: string; updated_at: string; } export interface PortfolioListResponse { items: PortfolioListItem[]; total: number; limit: number; offset: number; } export interface MatchedSkill { skill: string; evidence: string; strength: "strong" | "moderate" | "weak"; } export interface MissingSkill { skill: string; importance: "critical" | "important" | "nice_to_have"; suggestion: string; } export interface Improvement { area: string; current_state: string; recommended_action: string; impact: "high" | "medium" | "low"; } export interface FitnessScoreResponse { fitness_id: string; analysis_id: string; fitness_score: number; verdict: "strong_fit" | "good_fit" | "partial_fit" | "weak_fit"; matched_skills: MatchedSkill[]; missing_skills: MissingSkill[]; strengths: string[]; improvements: Improvement[]; detailed_feedback: string; processing_time_ms?: number; } export interface FitnessListItem { id: string; analysis_id: string; fitness_score: number; verdict: string; status: string; processing_time_ms?: number; created_at: string; updated_at: string; } export interface FitnessListResponse { scores: FitnessListItem[]; total: number; limit: number; offset: number; } export const api = new ApiClient();