"use client"; import React, { useState, useCallback, useMemo } from "react"; import { useTranslations } from "next-intl"; import { OPENAPI_ENDPOINTS, OPENAPI_TAGS, OPENAPI_VERSION, type OpenApiEndpoint, } from "../lib/openapi.generated"; const METHOD_COLORS: Record = { GET: "bg-emerald-500/10 text-emerald-600 border-emerald-500/20", POST: "bg-blue-500/10 text-blue-600 border-blue-500/20", PUT: "bg-amber-500/10 text-amber-600 border-amber-500/20", DELETE: "bg-red-500/10 text-red-600 border-red-500/20", PATCH: "bg-purple-500/10 text-purple-600 border-purple-500/20", }; /** * A small map of richer "Try It" example bodies keyed by full path. The * generated OpenAPI module exposes every endpoint with its description and * summary; only paths in this map get a non-empty default request body when * selected. Anything not listed here falls back to the empty placeholder * (the user can paste their own body). */ const EXAMPLE_BODIES: Record = { "/api/v1/chat/completions": JSON.stringify( { model: "openai/gpt-4o-mini", messages: [{ role: "user", content: "Hello!" }], stream: true, }, null, 2 ), "/api/v1/embeddings": JSON.stringify( { model: "openai/text-embedding-3-small", input: "Hello world" }, null, 2 ), "/api/v1/images/generations": JSON.stringify( { model: "openai/gpt-image-2", prompt: "A sunset over mountains", n: 1 }, null, 2 ), "/api/v1/responses": JSON.stringify( { model: "openai/gpt-4o-mini", input: "What is OmniRoute?" }, null, 2 ), "/api/v1/messages": JSON.stringify( { model: "anthropic/claude-3-5-sonnet", max_tokens: 1024, messages: [{ role: "user", content: "Hi" }], }, null, 2 ), "/api/v1/messages/count_tokens": JSON.stringify( { model: "anthropic/claude-3-5-sonnet", messages: [{ role: "user", content: "Hi" }], }, null, 2 ), "/api/v1/moderations": JSON.stringify( { model: "openai/omni-moderation-latest", input: "Sample text" }, null, 2 ), "/api/v1/rerank": JSON.stringify( { model: "cohere/rerank-v3.5", query: "best ai gateway", documents: ["Document 1", "Document 2"], }, null, 2 ), "/api/v1/audio/transcriptions": "", "/api/v1/audio/speech": JSON.stringify( { model: "openai/tts-1", input: "Hello world", voice: "alloy" }, null, 2 ), }; export function ApiExplorerClient() { const t = useTranslations("docs"); const te = useTranslations("endpoint"); const [selected, setSelected] = useState(null); const [baseUrl, setBaseUrl] = useState("http://localhost:20128"); const [apiKey, setApiKey] = useState(""); const [requestBody, setRequestBody] = useState(""); const [response, setResponse] = useState(null); const [loading, setLoading] = useState(false); const [filterTag, setFilterTag] = useState(null); const filteredEndpoints = useMemo( () => (filterTag ? OPENAPI_ENDPOINTS.filter((e) => e.tag === filterTag) : OPENAPI_ENDPOINTS), [filterTag] ); const handleSelect = useCallback((endpoint: OpenApiEndpoint) => { setSelected(endpoint); setResponse(null); const example = EXAMPLE_BODIES[endpoint.path] ?? ""; setRequestBody(example); }, []); const handleTryIt = async () => { if (!selected) return; setLoading(true); setResponse(null); try { const headers: Record = { "Content-Type": "application/json" }; if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; const opts: RequestInit = { method: selected.method, headers }; if (selected.method !== "GET" && requestBody.trim()) { opts.body = requestBody; } // Strip the leading /api so callers can paste `http://localhost:20128` // as the base URL without doubling the prefix. The OpenAPI spec uses // `/api/v1/...` because that is the Next.js route; the runtime client // hits the same path. const res = await fetch(`${baseUrl}${selected.path}`, opts); const contentType = res.headers.get("content-type") || ""; if (contentType.includes("text/event-stream")) { setResponse(t("apiExplorerSseStarted")); } else { const data = await res.json(); setResponse(JSON.stringify(data, null, 2)); } } catch (err) { setResponse( t("apiExplorerError", { message: err instanceof Error ? err.message : te("requestFailed"), }) ); } finally { setLoading(false); } }; return (
{OPENAPI_ENDPOINTS.length} endpoints ยท OpenAPI v{OPENAPI_VERSION}
{OPENAPI_TAGS.map((tag) => ( ))}
{filteredEndpoints.map((endpoint) => ( ))}
{selected ? (
{selected.method} {selected.path} {selected.requiresAuth && ( {t("apiExplorerAuth")} )}

{selected.summary || selected.description}

{selected.description && selected.description !== selected.summary && (

{selected.description}

)}
setBaseUrl(e.target.value)} className="w-full px-3 py-2 text-sm bg-bg-subtle border border-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary" />
setApiKey(e.target.value)} placeholder="sk-..." className="w-full px-3 py-2 text-sm bg-bg-subtle border border-border rounded-lg focus:outline-none focus:ring-1 focus:ring-primary" />
{selected.method !== "GET" && selected.hasRequestBody && (