feat(admin): 添加模型算力点配置和扣费功能

- 在管理后台设置页面添加模型算力点配置表格
- 实现后端AI接口调用时按模型扣除用户算力点
- 添加算力点余额检查和不足时的错误处理
- 在credit_logs中记录AI调用消费流水
- 更新前端远程调用后刷新用户信息显示
- 文档中补充模型算力点配置说明
This commit is contained in:
HouYunFei
2026-05-25 14:01:04 +08:00
parent 392bd49d8c
commit 2181b3b885
14 changed files with 262 additions and 18 deletions
+6
View File
@@ -169,6 +169,7 @@ export type AdminModelChannel = {
export type AdminPublicModelChannelSettings = {
availableModels: string[];
modelCosts: AdminModelCost[];
defaultModel: string;
defaultImageModel: string;
defaultVideoModel: string;
@@ -177,6 +178,11 @@ export type AdminPublicModelChannelSettings = {
allowCustomChannel: boolean;
};
export type AdminModelCost = {
model: string;
credits: number;
};
export type AdminPublicSettings = {
modelChannel: AdminPublicModelChannelSettings;
auth: {
+11 -2
View File
@@ -121,6 +121,10 @@ function aiHeaders(config: AiConfig, contentType?: string) {
};
}
function refreshRemoteUser(config: AiConfig) {
if (config.channelMode === "remote") void useUserStore.getState().hydrateUser();
}
function withSystemMessage(config: AiConfig, messages: ChatCompletionMessage[]) {
const systemPrompt = config.systemPrompt.trim();
return systemPrompt ? [{ role: "system" as const, content: systemPrompt }, ...messages] : messages;
@@ -143,7 +147,9 @@ export async function requestGeneration(config: AiConfig, prompt: string) {
headers: aiHeaders(config, "application/json"),
},
);
return parseImagePayload(response.data);
const images = parseImagePayload(response.data);
refreshRemoteUser(config);
return images;
} catch (error) {
throw new Error(readAxiosError(error, "请求失败"));
}
@@ -166,7 +172,9 @@ export async function requestEdit(config: AiConfig, prompt: string, references:
try {
const response = await axios.post<ImageApiResponse>(aiApiUrl(config, "/images/edits"), formData, { headers: aiHeaders(config) });
return parseImagePayload(response.data);
const images = parseImagePayload(response.data);
refreshRemoteUser(config);
return images;
} catch (error) {
throw new Error(readAxiosError(error, "请求失败"));
}
@@ -230,6 +238,7 @@ export async function requestImageQuestion(config: AiConfig, messages: ChatCompl
} catch (error) {
throw new Error(readAxiosError(error, "请求失败"));
}
refreshRemoteUser(config);
return answer || "没有返回内容";
}
+5
View File
@@ -17,6 +17,10 @@ function aiHeaders(config: AiConfig) {
return config.channelMode === "remote" ? (token ? { Authorization: `Bearer ${token}` } : undefined) : { Authorization: `Bearer ${config.apiKey}` };
}
function refreshRemoteUser(config: AiConfig) {
if (config.channelMode === "remote") void useUserStore.getState().hydrateUser();
}
export async function requestVideoGeneration(config: AiConfig, prompt: string, references: ReferenceImage[] = []) {
const model = config.model || config.videoModel;
const body = new FormData();
@@ -34,6 +38,7 @@ export async function requestVideoGeneration(config: AiConfig, prompt: string, r
await new Promise((resolve) => setTimeout(resolve, 2500));
}
const content = await axios.get<Blob>(aiApiUrl(config, `/videos/${created.data.id}/content`), { headers: aiHeaders(config), params: config.channelMode === "remote" ? { model } : undefined, responseType: "blob" });
refreshRemoteUser(config);
return content.data;
}