/** * HTTP / GraphQL 响应面板:Pretty / Raw / 响应头 + 状态条。 */ import { useMemo } from "react"; import { useWorkspace } from "../../workspaceStore"; import { CopyButton, JsonView, cx, formatBytes } from "../common"; import { ProxyErrorBanner } from "../ProxyErrorBanner"; export function ResponsePanel({ nodeId }: { nodeId: string }) { const runtime = useWorkspace((s) => s.runtime[nodeId]); const setRuntime = useWorkspace((s) => s.setRuntime); const tab = runtime?.responseTab ?? "body"; const result = runtime?.result ?? null; const error = runtime?.error ?? null; const loading = runtime?.loading ?? false; const contentType = useMemo(() => { const ct = result?.headers?.["content-type"] ?? ""; return ct.toLowerCase(); }, [result]); const isJson = contentType.includes("json") || (!contentType && looksLikeJson(result?.body ?? "")); const prettyBody = useMemo(() => { if (!result?.body) return ""; if (isJson) { try { return JSON.stringify(JSON.parse(result.body), null, 2); } catch { return result.body; } } return result.body; }, [result, isJson]); return (
{( [ ["body", "响应体"], ["raw", "Raw"], ["headers", "响应头"], ] as const ).map(([key, label]) => ( ))}
{result ? ( <> = 500 ? "ops-status-5xx" : result.status >= 400 ? "ops-status-4xx" : result.status >= 300 ? "ops-status-3xx" : "ops-status-2xx", )}> {result.status} 耗时 {result.timeMs} ms 大小 {formatBytes(result.size)} {contentType && ( {result.headers["content-type"]} )} ) : ( Status · Time · Size )}
{loading &&
请求发送中…
} {!loading && error && ( setRuntime(nodeId, { error: null })} /> )} {!loading && !error && !result && (
发送请求后这里显示响应
)} {!loading && result && tab === "body" && (
{isJson ? :
{result.body}
}
)} {!loading && result && tab === "raw" && (
{result.body}
)} {!loading && result && tab === "headers" && ( {Object.entries(result.headers).map(([k, v]) => ( ))}
{k} {v}
)}
); } function looksLikeJson(s: string): boolean { const t = s.trimStart(); return t.startsWith("{") || t.startsWith("["); }