Skip to content

Codex/fix grok video url - #3489

Closed
LJR199887 wants to merge 42 commits into
QuantumNous:mainfrom
LJR199887:codex/fix-grok-video-url
Closed

Codex/fix grok video url#3489
LJR199887 wants to merge 42 commits into
QuantumNous:mainfrom
LJR199887:codex/fix-grok-video-url

Conversation

@LJR199887

@LJR199887 LJR199887 commented Mar 29, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added image-edit and OpenAI video endpoints, new playground image/video routes, a Creation Center and explicit chat/image/video playground modes; per-duration (seconds-based) pricing support.
  • UI Improvements

    • Enhanced playground/settings with media controls (size, aspect ratio, resolution, duration, quality, presets); model test/prefill now include image-edit/video; task logs show model names.
  • Enhancements

    • Improved task-result parsing to reflect upstream status/progress and result URLs; routing and payloads now route media models to dedicated endpoints.
  • Tests

    • Added unit tests for routing, adaptors, pricing, and media flows.

Add video parameters (size/seconds/quality) to UI, payload and DTO
Add video generation support: UI controls, payload handling, and DTO fields
Playground: add video generation support and refactor token context setup
Add playground video generation endpoints and UI; wire video parameters through frontend and backend
Revert "Add playground video generation endpoints and UI; wire video parameters through frontend and backend"
@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds image-edit and OpenAI video endpoints and model classification, playground routes/controllers, relay adaptors and task-result parsing, per-seconds pricing and admin UI, frontend playground mode controls and payload normalization, plus related tests and i18n entries.

Changes

