Spaces:
Sleeping
Sleeping
| export type RankedCandidate = { | |
| rank: number; | |
| canonical_smiles: string; | |
| structure_svg: string; | |
| reference_structure_svg: string; | |
| candidate_change_svg: string; | |
| change_summary: string; | |
| score: number; | |
| similarity: number; | |
| metrics: Record<string, number>; | |
| recommendation: string; | |
| warnings: string[]; | |
| }; | |
| type ApiErrorBody = { | |
| detail?: unknown; | |
| error?: unknown; | |
| message?: unknown; | |
| }; | |
| export type DrugLookupResult = { | |
| drug_name: string; | |
| structure_input: string; | |
| structure_format: "smiles" | "inchi"; | |
| structure_svg: string; | |
| target_name: string; | |
| indication: string; | |
| mechanism: string; | |
| source: string; | |
| }; | |
| export async function lookupDrug(name: string): Promise<DrugLookupResult> { | |
| const response = await fetch(`/api/drugs/lookup?name=${encodeURIComponent(name)}`); | |
| if (!response.ok) throw new Error(await buildApiError("Drug lookup failed", response)); | |
| return readJsonResponse<DrugLookupResult>("Drug lookup failed", response); | |
| } | |
| export async function createProjectAndRun(payload: Record<string, string>) { | |
| const projectResponse = await fetch("/api/projects", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (!projectResponse.ok) throw new Error(await buildApiError("Project creation failed", projectResponse)); | |
| const project = await readJsonResponse<{ id: number }>("Project creation failed", projectResponse); | |
| const runResponse = await fetch(`/api/projects/${project.id}/run`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (!runResponse.ok) throw new Error(await buildApiError("Pipeline run failed", runResponse)); | |
| return readJsonResponse("Pipeline run failed", runResponse); | |
| } | |
| async function buildApiError(prefix: string, response: Response) { | |
| const text = await response.text(); | |
| if (looksLikeHtml(response, text)) { | |
| return `${prefix}: API returned HTML instead of JSON. Check that the backend server is running and the /api proxy points to FastAPI.`; | |
| } | |
| const detail = readErrorDetail(text); | |
| return detail ? `${prefix}: ${detail}` : prefix; | |
| } | |
| async function readJsonResponse<T>(prefix: string, response: Response): Promise<T> { | |
| const text = await response.text(); | |
| if (looksLikeHtml(response, text)) { | |
| throw new Error( | |
| `${prefix}: API returned HTML instead of JSON. Check that the backend server is running and the /api proxy points to FastAPI.`, | |
| ); | |
| } | |
| try { | |
| return JSON.parse(text) as T; | |
| } catch { | |
| throw new Error(`${prefix}: API returned invalid JSON.`); | |
| } | |
| } | |
| function readErrorDetail(text: string) { | |
| try { | |
| const body = JSON.parse(text) as ApiErrorBody; | |
| return stringifyErrorValue(body.detail ?? body.error ?? body.message); | |
| } catch { | |
| return ""; | |
| } | |
| } | |
| function looksLikeHtml(response: Response, text: string) { | |
| const contentType = response.headers.get("Content-Type") ?? ""; | |
| const trimmed = text.trim().toLowerCase(); | |
| return contentType.includes("text/html") || trimmed.startsWith("<!doctype") || trimmed.startsWith("<html"); | |
| } | |
| function stringifyErrorValue(value: unknown): string { | |
| if (typeof value === "string") return value; | |
| if (Array.isArray(value)) return value.map(stringifyErrorValue).filter(Boolean).join("; "); | |
| if (value && typeof value === "object") { | |
| if ("msg" in value && typeof value.msg === "string") return value.msg; | |
| return JSON.stringify(value); | |
| } | |
| return ""; | |
| } | |