ai-chatbot/lib/ai/runtime/image-balance.ts
WuYongJie 00cef71be7 暂停欠费生图线路并修正 98K 文生图尺寸传递
## Summary

生图线路确认欠费后立即停止新请求,跳过同一上游账户的其他模型,保留已生成结果和已提交任务。欠费状态须经管理员恢复,不再定时自动解封,也不会被旧任务的成功回执清除。旧模型选择仅使用明确配置且尺寸、比例和参考图数量兼容的备用模型;固定模型模式仍要求用户自行选择。

98K 文生图优先使用其公开声明的任务接口,原生传递分辨率和比例,清除文生图请求中的示例参考图片。

## Scope

- [x] API / backend logic
- [x] Settings / admin configuration

## Required Verification

验证普通 Pro、Banana 2、两个 Plus 和 GPT Image 2 共 10 个真实文生图及参考图编辑用例,均获得可下载的 2048×2048 图片。625 项 helper 回归通过,1 项跳过;TypeScript、日志/品牌/CI/环境/凭据/元数据/测试/产物检查通过。全仓 Biome 仍有历史格式和 lint 诊断(1005 项)。构建机执行生产构建及路由烟测;生产切换前核对任务与请求,保留旧构建和模型配置回滚记录。

## Release Readiness

仅修改本次生图运行逻辑与测试,凭据和验证产物保存在仓库外或忽略目录中。内部生成接口的权限与访问方式保持兼容。
2026-11-03 19:20:18 +08:00

75 lines
2.1 KiB
TypeScript

import { isProviderBalanceError } from "@/lib/request-error";
type BalanceState = {
healthLastEventType?: unknown;
healthLastError?: unknown;
healthAutoDisabledReason?: unknown;
};
export function isImageModelBalanceBlocked(model: BalanceState | null | undefined) {
return Boolean(
model &&
((model.healthLastEventType === "quota" && isProviderBalanceError(model.healthLastError)) ||
isProviderBalanceError(model.healthAutoDisabledReason)),
);
}
export function resolveConfiguredImageBalanceFallback<
T extends {
model: BalanceState & {
id: string;
modelId: string;
isEnabled: boolean;
supportsImageGen: boolean;
imageGenConfig?: unknown;
};
provider: { isEnabled?: boolean };
},
>(
requested: BalanceState & { fallbackModelId1?: string | null; fallbackModelId2?: string | null },
entries: T[],
input: {
imageSize?: unknown;
aspectRatio?: unknown;
referenceImageCount?: number;
},
) {
if (!isImageModelBalanceBlocked(requested)) return undefined;
for (const ref of [requested.fallbackModelId1, requested.fallbackModelId2]) {
if (!ref) continue;
const entry = entries.find(({ model }) => model.id === ref || model.modelId === ref);
if (
!entry ||
!entry.model.isEnabled ||
entry.provider.isEnabled === false ||
!entry.model.supportsImageGen ||
isImageModelBalanceBlocked(entry.model)
)
continue;
const config = entry.model.imageGenConfig as Record<string, unknown> | null;
const size = String(input.imageSize || "").toUpperCase();
const ratio = String(input.aspectRatio || "");
if (
size &&
Array.isArray(config?.supportedImageSizes) &&
!config.supportedImageSizes.includes(size)
)
continue;
if (
ratio &&
ratio !== "auto" &&
Array.isArray(config?.supportedAspectRatios) &&
!config.supportedAspectRatios.includes(ratio)
)
continue;
if (
input.referenceImageCount &&
typeof config?.maxReferenceImages === "number" &&
input.referenceImageCount > config.maxReferenceImages
)
continue;
return entry;
}
return undefined;
}