feat: 完成 issue #183 [E2] 前端接入注册表 API——registry.ts 四接口封装 + models.vue API 优先/localStorage 降级/迁移提示 + version.vue 发布=注册

This commit is contained in:
2026-08-06 15:41:59 +08:00
parent 14e419be36
commit 1043dd473e
3 changed files with 300 additions and 90 deletions
@@ -0,0 +1,106 @@
/**
* 模型注册表 API 客户端(issue #183 [E2])
*
* 封装 E1(#182)四接口(nginx 反代 /api/v1/registry/ → registry 服务 :8002):
* GET /api/v1/registry/models 列表(?stage= 过滤)
* POST /api/v1/registry/models 注册新版本
* POST /api/v1/registry/models/{name}/{ver}/promote 阶段提升
* POST /api/v1/registry/models/{name}/rollback 回滚
*
* 鉴权:携带 FBA access token(useAccessStore,与 requestClient 同源)。
* 统一错误:抛 Error(msg 来自服务端 / HTTP 状态),由调用方降级或提示。
*/
import { useAccessStore } from '@vben/stores';
export interface RegistryModel {
name: string;
version: string;
backbone: string;
stage: 'dev' | 'staging' | 'prod';
description?: string;
registered_at?: number;
[k: string]: unknown;
}
export interface RegistryListResp {
models: RegistryModel[];
stage_pointers?: Record<string, Record<string, string>>;
}
const REG_BASE = '/api/v1/registry';
function bearer(): string {
const accessStore = useAccessStore();
const token = accessStore.accessToken as string | undefined;
return token ? `Bearer ${token}` : '';
}
async function regFetch<T>(path: string, init?: RequestInit): Promise<T> {
let resp: Response;
try {
resp = await fetch(`${REG_BASE}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
Authorization: bearer(),
...(init?.headers || {}),
},
});
} catch {
throw new Error('注册表服务不可达');
}
const raw = await resp.text();
const data = (raw ? JSON.parse(raw) : {}) as { msg?: string; [k: string]: unknown };
if (!resp.ok) {
throw new Error(data.msg || `注册表 HTTP ${resp.status}`);
}
return data as T;
}
/** 模型列表(可按 stage 过滤) */
export async function fetchRegistryModels(stage?: string): Promise<RegistryListResp> {
return regFetch<RegistryListResp>(stage ? `/models?stage=${stage}` : '/models');
}
/** 注册新版本(stage 默认 dev) */
export async function registerModel(body: {
name: string;
version: string;
backbone?: string;
description?: string;
stage?: string;
}): Promise<{ ok: boolean; model: RegistryModel }> {
return regFetch('/models', { method: 'POST', body: JSON.stringify(body) });
}
/** 阶段提升 */
export async function promoteModel(
name: string,
version: string,
): Promise<{ ok: boolean; model: RegistryModel }> {
return regFetch(`/models/${encodeURIComponent(name)}/${encodeURIComponent(version)}/promote`, {
method: 'POST',
});
}
/** 回滚某 stage 指针到指定版本 */
export async function rollbackModel(
name: string,
stage: string,
version: string,
): Promise<{ ok: boolean; model: RegistryModel }> {
return regFetch(`/models/${encodeURIComponent(name)}/rollback`, {
method: 'POST',
body: JSON.stringify({ stage, version }),
});
}
/** 注册表可用性探测(离线时调用方降级 localStorage) */
export async function registryAvailable(): Promise<boolean> {
try {
await fetchRegistryModels();
return true;
} catch {
return false;
}
}