Spaces:
Running
Running
| /** | |
| * Shared API client for all MesmerTools HuggingFace Spaces. | |
| * | |
| * Spaces are static pages that call the mesmer.tools API directly from the | |
| * visitor's browser. That is deliberate: per-IP rate limits then apply per | |
| * visitor instead of being shared through one Space backend. CORS on | |
| * /api/v1/* is already `*`, so cross-origin fetches work with no proxy. | |
| * | |
| * Every helper throws `RateLimitError` on a 429 so the UI can surface the | |
| * "use the full tool for higher limits" cross-sell, and `ApiError` otherwise. | |
| */ | |
| import { SITE } from "./config.js"; | |
| export class RateLimitError extends Error { | |
| constructor(message) { | |
| super(message || "You've hit the free hourly limit."); | |
| this.name = "RateLimitError"; | |
| this.isRateLimit = true; | |
| } | |
| } | |
| export class ApiError extends Error { | |
| constructor(message, status = 0) { | |
| super(message || "Something went wrong."); | |
| this.name = "ApiError"; | |
| this.status = status; | |
| } | |
| } | |
| const DEFAULT_TIMEOUT = 60_000; | |
| async function doFetch(url, init, timeout) { | |
| const ctrl = new AbortController(); | |
| const timer = setTimeout(() => ctrl.abort(), timeout ?? DEFAULT_TIMEOUT); | |
| try { | |
| return await fetch(url, { ...init, signal: ctrl.signal }); | |
| } catch (err) { | |
| if (err && err.name === "AbortError") { | |
| throw new ApiError( | |
| "The request timed out. The free demo can be slow under load — try the full tool on mesmer.tools.", | |
| 408, | |
| ); | |
| } | |
| throw new ApiError("Network error. Check your connection and try again.", 0); | |
| } finally { | |
| clearTimeout(timer); | |
| } | |
| } | |
| async function readJson(res) { | |
| try { | |
| return await res.json(); | |
| } catch { | |
| return null; | |
| } | |
| } | |
| /** | |
| * REST call. GET with `params`, or POST with a JSON `body`. | |
| * @returns parsed JSON response | |
| */ | |
| export async function callRest(apiPath, { method = "GET", params, body, timeout } = {}) { | |
| let url = SITE.origin + apiPath; | |
| const init = { method, headers: {} }; | |
| if (params) { | |
| const qs = new URLSearchParams(params).toString(); | |
| if (qs) url += (url.includes("?") ? "&" : "?") + qs; | |
| } | |
| if (body !== undefined) { | |
| init.headers["Content-Type"] = "application/json"; | |
| init.body = JSON.stringify(body); | |
| } | |
| const res = await doFetch(url, init, timeout); | |
| const data = await readJson(res); | |
| if (res.status === 429) throw new RateLimitError(data && data.error); | |
| if (!res.ok) throw new ApiError((data && data.error) || `Request failed (${res.status}).`, res.status); | |
| return data; | |
| } | |
| /** | |
| * tRPC v11 mutation (superjson transformer). Used by the logo space. | |
| * Input is wrapped as `{ json: input }`; the unwrapped value is returned. | |
| */ | |
| export async function callTrpcMutation(trpcPath, input, { timeout } = {}) { | |
| const res = await doFetch( | |
| SITE.origin + trpcPath, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ json: input }), | |
| }, | |
| timeout, | |
| ); | |
| const data = await readJson(res); | |
| if (res.status === 429) throw new RateLimitError(extractTrpcError(data)); | |
| if (!res.ok) throw new ApiError(extractTrpcError(data) || `Request failed (${res.status}).`, res.status); | |
| // superjson success envelope: { result: { data: { json: <value> } } } | |
| const out = data && data.result && data.result.data; | |
| return out && typeof out === "object" && "json" in out ? out.json : out; | |
| } | |
| function extractTrpcError(data) { | |
| if (!data || !data.error) return null; | |
| // superjson error envelope: { error: { json: { message, data: {...} } } } | |
| return (data.error.json && data.error.json.message) || data.error.message || null; | |
| } | |
| /** Fetch a generated JSON artifact (benchmark, voices) from mesmer.tools. */ | |
| export async function fetchData(url, { timeout } = {}) { | |
| const res = await doFetch(url, { method: "GET" }, timeout ?? 20_000); | |
| if (!res.ok) throw new ApiError(`Could not load data (${res.status}).`, res.status); | |
| return readJson(res); | |
| } | |