feat(admin): 新增管理后台模型渠道配置功能

- 实现了管理后台模型渠道的增删改查功能
- 将前端直接调用API改为通过后端接口代理获取模型列表
- 添加了渠道测试连接和模型可用性检测功能
- 实现了API Key安全存储机制,前端不再明文显示
- 更新了管理后台设置页面的渠道配置界面
- 添加了表格选中行背景色主题配置支持
- 重构了错误处理机制,统一返回安全错误信息
- 整理了后端模型代理路径为OpenAI风格的/api/v1/*
This commit is contained in:
HouYunFei
2026-05-22 15:29:31 +08:00
parent 499439d66b
commit db5d114428
10 changed files with 321 additions and 83 deletions
+10 -32
View File
@@ -1,7 +1,5 @@
import { apiDelete, apiGet, apiPost, compactApiParams } from "@/services/api/request";
import type { Prompt, PromptListResponse } from "@/services/api/prompts";
import { buildApiUrl } from "@/stores/use-config-store";
import axios from "axios";
export type AdminPromptCategory = {
category: string;
@@ -125,36 +123,16 @@ 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 type AdminChannelActionRequest = {
index?: number;
channel: AdminModelChannel;
model?: string;
};
export async function fetchChannelModels(token: string, payload: AdminChannelActionRequest) {
return apiPost<string[]>("/api/admin/settings/channel-models", payload, token);
}
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;
export async function testChannelModel(token: string, payload: AdminChannelActionRequest) {
return apiPost<string>("/api/admin/settings/channel-test", payload, token);
}