## 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 仅修改本次生图运行逻辑与测试,凭据和验证产物保存在仓库外或忽略目录中。内部生成接口的权限与访问方式保持兼容。
75 lines
2.1 KiB
TypeScript
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;
|
|
}
|