diff --git a/backend/internal/http/handler/user_generation.go b/backend/internal/http/handler/user_generation.go index 1cad5ff..8201acd 100644 --- a/backend/internal/http/handler/user_generation.go +++ b/backend/internal/http/handler/user_generation.go @@ -70,7 +70,7 @@ func (h *UserGenerationHandler) Generate(c *gin.Context) { switch { case errors.Is(err, service.ErrUnknownModel): c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()}) - case errors.Is(err, service.ErrUnsupportedParams), errors.Is(err, service.ErrPromptTooLong): + case errors.Is(err, service.ErrUnsupportedParams): c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) case errors.Is(err, service.ErrInsufficientFunds): c.JSON(http.StatusPaymentRequired, gin.H{"detail": "积分不足"}) @@ -132,7 +132,7 @@ func (h *UserGenerationHandler) Test(c *gin.Context) { switch { case errors.Is(err, service.ErrUnknownModel): c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()}) - case errors.Is(err, service.ErrUnsupportedParams), errors.Is(err, service.ErrPromptTooLong): + case errors.Is(err, service.ErrUnsupportedParams): c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) case errors.Is(err, service.ErrProviderQuota): c.JSON(http.StatusTooManyRequests, gin.H{"detail": err.Error()}) @@ -356,17 +356,17 @@ func (h *UserGenerationHandler) Models(c *gin.Context) { func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error) { items := []gin.H{ { - "id": "gpt-image-2", - "provider": "chatgpt", - "type": "image", + "id": "gpt-image-2", + "provider": "chatgpt", + "type": "image", // ChatGPT web backend only reliably produces 1K and honors a limited // ratio set; size params are advisory prompt hints. Mirrors the Python // reference (providers/chatgpt/provider.py) — do not offer 2K/4K. - "ratios": []string{"1:1", "16:9", "9:16", "4:3", "3:4"}, - "resolutions": []string{"1K"}, - "image_to_image": true, + "ratios": []string{"1:1", "16:9", "9:16", "4:3", "3:4"}, + "resolutions": []string{"1K"}, + "image_to_image": true, "max_reference_images": 3, - "description": "ChatGPT image generation", + "description": "ChatGPT image generation", }, { "id": "firefly-gpt-image-2", @@ -521,9 +521,9 @@ func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error) func (h *UserGenerationHandler) publicModels() ([]gin.H, error) { items := []gin.H{ { - "id": "gpt-image-2", - "provider": "chatgpt", - "kind": "image", + "id": "gpt-image-2", + "provider": "chatgpt", + "kind": "image", // See catalogEntries — ChatGPT only reliably does 1K and a limited // ratio set; matches the Python reference. Keep both lists in sync. "ratios": []string{"1:1", "16:9", "9:16", "4:3", "3:4"}, diff --git a/backend/internal/http/handler/v1.go b/backend/internal/http/handler/v1.go index a5a61af..4bd0827 100644 --- a/backend/internal/http/handler/v1.go +++ b/backend/internal/http/handler/v1.go @@ -316,7 +316,7 @@ func (h *V1Handler) writeV1Error(c *gin.Context, err error, payload map[string]a switch { case errors.Is(err, service.ErrUnknownModel): c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()}) - case errors.Is(err, service.ErrUnsupportedParams), errors.Is(err, service.ErrPromptTooLong): + case errors.Is(err, service.ErrUnsupportedParams): c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) case errors.Is(err, service.ErrInsufficientFunds): c.JSON(http.StatusPaymentRequired, gin.H{"detail": err.Error()}) diff --git a/backend/internal/service/v1.go b/backend/internal/service/v1.go index bf8e0dc..dea0209 100644 --- a/backend/internal/service/v1.go +++ b/backend/internal/service/v1.go @@ -14,7 +14,6 @@ import ( "sync" "sync/atomic" "time" - "unicode/utf8" "strconv" @@ -38,7 +37,6 @@ var ( ErrInvalidAPIKey = errors.New("invalid api key") ErrUnknownModel = errors.New("unknown model") ErrUnsupportedParams = errors.New("unsupported or unpriced parameters for this model") - ErrPromptTooLong = errors.New("prompt too long (max 1500 characters)") ErrInsufficientFunds = errors.New("insufficient credits") ErrGenerationPending = errors.New("generation executor not implemented yet") ErrProviderAuth = errors.New("provider token invalid or expired") @@ -59,10 +57,6 @@ var ( ErrVideoNotReady = errors.New("video is not ready yet") ) -// maxPromptRunes caps prompt length (counted as characters, so Chinese = 1), -// matching the 画图台 counter. Enforced for both 画图台 and API-key calls. -const maxPromptRunes = 1500 - // maxReferenceImageBytes bounds a single decoded reference image. 8 MB // comfortably covers real photos/screenshots; anything larger is almost // certainly abuse or a mistake. Mirrors Python core/refs.py. @@ -88,7 +82,7 @@ type V1Service struct { // refresh re-mints an Adobe access token from its cookie when a request hits a // 401 mid-flight (set via SetRefresh — wired after construction to avoid an // init cycle). nil for deployments without cookie refresh. - refresh *RefreshProfileService + refresh *RefreshProfileService // tokenCursors holds one strict round-robin cursor per pool (key: pool name, // value: *uint64). Each pick advances the pool's cursor by one so accounts @@ -188,9 +182,9 @@ type APIPrincipal struct { } type V1ImageRequest struct { - Model string - Prompt string - Size string + Model string + Prompt string + Size string // Quality is OpenAI's image quality (low|medium|high|auto). For our tiered // models it selects the resolution (low→1K, medium→2K, high→4K, auto→default), // clamped to whatever tiers the model actually prices. Only used when @@ -945,9 +939,6 @@ func (s *V1Service) prepareImage(ctx context.Context, principal *APIPrincipal, i if modelID == "" || prompt == "" { return nil, "", "", 0, errors.New("model and prompt required") } - if utf8.RuneCountInString(prompt) > maxPromptRunes { - return nil, "", "", 0, ErrPromptTooLong - } modelItem, err := s.models.Get(ctx, modelID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -1011,9 +1002,6 @@ func (s *V1Service) prepareVideo(ctx context.Context, principal *APIPrincipal, i if modelID == "" || prompt == "" { return nil, "", "", "", 0, errors.New("model and prompt required") } - if utf8.RuneCountInString(prompt) > maxPromptRunes { - return nil, "", "", "", 0, ErrPromptTooLong - } if duration == "" { return nil, "", "", "", 0, errors.New("duration required") } @@ -1278,13 +1266,13 @@ const maxTempDeadAccounts = 3 // fresh token; if it still auth-fails (or there's nothing to refresh, e.g. // chatgpt's JWT IS the credential), mark the account and fail over. // - 上游临时 temporary → behavior depends on tempFailover: -// • tempFailover=false (default): retry the SAME account up to -// maxSameAccountAttempts times (not counted); if still failing, STOP -// (no fan-out — an upstream-wide blip fails identically everywhere). -// • tempFailover=true (adobe): fail over to the next account WITHOUT -// penalizing this one (rate-limit/overload isn't the account's fault), -// capped at maxTempDeadAccounts accounts so a pool-wide blip can't fan -// a single request out across everything. +// - tempFailover=false (default): retry the SAME account up to +// maxSameAccountAttempts times (not counted); if still failing, STOP +// (no fan-out — an upstream-wide blip fails identically everywhere). +// - tempFailover=true (adobe): fail over to the next account WITHOUT +// penalizing this one (rate-limit/overload isn't the account's fault), +// capped at maxTempDeadAccounts accounts so a pool-wide blip can't fan +// a single request out across everything. // - 参数错 / request-level (anything else) → return immediately, no retry, no // account penalty (the account isn't at fault). // diff --git a/frontend/src/views/PlaygroundView.vue b/frontend/src/views/PlaygroundView.vue index 5d14867..8c1b2d5 100644 --- a/frontend/src/views/PlaygroundView.vue +++ b/frontend/src/views/PlaygroundView.vue @@ -526,9 +526,9 @@ onUnmounted(() => {
- {{ prompt.length }}/1500 + {{ prompt.length }}
-