Cohort / File(s) Summary
Endpoint Types & Defaults
constant/endpoint_type.go, common/endpoint_defaults.go, common/endpoint_type.go, common/endpoint_type_test.go
Added image-edit endpoint type and default /v1/images/edits; added default /v1/video/generations; adjusted endpoint selection to prefer image-edit for edit models and added tests.
Model Classification
common/model.go
Added ImageEditModels and IsImageEditModel; expanded IsImageGenerationModel to handle exact: entries, substring and prefix: matches.
Playground Controllers & Routes
controller/playground.go, router/relay-router.go
Introduced playground token setup helper and added /pg routes/endpoints for image generations, image edits, video submit and video fetch.
Relay Task Submission & Parsing
controller/relay.go, relay/relay_task.go, relay/relay_task_test.go
Parse task results via platform adaptor to set status/progress/timestamps/URL/reason; added quota calc helper integrating seconds-based pricing and accompanying tests.
Seconds-Based Pricing Core
setting/ratio_setting/model_ratio.go, setting/ratio_setting/model_price_by_seconds_test.go, model/option.go, model/pricing.go, relay/helper/price.go, types/price_data.go
Added per-model seconds pricing storage/APIs (serialize/update/get/min/copy), integrated into options and pricing output, fallback lookup in per-call pricing helper, and added BaseQuota to PriceData.
Sora & xAI Adaptors + Tests
relay/channel/task/sora/adaptor.go, relay/channel/task/sora/adaptor_test.go, relay/channel/xai/adaptor.go, relay/channel/xai/adaptor_test.go, relay/channel/xai/constants.go, relay/channel/xai/dto.go
Sora: normalize Grok video request fields, extract video URL(s) and populate video_config; xAI: propagate image/size in image requests and add Grok imagine variants; added tests.
Routing, Middleware & Utils
middleware/distributor.go, relay/constant/relay_mode.go, relay/constant/relay_mode_test.go, relay/common/relay_utils.go
Enabled group parsing for any /pg/ path, mapped /pg/images/* to image relay modes, added known task fields (seconds,quality,resolution_name,preset), and added route mapping tests.
DTOs & Request Structs
dto/openai_image.go, dto/openai_request.go, relay/common/relay_info.go
Added aspect_ratio and output_resolution to image DTOs; extended general OpenAI request with media fields and added quality,resolution_name,preset to TaskSubmitReq.
Frontend Playground & Hooks
web/src/components/playground/SettingsPanel.jsx, web/src/helpers/api.js, web/src/hooks/playground/useApiRequest.jsx, web/src/constants/playground.constants.js, web/src/helpers/playgroundMode.js, web/src/hooks/playground/usePlaygroundState.js, web/src/components/playground/PlaygroundCreationCenter.jsx, web/src/components/playground/configStorage.js
Added playground modes (chat/image/video), creation center UI, model-mode helpers, payload normalization for Grok/Adobe models, mode-aware settings UI, endpoint resolution (image/edit/video), and non-stream handling for media flows.
Frontend Admin: Duration Pricing UI
web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx, web/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js, web/src/pages/Setting/Ratio/ModelRatioSettings.jsx, web/src/components/settings/RatioSetting.jsx
Added duration-price editor/state (ModelPriceBySeconds/durationPrices), per-duration billing mode, normalization/validation, and handlers (handleDurationPriceChange, addDurationPrice, removeDurationPrice).
Frontend Models & Misc UI
web/src/components/table/models/modals/EditModelModal.jsx, web/src/components/table/models/modals/EditPrefillGroupModal.jsx, web/src/components/table/channels/modals/ModelTestModal.jsx, web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx, web/src/components/table/task-logs/TaskLogsColumnDefs.jsx, web/src/components/table/model-pricing/view/card/PricingCardView.jsx
Added image-edit and openai-video options/templates, updated stream-toggle logic, rendered seconds-based pricing in tables/cards, and added model extraction/rendering in task logs.
Frontend Large Changes & Memoization
web/src/components/playground/SettingsPanel.jsx, web/src/components/playground/OptimizedComponents.js, web/src/components/playground/ChatArea.jsx, web/src/components/playground/PlaygroundCreationCenter.jsx, web/src/hooks/playground/useApiRequest.jsx
UI and memoization updates to support playground modes, new props (title/subtitle/placeholder), and optimized re-render gating for mode changes.
i18n & Locales
web/src/i18n/locales/* (en.json,fr.json,ja.json,ru.json,vi.json,zh-CN.json,zh-TW.json)
Added translation entries for the Creation Center and playground modes across multiple locales.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant PlaygroundCtrl as Playground Controller
    participant TokenMgr as Token Context Manager
    participant RelayHandler as Relay Handler
    participant Adaptor as Platform Adaptor
    participant Upstream as Upstream Platform

    Client->>PlaygroundCtrl: POST /pg/images/edits (model, image, params)
    PlaygroundCtrl->>TokenMgr: setupPlaygroundTokenContext(name, group)
    TokenMgr-->>PlaygroundCtrl: context set
    PlaygroundCtrl->>RelayHandler: Relay(..., RelayFormatOpenAIImage)
    RelayHandler->>Adaptor: ConvertImageRequest(request)
    Adaptor->>Upstream: POST /v1/images/edits (normalized payload)
    Upstream-->>Adaptor: response (image URLs)
    Adaptor-->>RelayHandler: parsed response (urls)
    RelayHandler-->>Client: 200 OK (image URLs)
Loading
sequenceDiagram
    participant TaskSubmit as Task Submit Handler
    participant Adaptor as Platform Adaptor
    participant RatioSetting as Ratio Settings
    participant PricingMgr as Pricing/Quota Manager
    participant TaskDB as Task Database

    TaskSubmit->>Adaptor: ParseTaskResult(result.TaskData)
    Adaptor-->>TaskSubmit: taskInfo (status,url,reason,progress)
    TaskSubmit->>TaskSubmit: map status, set times, set ResultURL/FailReason
    TaskSubmit->>PricingMgr: calcTaskQuotaWithRatios(info, ratios)
    PricingMgr->>RatioSetting: GetModelPriceBySeconds(model, seconds)
    RatioSetting-->>PricingMgr: (price, found)
    PricingMgr->>TaskDB: Insert(task with computed quota)
    TaskDB-->>TaskSubmit: Insert OK
    TaskSubmit-->>Client: Task response (with URL/status)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • Calcium-Ion

"🐰
I hopped through code with floppy ears,
Added edits and videos and priced the years.
Grok paints frames while seconds tick the clock,
Playgrounds hum and tasks race round the block.
Hop deploy, then nap — I earned a carrot snack!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Codex/fix grok video url' refers to fixing a Grok video URL issue, but the changeset primarily adds a comprehensive playground creation center feature with image/video generation support, pricing adjustments, and extensive UI components—far beyond a simple URL fix. Revise the title to reflect the actual scope: consider 'Add playground creation center with image/video generation and pricing enhancements' or similar to accurately describe the substantial feature additions and architectural changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (1)

100-107: ⚠️ Potential issue | 🟡 Minor

Handle the new seconds-based quota_type in the billing label.

Rows with model_price_by_seconds now come through with quota_type = 2, but this branch still falls through to '-', so the table shows those models as an unknown billing type.

🔧 Proposed fix
         billingType:
           modelData?.quota_type === 0
             ? t('按量计费')
             : modelData?.quota_type === 1
               ? t('按次计费')
+              : modelData?.quota_type === 2
+                ? t('按时长固定价格')
               : '-',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
around lines 100 - 107, billingType currently maps quota_type 0 and 1 but falls
through to '-' for any other value; update the billingType logic in the block
that builds the row (the billingType expression referencing
modelData?.quota_type) to handle quota_type === 2 (models with
model_price_by_seconds) and return the appropriate localized seconds-based label
(use t('...') consistent with other labels), keeping the existing branches for 0
and 1 and leaving '-' only for truly unknown values; ensure this aligns with
secondsPriceItems/getSecondsPriceItems usage so seconds-priced models display
correctly.
🧹 Nitpick comments (5)
relay/channel/task/sora/adaptor_test.go (1)

5-47: Please add a regression around the URL field too.

These cases cover request normalization well, but the PR is about a Grok video URL fix. A small response-shape test for the field that becomes result_url would protect the original regression more directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/task/sora/adaptor_test.go` around lines 5 - 47, Add a
regression test that covers the URL -> result_url normalization: create a new
test (e.g., TestNormalizeGrokVideoRequestBackfillsResultURL) that builds a body
with "model": "grok-imagine-1.0-video" and an original "url" value, calls
normalizeGrokVideoRequest(body, "grok-imagine-1.0-video"), and asserts the
normalized body contains "result_url" equal to the original URL (and optionally
that any nested video_config also contains the same "result_url"); reference
normalizeGrokVideoRequest and the "url" / "result_url" field names when locating
where to add the test.
types/price_data.go (1)

25-27: Consider logging BaseQuota in ToSetting().

BaseQuota is now central to task quota recomputation, but current debug serialization omits it. Including it would improve troubleshooting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@types/price_data.go` around lines 25 - 27, The debug serialization in the
PriceData struct's ToSetting() method omits the BaseQuota field; update
ToSetting() to include BaseQuota in the returned/logged representation so
debugging and quota recomputation can see it (refer to the ToSetting() method
and the BaseQuota, Quota, QuotaToPreConsume fields on the struct) — add
BaseQuota to the output map/struct/string the method produces and ensure any
formatting/logging code that consumes ToSetting() will display the new field.
web/src/pages/Setting/Ratio/ModelRatioSettings.jsx (1)

167-192: Avoid validating a permanently hidden field.

This TextArea is rendered with display: 'none' but still validated/submitted. If this field ever contains invalid persisted JSON, users can’t correct it from UI while form submit still fails. Consider conditionally rendering it only when editable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Ratio/ModelRatioSettings.jsx` around lines 167 - 192,
The hidden Form.TextArea for ModelPriceBySeconds is still validated on submit
which blocks form submissions if persisted JSON is invalid; change the rendering
so the field is conditionally rendered only when editable/visible (instead of
using style={{display: 'none'}}), or skip its validation when hidden by removing
the rules/trigger for the hidden state; update the JSX that uses Form.TextArea
(and related state setInputs/inputs and verifyJSON validator) to render the
control only when editable (or toggle rules based on visibility) so users can’t
be blocked by an uneditable hidden field.
web/src/helpers/api.js (2)

118-146: Consider extracting shared model constants and helpers to reduce duplication.

The normalizeGrokImageSize function and model set definitions (grokImagineImageModels, adobeImageModels, adobeVideoModels) are duplicated across multiple files:

  • web/src/helpers/api.js
  • web/src/hooks/playground/useApiRequest.jsx
  • web/src/components/playground/SettingsPanel.jsx

Consider extracting these to a shared constants/utils file (e.g., web/src/constants/models.constants.js) to maintain a single source of truth.

♻️ Suggested extraction
// web/src/constants/models.constants.js
export const GROK_IMAGINE_IMAGE_MODELS = new Set([
  'grok-imagine-1.0',
  'grok-imagine-1.0-fast',
  'grok-imagine-1.0-edit',
]);

export const ADOBE_IMAGE_MODELS = new Set([
  'nano-banana',
  'nano-banana-4k',
  // ...
]);

export const ADOBE_VIDEO_MODELS = new Set([
  'sora2',
  'sora2-pro',
  'veo31',
  'veo31-ref',
  'veo31-fast',
]);

export const normalizeGrokImageSize = (size) => {
  if (size === '1536x1024') return '1792x1024';
  if (size === '1024x1536') return '1024x1792';
  return size;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/helpers/api.js` around lines 118 - 146, Extract the duplicated model
sets and helper into a shared constants module and import it where needed:
create a new file (e.g., models.constants.js) that exports
GROK_IMAGINE_IMAGE_MODELS (the Set currently named grokImagineImageModels),
ADOBE_IMAGE_MODELS (adobeImageModels), ADOBE_VIDEO_MODELS (adobeVideoModels) and
normalizeGrokImageSize (the function currently named normalizeGrokImageSize);
then replace the local definitions in web/src/helpers/api.js,
web/src/hooks/playground/useApiRequest.jsx and
web/src/components/playground/SettingsPanel.jsx with imports from that new
module to ensure a single source of truth.

231-247: Video quality normalization has redundant back-and-forth mapping.

The quality value is mapped from high/standard720p/480p, then immediately mapped back to high/standard for the payload. This round-trip is confusing.

const resolutionName =
  inputs.videoQuality === 'high'
    ? '720p'
    : inputs.videoQuality === 'standard'
      ? '480p'
      : inputs.videoQuality;
payload.quality =
  resolutionName === '720p'
    ? 'high'
    : resolutionName === '480p'
      ? 'standard'
      : resolutionName;

If inputs.videoQuality is already 'high', then payload.quality ends up as 'high' after unnecessary intermediate conversion.

♻️ Simplified approach
-      if (inputs.videoQuality) {
-        const resolutionName =
-          inputs.videoQuality === 'high'
-            ? '720p'
-            : inputs.videoQuality === 'standard'
-              ? '480p'
-              : inputs.videoQuality;
-        payload.quality =
-          resolutionName === '720p'
-            ? 'high'
-            : resolutionName === '480p'
-              ? 'standard'
-              : resolutionName;
+      if (inputs.videoQuality) {
+        // Normalize to API expected values
+        const qualityMap = { '720p': 'high', '480p': 'standard' };
+        const resolutionMap = { 'high': '720p', 'standard': '480p' };
+        
+        payload.quality = qualityMap[inputs.videoQuality] || inputs.videoQuality;
+        const resolutionName = resolutionMap[inputs.videoQuality] || inputs.videoQuality;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/helpers/api.js` around lines 231 - 247, The current round-trip
mapping between inputs.videoQuality and resolutionName is redundant; instead,
set payload.quality directly from inputs.videoQuality normalized to the
canonical quality tokens and only derive payload.resolution_name for grok
models. Specifically, in the block using inputs.videoQuality, compute a single
normalized quality value for payload.quality (map '720p'->'high',
'480p'->'standard', otherwise pass through 'high'/'standard'/custom as-is) and
if isGrokImagineVideoModel set payload.resolution_name to the resolution token
('720p' for 'high', '480p' for 'standard', or inputs.videoQuality if already a
resolution); remove the intermediate resolutionName variable and the
back-and-forth mapping so payload.quality and payload.resolution_name are set
deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/playground.go`:
- Around line 45-103: The new handlers PlaygroundImageGenerations,
PlaygroundImageEdits, and PlaygroundVideoFetch skip the same access-token
rejection that the existing Playground handler enforces; after calling
setupPlaygroundTokenContext in each of these functions, add the identical guard
from the Playground handler that checks for use_access_token and sets/returns
the same *types.NewAPIError when access tokens are present, before calling Relay
or RelayTaskFetch, so the policy is consistent across /pg endpoints.

In `@dto/openai_image.go`:
- Around line 19-20: The AspectRatio and OutputResolution fields on
dto.ImageRequest should be changed from value strings to pointer types (e.g.,
*string) so that omitempty preserves explicit empty values when re-marshaling
for relays; update the struct fields AspectRatio and OutputResolution to use
pointer-typed strings and then adjust any construction sites that set
ImageRequest fields to take addresses (or nil) accordingly while keeping the
same json tags.

In `@dto/openai_request.go`:
- Around line 49-55: The struct fields on dto.GeneralOpenAIRequest using plain
string types (AspectRatio, OutputResolution, Resolution, ReferenceMode) must be
changed to *string so explicit empty string values from client JSON are
preserved when re-marshaling; update AspectRatio, OutputResolution, Resolution,
and ReferenceMode to pointer-to-string types (keep `json:"...,omitempty"` tags)
and then audit places constructing or reading those fields to handle nil vs
non-nil appropriately (e.g., check for nil before dereferencing or use helper to
convert to string).

In `@relay/channel/task/sora/adaptor.go`:
- Around line 129-163: The code currently reconstructs bodyMap["video_config"]
from scratch using resolutionName/preset, dropping any other client-supplied
keys; instead, read the existing videoConfig map (if any) and mutate it: use the
same extraction used earlier (bodyMap["video_config"].(map[string]interface{}))
or create a new map if nil, then set/override only "resolution_name" and
"preset" based on resolutionName and preset variables, and finally assign that
merged map back to bodyMap["video_config"]; update the block that builds
videoConfig so it preserves other nested options and only updates the fields you
need (refer to bodyMap, video_config, resolutionName, preset,
stringifyBodyValue).
- Around line 166-181: extractVideoURL currently returns any non-empty string
from gjson paths, which can propagate large inline base64 "data:" URLs into
DoResponse and ParseTaskResult; update extractVideoURL to ignore values that
start with "data:" by checking the trimmed URL with strings.HasPrefix(url,
"data:") (case-sensitive check is fine) and continue searching for the next path
if a data: URL is found, so only real external URLs are returned to callers like
DoResponse and ParseTaskResult.

In `@relay/common/relay_utils.go`:
- Around line 193-196: The three fields ("quality", "resolution_name", "preset")
were mistakenly pre-allowlisted in the metadata allowlist which prevents them
from being hydrated and then forwarded; remove these keys from the allowlist map
literal so they are treated as unknown (hydrated) fields, and ensure
validateMultipartTaskRequest continues to copy hydrated values from Metadata
into TaskSubmitReq (referencing validateMultipartTaskRequest, Metadata, and
TaskSubmitReq) so Grok video options are included in the upstream request.

In `@web/src/components/playground/SettingsPanel.jsx`:
- Around line 93-99: The imageSizeOptions array in SettingsPanel.jsx uses
hardcoded Chinese labels; update it to use the i18n hook by importing and
calling useTranslation() in the component, then replace each label string with
t('<key>') (e.g., t('image.size.square'), t('image.size.landscape_3_2'), etc.),
create corresponding translation keys in your locale files, and ensure the
imageSizeOptions constant (and any place that reads its label) uses the t()
values so labels are translated at render time.
- Line 337: In SettingsPanel.jsx, replace hardcoded English labels with i18n
calls: import and use the useTranslation() hook in the SettingsPanel component
(or ensure the existing hook is used) and wrap the string labels like "Aspect
Ratio" (both occurrences), "Auto Size", "Output Resolution", "Duration",
"Resolution", and "Reference Mode" with t('...') calls (use appropriate
translation keys matching your project's key naming), e.g., t('aspect_ratio')
etc.; update the JSX where these labels appear so each label uses t(...) instead
of a raw string, and ensure the component has const { t } = useTranslation()
available before usage.

In `@web/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js`:
- Around line 68-80: normalizeDurationPrices currently only trims the seconds
key but doesn't verify it's numeric, so update normalizeDurationPrices to
validate each seconds key against DURATION_SECONDS_REGEX (or an equivalent
numeric-seconds regex) and skip entries whose normalizedSeconds do not match;
likewise add the same validation in addDurationPrice and
handleDurationPriceChange before mutating durationPrices so keys like "30s" or
"abc" are rejected and only valid numeric-second keys are written back to
ModelPriceBySeconds.
- Around line 143-148: The billingMode determination treats any key in
durationPrices as "per-duration" even when serializeModel will strip out
blank/invalid entries; update the checks that set billingMode (the billingMode
assignment near durationPrices and the similar logic at the other locations
flagged) to verify there is at least one valid duration price before returning
'per-duration' — e.g., reuse or mirror the validation logic used by
serializeModel (filter out blank/invalid prices) to test durationPrices for any
remaining valid entries; reference the durationPrices variable, billingMode
assignment, serializeModel, and buildModelState so the saved config round-trips
correctly.

In `@web/src/pages/Setting/Ratio/ModelRatioSettings.jsx`:
- Around line 183-185: The validator's user-facing error message is not
internationalized; update the validation rule in ModelRatioSettings.jsx to use
the i18n t() call from useTranslation: import/use the useTranslation() hook in
the component, call const { t } = useTranslation(), and replace the literal
message '不是合法的 JSON 字符串' with t('你的.i18n.key.for.invalid_json') (or the
appropriate key), keeping the verifyJSON(rule, value) usage intact so the
validator function still calls verifyJSON.

---

Outside diff comments:
In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`:
- Around line 100-107: billingType currently maps quota_type 0 and 1 but falls
through to '-' for any other value; update the billingType logic in the block
that builds the row (the billingType expression referencing
modelData?.quota_type) to handle quota_type === 2 (models with
model_price_by_seconds) and return the appropriate localized seconds-based label
(use t('...') consistent with other labels), keeping the existing branches for 0
and 1 and leaving '-' only for truly unknown values; ensure this aligns with
secondsPriceItems/getSecondsPriceItems usage so seconds-priced models display
correctly.

---

Nitpick comments:
In `@relay/channel/task/sora/adaptor_test.go`:
- Around line 5-47: Add a regression test that covers the URL -> result_url
normalization: create a new test (e.g.,
TestNormalizeGrokVideoRequestBackfillsResultURL) that builds a body with
"model": "grok-imagine-1.0-video" and an original "url" value, calls
normalizeGrokVideoRequest(body, "grok-imagine-1.0-video"), and asserts the
normalized body contains "result_url" equal to the original URL (and optionally
that any nested video_config also contains the same "result_url"); reference
normalizeGrokVideoRequest and the "url" / "result_url" field names when locating
where to add the test.

In `@types/price_data.go`:
- Around line 25-27: The debug serialization in the PriceData struct's
ToSetting() method omits the BaseQuota field; update ToSetting() to include
BaseQuota in the returned/logged representation so debugging and quota
recomputation can see it (refer to the ToSetting() method and the BaseQuota,
Quota, QuotaToPreConsume fields on the struct) — add BaseQuota to the output
map/struct/string the method produces and ensure any formatting/logging code
that consumes ToSetting() will display the new field.

In `@web/src/helpers/api.js`:
- Around line 118-146: Extract the duplicated model sets and helper into a
shared constants module and import it where needed: create a new file (e.g.,
models.constants.js) that exports GROK_IMAGINE_IMAGE_MODELS (the Set currently
named grokImagineImageModels), ADOBE_IMAGE_MODELS (adobeImageModels),
ADOBE_VIDEO_MODELS (adobeVideoModels) and normalizeGrokImageSize (the function
currently named normalizeGrokImageSize); then replace the local definitions in
web/src/helpers/api.js, web/src/hooks/playground/useApiRequest.jsx and
web/src/components/playground/SettingsPanel.jsx with imports from that new
module to ensure a single source of truth.
- Around line 231-247: The current round-trip mapping between
inputs.videoQuality and resolutionName is redundant; instead, set
payload.quality directly from inputs.videoQuality normalized to the canonical
quality tokens and only derive payload.resolution_name for grok models.
Specifically, in the block using inputs.videoQuality, compute a single
normalized quality value for payload.quality (map '720p'->'high',
'480p'->'standard', otherwise pass through 'high'/'standard'/custom as-is) and
if isGrokImagineVideoModel set payload.resolution_name to the resolution token
('720p' for 'high', '480p' for 'standard', or inputs.videoQuality if already a
resolution); remove the intermediate resolutionName variable and the
back-and-forth mapping so payload.quality and payload.resolution_name are set
deterministically.

In `@web/src/pages/Setting/Ratio/ModelRatioSettings.jsx`:
- Around line 167-192: The hidden Form.TextArea for ModelPriceBySeconds is still
validated on submit which blocks form submissions if persisted JSON is invalid;
change the rendering so the field is conditionally rendered only when
editable/visible (instead of using style={{display: 'none'}}), or skip its
validation when hidden by removing the rules/trigger for the hidden state;
update the JSX that uses Form.TextArea (and related state setInputs/inputs and
verifyJSON validator) to render the control only when editable (or toggle rules
based on visibility) so users can’t be blocked by an uneditable hidden field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11189672-d6ed-4b2d-b092-ad631bbe37f0

📥 Commits

Reviewing files that changed from the base of the PR and between fbf235d and 0e364e5.

📒 Files selected for processing (42)
  • common/endpoint_defaults.go
  • common/endpoint_type.go
  • common/endpoint_type_test.go
  • common/model.go
  • constant/endpoint_type.go
  • controller/playground.go
  • controller/relay.go
  • dto/openai_image.go
  • dto/openai_request.go
  • middleware/distributor.go
  • model/option.go
  • model/pricing.go
  • relay/channel/task/sora/adaptor.go
  • relay/channel/task/sora/adaptor_test.go
  • relay/channel/xai/adaptor.go
  • relay/channel/xai/adaptor_test.go
  • relay/channel/xai/constants.go
  • relay/channel/xai/dto.go
  • relay/common/relay_info.go
  • relay/common/relay_utils.go
  • relay/constant/relay_mode.go
  • relay/constant/relay_mode_test.go
  • relay/helper/price.go
  • relay/relay_task.go
  • relay/relay_task_test.go
  • router/relay-router.go
  • setting/ratio_setting/model_price_by_seconds_test.go
  • setting/ratio_setting/model_ratio.go
  • types/price_data.go
  • web/src/components/playground/SettingsPanel.jsx
  • web/src/components/settings/RatioSetting.jsx
  • web/src/components/table/channels/modals/ModelTestModal.jsx
  • web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx
  • web/src/components/table/models/modals/EditModelModal.jsx
  • web/src/components/table/models/modals/EditPrefillGroupModal.jsx
  • web/src/components/table/task-logs/TaskLogsColumnDefs.jsx
  • web/src/constants/playground.constants.js
  • web/src/helpers/api.js
  • web/src/hooks/playground/useApiRequest.jsx
  • web/src/pages/Setting/Ratio/ModelRatioSettings.jsx
  • web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx
  • web/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js

Comment thread controller/playground.go
Comment on lines +45 to +103
func PlaygroundVideoSubmit(c *gin.Context) {
var newAPIError *types.NewAPIError
defer func() {
if newAPIError != nil {
c.JSON(newAPIError.StatusCode, gin.H{
"error": newAPIError.ToOpenAIError(),
})
}
}()
if newAPIError = setupPlaygroundTokenContext(c, "playground-video", c.GetString("group")); newAPIError != nil {
return
}
RelayTask(c)
}

func PlaygroundImageGenerations(c *gin.Context) {
var newAPIError *types.NewAPIError
defer func() {
if newAPIError != nil {
c.JSON(newAPIError.StatusCode, gin.H{
"error": newAPIError.ToOpenAIError(),
})
}
}()
if newAPIError = setupPlaygroundTokenContext(c, "playground-image", c.GetString("group")); newAPIError != nil {
return
}
Relay(c, types.RelayFormatOpenAIImage)
}

func PlaygroundImageEdits(c *gin.Context) {
var newAPIError *types.NewAPIError
defer func() {
if newAPIError != nil {
c.JSON(newAPIError.StatusCode, gin.H{
"error": newAPIError.ToOpenAIError(),
})
}
}()
if newAPIError = setupPlaygroundTokenContext(c, "playground-image-edit", c.GetString("group")); newAPIError != nil {
return
}
Relay(c, types.RelayFormatOpenAIImage)
}

func PlaygroundVideoFetch(c *gin.Context) {
var newAPIError *types.NewAPIError
defer func() {
if newAPIError != nil {
c.JSON(newAPIError.StatusCode, gin.H{
"error": newAPIError.ToOpenAIError(),
})
}
}()
if newAPIError = setupPlaygroundTokenContext(c, "playground-video-fetch", c.GetString("group")); newAPIError != nil {
return
}
RelayTaskFetch(c)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the access-token restriction consistent on the new /pg handlers.

Playground still rejects use_access_token, but these new image/video handlers skip that check and relay immediately after token setup. That opens a policy gap between the existing playground endpoint and /pg/images/* / /pg/video/*.

🛠️ Proposed fix
+func rejectPlaygroundAccessToken(c *gin.Context) *types.NewAPIError {
+	if c.GetBool("use_access_token") {
+		return types.NewError(errors.New("暂不支持使用 access token"), types.ErrorCodeAccessDenied, types.ErrOptionWithSkipRetry())
+	}
+	return nil
+}
+
 func PlaygroundVideoSubmit(c *gin.Context) {
 	var newAPIError *types.NewAPIError
 	defer func() {
 		if newAPIError != nil {
 			c.JSON(newAPIError.StatusCode, gin.H{
 				"error": newAPIError.ToOpenAIError(),
 			})
 		}
 	}()
+	if newAPIError = rejectPlaygroundAccessToken(c); newAPIError != nil {
+		return
+	}
 	if newAPIError = setupPlaygroundTokenContext(c, "playground-video", c.GetString("group")); newAPIError != nil {
 		return
 	}
 	RelayTask(c)
 }

Apply the same guard in PlaygroundImageGenerations, PlaygroundImageEdits, and PlaygroundVideoFetch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/playground.go` around lines 45 - 103, The new handlers
PlaygroundImageGenerations, PlaygroundImageEdits, and PlaygroundVideoFetch skip
the same access-token rejection that the existing Playground handler enforces;
after calling setupPlaygroundTokenContext in each of these functions, add the
identical guard from the Playground handler that checks for use_access_token and
sets/returns the same *types.NewAPIError when access tokens are present, before
calling Relay or RelayTaskFetch, so the policy is consistent across /pg
endpoints.

Comment thread dto/openai_image.go
Comment on lines +19 to +20
AspectRatio string `json:"aspect_ratio,omitempty"`
OutputResolution string `json:"output_resolution,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the actual content of dto/openai_image.go around lines 19-20
sed -n '1,30p' dto/openai_image.go

Repository: QuantumNous/new-api

Length of output: 1296


🏁 Script executed:

# Search for ImageRequest usage in relay/convert paths
rg -A 3 -B 3 "ImageRequest" relay/ --type go | head -100

Repository: QuantumNous/new-api

Length of output: 5864


🏁 Script executed:

# Check if there are similar pointer-typed string fields in other relay DTOs
rg '\*string.*json:.*omitempty' dto/ --type go | head -20

Repository: QuantumNous/new-api

Length of output: 1056


🏁 Script executed:

# Verify whether ImageRequest is marshaled/unmarshaled and where
rg "ImageRequest" . --type go | grep -E "(Marshal|Unmarshal|convert)" | head -20

Repository: QuantumNous/new-api

Length of output: 985


Make the new optional request fields pointer-typed.

dto.ImageRequest is re-marshaled for upstream providers in relay paths, so string + omitempty on aspect_ratio and output_resolution will drop explicitly empty values the client sent. Use pointer types instead.

Proposed fix
-	AspectRatio       string          `json:"aspect_ratio,omitempty"`
-	OutputResolution  string          `json:"output_resolution,omitempty"`
+	AspectRatio       *string         `json:"aspect_ratio,omitempty"`
+	OutputResolution  *string         `json:"output_resolution,omitempty"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AspectRatio string `json:"aspect_ratio,omitempty"`
OutputResolution string `json:"output_resolution,omitempty"`
AspectRatio *string `json:"aspect_ratio,omitempty"`
OutputResolution *string `json:"output_resolution,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dto/openai_image.go` around lines 19 - 20, The AspectRatio and
OutputResolution fields on dto.ImageRequest should be changed from value strings
to pointer types (e.g., *string) so that omitempty preserves explicit empty
values when re-marshaling for relays; update the struct fields AspectRatio and
OutputResolution to use pointer-typed strings and then adjust any construction
sites that set ImageRequest fields to take addresses (or nil) accordingly while
keeping the same json tags.

Comment thread dto/openai_request.go
Comment on lines +49 to +55
AspectRatio string `json:"aspect_ratio,omitempty"`
OutputResolution string `json:"output_resolution,omitempty"`
Seconds *string `json:"seconds,omitempty"`
Duration *int `json:"duration,omitempty"`
Quality *string `json:"quality,omitempty"`
Resolution string `json:"resolution,omitempty"`
ReferenceMode string `json:"reference_mode,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the dto/openai_request.go file and examine the GeneralOpenAIRequest struct
cd / && find . -name "openai_request.go" -path "*/dto/*" 2>/dev/null | head -5

Repository: QuantumNous/new-api

Length of output: 104


🏁 Script executed:

# Check the GeneralOpenAIRequest struct definition
rg "type GeneralOpenAIRequest" --type go -A 50 | head -80

Repository: QuantumNous/new-api

Length of output: 4621


🏁 Script executed:

# Search for usage of GeneralOpenAIRequest in relay/convert paths
rg "GeneralOpenAIRequest" --type go | grep -E "(relay|convert)" | head -20

Repository: QuantumNous/new-api

Length of output: 2710


Use pointers for the new optional string fields.

dto.GeneralOpenAIRequest is parsed from client JSON and re-marshaled upstream to provider APIs. With string + omitempty, an explicit empty value for aspect_ratio, output_resolution, resolution, or reference_mode is indistinguishable from omission and gets dropped on re-marshal.

🔧 Proposed fix
-	AspectRatio         string            `json:"aspect_ratio,omitempty"`
-	OutputResolution    string            `json:"output_resolution,omitempty"`
+	AspectRatio         *string           `json:"aspect_ratio,omitempty"`
+	OutputResolution    *string           `json:"output_resolution,omitempty"`
 	Seconds             *string           `json:"seconds,omitempty"`
 	Duration            *int              `json:"duration,omitempty"`
 	Quality             *string           `json:"quality,omitempty"`
-	Resolution          string            `json:"resolution,omitempty"`
-	ReferenceMode       string            `json:"reference_mode,omitempty"`
+	Resolution          *string           `json:"resolution,omitempty"`
+	ReferenceMode       *string           `json:"reference_mode,omitempty"`

Per coding guidelines for dto/**/*.go: optional scalar fields MUST use pointer types with omitempty to preserve explicit zero values set by the client.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AspectRatio string `json:"aspect_ratio,omitempty"`
OutputResolution string `json:"output_resolution,omitempty"`
Seconds *string `json:"seconds,omitempty"`
Duration *int `json:"duration,omitempty"`
Quality *string `json:"quality,omitempty"`
Resolution string `json:"resolution,omitempty"`
ReferenceMode string `json:"reference_mode,omitempty"`
AspectRatio *string `json:"aspect_ratio,omitempty"`
OutputResolution *string `json:"output_resolution,omitempty"`
Seconds *string `json:"seconds,omitempty"`
Duration *int `json:"duration,omitempty"`
Quality *string `json:"quality,omitempty"`
Resolution *string `json:"resolution,omitempty"`
ReferenceMode *string `json:"reference_mode,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dto/openai_request.go` around lines 49 - 55, The struct fields on
dto.GeneralOpenAIRequest using plain string types (AspectRatio,
OutputResolution, Resolution, ReferenceMode) must be changed to *string so
explicit empty string values from client JSON are preserved when re-marshaling;
update AspectRatio, OutputResolution, Resolution, and ReferenceMode to
pointer-to-string types (keep `json:"...,omitempty"` tags) and then audit places
constructing or reading those fields to handle nil vs non-nil appropriately
(e.g., check for nil before dereferencing or use helper to convert to string).

