- 将系统设置页面改为公开/私有双标签页结构 - 新增渠道协议、权重配置和加权随机选择算法 - 替换自定义模型为自定义渠道配置选项 - 实现渠道模型远程获取和在线测试功能 - 添加批量测试和抽屉式编辑界面 - 更新文档说明和数据表结构定义
157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
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;
|
||
}
|