fix: grok-image 补齐 aspect_ratio 与 resolution 传参 (#184)

* fix: grok-image 补齐 aspect_ratio 与 resolution 传参

- 能力配置开放比例与 1k/2k 分辨率
- 请求体映射 size→aspect_ratio、quality→resolution
- 画布设置展示「分辨率」选项
- 补充单测

Closes #182

* fix(create): 创作页图片质量支持 1k/2k 分辨率选项

创作页原先硬编码 auto/low/medium/high,导致 grok 能力配置里的 1k/2k
无法显示;改为按模型 quality.values 动态渲染,有 1k/2k 时标题显示「分辨率」。
This commit is contained in:
Andrew authored and GitHub committed 2026-08-08 18:27:36 +08:00
1 parent 99cc880eb8
commit 1b0fd7fd49
8 files changed
+164 -15

No files matched your search

+5 -4
View File
@@ -109,8 +109,9 @@ func DefaultImageCapabilityConfig(protocol string, modelName string) *ImageCapab
case model.ChannelInterfaceGrokImage:
image.References.MaxImages = 1
image.References.MaskSupported = false
image.Size = ImageSizeConfig{Parameter: "none", Values: []string{}, Default: "auto", AllowCustom: false}
image.Quality = ImageQualityConfig{Supported: false, Values: []string{}, Default: "auto"}
// 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}
@@ -132,8 +133,8 @@ func DefaultImageCapabilityConfig(protocol string, modelName string) *ImageCapab
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: "none", Values: []string{}, Default: "auto", AllowCustom: false}
image.Quality = ImageQualityConfig{Supported: false, Values: []string{}, Default: "auto"}
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}
+66 -1
View File
@@ -746,7 +746,15 @@ func grokImageRequestBody(input canvasGenerationInput) (grokImageRequest, string
if input.Mask != nil {
return grokImageRequest{}, "", errors.New("Grok 图片协议不支持蒙版编辑,请移除蒙版后重试")
}
body := grokImageRequest{Model: input.Config.Model, Prompt: withSystemPrompt(input.Config, input.Prompt), N: 1, ResponseFormat: "url"}
body := grokImageRequest{
Model: input.Config.Model,
Prompt: withSystemPrompt(input.Config, input.Prompt),
N: 1,
ResponseFormat: "url",
Size: strings.TrimSpace(input.Config.Size),
AspectRatio: normalizeGrokImageAspectRatio(input.Config.Size),
Resolution: normalizeGrokImageResolution(input.Config.Quality),
}
if len(input.ReferenceImages) == 0 {
return body, "/images/generations", nil
}
@@ -761,6 +769,63 @@ func grokImageRequestBody(input canvasGenerationInput) (grokImageRequest, string
return body, "/images/edits", nil
}
// normalizeGrokImageResolution 把画布 quality(1k/2k/high…)映射为 grok2api / xAI 的 resolution。
func normalizeGrokImageResolution(quality string) string {
raw := strings.ToLower(strings.TrimSpace(quality))
switch raw {
case "", "auto":
return ""
case "1k", "low", "standard":
return "1k"
case "2k", "medium", "hd", "high", "4k":
// xAI Imagine 图片通常最高 2k;超出则夹到 2k,避免上游拒参。
return "2k"
default:
return ""
}
}
// normalizeGrokImageAspectRatio 把画布 size(如 1280x720 / 9:16)转成 grok2api / xAI 接受的 aspect_ratio。
func normalizeGrokImageAspectRatio(size string) string {
raw := strings.ToLower(strings.TrimSpace(strings.ReplaceAll(size, "×", "x")))
if raw == "" || raw == "auto" {
return ""
}
if strings.Contains(raw, ":") {
switch raw {
case "1:1", "3:4", "4:3", "9:16", "16:9", "2:3", "3:2", "9:19.5", "19.5:9", "1:2", "2:1":
return raw
}
}
parts := strings.Split(raw, "x")
if len(parts) != 2 {
return ""
}
w, wErr := strconv.Atoi(parts[0])
h, hErr := strconv.Atoi(parts[1])
if wErr != nil || hErr != nil || w <= 0 || h <= 0 {
return ""
}
if w == h {
return "1:1"
}
ratio := float64(w) / float64(h)
switch {
case w*9 == h*16 || (ratio >= 1.7 && ratio <= 1.8):
return "16:9"
case h*9 == w*16 || (ratio > 0 && ratio <= 1.0/1.7 && ratio >= 1.0/1.8):
return "9:16"
case w*3 == h*4 || (ratio > 1.2 && ratio < 1.4):
return "4:3"
case h*3 == w*4 || (ratio > 0.7 && ratio < 0.85):
return "3:4"
case w > h:
return "16:9"
default:
return "9:16"
}
}
func grokImageInputURL(media providerMedia) (string, error) {
if isPublicMediaURL(strings.TrimSpace(media.URL)) {
return strings.TrimSpace(media.URL), nil
@@ -58,6 +58,10 @@ type grokImageRequest struct {
Image *grokImageInput `json:"image,omitempty"`
N int `json:"n"`
ResponseFormat string `json:"response_format"`
Size string `json:"size,omitempty"`
AspectRatio string `json:"aspect_ratio,omitempty"`
// Resolution 对应 xAI / grok2api 的 resolution(常见 1k / 2k)。
Resolution string `json:"resolution,omitempty"`
}
type grokImageInput struct {
+34
View File
@@ -196,6 +196,40 @@ func TestRunGrokImageTaskUsesJSONEditContract(t *testing.T) {
}
}
func TestGrokImageRequestBodyMapsAspectRatio(t *testing.T) {
body, path, err := grokImageRequestBody(canvasGenerationInput{
Prompt: "a cat",
Config: providerConfig{Model: "grok-imagine-image", InterfaceType: "grok-image", Size: "9:16", Quality: "2k"},
})
if err != nil {
t.Fatalf("grokImageRequestBody() error = %v", err)
}
if path != "/images/generations" {
t.Fatalf("path = %q", path)
}
if body.AspectRatio != "9:16" || body.Size != "9:16" || body.Resolution != "2k" {
t.Fatalf("body = %#v", body)
}
if got := normalizeGrokImageAspectRatio("1280x720"); got != "16:9" {
t.Fatalf("normalize 1280x720 = %q", got)
}
if got := normalizeGrokImageAspectRatio("720x1280"); got != "9:16" {
t.Fatalf("normalize 720x1280 = %q", got)
}
}
func TestNormalizeGrokImageResolution(t *testing.T) {
if got := normalizeGrokImageResolution("1k"); got != "1k" {
t.Fatalf("1k = %q", got)
}
if got := normalizeGrokImageResolution("high"); got != "2k" {
t.Fatalf("high = %q", got)
}
if got := normalizeGrokImageResolution("auto"); got != "" {
t.Fatalf("auto = %q", got)
}
}
func TestGrokImageRequestBodyRejectsMaskAndMultipleReferences(t *testing.T) {
if _, _, err := grokImageRequestBody(canvasGenerationInput{Config: providerConfig{InterfaceType: "grok-image"}, Mask: &providerMedia{DataURL: testReferenceImageDataURL}}); err == nil || !strings.Contains(err.Error(), "不支持蒙版") {
t.Fatalf("mask error = %v", err)