Team Ai
Apppublic

Decoder2704/python-chatbot-jay

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
api.js159 linesDownload Raw Back to services
1/**2 * FastAPI chat client (JSON chat request with a message field).3 * Response fields used by the UI include answer text and retrieval metadata.4 *5 * Base URL resolution (no chatbot logic here):6 * - If VITE_API_URL is set → use it (cross-origin: separate frontend + API).7 * - Otherwise → "" (relative URLs: same origin as the page — Hugging Face Space, Docker, etc.).8 *9 * Local dev: leave VITE_API_URL unset and run `npm run dev` with Vite proxying API routes to10 * FastAPI (see vite.config.js), or set VITE_API_URL if you prefer direct calls + CORS.11 */12 13function getApiBase() {14  const raw = import.meta.env.VITE_API_URL;15  if (typeof raw === "string" && raw.trim()) {16    return raw.replace(/\/$/, "");17  }18  return "";19}20 21const API_BASE = getApiBase();22const CHAT_URL = `${API_BASE}/chat`;23 24/** Thrown for client validation, HTTP errors, network failures, or malformed responses. */25export class ChatApiError extends Error {26  /**27   * @param {string} message28   * @param {{ status?: number, cause?: unknown }} [opts]29   */30  constructor(message, opts = {}) {31    super(message);32    this.name = "ChatApiError";33    this.status = opts.status;34    this.cause = opts.cause;35  }36}37 38/**39 * @param {unknown} json40 * @returns {string}41 */42function detailFromFastApiError(json) {43  if (!json || typeof json !== "object") return "";44  const d = /** @type {{ detail?: unknown }} */ (json).detail;45  if (typeof d === "string") return d;46  if (Array.isArray(d)) {47    return d48      .map((item) => {49        if (item && typeof item === "object" && "msg" in item) {50          return String(/** @type {{ msg?: string }} */ (item).msg ?? "");51        }52        return String(item);53      })54      .filter(Boolean)55      .join(" ");56  }57  return "";58}59 60/**61 * Ensures JSON matches expected ChatResponse shape (display needs `answer`).62 * @param {unknown} data63 * @returns {{ answer: string, alternate: number, qid: number, aid: number }}64 */65function parseChatResponse(data) {66  if (!data || typeof data !== "object") {67    throw new ChatApiError("Invalid response: expected a JSON object");68  }69 70  const o = /** @type {Record<string, unknown>} */ (data);71 72  if (typeof o.answer !== "string") {73    throw new ChatApiError("Invalid response: missing or invalid `answer`");74  }75 76  const qid = o.qid;77  const aid = o.aid;78  const alt = o.alternate;79 80  if (typeof qid !== "number" || !Number.isFinite(qid)) {81    throw new ChatApiError("Invalid response: missing or invalid `qid`");82  }83  if (typeof aid !== "number" || !Number.isFinite(aid)) {84    throw new ChatApiError("Invalid response: missing or invalid `aid`");85  }86  if (typeof alt !== "number" || !Number.isFinite(alt)) {87    throw new ChatApiError("Invalid response: missing or invalid `alternate`");88  }89 90  return {91    answer: o.answer,92    alternate: alt,93    qid: Math.trunc(qid),94    aid: Math.trunc(aid),95  };96}97 98/**99 * @param {string} message100 * @returns {Promise<{ answer: string, alternate: number, qid: number, aid: number }>}101 */102export async function sendChatMessage(message) {103  const text = typeof message === "string" ? message.trim() : "";104  if (!text) {105    throw new ChatApiError("Message cannot be empty");106  }107 108  let res;109  try {110    res = await fetch(CHAT_URL, {111      method: "POST",112      headers: { "Content-Type": "application/json", Accept: "application/json" },113      body: JSON.stringify({ message: text }),114    });115  } catch (err) {116    const hint =117      API_BASE === ""118        ? "same-origin API (is the backend running, or Vite dev proxy configured?)"119        : `API at ${API_BASE}`;120    const msg =121      err instanceof TypeError && err.message === "Failed to fetch"122        ? `Could not reach the ${hint}`123        : err instanceof Error124          ? `Network error: ${err.message}`125          : "Network error";126    throw new ChatApiError(msg, { cause: err });127  }128 129  const raw = await res.text();130  const ct = res.headers.get("content-type") || "";131 132  if (!res.ok) {133    let detail = res.statusText || `HTTP ${res.status}`;134    if (ct.includes("application/json") && raw) {135      try {136        const j = JSON.parse(raw);137        const d = detailFromFastApiError(j);138        if (d) detail = d;139      } catch {140        /* keep statusText */141      }142    }143    throw new ChatApiError(detail || "Request failed", { status: res.status });144  }145 146  if (!ct.includes("application/json")) {147    throw new ChatApiError("Invalid response: expected application/json");148  }149 150  let parsed;151  try {152    parsed = raw ? JSON.parse(raw) : null;153  } catch {154    throw new ChatApiError("Invalid response: body is not valid JSON");155  }156 157  return parseChatResponse(parsed);158}159