File size: 3,471 Bytes
40ad0f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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 "";
}