Files
MiragenFlow/web/src/services/api/admin.ts
T
HouYunFei 330f06bb05 feat(admin): 重构系统设置页面并新增渠道管理功能
- 将系统设置页面改为公开/私有双标签页结构
- 新增渠道协议、权重配置和加权随机选择算法
- 替换自定义模型为自定义渠道配置选项
- 实现渠道模型远程获取和在线测试功能
- 添加批量测试和抽屉式编辑界面
- 更新文档说明和数据表结构定义
2026-05-21 10:30:03 +08:00

157 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { apiDelete, apiGet, apiPost, compactApiParams } from "@/services/api/request";
import type { Prompt, PromptListResponse } from "@/services/api/prompts";
import { buildApiUrl } from "@/lib/ai-config";
import axios from "axios";
export type AdminPromptCategory = {
category: string;
name: string;
description: string;
file: string;
githubUrl: string;
remote: boolean;
};
export async function fetchAdminPromptCategories(token: string) {
return apiGet<AdminPromptCategory[]>("/api/admin/prompt-categories", undefined, token);
}
export async function syncAdminPromptCategory(token: string, category: string) {
return apiPost<AdminPromptCategory[]>("/api/admin/prompt-categories/sync", { category }, token);
}
export type AdminPromptQuery = {
keyword?: string;
category?: string;
tag?: string[];
page?: number;
pageSize?: number;
};
export type AdminAsset = {
id: string;
title: string;
type: "text" | "image" | "video";
coverUrl: string;
tags: string[];
category: string;
description: string;
content: string;
url: string;
createdAt: string;
updatedAt: string;
};
export type AdminAssetListResponse = {
items: AdminAsset[];
tags: string[];
total: number;
};
export async function fetchAdminPrompts(token: string, query: AdminPromptQuery = {}) {
return apiGet<PromptListResponse>("/api/admin/prompts", compactApiParams(query), token);
}
export async function saveAdminPrompt(token: string, prompt: Partial<Prompt>) {
return apiPost<Prompt>("/api/admin/prompts", prompt, token);
}
export async function deleteAdminPrompt(token: string, id: string) {
return apiDelete<boolean>(`/api/admin/prompts/${encodeURIComponent(id)}`, token);
}
export type AdminAssetQuery = {
keyword?: string;
type?: string;
tag?: string[];
page?: number;
pageSize?: number;
};
export async function fetchAdminAssets(token: string, query: AdminAssetQuery = {}) {
return apiGet<AdminAssetListResponse>(
"/api/admin/assets",
compactApiParams(query),
token,
);
}
export async function saveAdminAsset(token: string, asset: Partial<AdminAsset>) {
return apiPost<AdminAsset>("/api/admin/assets", asset, token);
}
export async function deleteAdminAsset(token: string, id: string) {
return apiDelete<boolean>(`/api/admin/assets/${encodeURIComponent(id)}`, token);
}
export type AdminModelChannel = {
protocol: "openai";
name: string;
baseUrl: string;
apiKey: string;
models: string[];
weight: number;
enabled: boolean;
remark: string;
};
export type AdminPublicSettings = {
availableModels: string[];
defaultModel: string;
defaultImageModel: string;
defaultTextModel: string;
systemPrompt: string;
allowCustomChannel: boolean;
};
export type AdminPrivateSettings = {
channels: AdminModelChannel[];
};
export type AdminSettings = {
public: AdminPublicSettings;
private: AdminPrivateSettings;
};
export async function fetchAdminSettings(token: string) {
return apiGet<AdminSettings>("/api/admin/settings", undefined, token);
}
export async function saveAdminSettings(token: string, settings: AdminSettings) {
return apiPost<AdminSettings>("/api/admin/settings", settings, token);
}
export async function fetchChannelModels(channel: Pick<AdminModelChannel, "baseUrl" | "apiKey">) {
try {
const response = await axios.get<{ data?: Array<{ id?: string }>; error?: { message?: string } }>(buildApiUrl(channel.baseUrl, "/models"), {
headers: { Authorization: `Bearer ${channel.apiKey}` },
});
return (response.data.data || [])
.map((item) => item.id)
.filter((id): id is string => Boolean(id))
.sort((a, b) => a.localeCompare(b));
} catch (error) {
throw new Error(readChannelError(error, "读取模型失败"));
}
}
export async function testChannelModel(channel: Pick<AdminModelChannel, "baseUrl" | "apiKey">, model: string) {
try {
const response = await axios.post<{ choices?: Array<{ message?: { content?: string } }>; error?: { message?: string } }>(
buildApiUrl(channel.baseUrl, "/chat/completions"),
{ model, messages: [{ role: "user", content: "hi" }] },
{ headers: { Authorization: `Bearer ${channel.apiKey}`, "Content-Type": "application/json" } },
);
return response.data.choices?.[0]?.message?.content || "ok";
} catch (error) {
throw new Error(readChannelError(error, "测试失败"));
}
}
function readChannelError(error: unknown, fallback: string) {
if (axios.isAxiosError<{ error?: { message?: string } }>(error)) {
return error.response?.data?.error?.message || (error.response?.status ? `${fallback}:${error.response.status}` : fallback);
}
return error instanceof Error ? error.message : fallback;
}