Codex/fix grok video url - #3489
Conversation
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
…parameters through frontend and backend"
Revert "Add playground video generation endpoints and UI; wire video parameters through frontend and backend"
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 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
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)
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorHandle the new seconds-based
quota_typein the billing label.Rows with
model_price_by_secondsnow come through withquota_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_urlwould 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 loggingBaseQuotainToSetting().
BaseQuotais 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
normalizeGrokImageSizefunction and model set definitions (grokImagineImageModels,adobeImageModels,adobeVideoModels) are duplicated across multiple files:
web/src/helpers/api.jsweb/src/hooks/playground/useApiRequest.jsxweb/src/components/playground/SettingsPanel.jsxConsider 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/standard→720p/480p, then immediately mapped back tohigh/standardfor 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.videoQualityis already'high', thenpayload.qualityends 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
📒 Files selected for processing (42)
common/endpoint_defaults.gocommon/endpoint_type.gocommon/endpoint_type_test.gocommon/model.goconstant/endpoint_type.gocontroller/playground.gocontroller/relay.godto/openai_image.godto/openai_request.gomiddleware/distributor.gomodel/option.gomodel/pricing.gorelay/channel/task/sora/adaptor.gorelay/channel/task/sora/adaptor_test.gorelay/channel/xai/adaptor.gorelay/channel/xai/adaptor_test.gorelay/channel/xai/constants.gorelay/channel/xai/dto.gorelay/common/relay_info.gorelay/common/relay_utils.gorelay/constant/relay_mode.gorelay/constant/relay_mode_test.gorelay/helper/price.gorelay/relay_task.gorelay/relay_task_test.gorouter/relay-router.gosetting/ratio_setting/model_price_by_seconds_test.gosetting/ratio_setting/model_ratio.gotypes/price_data.goweb/src/components/playground/SettingsPanel.jsxweb/src/components/settings/RatioSetting.jsxweb/src/components/table/channels/modals/ModelTestModal.jsxweb/src/components/table/model-pricing/modal/components/ModelPricingTable.jsxweb/src/components/table/models/modals/EditModelModal.jsxweb/src/components/table/models/modals/EditPrefillGroupModal.jsxweb/src/components/table/task-logs/TaskLogsColumnDefs.jsxweb/src/constants/playground.constants.jsweb/src/helpers/api.jsweb/src/hooks/playground/useApiRequest.jsxweb/src/pages/Setting/Ratio/ModelRatioSettings.jsxweb/src/pages/Setting/Ratio/components/ModelPricingEditor.jsxweb/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js
| 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) | ||
| } |
There was a problem hiding this comment.
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.
| AspectRatio string `json:"aspect_ratio,omitempty"` | ||
| OutputResolution string `json:"output_resolution,omitempty"` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the actual content of dto/openai_image.go around lines 19-20
sed -n '1,30p' dto/openai_image.goRepository: 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 -100Repository: 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 -20Repository: 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 -20Repository: 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.
| 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.
| 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"` |
There was a problem hiding this comment.
🧩 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 -5Repository: QuantumNous/new-api
Length of output: 104
🏁 Script executed:
# Check the GeneralOpenAIRequest struct definition
rg "type GeneralOpenAIRequest" --type go -A 50 | head -80Repository: 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 -20Repository: 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.
| 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).
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| 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).
| 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 "" | ||
| } |
There was a problem hiding this comment.
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.
| 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' }, | ||
| ]; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| 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; | ||
| }, {}); |
There was a problem hiding this comment.
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.
| validator: (rule, value) => verifyJSON(value), | ||
| message: '不是合法的 JSON 字符串', | ||
| }, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx
| 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1)
225-233: SimplifyrenderLimitedItemsusage to match codebase conventions.The
customTagsarray already contains Tag elements with keys. The wrapping/unwrapping pattern deviates from howrenderLimitedItemsis used elsewhere (seeModelsColumnDefs.jsxfor 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
📒 Files selected for processing (1)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx
|
如果不想遵循PR规则,请自行fork |
There was a problem hiding this comment.
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 | 🟡 MinorTranslate 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}.jsonmust be flat JSON with Chinese source strings as keys. UseuseTranslation()hook and callt('中文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
📒 Files selected for processing (19)
web/src/components/playground/ChatArea.jsxweb/src/components/playground/OptimizedComponents.jsweb/src/components/playground/PlaygroundCreationCenter.jsxweb/src/components/playground/SettingsPanel.jsxweb/src/components/playground/configStorage.jsweb/src/constants/playground.constants.jsweb/src/helpers/api.jsweb/src/helpers/index.jsweb/src/helpers/playgroundMode.jsweb/src/hooks/playground/useApiRequest.jsxweb/src/hooks/playground/usePlaygroundState.jsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-CN.jsonweb/src/i18n/locales/zh-TW.jsonweb/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
| {title || t('AI 对话')} | ||
| </Typography.Title> | ||
| <Typography.Text className='!text-white/80 text-sm hidden sm:inline'> | ||
| {inputs.model || t('选择模型开始对话')} | ||
| {subtitle || t('选择模型开始创作')} |
There was a problem hiding this comment.
🧩 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/localesRepository: 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.
| {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.
| <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} |
There was a problem hiding this comment.
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.
| 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', | ||
| ]); |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| 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).
| 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() || ''; | ||
| }, []); |
There was a problem hiding this comment.
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.
| 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).
| 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; |
There was a problem hiding this comment.
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.
| "创作中心": "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." |
There was a problem hiding this comment.
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.
Summary by CodeRabbit
New Features
UI Improvements
Enhancements
Tests