grok2api / frontend /src /features /docs /api-docs-page.tsx
fromozuzhouzzz
Deploy grok2api v3.0.11 to HF Spaces
f1dd159
Raw
History Blame Contribute Delete
20.3 kB
import { useQuery } from "@tanstack/react-query";
import { Braces, Code2, Info, Link2 } from "lucide-react";
import { useState, type ReactNode } from "react";
import { Navigate, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { listModels } from "@/entities/model/model-api";
import type { ModelRouteDTO } from "@/entities/model/types";
import { getSystemInfo } from "@/entities/system/system-api";
import { runtimeConfig } from "@/shared/config/runtime-config";
import { CopyButton } from "@/shared/components/copy-button";
import { cn } from "@/shared/lib/cn";
type ExampleLanguage = "curl" | "python" | "javascript";
type ExampleView = "request" | "response";
type Method = "GET" | "POST";
type Capability = ModelRouteDTO["capability"];
type FieldDefinition = {
name: string;
required?: boolean;
descriptionKey: string;
};
type EndpointDefinition = {
key: string;
category: string;
title: string;
method: Method;
path: string;
descriptionKey: string;
capabilities: Capability[];
fields: FieldDefinition[];
noteKeys: string[];
request: (model: string) => Record<string, unknown> | undefined;
response: Record<string, unknown>;
};
const exampleLanguages: ExampleLanguage[] = ["curl", "python", "javascript"];
const endpoints: Record<string, EndpointDefinition> = {
"chat/completions": {
key: "chat/completions", category: "Chat", title: "Chat completions", method: "POST", path: "/chat/completions",
descriptionKey: "docs.endpointChat", capabilities: ["chat", "responses"],
fields: [
{ name: "model", required: true, descriptionKey: "docs.reference.fieldModel" },
{ name: "messages", required: true, descriptionKey: "docs.reference.fieldChatMessages" },
{ name: "stream", descriptionKey: "docs.reference.fieldStream" },
{ name: "max_completion_tokens", descriptionKey: "docs.reference.fieldMaxCompletionTokens" },
{ name: "tools", descriptionKey: "docs.reference.fieldTools" },
{ name: "tool_choice", descriptionKey: "docs.reference.fieldToolChoice" },
],
noteKeys: ["docs.reference.noteChatImages", "docs.reference.noteChatTools"],
request: (model) => ({ model, messages: [{ role: "system", content: "You are a concise assistant." }, { role: "user", content: "Explain HTTP streaming." }], stream: false }),
response: { id: "chatcmpl_example", object: "chat.completion", model: "grok-chat-fast", choices: [{ index: 0, message: { role: "assistant", content: "HTTP streaming sends response data incrementally." }, finish_reason: "stop" }], usage: { prompt_tokens: 24, completion_tokens: 12, total_tokens: 36 } },
},
"chat/responses": {
key: "chat/responses", category: "Chat", title: "Responses", method: "POST", path: "/responses",
descriptionKey: "docs.endpointResponses", capabilities: ["chat", "responses"],
fields: [
{ name: "model", required: true, descriptionKey: "docs.reference.fieldModel" },
{ name: "input", required: true, descriptionKey: "docs.reference.fieldInput" },
{ name: "instructions", descriptionKey: "docs.reference.fieldInstructions" },
{ name: "stream", descriptionKey: "docs.reference.fieldStream" },
{ name: "store", descriptionKey: "docs.reference.fieldStore" },
{ name: "previous_response_id", descriptionKey: "docs.reference.fieldPreviousResponse" },
{ name: "tools", descriptionKey: "docs.reference.fieldTools" },
],
noteKeys: ["docs.reference.noteResponsesState", "docs.reference.noteResponsesCompact"],
request: (model) => ({ model, input: "Explain HTTP streaming.", store: false, stream: false }),
response: { id: "resp_example", object: "response", status: "completed", model: "grok-chat-fast", output: [{ type: "message", role: "assistant", status: "completed", content: [{ type: "output_text", text: "HTTP streaming sends response data incrementally." }] }], usage: { input_tokens: 18, output_tokens: 12, total_tokens: 30 } },
},
"chat/messages": {
key: "chat/messages", category: "Chat", title: "Messages", method: "POST", path: "/messages",
descriptionKey: "docs.endpointMessages", capabilities: ["chat", "responses"],
fields: [
{ name: "model", required: true, descriptionKey: "docs.reference.fieldModel" },
{ name: "max_tokens", required: true, descriptionKey: "docs.reference.fieldMaxTokens" },
{ name: "messages", required: true, descriptionKey: "docs.reference.fieldMessages" },
{ name: "system", descriptionKey: "docs.reference.fieldSystem" },
{ name: "stream", descriptionKey: "docs.reference.fieldStream" },
{ name: "tools", descriptionKey: "docs.reference.fieldAnthropicTools" },
{ name: "tool_choice", descriptionKey: "docs.reference.fieldToolChoice" },
],
noteKeys: ["docs.reference.noteMessagesEvents", "docs.reference.noteMessagesLimits"],
request: (model) => ({ model, max_tokens: 1024, system: "You are a concise assistant.", messages: [{ role: "user", content: "Explain HTTP streaming." }], stream: false }),
response: { id: "msg_example", type: "message", role: "assistant", model: "grok-chat-fast", content: [{ type: "text", text: "HTTP streaming sends response data incrementally." }], stop_reason: "end_turn", stop_sequence: null, usage: { input_tokens: 18, output_tokens: 12 } },
},
"image/generations": {
key: "image/generations", category: "Image", title: "Image generations", method: "POST", path: "/images/generations",
descriptionKey: "docs.endpointImageGeneration", capabilities: ["image"],
fields: [
{ name: "model", required: true, descriptionKey: "docs.reference.fieldModel" },
{ name: "prompt", required: true, descriptionKey: "docs.reference.fieldPrompt" },
{ name: "n", descriptionKey: "docs.reference.fieldImageCount" },
{ name: "aspect_ratio", descriptionKey: "docs.reference.fieldAspectRatio" },
{ name: "resolution", descriptionKey: "docs.reference.fieldResolution" },
{ name: "response_format", descriptionKey: "docs.reference.fieldResponseFormat" },
{ name: "stream", descriptionKey: "docs.reference.fieldImageStream" },
],
noteKeys: ["docs.reference.noteImageCount", "docs.reference.noteImageStorage"],
request: (model) => ({ model, prompt: "A minimal red chair in a bright studio", n: 1, response_format: "url" }),
response: { created: 1783860000, data: [{ url: "http://127.0.0.1:8000/v1/media/images/example" }] },
},
"image/edits": {
key: "image/edits", category: "Image", title: "Image edits", method: "POST", path: "/images/edits",
descriptionKey: "docs.endpointImageEdit", capabilities: ["image_edit"],
fields: [
{ name: "model", required: true, descriptionKey: "docs.reference.fieldModel" },
{ name: "prompt", required: true, descriptionKey: "docs.reference.fieldPrompt" },
{ name: "image / images", required: true, descriptionKey: "docs.reference.fieldEditImages" },
{ name: "n", descriptionKey: "docs.reference.fieldImageCount" },
{ name: "response_format", descriptionKey: "docs.reference.fieldResponseFormat" },
],
noteKeys: ["docs.reference.noteEditJSON", "docs.reference.noteEditSources", "docs.reference.noteImageStorage"],
request: (model) => ({ model, prompt: "Change the chair to black", image: { url: "https://example.com/chair.png" }, n: 1, response_format: "url" }),
response: { created: 1783860000, data: [{ url: "http://127.0.0.1:8000/v1/media/images/example" }] },
},
"video/generations": {
key: "video/generations", category: "Video", title: "Video generations", method: "POST", path: "/videos/generations",
descriptionKey: "docs.endpointVideoCreate", capabilities: ["video"],
fields: [
{ name: "model", required: true, descriptionKey: "docs.reference.fieldModel" },
{ name: "prompt", descriptionKey: "docs.reference.fieldVideoPrompt" },
{ name: "duration", descriptionKey: "docs.reference.fieldDuration" },
{ name: "aspect_ratio", descriptionKey: "docs.reference.fieldAspectRatio" },
{ name: "resolution", descriptionKey: "docs.reference.fieldResolution" },
{ name: "image", descriptionKey: "docs.reference.fieldVideoImage" },
{ name: "reference_images", descriptionKey: "docs.reference.fieldReferenceImages" },
],
noteKeys: ["docs.reference.noteVideoAsync", "docs.reference.noteVideoStrict"],
request: (model) => ({ model, prompt: "A paper airplane flying over a city", duration: 8, aspect_ratio: "16:9", resolution: "720p" }),
response: { request_id: "video_example" },
},
"video/get": {
key: "video/get", category: "Video", title: "Get video", method: "GET", path: "/videos/{request_id}",
descriptionKey: "docs.endpointVideoGet", capabilities: ["video"],
fields: [{ name: "request_id", required: true, descriptionKey: "docs.reference.fieldRequestId" }],
noteKeys: ["docs.reference.noteVideoPolling", "docs.reference.noteVideoStatus"],
request: () => undefined,
response: { status: "done", model: "grok-imagine-video", progress: 100, video: { url: "https://example.com/generated.mp4", duration: 8, respect_moderation: true } },
},
};
export function ApiDocsPage() {
const { t } = useTranslation();
const { category, endpoint } = useParams();
const definition = endpoints[`${category ?? ""}/${endpoint ?? ""}`];
const [language, setLanguage] = useState<ExampleLanguage>("curl");
const [exampleView, setExampleView] = useState<ExampleView>("request");
const [selectedModel, setSelectedModel] = useState("");
const systemQuery = useQuery({ queryKey: ["system-info"], queryFn: getSystemInfo, staleTime: Number.POSITIVE_INFINITY, retry: 1 });
const modelsQuery = useQuery({ queryKey: ["docs", "available-models"], queryFn: () => listModels({ page: 1, pageSize: 100 }), staleTime: 30_000 });
if (!definition) return <Navigate to="/docs/chat/completions" replace />;
const publicApiBaseUrl = systemQuery.data?.publicApiBaseURL || runtimeConfig.publicApiBaseUrl;
const baseUrl = `${publicApiBaseUrl.replace(/\/$/, "")}/v1`;
const availableModels = (modelsQuery.data?.items ?? []).filter((model) => model.enabled && model.available && definition.capabilities.includes(model.capability));
const selectedModelAvailable = availableModels.some((model) => model.publicId === selectedModel);
const exampleModel = (selectedModelAvailable ? selectedModel : availableModels[0]?.publicId) || fallbackModel(definition.key);
const examples = createExamples(definition, baseUrl, exampleModel);
const responseExample = JSON.stringify(withExampleModel(definition.response, exampleModel), null, 2);
return (
<div className="w-full space-y-10">
<header className="space-y-4">
<div className="min-w-0">
<h1 className="text-2xl font-medium text-foreground">{definition.title}</h1>
<p className="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">{t(definition.descriptionKey)}</p>
</div>
<EndpointSignature method={definition.method} path={`/v1${definition.path}`} />
</header>
<div className="space-y-10">
<DocsSection icon={<Link2 />} title={t("docs.reference.connection")}>
<div className="grid gap-4 sm:grid-cols-2">
<ConnectionItem label={t("docs.baseUrl")} value={baseUrl} />
<ConnectionItem label={t("docs.authentication")} value={definition.key === "chat/messages" ? "x-api-key: g2a_..." : "Authorization: Bearer g2a_..."} />
{definition.key === "chat/messages" ? <ConnectionItem label="anthropic-version" value="2023-06-01" /> : null}
</div>
</DocsSection>
<DocsSection icon={<Braces />} title={definition.method === "GET" ? t("docs.reference.pathParameters") : t("docs.reference.requestBody")}>
<div className="overflow-hidden rounded-md bg-card">
<div className="hidden grid-cols-[minmax(120px,180px)_minmax(0,1fr)] gap-5 bg-secondary/35 px-4 py-2 text-xs text-muted-foreground sm:grid">
<span>{t("docs.reference.parameter")}</span>
<span>{t("docs.reference.description")}</span>
</div>
<div>{definition.fields.map((field, index) => <ParameterItem key={field.name} field={field} muted={index % 2 === 1} />)}</div>
</div>
</DocsSection>
<DocsSection icon={<Code2 />} title={t("docs.reference.example")}>
<ExamplePanel
view={exampleView}
onViewChange={setExampleView}
language={language}
onLanguageChange={setLanguage}
code={exampleView === "request" ? examples[language] : responseExample}
models={availableModels}
selectedModel={exampleModel}
onModelChange={setSelectedModel}
/>
</DocsSection>
{definition.noteKeys.length > 0 ? (
<DocsSection icon={<Info />} title={t("docs.reference.notes")}>
<ul className="space-y-2 rounded-md bg-secondary/35 px-4 py-3 text-xs leading-5 text-muted-foreground">
{definition.noteKeys.map((key) => <li key={key} className="relative pl-3 before:absolute before:left-0 before:top-[0.55rem] before:size-1 before:rounded-full before:bg-muted-foreground/55">{t(key)}</li>)}
</ul>
</DocsSection>
) : null}
</div>
</div>
);
}
function withExampleModel(response: Record<string, unknown>, model: string): Record<string, unknown> {
return "model" in response ? { ...response, model } : response;
}
function fallbackModel(key: string): string {
if (key.startsWith("image/")) return key === "image/edits" ? "grok-imagine-image-edit" : "grok-imagine-image";
if (key.startsWith("video/")) return "grok-imagine-video";
return "your-enabled-model";
}
function createExamples(definition: EndpointDefinition, baseUrl: string, model: string): Record<ExampleLanguage, string> {
const request = definition.request(model);
const url = `${baseUrl}${definition.path.replace("{request_id}", "video_example")}`;
const messageHeaders = definition.key === "chat/messages";
const curlHeaders = messageHeaders
? [
' -H "x-api-key: $GROK2API_API_KEY"',
' -H "anthropic-version: 2023-06-01"',
' -H "Content-Type: application/json"',
].join(" \\\n")
: [
' -H "Authorization: Bearer $GROK2API_API_KEY"',
' -H "Content-Type: application/json"',
].join(" \\\n");
const curlBody = request ? ` \\\n -d '${JSON.stringify(request, null, 2)}'` : "";
const headers = messageHeaders
? { "x-api-key": "g2a_your_api_key", "anthropic-version": "2023-06-01", "Content-Type": "application/json" }
: { Authorization: "Bearer g2a_your_api_key", "Content-Type": "application/json" };
const pythonImports = request ? "import json\nimport requests" : "import requests";
const pythonPayload = request ? `\n\npayload = json.loads(r'''${JSON.stringify(request, null, 2)}''')` : "";
const pythonBody = request ? ",\n json=payload" : "";
const javascriptBody = request ? `,\n body: JSON.stringify(${JSON.stringify(request, null, 2)})` : "";
return {
curl: `export GROK2API_API_KEY="g2a_your_api_key"\n\ncurl -X ${definition.method} "${url}" \\\n${curlHeaders}${curlBody}`,
python: `${pythonImports}${pythonPayload}\n\nresponse = requests.${definition.method.toLowerCase()}(\n "${url}",\n headers=${JSON.stringify(headers, null, 2)}${pythonBody}\n)\nresponse.raise_for_status()\nprint(response.json())`,
javascript: `const response = await fetch("${url}", {\n method: "${definition.method}",\n headers: ${JSON.stringify(headers, null, 2)}${javascriptBody}\n});\n\nif (!response.ok) throw new Error(await response.text());\nconsole.log(await response.json());`,
};
}
function MethodLabel({ method }: { method: Method }) {
return <span className={cn("font-mono text-xs font-semibold", method === "GET" ? "text-emerald-600 dark:text-emerald-400" : "text-sky-600 dark:text-sky-400")}>{method}</span>;
}
function EndpointSignature({ method, path }: { method: Method; path: string }) {
return (
<div className="flex h-8 w-fit max-w-full items-center gap-2 rounded-md bg-card px-3">
<MethodLabel method={method} />
<code className="min-w-0 truncate text-xs" title={path}>{path}</code>
<CopyButton value={path} />
</div>
);
}
function DocsSection({ icon, title, children }: { icon: ReactNode; title: string; children: ReactNode }) {
return (
<section className="space-y-3.5">
<div className="flex items-center gap-2 text-sm font-medium [&_svg]:size-4 [&_svg]:text-muted-foreground">{icon}{title}</div>
<div className="min-w-0">{children}</div>
</section>
);
}
function ConnectionItem({ label, value }: { label: string; value: string }) {
return (
<div className="min-w-0">
<div className="mb-1.5 text-xs text-muted-foreground">{label}</div>
<div className="flex h-8 min-w-0 items-center rounded-md bg-secondary/55 pl-3 pr-0.5">
<code className="min-w-0 flex-1 truncate text-xs text-muted-foreground" title={value}>{value}</code>
<CopyButton value={value} />
</div>
</div>
);
}
function ParameterItem({ field, muted }: { field: FieldDefinition; muted: boolean }) {
const { t } = useTranslation();
return (
<div className={cn("grid grid-cols-1 gap-1.5 px-4 py-3 sm:grid-cols-[minmax(120px,180px)_minmax(0,1fr)] sm:gap-5", muted && "bg-secondary/20")}>
<div className="min-w-0">
<code className="break-all text-xs font-medium text-foreground">
{field.name}
{field.required ? <span className="ml-1 text-destructive" title={t("docs.reference.required")}>*</span> : null}
</code>
</div>
<div className="min-w-0 text-xs leading-5 text-muted-foreground">{t(field.descriptionKey)}</div>
</div>
);
}
function ExamplePanel({
view,
onViewChange,
language,
onLanguageChange,
code,
models,
selectedModel,
onModelChange,
}: {
view: ExampleView;
onViewChange: (view: ExampleView) => void;
language: ExampleLanguage;
onLanguageChange: (language: ExampleLanguage) => void;
code: string;
models: ModelRouteDTO[];
selectedModel: string;
onModelChange: (model: string) => void;
}) {
const { t } = useTranslation();
return (
<div className="overflow-hidden rounded-lg bg-card">
<div className="flex min-h-12 flex-wrap items-center gap-2 px-3 py-2">
<Tabs value={view} onValueChange={(value) => onViewChange(value as ExampleView)}>
<TabsList>
<TabsTrigger value="request">{t("docs.reference.request")}</TabsTrigger>
<TabsTrigger value="response">{t("docs.reference.response")}</TabsTrigger>
</TabsList>
</Tabs>
<div className="flex min-w-0 flex-1 flex-wrap items-center justify-end gap-2">
{view === "request" ? (
<Select value={language} onValueChange={(value) => onLanguageChange(value as ExampleLanguage)}>
<SelectTrigger className="h-8 w-28 bg-background text-xs" aria-label={t("docs.exampleLanguage")}><SelectValue /></SelectTrigger>
<SelectContent>
{exampleLanguages.map((item) => <SelectItem key={item} value={item}>{item === "javascript" ? "JavaScript" : item === "python" ? "Python" : "cURL"}</SelectItem>)}
</SelectContent>
</Select>
) : null}
{models.length > 0 ? (
<Select value={selectedModel} onValueChange={onModelChange}>
<SelectTrigger className="h-8 w-[190px] max-w-full bg-background text-xs" aria-label={t("docs.reference.exampleModel")}><SelectValue /></SelectTrigger>
<SelectContent>{models.map((model) => <SelectItem key={model.id} value={model.publicId}>{model.publicId}</SelectItem>)}</SelectContent>
</Select>
) : null}
<CopyButton value={code} />
</div>
</div>
<pre className="max-h-[480px] overflow-auto bg-secondary/45 p-4 text-xs leading-5 text-foreground"><code>{code}</code></pre>
</div>
);
}