// 各类模型的默认能力配置与按模型的特例修正(例如固定分辨率、特定时长档位)。 package app import ( "strings" "infinite-canvas/backend/internal/model" ) func DefaultModelCapabilityConfig(protocol string) *ModelCapabilityConfig { return DefaultModelCapabilityConfigForModel(protocol, "") } func videoDurationSupported(value *VideoCapabilityConfig) bool { return value == nil || value.DurationSupported == nil || *value.DurationSupported } func DefaultImageCapabilityConfig(protocol string, modelName string) *ImageCapabilityConfig { image := &ImageCapabilityConfig{ References: ImageReferenceConfig{PromptMaxChars: 32000, MaxImages: 16, MaxImageBytes: 30 * 1024 * 1024, MaskSupported: true}, Size: ImageSizeConfig{Parameter: "size", Values: defaultImageSizeValues(), Default: "1:1", AllowCustom: true}, Quality: ImageQualityConfig{Supported: true, Values: []string{"auto", "low", "medium", "high"}, Default: "auto"}, TransparentBackground: VideoBooleanConfig{Supported: true, Default: false}, ResponseFormat: ParameterSupport{Supported: true}, OutputFormat: ParameterSupport{Supported: true}, MaxOutputs: 15, } switch model.ChannelInterfaceType(protocol) { case model.ChannelInterfaceGrokImage: image.References.MaxImages = 1 image.References.MaskSupported = false // grok2api / xAI Imagine:size→aspect_ratio,quality→resolution(1k/2k)。 image.Size = ImageSizeConfig{Parameter: "aspect_ratio", Values: []string{"1:1", "3:4", "4:3", "9:16", "16:9", "2:3", "3:2"}, Default: "1:1", AllowCustom: false} image.Quality = ImageQualityConfig{Supported: true, Values: []string{"1k", "2k"}, Default: "2k"} image.TransparentBackground = VideoBooleanConfig{Supported: false, Default: false} image.ResponseFormat = ParameterSupport{Supported: true} image.OutputFormat = ParameterSupport{Supported: false} image.MaxOutputs = 1 case model.ChannelInterfaceVolcengineArkImage, model.ChannelInterfaceVolcengineArkAgentPlanImage: image.References.MaskSupported = false image.Quality.Supported = false image.TransparentBackground.Supported = false image.ResponseFormat.Supported = false image.OutputFormat.Supported = false case model.ChannelInterfaceVolcengineJiMengImage: image.References.MaxImages = 14 image.References.MaskSupported = false image.Quality.Supported = false image.TransparentBackground.Supported = false image.ResponseFormat.Supported = false image.OutputFormat.Supported = false case model.ChannelInterfaceGeminiImage: image.References.MaskSupported = false // Gemini Images uses imageConfig.aspectRatio, not the OpenAI-style pixel size field. image.Size = ImageSizeConfig{Parameter: "aspect_ratio", Values: []string{"auto", "1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9"}, Default: "1:1", AllowCustom: false} image.TransparentBackground.Supported = false image.ResponseFormat.Supported = false image.OutputFormat.Supported = false image.MaxOutputs = 4 } if model.ChannelInterfaceType(protocol) != model.ChannelInterfaceGrokImage && strings.HasPrefix(strings.ToLower(strings.TrimSpace(modelName)), "grok-imagine-image") { image.References.MaxImages = 0 image.References.MaskSupported = false image.Size = ImageSizeConfig{Parameter: "aspect_ratio", Values: []string{"1:1", "3:4", "4:3", "9:16", "16:9", "2:3", "3:2"}, Default: "1:1", AllowCustom: false} image.Quality = ImageQualityConfig{Supported: true, Values: []string{"1k", "2k"}, Default: "2k"} image.TransparentBackground = VideoBooleanConfig{Supported: false, Default: false} image.ResponseFormat = ParameterSupport{Supported: true} image.OutputFormat = ParameterSupport{Supported: false} image.MaxOutputs = 1 } return image } func defaultImageSizeValues() []string { return []string{ "auto", "1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "21:9", "9:16", "1024x1024", "1360x1024", "1024x1360", "1536x1024", "1024x1536", "1024x1280", "1280x1024", "2048x878", "1824x1024", "1024x1824", "2048x2048", "2304x1728", "1728x2304", "2496x1664", "1664x2496", "1792x2240", "2240x1792", "3136x1344", "2752x1536", "1536x2752", "2880x2880", "3264x2448", "2448x3264", "3504x2336", "2336x3504", "2560x3200", "3200x2560", "3808x1632", "3840x2160", "2160x3840", } } // legacyImageSizeValues 用于修复旧数据中仅保存了 "*" 的图片尺寸能力。 // 这组值是前后台共同展示的基础预设,不能让历史通配符配置继续污染用户生成参数。 func legacyImageSizeValues() []string { return []string{ "1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "21:9", "9:16", "1024x1024", "1536x1024", "1024x1536", } } func DefaultModelCapabilityConfigForModel(protocol string, modelName string) *ModelCapabilityConfig { // 文本模型是否支持视觉输入不能从协议或模型名可靠推断,默认关闭,由管理员按真实上游能力开启。 streaming := true text := &TextCapabilityConfig{Streaming: &streaming, ContextWindowTokens: 128000, MaxOutputTokens: 16384, References: TextReferenceConfig{PromptMaxChars: 32000}} video := &VideoCapabilityConfig{ References: VideoReferenceConfig{PromptMaxChars: DefaultVideoPromptMaxChars, MinImages: 0, MaxImages: 9, MaxImageBytes: 30 * 1024 * 1024, MaxVideos: 0, MaxVideoBytes: 0, MaxVideoDuration: 0, MaxAudios: 0, MaxAudioBytes: 0, MaxAudioDuration: 0}, Duration: VideoDurationConfig{Selection: "range", Min: 1, Max: 15, Step: 1, Default: 6}, Ratios: []string{"16:9", "9:16", "1:1", "4:3", "3:4", "21:9"}, DefaultRatio: "16:9", Resolutions: []string{"480p", "720p", "1080p", "1440p", "2160p"}, DefaultResolution: "720p", GenerateAudio: VideoBooleanConfig{Supported: false, Default: false}, Watermark: VideoBooleanConfig{Supported: false, Default: false}, Operations: []string{"text_to_video", "image_to_video"}, DefaultOperation: "text_to_video", } switch model.ChannelInterfaceType(protocol) { case model.ChannelInterfaceVolcengineJiMengVideo: video.Duration = VideoDurationConfig{Selection: "enum", Values: []int{5, 10}, Default: 5} video.Resolutions = []string{"720p"} case model.ChannelInterfaceGeminiVeo: video.Duration = VideoDurationConfig{Selection: "enum", Values: []int{4, 6, 8}, Default: 6} video.Resolutions = []string{"720p", "1080p"} case model.ChannelInterfaceVolcengineArkVideo, model.ChannelInterfaceVolcengineArkAgentPlanVideo: video.Operations = append(video.Operations, "reference_to_video", "audio_to_video") video.References.MaxVideos, video.References.MaxAudios = 3, 3 video.References.MaxVideoBytes, video.References.MaxAudioBytes = 200*1024*1024, 15*1024*1024 video.References.MaxVideoDuration, video.References.MaxAudioDuration = 15, 15 video.GenerateAudio = VideoBooleanConfig{Supported: true, Default: true} video.Watermark = VideoBooleanConfig{Supported: true, Default: false} video.Resolutions = []string{"480p", "720p", "1080p"} case model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2: video.References.MaxVideos, video.References.MaxAudios = 3, 3 video.References.MaxVideoBytes, video.References.MaxAudioBytes = 200*1024*1024, 15*1024*1024 video.References.MaxVideoDuration, video.References.MaxAudioDuration = 15, 15 video.GenerateAudio = VideoBooleanConfig{Supported: true, Default: true} if model.ChannelInterfaceType(protocol) == model.ChannelInterfaceNewAPIChannel1 { video.Resolutions = []string{"480p", "720p", "1080p"} } case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceXAIVideo: video.GenerateAudio = VideoBooleanConfig{Supported: false, Default: false} case model.ChannelInterfaceNovitaVideo: video.References.MaxImages, video.References.MaxImageBytes = 1, 10*1024*1024 video.Duration = VideoDurationConfig{Selection: "enum", Values: []int{5, 10}, Default: 5} video.Ratios = []string{"16:9", "9:16", "1:1"} video.Resolutions = []string{"1080p"} video.DefaultResolution = "1080p" case model.ChannelInterfaceMiniMaxVideo: video.Operations = append(video.Operations, "reference_to_video") video.References.MaxImages = 9 video.References.MaxImageBytes = 30 * 1024 * 1024 video.References.MaxVideos = 3 video.References.MaxVideoBytes = 50 * 1024 * 1024 video.References.MaxVideoDuration = 15 video.References.MaxAudios = 3 video.References.MaxAudioBytes = 15 * 1024 * 1024 video.References.MaxAudioDuration = 15 video.Duration = VideoDurationConfig{Selection: "enum", Values: []int{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Default: 5} video.Ratios = []string{"adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"} video.DefaultRatio = "16:9" video.Resolutions = []string{"768P", "2K"} video.DefaultResolution = "768P" video.Watermark = VideoBooleanConfig{Supported: true, Default: false} case model.ChannelInterfaceAgnesVideo: video = applyModelSpecificVideoCapability(video, protocol, modelName) } return &ModelCapabilityConfig{Version: 1, Text: text, Image: DefaultImageCapabilityConfig(protocol, modelName), Video: video} }