Comment on lines +129 to +163
if videoConfig, ok := bodyMap["video_config"].(map[string]interface{}); ok {
if resolutionName == "" {
resolutionName = stringifyBodyValue(videoConfig["resolution_name"])
}
if preset == "" {
preset = stringifyBodyValue(videoConfig["preset"])
}
}

if quality == "" {
quality = qualityFromResolutionName(resolutionName)
}
if resolutionName == "" {
resolutionName = resolutionNameFromQuality(quality)
}

if quality != "" {
bodyMap["quality"] = quality
}
if resolutionName != "" {
bodyMap["resolution_name"] = resolutionName
}
if preset != "" {
bodyMap["preset"] = preset
}
if resolutionName != "" || preset != "" {
videoConfig := map[string]interface{}{}
if resolutionName != "" {
videoConfig["resolution_name"] = resolutionName
}
if preset != "" {
videoConfig["preset"] = preset
}
bodyMap["video_config"] = videoConfig
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't replace the entire video_config object.

This helper reads video_config, but once either resolution_name or preset is present it rebuilds the object from scratch. Any other client-supplied nested options are silently dropped before the upstream request is sent.

🔧 Proposed fix
-	if resolutionName != "" || preset != "" {
-		videoConfig := map[string]interface{}{}
+	if resolutionName != "" || preset != "" {
+		videoConfig, _ := bodyMap["video_config"].(map[string]interface{})
+		if videoConfig == nil {
+			videoConfig = map[string]interface{}{}
+		}
 		if resolutionName != "" {
 			videoConfig["resolution_name"] = resolutionName
 		}
 		if preset != "" {
 			videoConfig["preset"] = preset
 		}
 		bodyMap["video_config"] = videoConfig
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if videoConfig, ok := bodyMap["video_config"].(map[string]interface{}); ok {
if resolutionName == "" {
resolutionName = stringifyBodyValue(videoConfig["resolution_name"])
}
if preset == "" {
preset = stringifyBodyValue(videoConfig["preset"])
}
}
if quality == "" {
quality = qualityFromResolutionName(resolutionName)
}
if resolutionName == "" {
resolutionName = resolutionNameFromQuality(quality)
}
if quality != "" {
bodyMap["quality"] = quality
}
if resolutionName != "" {
bodyMap["resolution_name"] = resolutionName
}
if preset != "" {
bodyMap["preset"] = preset
}
if resolutionName != "" || preset != "" {
videoConfig := map[string]interface{}{}
if resolutionName != "" {
videoConfig["resolution_name"] = resolutionName
}
if preset != "" {
videoConfig["preset"] = preset
}
bodyMap["video_config"] = videoConfig
}
if videoConfig, ok := bodyMap["video_config"].(map[string]interface{}); ok {
if resolutionName == "" {
resolutionName = stringifyBodyValue(videoConfig["resolution_name"])
}
if preset == "" {
preset = stringifyBodyValue(videoConfig["preset"])
}
}
if quality == "" {
quality = qualityFromResolutionName(resolutionName)
}
if resolutionName == "" {
resolutionName = resolutionNameFromQuality(quality)
}
if quality != "" {
bodyMap["quality"] = quality
}
if resolutionName != "" {
bodyMap["resolution_name"] = resolutionName
}
if preset != "" {
bodyMap["preset"] = preset
}
if resolutionName != "" || preset != "" {
videoConfig, _ := bodyMap["video_config"].(map[string]interface{})
if videoConfig == nil {
videoConfig = map[string]interface{}{}
}
if resolutionName != "" {
videoConfig["resolution_name"] = resolutionName
}
if preset != "" {
videoConfig["preset"] = preset
}
bodyMap["video_config"] = videoConfig
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/task/sora/adaptor.go` around lines 129 - 163, The code
currently reconstructs bodyMap["video_config"] from scratch using
resolutionName/preset, dropping any other client-supplied keys; instead, read
the existing videoConfig map (if any) and mutate it: use the same extraction
used earlier (bodyMap["video_config"].(map[string]interface{})) or create a new
map if nil, then set/override only "resolution_name" and "preset" based on
resolutionName and preset variables, and finally assign that merged map back to
bodyMap["video_config"]; update the block that builds videoConfig so it
preserves other nested options and only updates the fields you need (refer to
bodyMap, video_config, resolutionName, preset, stringifyBodyValue).

Comment on lines +166 to +181
func extractVideoURL(respBody []byte) string {
for _, path := range []string{
"url",
"video_url",
"metadata.url",
"data.url",
"data.video_url",
"output.video_url",
"task_result.videos.0.url",
} {
if url := strings.TrimSpace(gjson.GetBytes(respBody, path).String()); url != "" {
return url
}
}
return ""
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ignore inline data: URLs when extracting the result URL.

Both DoResponse and ParseTaskResult now trust this helper. If the upstream inlines base64 video content as a data: URL, this will copy that huge payload into response/task URL fields again.

🔧 Proposed fix
 	for _, path := range []string{
 		"url",
 		"video_url",
 		"metadata.url",
 		"data.url",
 		"data.video_url",
 		"output.video_url",
 		"task_result.videos.0.url",
 	} {
 		if url := strings.TrimSpace(gjson.GetBytes(respBody, path).String()); url != "" {
+			if strings.HasPrefix(strings.ToLower(url), "data:") {
+				continue
+			}
 			return url
 		}
 	}

Based on learnings, "In controller/task_video.go, data: URLs (containing base64 encoded video data) are prevented from being stored in task.FailReason by checking if the URL starts with data: before assignment. This same pattern should be applied consistently across the codebase."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/task/sora/adaptor.go` around lines 166 - 181, extractVideoURL
currently returns any non-empty string from gjson paths, which can propagate
large inline base64 "data:" URLs into DoResponse and ParseTaskResult; update
extractVideoURL to ignore values that start with "data:" by checking the trimmed
URL with strings.HasPrefix(url, "data:") (case-sensitive check is fine) and
continue searching for the next path if a data: URL is found, so only real
external URLs are returned to callers like DoResponse and ParseTaskResult.

Comment on lines +93 to +99
const imageSizeOptions = [
{ label: '1:1 方图 (1024x1024)', value: '1024x1024' },
{ label: '3:2 横图 (1792x1024)', value: '1792x1024' },
{ label: '2:3 竖图 (1024x1792)', value: '1024x1792' },
{ label: '16:9 宽屏 (1280x720)', value: '1280x720' },
{ label: '9:16 竖屏 (720x1280)', value: '720x1280' },
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Option labels should be translated.

The imageSizeOptions labels contain Chinese text that should be wrapped with t() for proper i18n support:

const imageSizeOptions = [
  { label: '1:1 方图 (1024x1024)', value: '1024x1024' },
  // ...
];
🌐 Suggested fix
-  const imageSizeOptions = [
-    { label: '1:1 方图 (1024x1024)', value: '1024x1024' },
-    { label: '3:2 横图 (1792x1024)', value: '1792x1024' },
-    { label: '2:3 竖图 (1024x1792)', value: '1024x1792' },
-    { label: '16:9 宽屏 (1280x720)', value: '1280x720' },
-    { label: '9:16 竖屏 (720x1280)', value: '720x1280' },
-  ];
+  const imageSizeOptions = [
+    { label: t('1:1 方图 (1024x1024)'), value: '1024x1024' },
+    { label: t('3:2 横图 (1792x1024)'), value: '1792x1024' },
+    { label: t('2:3 竖图 (1024x1792)'), value: '1024x1792' },
+    { label: t('16:9 宽屏 (1280x720)'), value: '1280x720' },
+    { label: t('9:16 竖屏 (720x1280)'), value: '720x1280' },
+  ];

As per coding guidelines: "Use useTranslation() hook and call t('中文key') in components."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/playground/SettingsPanel.jsx` around lines 93 - 99, The
imageSizeOptions array in SettingsPanel.jsx uses hardcoded Chinese labels;
update it to use the i18n hook by importing and calling useTranslation() in the
component, then replace each label string with t('<key>') (e.g.,
t('image.size.square'), t('image.size.landscape_3_2'), etc.), create
corresponding translation keys in your locale files, and ensure the
imageSizeOptions constant (and any place that reads its label) uses the t()
values so labels are translated at render time.

<div className='space-y-4'>
<div>
<Typography.Text strong className='text-sm'>
Aspect Ratio

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded English labels should use i18n.

Several labels are hardcoded in English instead of using the t() translation function:

  • Line 337: "Aspect Ratio"
  • Line 350: "Auto Size"
  • Line 363: "Output Resolution"
  • Line 455: "Duration"
  • Line 471: "Aspect Ratio"
  • Line 484: "Resolution"
  • Line 498: "Reference Mode"

These should be wrapped with t() for consistency with other translated labels in this file.

🌐 Suggested fix
-              <Typography.Text strong className='text-sm'>
-                Aspect Ratio
-              </Typography.Text>
+              <Typography.Text strong className='text-sm'>
+                {t('Aspect Ratio')}
+              </Typography.Text>

Apply similar changes to all hardcoded labels.

As per coding guidelines: "Use useTranslation() hook and call t('中文key') in components."

Also applies to: 350-350, 363-363, 455-455, 471-471, 484-484, 498-498

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/playground/SettingsPanel.jsx` at line 337, In
SettingsPanel.jsx, replace hardcoded English labels with i18n calls: import and
use the useTranslation() hook in the SettingsPanel component (or ensure the
existing hook is used) and wrap the string labels like "Aspect Ratio" (both
occurrences), "Auto Size", "Output Resolution", "Duration", "Resolution", and
"Reference Mode" with t('...') calls (use appropriate translation keys matching
your project's key naming), e.g., t('aspect_ratio') etc.; update the JSX where
these labels appear so each label uses t(...) instead of a raw string, and
ensure the component has const { t } = useTranslation() available before usage.

Comment on lines +68 to +80
const normalizeDurationPrices = (rawValue) => {
if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
return {};
}
return Object.entries(rawValue).reduce((acc, [seconds, price]) => {
const normalizedSeconds = String(seconds).trim();
const normalizedPrice = toNumericString(price);
if (!normalizedSeconds || normalizedPrice === '') {
return acc;
}
acc[normalizedSeconds] = normalizedPrice;
return acc;
}, {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate seconds as a numeric duration key.

This hook only trims seconds; it never checks that the key is actually numeric. Values like 30s or abc will survive normalization and be written back into ModelPriceBySeconds.

🛠️ Proposed fix
+const DURATION_SECONDS_REGEX = /^\d+$/;
+
 const normalizeDurationPrices = (rawValue) => {
   if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
     return {};
   }
   return Object.entries(rawValue).reduce((acc, [seconds, price]) => {
     const normalizedSeconds = String(seconds).trim();
     const normalizedPrice = toNumericString(price);
-    if (!normalizedSeconds || normalizedPrice === '') {
+    if (
+      !DURATION_SECONDS_REGEX.test(normalizedSeconds) ||
+      normalizedPrice === ''
+    ) {
       return acc;
     }
     acc[normalizedSeconds] = normalizedPrice;
     return acc;
   }, {});
 };

Apply the same DURATION_SECONDS_REGEX check in handleDurationPriceChange and addDurationPrice before mutating durationPrices.

Also applies to: 891-923

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js` around lines
68 - 80, normalizeDurationPrices currently only trims the seconds key but
doesn't verify it's numeric, so update normalizeDurationPrices to validate each
seconds key against DURATION_SECONDS_REGEX (or an equivalent numeric-seconds
regex) and skip entries whose normalizedSeconds do not match; likewise add the
same validation in addDurationPrice and handleDurationPriceChange before
mutating durationPrices so keys like "30s" or "abc" are rejected and only valid
numeric-second keys are written back to ModelPriceBySeconds.

Comment thread web/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js
Comment on lines +183 to +185
validator: (rule, value) => verifyJSON(value),
message: '不是合法的 JSON 字符串',
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use t() for the new validation message.

Line 184 introduces a user-facing message without i18n wrapping.

🌐 Minimal fix
               rules={[
                 {
                   validator: (rule, value) => verifyJSON(value),
-                  message: '不是合法的 JSON 字符串',
+                  message: t('不是合法的 JSON 字符串'),
                 },
               ]}

As per coding guidelines, "Frontend i18n: ... Use useTranslation() hook and call t('中文key') in components."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Ratio/ModelRatioSettings.jsx` around lines 183 - 185,
The validator's user-facing error message is not internationalized; update the
validation rule in ModelRatioSettings.jsx to use the i18n t() call from
useTranslation: import/use the useTranslation() hook in the component, call
const { t } = useTranslation(), and replace the literal message '不是合法的 JSON 字符串'
with t('你的.i18n.key.for.invalid_json') (or the appropriate key), keeping the
verifyJSON(rule, value) usage intact so the validator function still calls
verifyJSON.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`:
- Around line 65-87: The duration price items are being wrongly scaled by
groupRatio when building the display list in getSecondsPriceItems: remove the
multiplication by ratio so the displayed value uses the raw priceValue (i.e.
change value: displayPrice(priceValue * ratio) to value:
displayPrice(priceValue)); do the same change for the analogous per-duration
builder later in the file (the block around the other duration mapping that
currently multiplies by groupRatio). Ensure you keep keys/labels/suffixes (key:
`seconds-${seconds}`, label, suffix) and only remove the groupRatio scaling when
computing displayPrice.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3466606f-b047-4d7d-9aec-0c90a44d5f3a

📥 Commits

Reviewing files that changed from the base of the PR and between f0c3684 and 9338346.

📒 Files selected for processing (1)
  • web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx

Comment on lines +65 to +87
const getSecondsPriceItems = (ratio) =>
Object.entries(modelPriceBySeconds)
.map(([seconds, price]) => {
const secondsValue = Number(seconds);
const priceValue = Number(price);
if (
!Number.isFinite(secondsValue) ||
secondsValue <= 0 ||
!Number.isFinite(priceValue)
) {
return null;
}

return {
key: `seconds-${seconds}`,
label: `${secondsValue}${t('秒')}`,
value: displayPrice(priceValue * ratio),
suffix: `/ ${t('次')}`,
seconds: secondsValue,
};
})
.filter(Boolean)
.sort((a, b) => a.seconds - b.seconds);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove the extra groupRatio scaling from duration prices.

Line 81 multiplies each model_price_by_seconds entry by groupRatio, but web/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js:350-363 already submits those duration prices directly as the active quota_type === 2 values (model/pricing.go:297-313). That makes every displayed per-duration price too high or too low in this table.

💡 Proposed fix
-  const getSecondsPriceItems = (ratio) =>
+  const getSecondsPriceItems = () =>
     Object.entries(modelPriceBySeconds)
       .map(([seconds, price]) => {
         const secondsValue = Number(seconds);
         const priceValue = Number(price);
@@
         return {
           key: `seconds-${seconds}`,
           label: `${secondsValue}${t('秒')}`,
-          value: displayPrice(priceValue * ratio),
+          value: displayPrice(priceValue),
           suffix: `/ ${t('次')}`,
           seconds: secondsValue,
         };
       })
@@
-        secondsPriceItems: getSecondsPriceItems(groupRatioValue),
+        secondsPriceItems: getSecondsPriceItems(),

Also applies to: 153-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
around lines 65 - 87, The duration price items are being wrongly scaled by
groupRatio when building the display list in getSecondsPriceItems: remove the
multiplication by ratio so the displayed value uses the raw priceValue (i.e.
change value: displayPrice(priceValue * ratio) to value:
displayPrice(priceValue)); do the same change for the analogous per-duration
builder later in the file (the block around the other duration mapping that
currently multiplies by groupRatio). Ensure you keep keys/labels/suffixes (key:
`seconds-${seconds}`, label, suffix) and only remove the groupRatio scaling when
computing displayPrice.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1)

225-233: Simplify renderLimitedItems usage to match codebase conventions.

The customTags array already contains Tag elements with keys. The wrapping/unwrapping pattern deviates from how renderLimitedItems is used elsewhere (see ModelsColumnDefs.jsx for reference).

♻️ Proposed simplification
 {customTags.length > 0 &&
   renderLimitedItems({
-    items: customTags.map((tag, idx) => ({
-      key: `custom-${idx}`,
-      element: tag,
-    })),
-    renderItem: (item) => item.element,
+    items: customTags,
+    renderItem: (tag) => tag,
     maxDisplay: 3,
   })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/table/model-pricing/view/card/PricingCardView.jsx` around
lines 225 - 233, The current code maps customTags into objects with
keys/elements before calling renderLimitedItems which is unnecessary; change the
call to pass the existing customTags array directly as items (i.e. items:
customTags), use a renderItem that returns the element identity (renderItem:
(item) => item), and keep maxDisplay: 3 so renderLimitedItems handles keys
already present on Tag elements—this aligns usage with the pattern in
ModelsColumnDefs.jsx and removes the wrapping/unwrapping logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web/src/components/table/model-pricing/view/card/PricingCardView.jsx`:
- Around line 225-233: The current code maps customTags into objects with
keys/elements before calling renderLimitedItems which is unnecessary; change the
call to pass the existing customTags array directly as items (i.e. items:
customTags), use a renderItem that returns the element identity (renderItem:
(item) => item), and keep maxDisplay: 3 so renderLimitedItems handles keys
already present on Tag elements—this aligns usage with the pattern in
ModelsColumnDefs.jsx and removes the wrapping/unwrapping logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce8ad159-8423-4f35-afff-bf52c9ce8e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 9338346 and e7cde69.

📒 Files selected for processing (1)
  • web/src/components/table/model-pricing/view/card/PricingCardView.jsx

@RedwindA

Copy link
Copy Markdown
Contributor

如果不想遵循PR规则,请自行fork

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/hooks/playground/usePlaygroundState.js (1)

259-266: ⚠️ Potential issue | 🔴 Critical

playgroundMode 状态未从 Hook 返回,导致外部无法读取当前模式。

你新增了 setPlaygroundMode,但遗漏了 playgroundMode 本身。消费方会拿到 undefined,模式相关 UI/逻辑会异常。

🐛 Proposed fix
   return {
     // 配置状态
     inputs,
     parameterEnabled,
     showDebugPanel,
     customRequestMode,
     customRequestBody,
+    playgroundMode,
@@
     setShowDebugPanel,
     setCustomRequestMode,
     setCustomRequestBody,
     setPlaygroundMode,

Also applies to: 296-297

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/hooks/playground/usePlaygroundState.js` around lines 259 - 266,
Return value of the usePlaygroundState hook is missing the playgroundMode state
so callers get undefined; add playgroundMode to the object(s) returned by the
hook wherever setPlaygroundMode is returned (references: setPlaygroundMode and
playgroundMode) — update the return near the inputs/parameterEnabled block and
the other return block mentioned (around the other return at lines 296–297) to
include playgroundMode so external consumers can read the current mode.
♻️ Duplicate comments (1)
web/src/components/playground/SettingsPanel.jsx (1)

136-160: ⚠️ Potential issue | 🟡 Minor

Translate the new option labels.

Several human-readable option labels here are still hardcoded (Normal, Fun, Auto, Square (...), Frame, Image, etc.), so the new controls won’t localize with the rest of the panel.

As per coding guidelines: "Translation files in web/src/i18n/locales/{lang}.json must be flat JSON with Chinese source strings as keys. Use useTranslation() hook and call t('中文key') in components."

Also applies to: 182-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/playground/SettingsPanel.jsx` around lines 136 - 160,
Replace all hardcoded human-readable labels in the option arrays
(videoPresetOptions, videoQualityOptions, adobeAspectRatioOptions,
adobeAutoImageSizeOptions and any other nearby option arrays like the controls
around 'Frame'/'Image') with calls to the i18n hook (useTranslation) by using
t('中文key') for each label; add corresponding flat JSON keys with Chinese source
strings to web/src/i18n/locales/{lang}.json per project guidelines so labels are
localized. Ensure you import and call const { t } = useTranslation() in
SettingsPanel.jsx and use those t('...') keys in the option objects' label
properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/playground/ChatArea.jsx`:
- Around line 74-77: Replace the non-existent i18n key used in ChatArea.jsx:
change the subtitle translation call t('选择模型开始创作') to the existing key
t('选择模型开始对话') (in the JSX where subtitle is rendered next to Typography.Text) so
the component uses the correct, fully translated locale key.

In `@web/src/components/playground/SettingsPanel.jsx`:
- Around line 450-459: The generic "视频时长" Select (inputs.videoSeconds) is
rendered alongside Adobe's dedicated "Duration" control causing conflicting
payload fields; update SettingsPanel.jsx to conditionally hide this Select when
the currently selected model is an Adobe video model (or when the Adobe Duration
control is active) by adding a guard around the Select rendering (check the
model/provider flag used elsewhere in the component, e.g., selectedModel or
model.provider) and also remove/avoid setting the duplicate field in
buildApiPayload() so only one of seconds vs duration is serialized; apply the
same conditional hiding to the other occurrence around lines 498-515.

In `@web/src/helpers/api.js`:
- Around line 176-191: The builder currently defaults adobeAspectRatioRaw to
'1:1' for non-video Adobe models which prevents the UI's empty-as-auto behavior;
update the calculation of adobeAspectRatioRaw so that an empty
inputs.aspectRatio ('' from SettingsPanel.jsx) is treated as 'auto' and the
fallback for non-video Adobe models is 'auto' (not '1:1'), e.g. adjust the
adobeAspectRatioRaw expression that sets adobeAspectRatioRaw (and keep the
existing adobeAspectRatio logic) so isAdobeImageModel cases yield 'auto' when
inputs.aspectRatio is empty/undefined—this will let the isAdobeImageModel branch
omit payload.aspect_ratio and fall back to inputs.autoImageSize as intended
(refer to adobeAspectRatioRaw, adobeAspectRatio, isAdobeImageModel, payload, and
inputs.autoImageSize).

In `@web/src/helpers/playgroundMode.js`:
- Around line 7-29: The model-ID sets (GROK_IMAGE_GENERATION_MODELS,
GROK_IMAGE_EDIT_MODELS, ADOBE_IMAGE_MODELS, ADOBE_VIDEO_MODELS) are out of sync
with backend IDs and cause getAvailableModelsForPlaygroundMode() to misclassify
media models; update these sets to include the backend names and common aliases
(e.g., include 'grok-imagine-video' and its versioned variants, 'sora-2' and
'sora-2-pro' alongside 'sora2', 'veo-3.1-generate-preview' and other veo-3.1
variants alongside 'veo31*', and 'nano-banana-pro-preview' alongside
'nano-banana-pro*'), or replace the hardcoded Sets with a normalization/mapping
helper that canonicalizes backend model IDs to the playground categories used by
getAvailableModelsForPlaygroundMode(). Ensure all existing references (the four
Set constants and getAvailableModelsForPlaygroundMode) are updated to use the
expanded IDs or the new normalizer so image/video models are classified
correctly.

In `@web/src/hooks/playground/useApiRequest.jsx`:
- Around line 466-483: The early-return branches that update the last loading
message to a COMPLETE summary inside the setMessage callback (checking
lastMessage?.status === MESSAGE_STATUS.LOADING and using applyAutoCollapseLogic)
do not persist changes because they never call saveMessages(...) before
returning; update these branches to call the same persistence used in
completeMessage (e.g., call saveMessages(updatedMessages) or invoke the existing
persistence helper) immediately after building newMessages and before the return
so media completions (images/videos) are saved and survive refresh; apply the
same fix to the analogous branch later in the file that mirrors this logic.
- Around line 198-219: The extractVideoUrl helper currently misses video paths
used by the backend; update extractVideoUrl to also check
payload.output?.video_url and payload.task_result?.videos?.[0]?.url (and their
string-trim equivalents) in the candidates array so it covers output.video_url
and task_result.videos[0].url shapes coming from
relay/channel/task/sora/adaptor.go; locate the function extractVideoUrl in
useApiRequest.jsx and add those payload.output and payload.task_result checks to
the candidates list (with same typeof/string trim guard and optional chaining).
- Around line 129-145: The code re-checks the model string literal
'grok-imagine-1.0-video' instead of using the shared predicate, causing Grok
video models to miss resolution/video_config; update the block to use the
imported isGrokImagineVideoModel predicate (already imported into the hook) for
determining video behavior and resolutionName, remove the hard-coded string
check, and ensure when isGrokImagineVideoModel is true you attach
resolution_name/video_config to requestPayload so quality/preset are preserved
(refer to the local variables resolutionName, requestPayload, and
isGrokImagineVideoModel to locate and update the logic).

In `@web/src/i18n/locales/vi.json`:
- Around line 3864-3895: Replace the English placeholder values for the 32 new
keys in vi.json with proper Vietnamese translations (e.g. "创作中心" -> "Trung tâm
sáng tạo", "智能对话" -> "Trò chuyện thông minh", "图片创作" -> "Sáng tạo hình ảnh",
"视频创作" -> "Sáng tạo video", "可用模型" -> "Mô hình khả dụng", "当前模式" -> "Chế độ hiện
tại", and translate the workspace strings like "智能对话工作区", "图片创作工作区", "视频创作工作区"
accordingly); update every key shown in the diff so none remain with English
values, ensure UTF-8 encoding and JSON validity afterward, and run the i18n/lint
checks to confirm no missing translations or formatting issues.

---

Outside diff comments:
In `@web/src/hooks/playground/usePlaygroundState.js`:
- Around line 259-266: Return value of the usePlaygroundState hook is missing
the playgroundMode state so callers get undefined; add playgroundMode to the
object(s) returned by the hook wherever setPlaygroundMode is returned
(references: setPlaygroundMode and playgroundMode) — update the return near the
inputs/parameterEnabled block and the other return block mentioned (around the
other return at lines 296–297) to include playgroundMode so external consumers
can read the current mode.

---

Duplicate comments:
In `@web/src/components/playground/SettingsPanel.jsx`:
- Around line 136-160: Replace all hardcoded human-readable labels in the option
arrays (videoPresetOptions, videoQualityOptions, adobeAspectRatioOptions,
adobeAutoImageSizeOptions and any other nearby option arrays like the controls
around 'Frame'/'Image') with calls to the i18n hook (useTranslation) by using
t('中文key') for each label; add corresponding flat JSON keys with Chinese source
strings to web/src/i18n/locales/{lang}.json per project guidelines so labels are
localized. Ensure you import and call const { t } = useTranslation() in
SettingsPanel.jsx and use those t('...') keys in the option objects' label
properties.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a98f37d-80ac-45b0-b58b-dcbe4ced7b1b

📥 Commits

Reviewing files that changed from the base of the PR and between e7cde69 and 0277e88.

📒 Files selected for processing (19)
  • web/src/components/playground/ChatArea.jsx
  • web/src/components/playground/OptimizedComponents.js
  • web/src/components/playground/PlaygroundCreationCenter.jsx
  • web/src/components/playground/SettingsPanel.jsx
  • web/src/components/playground/configStorage.js
  • web/src/constants/playground.constants.js
  • web/src/helpers/api.js
  • web/src/helpers/index.js
  • web/src/helpers/playgroundMode.js
  • web/src/hooks/playground/useApiRequest.jsx
  • web/src/hooks/playground/usePlaygroundState.js
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/pages/Playground/index.jsx
✅ Files skipped from review due to trivial changes (4)
  • web/src/helpers/index.js
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/ja.json

Comment on lines +74 to +77
{title || t('AI 对话')}
</Typography.Title>
<Typography.Text className='!text-white/80 text-sm hidden sm:inline'>
{inputs.model || t('选择模型开始对话')}
{subtitle || t('选择模型开始创作')}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether the new fallback key exists across locale files.
rg -n '"选择模型开始创作"' web/src/i18n/locales
rg -n '"选择模型开始对话"' web/src/i18n/locales

Repository: QuantumNous/new-api

Length of output: 643


Use existing i18n key 选择模型开始对话 instead of undefined key 选择模型开始创作.

Line 77 uses t('选择模型开始创作'), which does not exist in any locale file. The existing key 选择模型开始对话 is available and fully translated across all supported languages (en, fr, ja, ru, vi, zh-CN, zh-TW). Replace with the existing key to ensure consistent translations.

Proposed fix
-                  {subtitle || t('选择模型开始创作')}
+                  {subtitle || t('选择模型开始对话')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{title || t('AI 对话')}
</Typography.Title>
<Typography.Text className='!text-white/80 text-sm hidden sm:inline'>
{inputs.model || t('选择模型开始对话')}
{subtitle || t('选择模型开始创作')}
{title || t('AI 对话')}
</Typography.Title>
<Typography.Text className='!text-white/80 text-sm hidden sm:inline'>
{subtitle || t('选择模型开始对话')}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/playground/ChatArea.jsx` around lines 74 - 77, Replace the
non-existent i18n key used in ChatArea.jsx: change the subtitle translation call
t('选择模型开始创作') to the existing key t('选择模型开始对话') (in the JSX where subtitle is
rendered next to Typography.Text) so the component uses the correct, fully
translated locale key.

Comment on lines +450 to +459
<div>
<Typography.Text strong className='text-sm'>
{t('视频时长')}
</Typography.Text>
<Select
className='!rounded-lg mt-2'
optionList={videoSecondsOptions}
value={inputs.videoSeconds}
onChange={(value) => onInputChange('videoSeconds', value)}
disabled={customRequestMode}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t show two competing duration inputs for Adobe video.

This generic 视频时长 select is rendered for every video model, and Adobe video models also get the dedicated Duration control below. buildApiPayload() then serializes both seconds and duration, so one of the two inputs is guaranteed to be ignored or conflict.

🛠️ Proposed fix
-              <div>
-                <Typography.Text strong className='text-sm'>
-                  {t('视频时长')}
-                </Typography.Text>
-                <Select
-                  className='!rounded-lg mt-2'
-                  optionList={videoSecondsOptions}
-                  value={inputs.videoSeconds}
-                  onChange={(value) => onInputChange('videoSeconds', value)}
-                  disabled={customRequestMode}
-                />
-              </div>
+              {!isCurrentAdobeVideoModel && (
+                <div>
+                  <Typography.Text strong className='text-sm'>
+                    {t('视频时长')}
+                  </Typography.Text>
+                  <Select
+                    className='!rounded-lg mt-2'
+                    optionList={videoSecondsOptions}
+                    value={inputs.videoSeconds}
+                    onChange={(value) => onInputChange('videoSeconds', value)}
+                    disabled={customRequestMode}
+                  />
+                </div>
+              )}

Also applies to: 498-515

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/playground/SettingsPanel.jsx` around lines 450 - 459, The
generic "视频时长" Select (inputs.videoSeconds) is rendered alongside Adobe's
dedicated "Duration" control causing conflicting payload fields; update
SettingsPanel.jsx to conditionally hide this Select when the currently selected
model is an Adobe video model (or when the Adobe Duration control is active) by
adding a guard around the Select rendering (check the model/provider flag used
elsewhere in the component, e.g., selectedModel or model.provider) and also
remove/avoid setting the duplicate field in buildApiPayload() so only one of
seconds vs duration is serialized; apply the same conditional hiding to the
other occurrence around lines 498-515.

Comment thread web/src/helpers/api.js
Comment thread web/src/helpers/playgroundMode.js Outdated
Comment on lines +7 to +29
const GROK_IMAGE_GENERATION_MODELS = new Set([
'grok-imagine-1.0',
'grok-imagine-1.0-fast',
]);

const GROK_IMAGE_EDIT_MODELS = new Set(['grok-imagine-1.0-edit']);

const ADOBE_IMAGE_MODELS = new Set([
'nano-banana',
'nano-banana-4k',
'nano-banana2',
'nano-banana2-4k',
'nano-banana-pro',
'nano-banana-pro-4k',
]);

const ADOBE_VIDEO_MODELS = new Set([
'sora2',
'sora2-pro',
'veo31',
'veo31-ref',
'veo31-fast',
]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

These model tables are already out of sync with the backend IDs.

The backend currently exposes names like grok-imagine-video, sora-2, sora-2-pro, veo-3.1-generate-preview, and nano-banana-pro-preview, but this classifier only knows grok-imagine-1.0-video, sora2, veo31*, and nano-banana-pro*. getAvailableModelsForPlaygroundMode() will filter out valid media models, and the pages/request builders that depend on these predicates will route them as chat models instead of image/video models.

Also applies to: 51-60

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/helpers/playgroundMode.js` around lines 7 - 29, The model-ID sets
(GROK_IMAGE_GENERATION_MODELS, GROK_IMAGE_EDIT_MODELS, ADOBE_IMAGE_MODELS,
ADOBE_VIDEO_MODELS) are out of sync with backend IDs and cause
getAvailableModelsForPlaygroundMode() to misclassify media models; update these
sets to include the backend names and common aliases (e.g., include
'grok-imagine-video' and its versioned variants, 'sora-2' and 'sora-2-pro'
alongside 'sora2', 'veo-3.1-generate-preview' and other veo-3.1 variants
alongside 'veo31*', and 'nano-banana-pro-preview' alongside 'nano-banana-pro*'),
or replace the hardcoded Sets with a normalization/mapping helper that
canonicalizes backend model IDs to the playground categories used by
getAvailableModelsForPlaygroundMode(). Ensure all existing references (the four
Set constants and getAvailableModelsForPlaygroundMode) are updated to use the
expanded IDs or the new normalizer so image/video models are classified
correctly.

Comment on lines +129 to +145
const isGrokImagineVideoModel = payload?.model === 'grok-imagine-1.0-video';
const resolutionName =
payload?.resolution_name ||
(isGrokImagineVideoModel ? formatVideoQuality(quality) : '');

const requestPayload = {
model: payload.model,
prompt,
seconds,
size,
quality: normalizeVideoQuality(quality),
preset,
...(image ? { image } : {}),
};

if (isGrokImagineVideoModel && resolutionName) {
requestPayload.resolution_name = resolutionName;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reuse the shared Grok video predicate here.

The hook already imports isGrokImagineVideoModel, but this block re-encodes the model name as 'grok-imagine-1.0-video'. The backend model list uses grok-imagine-video, so the real Grok video model never gets resolution_name/video_config, and the user's quality/preset settings are silently dropped.

🛠️ Proposed fix
-      const isGrokImagineVideoModel = payload?.model === 'grok-imagine-1.0-video';
+      const isGrokVideoModel = isGrokImagineVideoModel(payload?.model);
       const resolutionName =
         payload?.resolution_name ||
-        (isGrokImagineVideoModel ? formatVideoQuality(quality) : '');
+        (isGrokVideoModel ? formatVideoQuality(quality) : '');

-      if (isGrokImagineVideoModel && resolutionName) {
+      if (isGrokVideoModel && resolutionName) {
         requestPayload.resolution_name = resolutionName;
         requestPayload.video_config = {
           resolution_name: resolutionName,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isGrokImagineVideoModel = payload?.model === 'grok-imagine-1.0-video';
const resolutionName =
payload?.resolution_name ||
(isGrokImagineVideoModel ? formatVideoQuality(quality) : '');
const requestPayload = {
model: payload.model,
prompt,
seconds,
size,
quality: normalizeVideoQuality(quality),
preset,
...(image ? { image } : {}),
};
if (isGrokImagineVideoModel && resolutionName) {
requestPayload.resolution_name = resolutionName;
const isGrokVideoModel = isGrokImagineVideoModel(payload?.model);
const resolutionName =
payload?.resolution_name ||
(isGrokVideoModel ? formatVideoQuality(quality) : '');
const requestPayload = {
model: payload.model,
prompt,
seconds,
size,
quality: normalizeVideoQuality(quality),
preset,
...(image ? { image } : {}),
};
if (isGrokVideoModel && resolutionName) {
requestPayload.resolution_name = resolutionName;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/hooks/playground/useApiRequest.jsx` around lines 129 - 145, The code
re-checks the model string literal 'grok-imagine-1.0-video' instead of using the
shared predicate, causing Grok video models to miss resolution/video_config;
update the block to use the imported isGrokImagineVideoModel predicate (already
imported into the hook) for determining video behavior and resolutionName,
remove the hard-coded string check, and ensure when isGrokImagineVideoModel is
true you attach resolution_name/video_config to requestPayload so quality/preset
are preserved (refer to the local variables resolutionName, requestPayload, and
isGrokImagineVideoModel to locate and update the logic).

Comment on lines +198 to +219
const extractVideoUrl = useCallback((payload) => {
if (!payload || typeof payload !== 'object') {
return '';
}

const candidates = [
payload.url,
payload.video_url,
payload.result_url,
payload.metadata?.url,
payload.data?.url,
payload.data?.video_url,
payload.data?.result_url,
payload.data?.metadata?.url,
];

const matched = candidates.find(
(item) => typeof item === 'string' && item.trim() !== '',
);

return matched?.trim() || '';
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Cover the same video URL paths as the backend adaptor.

relay/channel/task/sora/adaptor.go also extracts output.video_url and task_result.videos.0.url, but this helper ignores both. Successful jobs returned in either shape will still render without an open link here.

🛠️ Proposed fix
     const candidates = [
       payload.url,
       payload.video_url,
       payload.result_url,
       payload.metadata?.url,
       payload.data?.url,
       payload.data?.video_url,
       payload.data?.result_url,
       payload.data?.metadata?.url,
+      payload.output?.video_url,
+      payload.task_result?.videos?.[0]?.url,
     ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const extractVideoUrl = useCallback((payload) => {
if (!payload || typeof payload !== 'object') {
return '';
}
const candidates = [
payload.url,
payload.video_url,
payload.result_url,
payload.metadata?.url,
payload.data?.url,
payload.data?.video_url,
payload.data?.result_url,
payload.data?.metadata?.url,
];
const matched = candidates.find(
(item) => typeof item === 'string' && item.trim() !== '',
);
return matched?.trim() || '';
}, []);
const extractVideoUrl = useCallback((payload) => {
if (!payload || typeof payload !== 'object') {
return '';
}
const candidates = [
payload.url,
payload.video_url,
payload.result_url,
payload.metadata?.url,
payload.data?.url,
payload.data?.video_url,
payload.data?.result_url,
payload.data?.metadata?.url,
payload.output?.video_url,
payload.task_result?.videos?.[0]?.url,
];
const matched = candidates.find(
(item) => typeof item === 'string' && item.trim() !== '',
);
return matched?.trim() || '';
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/hooks/playground/useApiRequest.jsx` around lines 198 - 219, The
extractVideoUrl helper currently misses video paths used by the backend; update
extractVideoUrl to also check payload.output?.video_url and
payload.task_result?.videos?.[0]?.url (and their string-trim equivalents) in the
candidates array so it covers output.video_url and task_result.videos[0].url
shapes coming from relay/channel/task/sora/adaptor.go; locate the function
extractVideoUrl in useApiRequest.jsx and add those payload.output and
payload.task_result checks to the candidates list (with same typeof/string trim
guard and optional chaining).

Comment on lines +466 to +483
setMessage((prevMessage) => {
const newMessages = [...prevMessage];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage?.status === MESSAGE_STATUS.LOADING) {
const autoCollapseState = applyAutoCollapseLogic(
lastMessage,
true,
);
newMessages[newMessages.length - 1] = {
...lastMessage,
content: summary,
status: MESSAGE_STATUS.COMPLETE,
...autoCollapseState,
};
}
return newMessages;
});
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Persist media completions before these early returns.

Both branches replace the loading message with the final summary and then return, but unlike completeMessage() they never call saveMessages(...). Since image/video requests are always forced onto the non-stream path, a refresh will bring back the stale loading bubble instead of the generated URLs.

Also applies to: 513-530

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/hooks/playground/useApiRequest.jsx` around lines 466 - 483, The
early-return branches that update the last loading message to a COMPLETE summary
inside the setMessage callback (checking lastMessage?.status ===
MESSAGE_STATUS.LOADING and using applyAutoCollapseLogic) do not persist changes
because they never call saveMessages(...) before returning; update these
branches to call the same persistence used in completeMessage (e.g., call
saveMessages(updatedMessages) or invoke the existing persistence helper)
immediately after building newMessages and before the return so media
completions (images/videos) are saved and survive refresh; apply the same fix to
the analogous branch later in the file that mirrors this logic.

Comment thread web/src/i18n/locales/vi.json Outdated
Comment on lines +3864 to +3895
"创作中心": "Creation Center",
"在一个操练场里切换对话、图片和视频创作": "Switch between chat, image, and video creation inside one playground",
"下方工作区继续复用现有操练场能力,你只需要在这里选择创作模式,系统会优先为你对齐合适的模型与配置。": "The workspace below keeps reusing the current playground capabilities. Choose a creation mode here and the system will align the most suitable model and settings first.",
"当前模型": "Current model",
"未选择模型": "No model selected",
"智能对话": "Smart Chat",
"适合长上下文、多轮追问、提示词调试和结构化输出。": "Best for long context, multi-turn follow-up, prompt tuning, and structured output.",
"图片创作": "Image Creation",
"围绕提示词、尺寸比例和参考图来生成或编辑图像。": "Generate or edit images around prompts, aspect ratios, and reference images.",
"视频创作": "Video Creation",
"聚焦时长、清晰度和参考模式,快速组织视频生成任务。": "Focus on duration, quality, and reference mode to organize video generation quickly.",
"可用模型": "Available models",
"当前模式": "Current mode",
"切换到此模式": "Switch to this mode",
"围绕系统提示词、多轮消息和流式响应来组织创作。": "Organize work around system prompts, multi-turn messages, and streaming responses.",
"优先调整图片尺寸、比例和参考图,让同一套操练场工作区更适合图像生成。": "Prioritize image size, ratio, and references so the same playground workspace feels tuned for image generation.",
"聚焦视频时长、清晰度和参考模式,继续复用操练场现有视频生成链路。": "Focus on video duration, quality, and reference mode while reusing the existing playground video flow.",
"当前账号下暂无适合此创作模式的模型,可切换模式或保留全量模型列表进行查看。": "This account currently has no models suited for this creation mode. Switch modes or keep the full model list visible to inspect what is available.",
"智能对话工作区": "Smart Chat Workspace",
"输入你的问题、任务或提示词方向...": "Enter your question, task, or prompt direction...",
"当前账号暂无适合智能对话的模型": "No models for smart chat are currently available on this account",
"可以先切换到图片创作或视频创作,也可以在左侧模型配置中查看当前账号返回的全量模型列表。": "You can switch to image or video creation first, or inspect the full model list returned for this account in the left configuration panel.",
"图片创作工作区": "Image Creation Workspace",
"可继续使用文生图与带图编辑能力": "Text-to-image and image-edit flows remain available here",
"描述想要生成的画面,或先开启图片输入后再进行图片编辑...": "Describe the image you want, or enable image input first and then continue with image editing...",
"当前账号暂无适合图片创作的模型": "No models for image creation are currently available on this account",
"图片创作会优先匹配图片模型;如果当前账号没有返回相关模型,请切换模式或等待模型配置更新。": "Image creation prefers image-capable models first. If this account has not returned any, switch modes or wait for the model configuration to update.",
"视频创作工作区": "Video Creation Workspace",
"可继续使用文生视频与参考图视频能力": "Text-to-video and reference-image video flows remain available here",
"描述想生成的视频镜头、节奏和风格,必要时可先开启图片输入作为参考图...": "Describe the shot, pacing, and style you want for the video. If needed, enable image input first as a reference...",
"当前账号暂无适合视频创作的模型": "No models for video creation are currently available on this account",
"视频创作会优先匹配视频模型;如果当前账号暂未返回相关模型,可先切换到其它创作模式。": "Video creation prefers video-capable models first. If this account has not returned any yet, switch to another creation mode for now."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Vietnamese translations missing — English placeholders provided instead.

All new entries for the "Creation Center" feature have English values, but this is the Vietnamese locale file (vi.json). Vietnamese users will see English text for these strings.

These values need proper Vietnamese translations. For example:

  • "创作中心" → should be "Trung tâm sáng tạo" (not "Creation Center")
  • "智能对话" → should be "Trò chuyện thông minh" (not "Smart Chat")
  • "图片创作" → should be "Sáng tạo hình ảnh" (not "Image Creation")
  • "视频创作" → should be "Sáng tạo video" (not "Video Creation")
  • "可用模型" → should be "Mô hình khả dụng" (not "Available models")
  • "当前模式" → should be "Chế độ hiện tại" (not "Current mode")

All 32 new entries require Vietnamese translations to maintain consistency with the rest of the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/vi.json` around lines 3864 - 3895, Replace the English
placeholder values for the 32 new keys in vi.json with proper Vietnamese
translations (e.g. "创作中心" -> "Trung tâm sáng tạo", "智能对话" -> "Trò chuyện thông
minh", "图片创作" -> "Sáng tạo hình ảnh", "视频创作" -> "Sáng tạo video", "可用模型" -> "Mô
hình khả dụng", "当前模式" -> "Chế độ hiện tại", and translate the workspace strings
like "智能对话工作区", "图片创作工作区", "视频创作工作区" accordingly); update every key shown in the
diff so none remain with English values, ensure UTF-8 encoding and JSON validity
afterward, and run the i18n/lint checks to confirm no missing translations or
formatting issues.

@seefs001 seefs001 closed this Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants