BlueFin / frontend /src /api /client.js
vedanshmadan21's picture
fix: use same-origin API base in deployed frontend
d061a75
import axios from "axios";
const configuredBaseUrl = import.meta.env.VITE_API_URL?.trim();
const BASE_URL = configuredBaseUrl || "";
const api = axios.create({ baseURL: BASE_URL, timeout: 30000 });
api.interceptors.response.use(
(response) => response,
(error) => {
const message =
error.response?.data?.error || error.message || "Request failed";
return Promise.reject(new Error(message));
},
);
export async function login(email, password) {
const res = await api.post("/api/auth/login", { email, password });
return res.data;
}
export async function signup(email, password) {
const res = await api.post("/api/auth/signup", { email, password });
return res.data;
}
export async function logout() {
const res = await api.post("/api/auth/logout");
return res.data;
}
export async function createJob(companyName, userEmail) {
const res = await api.post("/api/jobs", {
company_name: companyName,
user_email: userEmail,
});
return res.data;
}
export async function uploadFiles(jobId, formData) {
const res = await api.post("/api/upload/" + jobId, formData, {
headers: { "Content-Type": "multipart/form-data" },
timeout: 120000,
});
return res.data;
}
export async function getJobResult(jobId) {
const res = await api.get("/api/analysis/" + jobId + "/result");
return res.data;
}
export async function getJobStatus(jobId) {
const res = await api.get("/api/jobs/" + jobId + "/status");
return res.data;
}
export async function submitOfficerNotes(jobId, notes, officerId) {
const res = await api.post("/api/officer/" + jobId + "/notes", {
notes,
officer_id: officerId || "anonymous",
});
return res.data;
}
export async function getCAM(jobId) {
const res = await api.get("/api/cam/" + jobId);
return res.data;
}
export async function regenerateCAM(jobId) {
const res = await api.post("/api/cam/" + jobId + "/regenerate");
return res.data;
}
export async function downloadCAM(jobId, format) {
const res = await api.get(`/api/cam/${jobId}/download/${format}`, {
responseType: "blob",
timeout: 60000,
});
const mime =
format === "pdf"
? "application/pdf"
: "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
const blob = new Blob([res.data], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${jobId}_Credit_Memo.${format}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
export async function getCAMPdfBlobUrl(jobId) {
const res = await api.get(`/api/cam/${jobId}/download/pdf?preview=true`, {
responseType: "blob",
timeout: 60000,
});
const blob = new Blob([res.data], { type: "application/pdf" });
return URL.createObjectURL(blob);
}
export async function listJobs() {
const res = await api.get("/api/jobs");
return res.data;
}
export function createSSEConnection(jobId, onEvent) {
const url = BASE_URL
? BASE_URL + "/api/analysis/" + jobId + "/stream"
: "/api/analysis/" + jobId + "/stream";
const source = new EventSource(url);
let connected = false;
let done = false;
source.onopen = () => {
connected = true;
};
source.onmessage = (e) => {
try {
const parsed = JSON.parse(e.data);
onEvent(parsed);
// If server sent complete or error, we're done — close cleanly
if (parsed.type === "complete" || parsed.type === "error") {
done = true;
source.close();
}
} catch (err) {
console.error("SSE parse error", err);
}
};
source.onerror = () => {
source.close();
// Don't fire error if we already received a terminal event
if (done) return;
onEvent({
type: "error",
stage: "SSE",
message: connected
? "Connection to pipeline lost. Please try again."
: "Unable to connect to analysis pipeline. Check that the backend is running.",
percent: 0,
});
};
return source;
}