feat(web): 分类展示公开 API 文档
将通用开放接口与兼容接口分层展示,补充完整接口目录、分类路由、搜索能力和接入约定。\n\n验证:pnpm nx run web:test;pnpm nx run web:build
This commit is contained in:
@@ -6,14 +6,24 @@ import { getOpsManagementSkillMetadata, resolveApiAssetUrl } from '../api';
|
||||
import { applyTaskSubmissionMode, type RunTaskOptions } from '../lib/run-task';
|
||||
import type { ApiDocSection, LoadState, TaskForm, TaskKind, TaskSubmissionMode } from '../types';
|
||||
import { ApiKeySelect, apiKeyNoticeText, resolveSelectedApiKeyId } from './playground-shared';
|
||||
import {
|
||||
publicApiCatalogGroups,
|
||||
publicApiEndpointCount,
|
||||
type PublicApiFamily,
|
||||
type PublicApiMethod,
|
||||
type PublicApiSurface,
|
||||
} from './api-docs-catalog';
|
||||
|
||||
interface ApiDocItem {
|
||||
catalogFamily?: PublicApiFamily;
|
||||
catalogSurface?: PublicApiSurface;
|
||||
group: string;
|
||||
key: ApiDocSection;
|
||||
kind?: TaskKind;
|
||||
lead: string;
|
||||
method?: 'GET' | 'POST';
|
||||
method?: PublicApiMethod;
|
||||
path?: string;
|
||||
surface: PublicApiSurface;
|
||||
title: string;
|
||||
}
|
||||
|
||||
@@ -35,17 +45,23 @@ interface ApiGuideItem {
|
||||
}
|
||||
|
||||
export const apiDocs: ApiDocItem[] = [
|
||||
{ key: 'chat', group: '文本', kind: 'chat.completions', method: 'POST', path: '/api/v1/chat/completions', title: 'Chat Completions', lead: 'OpenAI 兼容的对话接口,支持本地 API Key 授权、simulation 测试和非流式/流式响应。' },
|
||||
{ key: 'responses', group: '文本', kind: 'responses', method: 'POST', path: '/api/v1/responses', title: 'Responses', lead: 'OpenAI 兼容的 Responses 接口,原生支持 input、previous_response_id、工具调用和流式输出;不支持原生 Responses 的模型会由网关转换到 Chat Completions。' },
|
||||
{ key: 'embeddings', group: '文本', kind: 'embeddings', method: 'POST', path: '/api/v1/embeddings', title: '文本向量 Embeddings', lead: 'OpenAI 兼容的文本向量接口,可直接用 input 数组或字符串生成 embedding,API Key 需要 embedding 权限。' },
|
||||
{ key: 'reranks', group: '文本', kind: 'reranks', method: 'POST', path: '/api/v1/reranks', title: '文本重排序 Reranks', lead: 'OpenAI 风格的重排序接口,传入 query 和 documents 后返回 relevance_score,API Key 需要 rerank 权限。' },
|
||||
{ key: 'imageGeneration', group: '图片', kind: 'images.generations', method: 'POST', path: '/api/v1/images/generations', title: '创建图片', lead: 'OpenAI 兼容的图片生成接口,支持 prompt、size、quality 和 simulation 测试。' },
|
||||
{ key: 'imageEdit', group: '图片', kind: 'images.edits', method: 'POST', path: '/api/v1/images/edits', title: '编辑图片', lead: 'OpenAI 兼容的图片编辑接口,支持 image、mask、prompt 和 simulation 测试。' },
|
||||
{ key: 'videoGeneration', group: '视频', kind: 'videos.generations', method: 'POST', path: '/api/v1/videos/generations', title: '生成视频', lead: '视频生成任务接口,支持文生视频、首尾帧、图片/视频/音频参考,以及时长、分辨率、画幅和声音等模型能力参数。' },
|
||||
{ key: 'asyncMode', group: '异步任务', title: '异步模式', lead: '所有 AI 任务创建接口使用同一种异步开启方式:保留原接口和原请求 Body,只需增加 X-Async: true。' },
|
||||
{ key: 'taskRetrieve', group: '异步任务', kind: 'tasks.retrieve', method: 'GET', path: '/api/v1/tasks/{taskID}', title: '取回任务', lead: '使用异步提交返回的 taskId 查询任务状态、结果、错误、用量、计费和执行尝试;queued、running、submitting 为进行中状态。' },
|
||||
{ key: 'pricing', group: '计费', method: 'POST', path: '/api/v1/pricing/estimate', title: '价格预估', lead: '按请求体估算输入输出 token、模型倍率和折扣后的预估费用。' },
|
||||
{ key: 'files', group: '文件', method: 'POST', path: '/api/v1/files/upload', title: '上传文件', lead: '上传在线测试所需的图片、音频或视频资源,后续请求可复用返回的文件 URL。' },
|
||||
{ key: 'openApiOverview', surface: 'open', catalogSurface: 'open', group: '接口总览', title: '通用开放接口', lead: '统一的 Gateway 接口面向新业务接入,覆盖账号、模型、生成、异步任务、真人资产和安全集成;生产 Base URL 固定为 https://ai.51easyai.com/api/v1。' },
|
||||
{ key: 'chat', surface: 'open', group: '文本', kind: 'chat.completions', method: 'POST', path: '/api/v1/chat/completions', title: 'Chat Completions', lead: 'OpenAI 兼容的对话接口,支持本地 API Key 授权、simulation 测试和非流式/流式响应。' },
|
||||
{ key: 'responses', surface: 'open', group: '文本', kind: 'responses', method: 'POST', path: '/api/v1/responses', title: 'Responses', lead: 'OpenAI 兼容的 Responses 接口,原生支持 input、previous_response_id、工具调用和流式输出;不支持原生 Responses 的模型会由网关转换到 Chat Completions。' },
|
||||
{ key: 'embeddings', surface: 'open', group: '文本', kind: 'embeddings', method: 'POST', path: '/api/v1/embeddings', title: '文本向量 Embeddings', lead: 'OpenAI 兼容的文本向量接口,可直接用 input 数组或字符串生成 embedding,API Key 需要 embedding 权限。' },
|
||||
{ key: 'reranks', surface: 'open', group: '文本', kind: 'reranks', method: 'POST', path: '/api/v1/reranks', title: '文本重排序 Reranks', lead: 'OpenAI 风格的重排序接口,传入 query 和 documents 后返回 relevance_score,API Key 需要 rerank 权限。' },
|
||||
{ key: 'imageGeneration', surface: 'open', group: '图片', kind: 'images.generations', method: 'POST', path: '/api/v1/images/generations', title: '创建图片', lead: 'OpenAI 兼容的图片生成接口,支持 prompt、size、quality 和 simulation 测试。' },
|
||||
{ key: 'imageEdit', surface: 'open', group: '图片', kind: 'images.edits', method: 'POST', path: '/api/v1/images/edits', title: '编辑图片', lead: 'OpenAI 兼容的图片编辑接口,支持 image、mask、prompt 和 simulation 测试。' },
|
||||
{ key: 'videoGeneration', surface: 'open', group: '视频', kind: 'videos.generations', method: 'POST', path: '/api/v1/videos/generations', title: '生成视频', lead: '视频生成任务接口,支持文生视频、首尾帧、图片/视频/音频参考,以及时长、分辨率、画幅和声音等模型能力参数。' },
|
||||
{ key: 'asyncMode', surface: 'open', group: '异步任务', title: '异步模式', lead: '所有 AI 任务创建接口使用同一种异步开启方式:保留原接口和原请求 Body,只需增加 X-Async: true。' },
|
||||
{ key: 'taskRetrieve', surface: 'open', group: '异步任务', kind: 'tasks.retrieve', method: 'GET', path: '/api/v1/tasks/{taskID}', title: '取回任务', lead: '使用异步提交返回的 taskId 查询任务状态、结果、错误、用量、计费和执行尝试;queued、running、submitting 为进行中状态。' },
|
||||
{ key: 'pricing', surface: 'open', group: '计费', method: 'POST', path: '/api/v1/pricing/estimate', title: '价格预估', lead: '按请求体估算输入输出 token、模型倍率和折扣后的预估费用。' },
|
||||
{ key: 'files', surface: 'open', group: '文件', method: 'POST', path: '/api/v1/files/upload', title: '上传文件', lead: '上传在线测试所需的图片、音频或视频资源,后续请求可复用返回的文件 URL。' },
|
||||
{ key: 'compatApiOverview', surface: 'compatibility', catalogSurface: 'compatibility', group: '接口总览', title: '兼容接口', lead: '为已有厂商 SDK 和存量业务保留请求、响应及任务协议;调用方只需替换 Base URL 和 Gateway API Key。' },
|
||||
{ key: 'compatGemini', surface: 'compatibility', catalogFamily: 'gemini', group: '协议分类', title: 'Gemini 兼容接口', lead: '保留 generateContent 和 Gemini Files 请求结构,统一通过 /api/v1 前缀接入。' },
|
||||
{ key: 'compatKling', surface: 'compatibility', catalogFamily: 'kling', group: '协议分类', title: '可灵兼容接口', lead: '覆盖中国区可灵官方 V1 Omni、网关 V1 和 API 2.0;上游 AK/SK 只保存在网关服务端。' },
|
||||
{ key: 'compatVolces', surface: 'compatibility', catalogFamily: 'volces', group: '协议分类', title: '火山兼容接口', lead: '保留火山内容生成任务的创建、列表、查询和取消协议。' },
|
||||
{ key: 'compatServerMain', surface: 'compatibility', catalogFamily: 'serverMain', group: '协议分类', title: 'server-main 兼容接口', lead: '保留 server-main 的视频提交与结果轮询响应结构。' },
|
||||
];
|
||||
|
||||
const guideItems: ApiGuideItem[] = [
|
||||
@@ -98,9 +114,14 @@ export function ApiDocsPage(props: {
|
||||
const currentApiDoc = apiDocs.find((item) => item.key === props.activeDocSection) ?? (activeGuide ? undefined : apiDocs[0]);
|
||||
const current = activeGuide ?? currentApiDoc ?? apiDocs[0];
|
||||
const [opsSkillMetadata, setOpsSkillMetadata] = useState<GatewaySkillBundleMetadata>(defaultOpsSkillMetadata);
|
||||
const [docsSearch, setDocsSearch] = useState('');
|
||||
const normalizedDocsSearch = docsSearch.trim().toLowerCase();
|
||||
const visibleGuideItems = guideItems.filter((item) => guideMatchesSearch(item, normalizedDocsSearch));
|
||||
const visibleApiDocs = apiDocs.filter((item) => apiDocMatchesSearch(item, normalizedDocsSearch));
|
||||
const isFileDoc = currentApiDoc?.key === 'files';
|
||||
const isTaskRetrieveDoc = currentApiDoc?.key === 'taskRetrieve';
|
||||
const isAsyncModeDoc = currentApiDoc?.key === 'asyncMode';
|
||||
const isCatalogDoc = Boolean(currentApiDoc?.catalogSurface || currentApiDoc?.catalogFamily);
|
||||
const runnerAvailable = Boolean(currentApiDoc?.kind && currentApiDoc.method && currentApiDoc.path);
|
||||
const runnerModeEnabled = Boolean(runnerAvailable && currentApiDoc?.method !== 'GET');
|
||||
const apiKeyNotice = apiKeyNoticeText(props.apiKeys, props.apiKeySecretsById);
|
||||
@@ -218,28 +239,46 @@ export function ApiDocsPage(props: {
|
||||
</div>
|
||||
<label className="docsSearch">
|
||||
<Search size={15} />
|
||||
<input placeholder="搜索" />
|
||||
<input
|
||||
aria-label="搜索 API 文档"
|
||||
placeholder="搜索名称或路径"
|
||||
value={docsSearch}
|
||||
onChange={(event) => setDocsSearch(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<DocsGroup
|
||||
title="指南"
|
||||
items={guideItems.map((item) => ({
|
||||
active: item.key === props.activeDocSection,
|
||||
title: item.title,
|
||||
onClick: () => handleGuideClick(item),
|
||||
}))}
|
||||
/>
|
||||
{groupDocs(apiDocs).map((group) => (
|
||||
{visibleGuideItems.length > 0 && (
|
||||
<DocsGroup
|
||||
key={group.title}
|
||||
title={group.title}
|
||||
items={group.items.map((item) => ({
|
||||
title="指南"
|
||||
items={visibleGuideItems.map((item) => ({
|
||||
active: item.key === props.activeDocSection,
|
||||
method: item.method,
|
||||
title: item.title,
|
||||
onClick: () => handleDocClick(item),
|
||||
onClick: () => handleGuideClick(item),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
{groupDocsBySurface(visibleApiDocs).map((surface) => (
|
||||
<section className="docsSurface" data-surface={surface.key} key={surface.key}>
|
||||
<div className="docsSurfaceHeader">
|
||||
<strong>{apiSurfaceTitle(surface.key)}</strong>
|
||||
<span>{publicApiEndpointCount(surface.key)} 项</span>
|
||||
</div>
|
||||
{groupDocs(surface.items).map((group) => (
|
||||
<DocsGroup
|
||||
key={group.title}
|
||||
title={group.title}
|
||||
items={group.items.map((item) => ({
|
||||
active: item.key === props.activeDocSection,
|
||||
method: item.method,
|
||||
title: item.title,
|
||||
onClick: () => handleDocClick(item),
|
||||
}))}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
))}
|
||||
{normalizedDocsSearch && visibleGuideItems.length === 0 && visibleApiDocs.length === 0 && (
|
||||
<div className="docsSearchEmpty">没有找到匹配的接口或指南</div>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
<main className="docsArticle">
|
||||
@@ -253,7 +292,13 @@ export function ApiDocsPage(props: {
|
||||
)}
|
||||
<p className="docsLead">{current.lead}</p>
|
||||
|
||||
{isAsyncModeDoc ? (
|
||||
{isCatalogDoc && currentApiDoc ? (
|
||||
<ApiCatalogDetails
|
||||
family={currentApiDoc.catalogFamily}
|
||||
search={normalizedDocsSearch}
|
||||
surface={currentApiDoc.catalogSurface ?? currentApiDoc.surface}
|
||||
/>
|
||||
) : isAsyncModeDoc ? (
|
||||
<DocDetails section="asyncMode" />
|
||||
) : currentApiDoc ? (
|
||||
<>
|
||||
@@ -406,11 +451,15 @@ export function ApiDocsPage(props: {
|
||||
)}
|
||||
</form> : (
|
||||
<section className="docsGuideAside">
|
||||
<header><strong>{isAsyncModeDoc ? '能力说明' : '指南导航'}</strong></header>
|
||||
<header><strong>{isCatalogDoc ? '接口分类' : isAsyncModeDoc ? '能力说明' : '指南导航'}</strong></header>
|
||||
<div>
|
||||
<span>当前章节</span>
|
||||
<strong>{current.title}</strong>
|
||||
<p>{isAsyncModeDoc ? '异步模式不改变原接口的请求参数,只改变任务提交后的等待和响应方式。' : '使用左侧目录切换其他指南或 API 接口,浏览器前进、后退可保持当前章节。'}</p>
|
||||
<p>{isCatalogDoc
|
||||
? '所有新接入路径统一以 /api/v1 开头。选择兼容协议时只替换 Base URL 和 Gateway API Key。'
|
||||
: isAsyncModeDoc
|
||||
? '异步模式不改变原接口的请求参数,只改变任务提交后的等待和响应方式。'
|
||||
: '使用左侧目录切换其他指南或 API 接口,浏览器前进、后退可保持当前章节。'}</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -430,6 +479,151 @@ export function ApiDocsPage(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function ApiCatalogDetails(props: { family?: PublicApiFamily; search: string; surface: PublicApiSurface }) {
|
||||
const catalogGroups = publicApiCatalogGroups.filter((group) => (
|
||||
group.surface === props.surface && (!props.family || group.family === props.family)
|
||||
));
|
||||
const visibleGroups = catalogGroups
|
||||
.map((group) => {
|
||||
const groupMatches = searchText([group.title, group.description, group.family], props.search);
|
||||
const endpoints = groupMatches
|
||||
? group.endpoints
|
||||
: group.endpoints.filter((endpoint) => searchText([endpoint.method, endpoint.path, endpoint.description], props.search));
|
||||
return { ...group, endpoints };
|
||||
})
|
||||
.filter((group) => group.endpoints.length > 0);
|
||||
const endpointCount = catalogGroups.reduce((total, group) => total + group.endpoints.length, 0);
|
||||
const visibleEndpointCount = visibleGroups.reduce((total, group) => total + group.endpoints.length, 0);
|
||||
const isCompatibility = props.surface === 'compatibility';
|
||||
const baseUrl = props.family === 'kling'
|
||||
? 'https://ai.51easyai.com/api/v1/kling'
|
||||
: 'https://ai.51easyai.com/api/v1';
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="docsCatalogSummary" aria-label="API 接入摘要">
|
||||
<article>
|
||||
<span>{props.family === 'kling' ? '可灵客户端 Base URL' : '生产 Base URL'}</span>
|
||||
<code>{baseUrl}</code>
|
||||
</article>
|
||||
<article>
|
||||
<span>生成接口鉴权</span>
|
||||
<code>Authorization: Bearer {'<Gateway API Key>'}</code>
|
||||
</article>
|
||||
<article>
|
||||
<span>接口数量</span>
|
||||
<strong>{endpointCount}</strong>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section className="docsCatalogNotice" data-surface={props.surface}>
|
||||
<strong>{isCompatibility ? '存量协议接入规则' : '新业务接入规则'}</strong>
|
||||
<p>{isCompatibility
|
||||
? '保留对应厂商或存量系统的请求字段、响应包络和任务语义。调用方替换 Base URL 与 Gateway API Key 即可,上游凭据不会下发。'
|
||||
: '优先使用通用开放接口。所有路径统一位于 /api/v1;模型能力由模型目录与服务端配置决定,不要求额外传入 modelType。'}</p>
|
||||
</section>
|
||||
|
||||
{props.surface === 'open' && <OpenApiUsageNotes />}
|
||||
{isCompatibility && <CompatibilityUsageNotes family={props.family} />}
|
||||
|
||||
{props.search && (
|
||||
<p className="docsCatalogSearchResult">当前筛选显示 {visibleEndpointCount} / {endpointCount} 项接口</p>
|
||||
)}
|
||||
{visibleGroups.map((group) => (
|
||||
<section className="paramCard docsCatalogGroup" key={group.family}>
|
||||
<header>
|
||||
<div>
|
||||
<h2>{group.title}</h2>
|
||||
<p>{group.description}</p>
|
||||
</div>
|
||||
<Badge variant="outline">{group.endpoints.length} 项</Badge>
|
||||
</header>
|
||||
<div className="docsCatalogEndpoints">
|
||||
{group.endpoints.map((endpoint) => (
|
||||
<div className="docsCatalogEndpoint" key={`${endpoint.method}-${endpoint.path}`}>
|
||||
<MethodBadge method={endpoint.method} />
|
||||
<code>{endpoint.path}</code>
|
||||
<span>{endpoint.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
{visibleGroups.length === 0 && (
|
||||
<div className="docsCatalogEmpty">当前分类中没有匹配“{props.search}”的接口。</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenApiUsageNotes() {
|
||||
return (
|
||||
<section className="paramCard docsDetailCard">
|
||||
<header><h2>统一调用约定</h2></header>
|
||||
<div className="docsStatusGrid">
|
||||
<code>Base URL</code><p>SDK 配置到 <code>https://ai.51easyai.com/api/v1</code>,请求资源路径时不要再次拼接 <code>/api/v1</code>。</p>
|
||||
<code>同步 / 异步</code><p>生成接口保留原请求 Body;需要持久化异步任务时增加 <code>X-Async: true</code>,再通过 <code>/tasks/{'{taskID}'}</code> 取回结果。</p>
|
||||
<code>全模态能力</code><p>模型可直接使用服务端声明的文生图、文生视频、图生视频等能力,不要求调用方显式传入 <code>modelType</code>。</p>
|
||||
<code>公开端点</code><p>健康检查和 public 目录无需登录;账号管理使用登录会话,生成与任务接口使用 Gateway API Key。</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function CompatibilityUsageNotes(props: { family?: PublicApiFamily }) {
|
||||
if (props.family === 'kling') {
|
||||
return (
|
||||
<section className="paramCard docsDetailCard">
|
||||
<header><h2>可灵 V1 / API 2.0 约定</h2></header>
|
||||
<div className="docsStatusGrid">
|
||||
<code>SDK Base URL</code><p>设置为 <code>https://ai.51easyai.com/api/v1/kling</code>,之后按原客户端继续请求 <code>/v1/...</code> 或 <code>/v2/...</code>。</p>
|
||||
<code>O1</code><p>使用 <code>kling-video-o1</code>;兼容别名 <code>kling-o1</code> 会自动归一。</p>
|
||||
<code>3.0 Omni</code><p>使用 <code>kling-v3-omni</code>;兼容 <code>kling-3.0-omni</code> 和 <code>kling-3-omni</code>。</p>
|
||||
<code>鉴权</code><p>客户端只发送 Gateway API Key。可灵中国区 AK/SK 保存在网关平台配置中。</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (props.family === 'gemini') {
|
||||
return (
|
||||
<section className="paramCard docsDetailCard">
|
||||
<header><h2>Gemini 接入约定</h2></header>
|
||||
<div className="docsDetailContent">
|
||||
<p><code>generateContent</code> 保留 Gemini 内容结构;Files 上传的 <code>version</code> 支持 <code>v1</code> 和 <code>v1beta</code>。新接入路径仍统一放在 Gateway 的 <code>/api/v1</code> 前缀下。</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (props.family === 'volces') {
|
||||
return (
|
||||
<section className="paramCard docsDetailCard">
|
||||
<header><h2>火山任务约定</h2></header>
|
||||
<div className="docsDetailContent">
|
||||
<p>创建、列表、查询和删除接口保留火山任务结构。旧 <code>/api/v3</code> 仅作为兼容别名,不应写入新客户端配置。</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
if (props.family === 'serverMain') {
|
||||
return (
|
||||
<section className="paramCard docsDetailCard">
|
||||
<header><h2>server-main 接入约定</h2></header>
|
||||
<div className="docsDetailContent">
|
||||
<p>提交接口返回兼容的 <code>submitted</code> 和 <code>task_id</code>,现有调用方继续通过 <code>/ai/result/{'{taskID}'}</code> 轮询结果。</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<section className="paramCard docsDetailCard">
|
||||
<header><h2>兼容层边界</h2></header>
|
||||
<div className="docsDetailContent">
|
||||
<p>兼容接口用于无缝迁移已有 SDK 或存量调用。新业务优先选择通用开放接口,以获得统一的异步任务、结果归一、计费和模型能力语义。</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function OpsManagementSkillCard(props: { metadata: GatewaySkillBundleMetadata }) {
|
||||
const modules = props.metadata.modules.map(skillModuleLabel).join('、');
|
||||
return (
|
||||
@@ -787,18 +981,63 @@ function supportsAsyncMode(section: ApiDocSection) {
|
||||
|| section === 'videoGeneration';
|
||||
}
|
||||
|
||||
function MethodBadge(props: { method: NonNullable<ApiDocItem['method']> }) {
|
||||
return <Badge variant={props.method === 'GET' ? 'success' : 'warning'}>{props.method}</Badge>;
|
||||
function MethodBadge(props: { method: PublicApiMethod }) {
|
||||
const variant = props.method === 'GET'
|
||||
? 'success'
|
||||
: props.method === 'DELETE'
|
||||
? 'destructive'
|
||||
: props.method === 'PATCH'
|
||||
? 'secondary'
|
||||
: 'warning';
|
||||
return <Badge variant={variant}>{props.method}</Badge>;
|
||||
}
|
||||
|
||||
function groupDocs(items: typeof apiDocs) {
|
||||
const groups = new Map<string, typeof apiDocs>();
|
||||
function groupDocs(items: ApiDocItem[]) {
|
||||
const groups = new Map<string, ApiDocItem[]>();
|
||||
for (const item of items) {
|
||||
groups.set(item.group, [...(groups.get(item.group) ?? []), item]);
|
||||
}
|
||||
return Array.from(groups, ([title, groupItems]) => ({ title, items: groupItems }));
|
||||
}
|
||||
|
||||
function groupDocsBySurface(items: ApiDocItem[]) {
|
||||
const groups = new Map<PublicApiSurface, ApiDocItem[]>();
|
||||
for (const item of items) {
|
||||
groups.set(item.surface, [...(groups.get(item.surface) ?? []), item]);
|
||||
}
|
||||
return Array.from(groups, ([key, surfaceItems]) => ({ key, items: surfaceItems }));
|
||||
}
|
||||
|
||||
function apiSurfaceTitle(surface: PublicApiSurface) {
|
||||
return surface === 'open' ? '通用开放接口' : '兼容接口';
|
||||
}
|
||||
|
||||
function guideMatchesSearch(item: ApiGuideItem, search: string) {
|
||||
return searchText([item.title, item.lead, item.group], search);
|
||||
}
|
||||
|
||||
function apiDocMatchesSearch(item: ApiDocItem, search: string) {
|
||||
if (searchText([item.title, item.lead, item.path, item.group, apiSurfaceTitle(item.surface)], search)) {
|
||||
return true;
|
||||
}
|
||||
if (!item.catalogSurface && !item.catalogFamily) return false;
|
||||
return publicApiCatalogGroups
|
||||
.filter((group) => (
|
||||
(!item.catalogSurface || group.surface === item.catalogSurface)
|
||||
&& (!item.catalogFamily || group.family === item.catalogFamily)
|
||||
))
|
||||
.some((group) => searchText([
|
||||
group.title,
|
||||
group.description,
|
||||
...group.endpoints.flatMap((endpoint) => [endpoint.method, endpoint.path, endpoint.description]),
|
||||
], search));
|
||||
}
|
||||
|
||||
function searchText(values: Array<string | undefined>, search: string) {
|
||||
if (!search) return true;
|
||||
return values.some((value) => value?.toLowerCase().includes(search));
|
||||
}
|
||||
|
||||
function defaultTaskForDoc(kind: TaskForm['kind'], current: TaskForm, taskResult: GatewayTask | null): TaskForm {
|
||||
const next = defaultTaskForKind(kind, current);
|
||||
if (kind === 'tasks.retrieve' && !next.taskId && taskResult && !taskResult.id.startsWith('docs-')) {
|
||||
|
||||
Reference in New Issue
Block a user