'use client'; import { Copy, Play } from 'lucide-react'; import { useMemo, useState } from 'react'; import { cn } from '@/lib/cn'; import { clientTry } from '@/lib/client-api'; import { PUBLIC_API_BASE } from '@/lib/site'; import { buildPath, ROUTES, type RouteDef } from './routes'; /* Interactive request builder (client): route picker (static catalogue of the 1.1 routes), parameter fields with examples, "Try" against the same origin `/api/v1` (rewritten to the API), copyable curl / Python / JavaScript snippets and the response-shape excerpt from docs/API.md. Nothing is fetched until the user asks. */ type Lang = 'curl' | 'python' | 'js'; const MAX_BODY = 6000; function snippet(lang: Lang, url: string): string { if (lang === 'curl') return `curl -s "${url}" | jq .`; if (lang === 'python') return `import requests\n\nr = requests.get("${url}", headers={"User-Agent": "my-app/1.0 (contact@example.com)"}, timeout=30)\nr.raise_for_status()\ndata = r.json() # numeric aggregates may be strings — coerce before arithmetic\nprint(data)`; return `const res = await fetch("${url}", { headers: { accept: "application/json" } });\nif (!res.ok) throw new Error(\`API \${res.status}\`);\nconst data = await res.json(); // null means "not stated by any source"\nconsole.log(data);`; } export function RequestBuilder({ initialRoute = 'search' }: { initialRoute?: string }) { const [routeId, setRouteId] = useState(initialRoute); const route: RouteDef = ROUTES.find((r) => r.id === routeId) ?? ROUTES[0]!; const [values, setValues] = useState>({}); const [lang, setLang] = useState('curl'); const [result, setResult] = useState<{ status: number; ms: number; body: unknown; headers: Record } | null>(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const path = useMemo(() => buildPath(route, values), [route, values]); const url = `${PUBLIC_API_BASE}${path}`; const groups = useMemo(() => [...new Set(ROUTES.map((r) => r.group))], []); const pick = (id: string) => { setRouteId(id); setValues({}); setResult(null); setError(null); }; const tryIt = async () => { setBusy(true); setError(null); try { setResult(await clientTry(path)); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } }; const copy = async () => { try { await navigator.clipboard.writeText(snippet(lang, url)); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* ignore */ } }; const bodyText = result ? (typeof result.body === 'string' ? result.body : JSON.stringify(result.body, null, 2)) : ''; const cls = 'h-10 w-full border border-rule bg-surface px-2 text-sm text-ink focus:border-accent focus:outline-none'; return (

{route.method} {route.path}

{route.summary}

{route.params.length > 0 && (
{route.params.map((p) => ( ))}
)}
{url}
{(['curl', 'python', 'js'] as Lang[]).map((l) => ( ))}
            {snippet(lang, url)}
          

Response shape (docs/API.md)

            {route.returns}
          
{error &&

Request failed: {error}

} {result && ( <>

HTTP {result.status} {result.ms} ms {Object.entries(result.headers).map(([k, v]) => ( {k}: {v.length > 40 ? `${v.slice(0, 40)}…` : v} ))}

                {bodyText.length > MAX_BODY ? `${bodyText.slice(0, MAX_BODY)}\n… (${bodyText.length - MAX_BODY} more characters — open the URL for the full body)` : bodyText}
              
)}
); }