feat: 自托管 AI Gateway 运维管理 SKILL

This commit is contained in:
2026-07-16 23:57:14 +08:00
parent 30ad0e9f2c
commit a24eb1aeb0
23 changed files with 1544 additions and 3 deletions
+90 -3
View File
@@ -1,7 +1,8 @@
import { useEffect, useMemo, type FormEvent } from 'react';
import type { GatewayApiKey, GatewayTask } from '@easyai-ai-gateway/contracts';
import { BookOpen, KeyRound, Play, Search, Send } from 'lucide-react';
import { useEffect, useMemo, useState, type FormEvent } from 'react';
import type { GatewayApiKey, GatewaySkillBundleMetadata, GatewayTask } from '@easyai-ai-gateway/contracts';
import { BookOpen, Download, ExternalLink, FileJson, KeyRound, Play, Search, Send, Wrench } from 'lucide-react';
import { Badge, Button, Select, Textarea } from '../components/ui';
import { getOpsManagementSkillMetadata, resolveApiAssetUrl } from '../api';
import type { ApiDocSection, LoadState, TaskForm, TaskKind } from '../types';
import { ApiKeySelect, apiKeyNoticeText, resolveSelectedApiKeyId } from './playground-shared';
@@ -35,6 +36,17 @@ const taskKindOptions = [
['images.edits', '图像编辑'],
] as const;
const defaultOpsSkillMetadata: GatewaySkillBundleMetadata = {
name: 'ai-gateway-ops-management',
version: '',
displayName: 'AI Gateway 运维管理',
modules: ['model-runtime'],
fileName: 'ai-gateway-ops-management.zip',
downloadPath: '/api/v1/public/skills/ai-gateway-ops-management/download',
apiDocsJsonPath: '/api-docs-json',
apiDocsYamlPath: '/api-docs-yaml',
};
export function ApiDocsPage(props: {
activeDocSection: ApiDocSection;
apiKeySecretsById: Record<string, string>;
@@ -53,6 +65,7 @@ export function ApiDocsPage(props: {
onTaskFormChange: (value: TaskForm) => void;
}) {
const current = docs.find((item) => item.key === props.activeDocSection) ?? docs[0];
const [opsSkillMetadata, setOpsSkillMetadata] = useState<GatewaySkillBundleMetadata>(defaultOpsSkillMetadata);
const isFileDoc = current.key === 'files';
const apiKeyNotice = apiKeyNoticeText(props.apiKeys, props.apiKeySecretsById);
const activeApiKeyId = resolveSelectedApiKeyId(props.apiKeys, props.apiKeySecretsById, props.selectedApiKeyId);
@@ -64,6 +77,20 @@ export function ApiDocsPage(props: {
}
}, [current.kind, props.taskForm.kind]);
useEffect(() => {
let active = true;
getOpsManagementSkillMetadata()
.then((metadata) => {
if (active) setOpsSkillMetadata(metadata);
})
.catch(() => {
// Keep stable public fallback paths visible when metadata is temporarily unavailable.
});
return () => {
active = false;
};
}, []);
function handleSubmit(event: FormEvent<HTMLFormElement>) {
if (!props.canRun) {
event.preventDefault();
@@ -123,6 +150,8 @@ export function ApiDocsPage(props: {
</div>
<p className="docsLead">{current.lead}</p>
<OpsManagementSkillCard metadata={opsSkillMetadata} />
<section className="paramCard">
<header>
<h2>Header </h2>
@@ -215,6 +244,64 @@ export function ApiDocsPage(props: {
);
}
function OpsManagementSkillCard(props: { metadata: GatewaySkillBundleMetadata }) {
const modules = props.metadata.modules.map(skillModuleLabel).join('、');
return (
<section className="agentResourceCard">
<div className="agentResourceIcon">
<Wrench size={22} />
</div>
<div className="agentResourceContent">
<div className="agentResourceTitle">
<div>
<p className="eyebrow">Agent </p>
<h2>{props.metadata.displayName}</h2>
</div>
<Badge variant="outline">{props.metadata.version ? `v${props.metadata.version}` : '最新版本'}</Badge>
</div>
<p> Agent universal </p>
<div className="agentResourceModules">
<span></span>
<strong>{modules || '模型运行时'}</strong>
</div>
<div className="agentResourceActions">
<Button asChild size="sm">
<a href={resolveApiAssetUrl(props.metadata.downloadPath)}>
<Download size={14} />
SKILL
</a>
</Button>
<Button asChild size="sm" variant="secondary">
<a href={resolveApiAssetUrl(props.metadata.apiDocsJsonPath)} rel="noreferrer" target="_blank">
<FileJson size={14} />
Swagger JSON
</a>
</Button>
<Button asChild size="sm" variant="outline">
<a href={resolveApiAssetUrl(props.metadata.apiDocsYamlPath)} rel="noreferrer" target="_blank">
<ExternalLink size={14} />
Swagger YAML
</a>
</Button>
<a
className="agentResourceMetadataLink"
href={resolveApiAssetUrl('/api/v1/public/skills/ai-gateway-ops-management/metadata')}
rel="noreferrer"
target="_blank"
>
metadata
</a>
</div>
</div>
</section>
);
}
function skillModuleLabel(module: string) {
if (module === 'model-runtime') return '模型运行时';
return module;
}
function DocsGroup(props: {
items: Array<{ active?: boolean; method?: string; onClick?: () => void; title: string }>;
title: string;