完善生图5.0的功能

This commit is contained in:
andy committed 2026-08-25 14:06:04 +08:00
1 parent 88a4268d40
commit 678d213f40
31 files changed
+3281 -107

No files matched your search

+12
View File
@@ -458,10 +458,22 @@ func writeJobError(w http.ResponseWriter, err error, public bool) {
message := "生成任务计费失败。"
if public {
message = "Generation job billing failed."
} else if errors.Is(err, billing.ErrInsufficientBalance) {
message = "余额不足,请先充值。"
} else if errors.Is(err, billing.ErrOrganizationUnavailable) {
message = "账号未绑定有效组织,请联系管理员。"
}
writeJSON(w, billingStatus.Status, map[string]string{"error": message})
return
}
if errors.Is(err, billing.ErrProviderUnavailable) {
message := "计费服务暂不可用,请稍后重试。"
if public {
message = "Generation billing is temporarily unavailable."
}
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": message})
return
}
writeJSON(w, 500, map[string]string{"error": "Internal server error."})
}
func randomJobID() string {
+12 -8
View File
@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@@ -231,7 +232,7 @@ func TestJobsHTTPMapsBillingChargeErrorsAndPublicUsesPublicCreation(t *testing.T
h, _ := NewJobsHandler(JobsDependencies{Service: jobs.NewService(store, time.Now), Platform: platform, Public: publicapi.NewAuthenticator(publicapi.Config{APIKeys: "agent-a:secret"}), Builder: &jobBuilderStub{}, Creation: creation}, JobsConfig{})
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/api/generations/image", bytes.NewBufferString(`{"prompt":"hello"}`)))
if w.Code != 402 || w.Body.String() != "{\"error\":\"生成任务计费失败。\"}\n" {
if w.Code != 402 || w.Body.String() != "{\"error\":\"余额不足,请先充值。\"}\n" {
t.Fatalf("platform=%d %s", w.Code, w.Body.String())
}
creation.err = nil
@@ -246,13 +247,16 @@ func TestJobsHTTPMapsBillingChargeErrorsAndPublicUsesPublicCreation(t *testing.T
func TestJobsHTTPMapsAllCreationBillingOutcomesWithoutLeakingErrors(t *testing.T) {
tests := []struct {
name string
err error
want int
name string
err error
want int
message string
}{
{name: "insufficient", err: &billing.StatusError{Status: 402, Err: billing.ErrInsufficientBalance}, want: 402},
{name: "idempotency", err: &billing.StatusError{Status: 409, Err: billing.ErrIdempotencyConflict}, want: 409},
{name: "unknown", err: errors.New("postgres password leaked"), want: 500},
{name: "insufficient", err: &billing.StatusError{Status: 402, Err: billing.ErrInsufficientBalance}, want: 402, message: "余额不足,请先充值。"},
{name: "organization", err: &billing.StatusError{Status: 409, Err: billing.ErrOrganizationUnavailable}, want: 409, message: "账号未绑定有效组织,请联系管理员。"},
{name: "idempotency", err: &billing.StatusError{Status: 409, Err: billing.ErrIdempotencyConflict}, want: 409, message: "生成任务计费失败。"},
{name: "unavailable", err: billing.ErrProviderUnavailable, want: 503, message: "计费服务暂不可用,请稍后重试。"},
{name: "unknown", err: errors.New("postgres password leaked"), want: 500, message: "Internal server error."},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -262,7 +266,7 @@ func TestJobsHTTPMapsAllCreationBillingOutcomesWithoutLeakingErrors(t *testing.T
h, _ := NewJobsHandler(JobsDependencies{Service: jobs.NewService(store, time.Now), Platform: platform, Public: publicapi.NewAuthenticator(publicapi.Config{APIKeys: "agent-a:secret"}), Builder: &jobBuilderStub{}, Creation: creation}, JobsConfig{})
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/api/generations/image", bytes.NewBufferString(`{"prompt":"hello"}`)))
if w.Code != test.want || strings.Contains(w.Body.String(), "postgres") {
if w.Code != test.want || w.Body.String() != fmt.Sprintf("{\"error\":%q}\n", test.message) || strings.Contains(w.Body.String(), "postgres") {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
})
+9 -7
View File
@@ -96,7 +96,7 @@ func openAPISchemas() map[string]any {
"capability": openAPIRef("GenerationCapability"),
"engine": map[string]any{"type": "string", "enum": []string{"jimeng", "seedream", "evolink", "bailian", "seedance"}},
"model": map[string]any{"type": "string", "enum": []string{"doubao-seedance-2-0-260128", "doubao-seedance-2-5-260628"}, "description": "Optional allow-listed Seedance model for video.generate."},
"prompt": map[string]any{"type": "string", "description": "Prompt text. Required for image.generate unless promptAssembly is supplied."},
"prompt": map[string]any{"type": "string", "description": "Prompt text. Required for generation except Seedream layer decomposition, where an empty prompt triggers automatic decomposition."},
"inputUrls": map[string]any{"type": "array", "items": map[string]any{"type": "string", "format": "uri"}, "description": "Reference image URLs for image capabilities."},
"imageUrls": map[string]any{"type": "array", "items": map[string]any{"type": "string", "format": "uri"}, "description": "Alias for image input URLs."},
"inputAssetIds": openAPIStringArray(),
@@ -104,12 +104,14 @@ func openAPISchemas() map[string]any {
"settings": map[string]any{
"type": "object", "description": "Provider-specific image or video settings.",
"properties": map[string]any{
"ratio": map[string]any{"type": "string", "enum": []string{"16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"}},
"duration": map[string]any{"type": "integer", "minimum": 4, "maximum": 30, "description": "Seedance 2.0 supports up to 15 seconds; Seedance 2.5 supports up to 30 seconds."},
"resolution": map[string]any{"type": "string", "enum": []string{"480p", "720p", "1080p"}},
"size": map[string]any{"type": "string", "enum": []string{"1K", "1.5K", "2K"}, "description": "Seedream 5.0 Pro output size."},
"outputFormat": map[string]any{"type": "string", "enum": []string{"png", "jpeg"}},
"optimizeMode": map[string]any{"type": "string", "enum": []string{"standard", "fast"}},
"ratio": map[string]any{"type": "string", "enum": []string{"16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"}},
"duration": map[string]any{"type": "integer", "minimum": 4, "maximum": 30, "description": "Seedance 2.0 supports up to 15 seconds; Seedance 2.5 supports up to 30 seconds."},
"resolution": map[string]any{"type": "string", "enum": []string{"480p", "720p", "1080p"}},
"size": map[string]any{"type": "string", "enum": []string{"auto", "1K", "1.5K", "2K"}, "description": "Seedream 5.0 Pro output size. auto is available for layer decomposition only."},
"outputFormat": map[string]any{"type": "string", "enum": []string{"png", "jpeg"}},
"optimizeMode": map[string]any{"type": "string", "enum": []string{"standard", "fast"}},
"creationMode": map[string]any{"type": "string", "enum": []string{"basic", "interactive", "layers"}, "description": "Seedream 5.0 Pro image workflow."},
"layerDecomposition": map[string]any{"type": "boolean", "description": "Enables Seedream 5.0 Pro layer decomposition; exactly one image is required."},
},
},
"width": map[string]any{"type": "integer", "example": 1440},