import util from "@/lib/util.ts"; import { DRAFT_MIN_VERSION, DRAFT_VERSION, RESOLUTION_OPTIONS, RESOLUTION_OPTIONS_NANOBANANAPRO_4K } from "@/api/consts/common.ts"; import { RegionInfo, getAssistantId } from "@/api/controllers/core.ts"; export type RegionKey = "CN" | "US" | "HK" | "JP" | "SG"; export interface ResolutionResult { width: number; height: number; imageRatio: number; resolutionType: string; isForced: boolean; } function getRegionKey(regionInfo: RegionInfo): RegionKey { if (regionInfo.isUS) return "US"; if (regionInfo.isHK) return "HK"; if (regionInfo.isJP) return "JP"; if (regionInfo.isSG) return "SG"; return "CN"; } function lookupResolution(resolution: string = "2k", ratio: string = "1:1", userModel?: string) { // nanobananapro 模型使用 4k 时,使用专用配置 if (userModel === "nanobananapro" && resolution === "4k") { const ratioConfig = RESOLUTION_OPTIONS_NANOBANANAPRO_4K[ratio]; if (!ratioConfig) { const supportedRatios = Object.keys(RESOLUTION_OPTIONS_NANOBANANAPRO_4K).join(", "); throw new Error(`nanobananapro 模型在 4k 分辨率下,不支持的比例 "${ratio}"。支持的比例: ${supportedRatios}`); } return { width: ratioConfig.width, height: ratioConfig.height, imageRatio: ratioConfig.ratio, resolutionType: resolution, }; } const resolutionGroup = RESOLUTION_OPTIONS[resolution]; if (!resolutionGroup) { const supportedResolutions = Object.keys(RESOLUTION_OPTIONS).join(", "); throw new Error(`不支持的分辨率 "${resolution}"。支持的分辨率: ${supportedResolutions}`); } const ratioConfig = resolutionGroup[ratio]; if (!ratioConfig) { const supportedRatios = Object.keys(resolutionGroup).join(", "); throw new Error(`在 "${resolution}" 分辨率下,不支持的比例 "${ratio}"。支持的比例: ${supportedRatios}`); } return { width: ratioConfig.width, height: ratioConfig.height, imageRatio: ratioConfig.ratio, resolutionType: resolution, }; } /** * 统一分辨率处理逻辑 * - CN 站: 不支持 nano 系列模型 (nanobanana/nanobananapro),抛出异常 * - US 站 nanobanana: 强制 1024x1024 @ 2k,image_ratio=1 * - HK/JP/SG 站 nanobanana: 强制 1k 分辨率,但 ratio 可自定义 * - 所有站点 nanobananapro: resolution 和 ratio 都可自定义 */ export function resolveResolution( userModel: string, regionInfo: RegionInfo, resolution: string = "2k", ratio: string = "1:1" ): ResolutionResult { const regionKey = getRegionKey(regionInfo); // ⚠️ 国内站不支持nano系列模型 if (regionKey === "CN" && (userModel === "nanobanana" || userModel === "nanobananapro")) { throw new Error( `国内站不支持${userModel}模型,请使用jimeng系列模型` ); } // ⚠️ nanobanana 模型的站点差异处理 if (userModel === "nanobanana") { if (regionKey === "US") { // US 站: 强制 1024x1024@2k, ratio 固定为 1 return { width: 1024, height: 1024, imageRatio: 1, resolutionType: "2k", isForced: true, }; } else if (regionKey === "HK" || regionKey === "JP" || regionKey === "SG") { // HK/JP/SG 站: 强制 1k 分辨率,但 ratio 可自定义 const params = lookupResolution("1k", ratio, userModel); return { width: params.width, height: params.height, imageRatio: params.imageRatio, resolutionType: "1k", isForced: true, }; } } // 其他所有情况: 使用用户指定的 resolution 和 ratio const params = lookupResolution(resolution, ratio, userModel); return { ...params, isForced: false, }; } /** * benefitCount 规则 * - 生图模式统一返回 4 * - 多图模式: 不加 */ export function getBenefitCount( userModel: string, regionInfo: RegionInfo, isMultiImage: boolean = false ): number | undefined { if (isMultiImage) return undefined; return 4; } export type GenerateMode = "text2img" | "img2img"; export interface BuildCoreParamOptions { userModel: string; // 用户模型名(如 'jimeng-4.0', 'nanobanana') model: string; // 映射后的内部模型名 prompt: string; imageCount?: number; // 图生图时的图片数量,用于生成动态 ## 前缀 negativePrompt?: string; seed?: number; sampleStrength: number; resolution: ResolutionResult; intelligentRatio?: boolean; mode?: GenerateMode; } /** * 构建 core_param * - 图生图: image_ratio 始终保留,prompt 前缀为 ## * imageCount * - 文生图: intelligent_ratio=true 时移除 image_ratio * - intelligent_ratio 仅对 jimeng-4.0/jimeng-4.1/jimeng-4.5 模型有效,其他模型忽略此参数 */ export function buildCoreParam(options: BuildCoreParamOptions) { const { userModel, model, prompt, imageCount = 0, negativePrompt, seed, sampleStrength, resolution, intelligentRatio = false, mode = "text2img", } = options; // ⚠️ intelligent_ratio 仅对 jimeng-4.0/jimeng-4.1/jimeng-4.5/jimeng-4.6/jimeng-5.0 模型有效 const effectiveIntelligentRatio = ['jimeng-4.0', 'jimeng-4.1', 'jimeng-4.5', 'jimeng-4.6', 'jimeng-5.0'].includes(userModel) ? intelligentRatio : false; // 图生图时,prompt 前缀规则: 每张图片对应 2 个 # // 1张图 → ##, 2张图 → ####, 3张图 → ###### const promptPrefix = mode === "img2img" ? '#'.repeat(imageCount * 2) : ''; const coreParam: any = { type: "", id: util.uuid(), model, prompt: `${promptPrefix}${prompt}`, sample_strength: sampleStrength, large_image_info: { type: "", id: util.uuid(), min_version: DRAFT_MIN_VERSION, height: resolution.height, width: resolution.width, resolution_type: resolution.resolutionType, }, intelligent_ratio: effectiveIntelligentRatio, }; if (mode === "img2img") { coreParam.image_ratio = resolution.imageRatio; } else if (!effectiveIntelligentRatio) { coreParam.image_ratio = resolution.imageRatio; } if (negativePrompt !== undefined) { coreParam.negative_prompt = negativePrompt; } if (seed !== undefined) { coreParam.seed = seed; } return coreParam; } export type SceneType = "ImageBasicGenerate" | "ImageMultiGenerate"; /** * metrics_extra 中 abilityList 的能力项 * - source.imageUrl: 前端使用 blob URL (如 blob:https://dreamina.capcut.com/[uuid]) * - 后端实现时需要生成占位符,保持 blob URL 格式 */ interface Ability { abilityName: string; strength: number; source?: { imageUrl: string; // 格式: blob:https://dreamina.capcut.com/[uuid] }; } export interface BuildMetricsExtraOptions { userModel: string; model: string; // 映射后的内部模型名 (如 high_aes_general_v50) regionInfo: RegionInfo; submitId: string; scene: SceneType; resolutionType: string; abilityList?: Ability[]; isMultiImage?: boolean; } /** * 构建 metrics_extra,自动处理 benefitCount 站点差异 & 多图禁用 */ export function buildMetricsExtra({ userModel, model, regionInfo, submitId, scene, resolutionType, abilityList = [], isMultiImage = false, }: BuildMetricsExtraOptions): string { const benefitCount = getBenefitCount(userModel, regionInfo, isMultiImage); const sceneOption: any = { type: "image", scene, modelReqKey: model, resolutionType, abilityList, reportParams: { enterSource: "generate", vipSource: "generate", extraVipFunctionKey: `${model}-${resolutionType}`, useVipFunctionDetailsReporterHoc: true, }, }; if (benefitCount !== undefined) { sceneOption.benefitCount = benefitCount; } const metrics: any = { promptSource: "custom", generateCount: 1, enterFrom: "click", sceneOptions: JSON.stringify([sceneOption]), generateId: submitId, isRegenerate: false, }; if (isMultiImage) { Object.assign(metrics, { templateId: "", templateSource: "", lastRequestId: "", originRequestId: "", }); } return JSON.stringify(metrics); } export interface BuildDraftContentOptions { componentId: string; generateType: "generate" | "blend"; coreParam: any; abilityList?: any[]; promptPlaceholderInfoList?: any[]; posteditParam?: any; imageCount?: number; // 图生图时的图片数量 } export function buildDraftContent({ componentId, generateType, coreParam, abilityList, promptPlaceholderInfoList, posteditParam, imageCount = 0, }: BuildDraftContentOptions): string { const abilities: any = { type: "", id: util.uuid(), }; // 图生图时,draft 和 blend 的 min_version 规则: // - draft.min_version: 始终为 "3.2.9" // - blend.min_version: 仅当 imageCount >= 2 时添加 "3.2.9" const isBlend = generateType === "blend"; const draftMinVersion = isBlend ? "3.2.9" : DRAFT_MIN_VERSION; if (generateType === "generate") { abilities.generate = { type: "", id: util.uuid(), core_param: coreParam, gen_option: { type: "", id: util.uuid(), generate_all: false, }, }; } else { abilities.blend = { type: "", id: util.uuid(), ...(imageCount >= 2 ? { min_version: "3.2.9" } : {}), min_features: [], core_param: coreParam, ability_list: abilityList, prompt_placeholder_info_list: promptPlaceholderInfoList, postedit_param: posteditParam, }; abilities.gen_option = { type: "", id: util.uuid(), generate_all: false, }; } const draftContent = { type: "draft", id: util.uuid(), min_version: draftMinVersion, min_features: [], is_from_tsn: true, version: DRAFT_VERSION, main_component_id: componentId, component_list: [ { type: "image_base_component", id: componentId, min_version: DRAFT_MIN_VERSION, aigc_mode: "workbench", metadata: { type: "", id: util.uuid(), created_platform: 3, created_platform_version: "", created_time_in_ms: Date.now().toString(), created_did: "", }, generate_type: generateType, abilities, }, ], }; return JSON.stringify(draftContent); } export interface BuildGenerateRequestOptions { model: string; regionInfo: RegionInfo; submitId: string; draftContent: string; metricsExtra: string; } export function buildGenerateRequest({ model, regionInfo, submitId, draftContent, metricsExtra, }: BuildGenerateRequestOptions) { return { extend: { root_model: model, }, submit_id: submitId, metrics_extra: metricsExtra, draft_content: draftContent, http_common_info: { aid: getAssistantId(regionInfo), }, }; } export function buildBlendAbilityList(uploadedImageIds: string[], strength: number): any[] { return uploadedImageIds.map((imageId) => ({ type: "", id: util.uuid(), name: "byte_edit", image_uri_list: [imageId], image_list: [ { type: "image", id: util.uuid(), source_from: "upload", platform_type: 1, name: "", image_uri: imageId, width: 0, height: 0, format: "", uri: imageId, }, ], strength, })); } export function buildPromptPlaceholderList(count: number): any[] { return Array.from({ length: count }, (_, index) => ({ type: "", id: util.uuid(), ability_index: index, })); }