coderound / frontend /src /lib /api.ts
ketannnn's picture
fix(ui): add navbar link to API Docs alongside system reset
5aed951
const API_BASE = typeof process.env.NEXT_PUBLIC_API_URL === "string"
? process.env.NEXT_PUBLIC_API_URL
: ""; // Fallback to relative paths for single-origin deployments (HF/Nginx)
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const url = path.startsWith("http") ? path : `${API_BASE}${path}`;
const res = await fetch(url, {
...options,
headers: { "Content-Type": "application/json", ...(options?.headers ?? {}) },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: res.statusText }));
throw new Error(err.detail || "Request failed");
}
if (res.status === 202) {
throw new Error("202_ACCEPTED");
}
return res.json();
}
export interface SessionInfo {
id: string;
name: string;
description: string | null;
candidate_count: number;
status: string;
created_at: string;
}
export interface JD {
id: string;
title: string;
raw_text: string;
status: string;
min_yoe: number | null;
role_type: string | null;
engineer_type: string | null;
location: string | null;
required_skills: string[];
jd_quality: JDQuality;
custom_weights: Record<string, number>;
created_at: string;
}
export interface JDQuality {
overall: "good" | "fair" | "poor";
vagueness_score: number;
breadth_score: number;
skill_count: number;
contradictions: string[];
warnings: string[];
}
export interface ComponentScores {
semantic: number;
skill: number;
yoe: number;
company: number;
growth: number;
education: number;
}
export interface GapItem {
type: string;
detail: string;
mitigated_by_remote?: boolean;
}
export interface MatchedCandidate {
candidate_id: string;
rank: number;
name: string | null;
email: string | null;
role_type: string | null;
engineer_type: string | null;
years_of_experience: number | null;
most_recent_company: string | null;
parsed_summary: string | null;
programming_languages: string[];
growth_velocity: number;
stage1_score: number;
stage2_score: number | null;
final_score: number;
component_scores: ComponentScores;
gaps: GapItem[];
}
export interface MatchResponse {
jd_id: string;
jd_title: string;
jd_quality: JDQuality;
total_matched: number;
results: MatchedCandidate[];
weights_used: Record<string, number>;
session_id: string | null;
}
export interface CandidateDetail {
jd_id: string;
candidate_id: string;
rank: number | null;
final_score: number;
component_scores: ComponentScores;
gaps: GapItem[];
explanation: string | null;
candidate: Record<string, unknown>;
jd: Record<string, unknown>;
}
export interface TaskStatus {
task_id: string;
status: string;
result: unknown;
}
export const api = {
createSession: (name: string, description?: string) =>
request<SessionInfo>("/api/sessions", { method: "POST", body: JSON.stringify({ name, description }) }),
listSessions: () => request<SessionInfo[]>("/api/sessions"),
getSession: (id: string) => request<SessionInfo>(`/api/sessions/${id}`),
deleteSession: (id: string) => request<void>(`/api/sessions/${id}`, { method: "DELETE" }),
createJD: (title: string, raw_text: string, session_id?: string) =>
request<JD>("/api/jds", { method: "POST", body: JSON.stringify({ title, raw_text, session_id }) }),
listJDs: (session_id?: string) => request<JD[]>(session_id ? `/api/jds?session_id=${session_id}` : "/api/jds"),
getJD: (id: string) => request<JD>(`/api/jds/${id}`),
updateJDWeights: (id: string, weights: Record<string, number>) =>
request<JD>(`/api/jds/${id}/weights`, { method: "PATCH", body: JSON.stringify({ weights }) }),
uploadCandidates: (file: File, sessionId?: string) => {
const fd = new FormData();
fd.append("file", file);
const url = sessionId
? `${API_BASE}/api/candidates/upload?session_id=${sessionId}`
: `${API_BASE}/api/candidates/upload`;
return fetch(url, { method: "POST", body: fd }).then((r) => {
if (!r.ok) throw new Error("Upload failed");
return r.json();
});
},
candidateCount: (sessionId?: string) => {
const url = sessionId ? `/api/candidates/count?session_id=${sessionId}` : "/api/candidates/count";
return request<{ count: number }>(url);
},
taskStatus: (id: string) => request<TaskStatus>(`/api/candidates/status/${id}`),
triggerMatch: async (jdId: string, sessionId?: string, stage1TopK: number = 100, stage2TopK: number = 40) => {
const params = new URLSearchParams();
if (sessionId) params.set("session_id", sessionId);
params.set("stage1_top_k", String(stage1TopK));
params.set("stage2_top_k", String(stage2TopK));
return request<MatchResponse>(`/api/match/${jdId}?${params.toString()}`, { method: "POST" });
},
getMatchResults: (jdId: string, sessionId?: string) => {
const url = sessionId ? `/api/match/${jdId}?session_id=${sessionId}` : `/api/match/${jdId}`;
return request<MatchResponse>(url);
},
getCandidateDetail: (jdId: string, candidateId: string, sessionId?: string) => {
const url = sessionId
? `/api/match/${jdId}/${candidateId}?session_id=${sessionId}`
: `/api/match/${jdId}/${candidateId}`;
return request<CandidateDetail>(url);
},
triggerExplanation: (jdId: string, candidateId: string, sessionId?: string) => {
const url = sessionId
? `/api/match/${jdId}/candidates/${candidateId}/explain?session_id=${sessionId}`
: `/api/match/${jdId}/candidates/${candidateId}/explain`;
return request<{ status: string }>(url, { method: "POST" });
},
rerank: (jdId: string, weights: Record<string, number>, sessionId?: string) => {
const url = sessionId
? `/api/match/${jdId}/rerank?session_id=${sessionId}`
: `/api/match/${jdId}/rerank`;
return request<MatchResponse>(url, { method: "POST", body: JSON.stringify({ weights }) });
},
checkHealth: async () => {
return request<{ status: string; version: string; qdrant: string; }>("/health");
}
};