Skip to content

Codex/dreambrand model aliases - #6529

Open
yuansirrrrr wants to merge 26 commits into
QuantumNous:mainfrom
yuansirrrrr:codex/dreambrand-model-aliases
Open

Codex/dreambrand model aliases#6529
yuansirrrrr wants to merge 26 commits into
QuantumNous:mainfrom
yuansirrrrr:codex/dreambrand-model-aliases

Conversation

@yuansirrrrr

@yuansirrrrr yuansirrrrr commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

Summary by CodeRabbit

  • New Features
    • Added DreamBrand image/video generation (sync + async tasks) plus routing support.
    • Added asset-audit relay endpoints (sync upload, async upload, task status).
    • Added ZLHub Video and ZLHub Asset channels.
    • Added official Alipay top-up support, including payment and webhook notification handling.
    • Added localized User Guides and a new docs-by-slug page.
  • Enhancements
    • Improved docs-link normalization and synchronized saved UI language.
    • Increased video proxy fetch timeout for long-running requests.
    • Refreshed homepage/footer/UI styling and added new channel UI options/icons.
  • Bug Fixes
    • Strengthened Alipay enablement/config validation and improved container build resilience.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The pull request adds DreamBrand and ZLHub relay support, Alipay payments, configurable container sources, localized user guides, channel and OAuth updates, and a redesigned default frontend with expanded translations and theme tokens.

Changes

Platform integrations

Layer / File(s) Summary
Relay and channel integrations
relay/channel/*, relay/relay_adaptor.go, controller/asset_audit.go, router/relay-router.go
Adds DreamBrand and Doubao adaptor compatibility, ZLHub asset-audit routing, model detection, billing, polling, and response normalization.
Alipay payment flow
service/alipay.go, controller/topup_alipay.go, router/api-router.go, web/default/src/features/wallet/*
Adds RSA2 signing and verification, payment creation, webhook settlement, configuration, and wallet payment selection.
Build and runtime configuration
Dockerfile*, docker-compose*.yml, controller/video_proxy.go
Adds configurable registries and Debian mirrors, apt retries/timeouts, local Compose builds, port 3004 mappings, and a ten-minute video fetch timeout.

Frontend and documentation

Layer / File(s) Summary
Channel administration and OAuth
web/default/src/features/channels/*
Adds channel metadata for IDs 58–60 and channel-scoped Codex OAuth credential refresh handling.
Localized user guides
web/default/src/docs/*, web/default/src/routes/$locale/docs/..., web/default/src/routeTree.gen.ts
Adds localized guide content, Markdown normalization, locale selection, routing, and language menus.
Default homepage redesign
web/default/src/features/home/*, web/default/src/components/layout/*
Replaces the CTA and hero composition, adds featured models and operations sections, updates protected links, and refreshes navigation styling.
Theme, language, and translations
web/default/src/styles/*, web/default/src/main.tsx, web/default/src/i18n/locales/*
Adds saved-language synchronization, normalized branding/docs links, updated theme tokens, reduced card motion, and expanded locale catalogs.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: seefs001

Poem

A rabbit hops through routes anew,
With DreamBrand skies and payments blue.
Guides bloom in languages bright,
While tasks poll through day and night.
Build, relay, and hop along—
The new API sings its song!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and matches a real part of the changeset, covering the new DreamBrand/Codex alias-related work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch codex/dreambrand-model-aliases
🧪 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.

@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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (3)
web/default/src/docs/user-guide/en/personal-setting.mdx (1)

54-66: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep localized guide coverage consistent.

The Chinese Personal Settings guide includes sections for available models, notification settings, price settings, IP logs, and API-key reset, while the English and Japanese docs only cover basic info, 2FA, passkeys, and third-party binding. Add equivalent English/Japanese coverage or explicitly document this locale as intentionally incomplete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide/en/personal-setting.mdx` around lines 54 -
66, Update the English and Japanese Personal Settings guides to cover the same
available models, notification settings, price settings, IP logs, and API-key
reset sections documented in the Chinese guide, using equivalent localized
headings and instructions; alternatively, explicitly mark the locale
documentation as intentionally incomplete if those sections are not supported.
web/default/src/features/wallet/lib/ui.tsx (1)

88-96: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Hide the decorative Alipay icon from screen readers.

The icon is rendered alongside the payment method’s visible text, so add aria-hidden='true' to the shared SiAlipay element to avoid redundant announcements.

As per coding guidelines, decorative icons must use aria-hidden="true".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/wallet/lib/ui.tsx` around lines 88 - 96, Add
aria-hidden="true" to the shared SiAlipay element in the PAYMENT_TYPES.ALIPAY
and PAYMENT_TYPES.ALIPAY_OFFICIAL cases so the decorative icon is hidden from
screen readers.

Source: Coding guidelines

web/default/src/features/wallet/hooks/use-payment.ts (1)

90-103: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace the nested ternary with explicit branching.

processPayment now uses a second-level ternary for Stripe, Alipay, and the fallback path. Replace it with an if/else chain or early returns to keep payment routing direct and extensible.

As per coding guidelines, two or more nested ternaries are prohibited and complex logic should use clear branches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/wallet/hooks/use-payment.ts` around lines 90 - 103,
Replace the nested ternary in processPayment with explicit if/else branching
that preserves the existing Stripe, Alipay official, and fallback request
routing and payloads. Keep the response assignment or return flow unchanged
while making each payment path a clear, directly extensible branch.

Source: Coding guidelines

🟡 Minor comments (24)
relay/channel/task/doubao/adaptor_test.go-16-188 (1)

16-188: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use Testify assertions in this new backend test suite.

Replace t.Fatalf checks with require.NoError and require.Equal (and assert where execution should continue). As per coding guidelines, “New or substantially rewritten Go backend tests must use testify/require for setup and fatal assertions and testify/assert for non-fatal checks.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/task/doubao/adaptor_test.go` around lines 16 - 188, Update the
new tests in TestBuildRequestURL, TestZLHubFetchTask, TestZLHubAdaptorMetadata,
TestSeedanceMiniVideoInputRatio, TestParseResponsePayloadWithEnvelope,
TestParseTaskResultWithEnvelope, and TestConvertToOpenAIVideoWithEnvelope to use
testify assertions: replace fatal setup and prerequisite checks with
require.NoError or require.Equal, and use assert.Equal or assert.Contains for
independent checks that should not stop execution. Add the required testify
imports and preserve each test’s existing expectations.

Source: Coding guidelines

web/classic/src/components/layout/Footer.jsx-66-82 (1)

66-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align footer labels with their destination pages.

Mappings such as 联系我们personal-setting, 安装指南token, and 功能特性pricing appear unrelated to the displayed labels. Map each label to the intended guide page or update the labels.

Also applies to: 98-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/classic/src/components/layout/Footer.jsx` around lines 66 - 82, Update
the footer anchor mappings in the JSX around the visible `t('关于项目')`,
`t('联系我们')`, and the additionally affected links near the pricing entry so every
displayed label matches its destination guide page. Correct mismatches such as
`联系我们` pointing to `personal-setting`, and review the corresponding
`安装指南`/`功能特性` links to either use their intended guide URLs or update labels to
reflect those pages.
web/classic/src/helpers/render.jsx-406-409 (1)

406-409: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an icon case for ZLHubAsset.

Line 406 adds adjacent ZLHub mappings, but channel type 59 falls through to default and renders no icon. Include case 59 with the intended provider icon.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/classic/src/helpers/render.jsx` around lines 406 - 409, Add a
channel-type 59 branch in the icon switch near the existing ZLHub cases in the
render helper, returning the intended provider icon for ZLHubAsset. Preserve the
current mappings for cases 58 and 60 and ensure type 59 no longer reaches the
default branch.
relay/channel/dreambrand/adaptor_test.go-139-145 (1)

139-145: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

t.Fatalf from the httptest handler goroutine.

The handler runs on the server's goroutine; t.Fatalf there calls runtime.Goexit on the wrong goroutine, so the test isn't reliably failed and the assertion message can be lost. Record with t.Errorf and return instead.

💚 Proposed fix
 		if r.URL.Path != "/ai/v1/images/generations/TASK_1" {
-			t.Fatalf("query path = %q", r.URL.Path)
+			t.Errorf("query path = %q", r.URL.Path)
+			w.WriteHeader(http.StatusInternalServerError)
+			return
 		}
 		if r.Header.Get("Authorization") != "Bearer upstream-key" {
-			t.Fatalf("authorization = %q", r.Header.Get("Authorization"))
+			t.Errorf("authorization = %q", r.Header.Get("Authorization"))
+			w.WriteHeader(http.StatusUnauthorized)
+			return
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/dreambrand/adaptor_test.go` around lines 139 - 145, Replace the
t.Fatalf assertions inside the httptest server handler with t.Errorf followed by
an immediate return, including for the path and Authorization checks. Keep the
existing validation messages and ensure the handler does not continue processing
after either failed assertion.
relay/channel/task/dreambrand/adaptor.go-518-544 (1)

518-544: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

errorReason leaks raw JSON when error.code is numeric.

The inner struct declares Code string, so an upstream payload like {"error":{"code":30001,"message":"..."}} fails to unmarshal and the function returns string(response.Error) — the whole raw object — as the user-facing reason. The image adaptor already types this field as json.RawMessage.

🐛 Proposed fix
 	var object struct {
-		Message string `json:"message"`
-		Msg     string `json:"msg"`
-		Code    string `json:"code"`
+		Message string          `json:"message"`
+		Msg     string          `json:"msg"`
+		Code    json.RawMessage `json:"code"`
 	}
 	if err := common.Unmarshal(response.Error, &object); err == nil {
-		for _, value := range []string{object.Message, object.Msg, object.Code} {
+		for _, value := range []string{object.Message, object.Msg, strings.Trim(string(object.Code), `"`)} {
 			if strings.TrimSpace(value) != "" {
 				return value
 			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/task/dreambrand/adaptor.go` around lines 518 - 544, Update
errorReason’s fallback object parsing to represent object.Code as
json.RawMessage, matching the image adaptor, so numeric error codes deserialize
successfully. Preserve the existing priority of Message, Msg, and Code, and
return the decoded code value rather than the raw response.Error object when no
message is available.
web/default/src/lib/docs-url.ts-1-24 (1)

1-24: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the active locale in the default docs URL. The resolver hard-codes /zh/, and its caller supplies no locale, so localized guides cannot open in the visitor’s language.

  • web/default/src/lib/docs-url.ts#L1-L24: accept a supported locale and build the user-guide path from it, with an explicit fallback.
  • web/default/src/hooks/use-top-nav-links.ts#L59-L91: pass the resolved interface locale to resolveDocsUrl.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/lib/docs-url.ts` around lines 1 - 24, Update
web/default/src/lib/docs-url.ts lines 1-24 to make resolveDocsUrl accept a
supported locale, construct USER_GUIDE_DOCS_PATH with that locale, and apply an
explicit fallback for unsupported or missing locales. Update
web/default/src/hooks/use-top-nav-links.ts lines 59-91 to pass the resolved
interface locale into resolveDocsUrl so the active language is preserved.
web/default/src/docs/user-guide/zh/topup.mdx-14-34 (1)

14-34: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document Alipay when it is enabled.

The table and payment-selection instructions omit the Alipay payment flow introduced in this PR, leaving users without the new option. Add it alongside the other configurable providers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide/zh/topup.mdx` around lines 14 - 34, Update
the payment-method table and the payment-selection step in the Chinese top-up
guide to include Alipay alongside EPay, Stripe, Creem, and Waffo. Keep the
existing descriptions and payment flow unchanged while documenting Alipay
wherever configurable providers are listed.
web/default/src/routes/$locale/docs/guide/feature-guide/user/$slug.tsx-209-216 (1)

209-216: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Name the mobile language-menu trigger.

Below sm, the visible label is hidden and the icon-only button has no accessible name. Add aria-label={label} to the trigger/button; mark the icon decorative with aria-hidden="true".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/routes/`$locale/docs/guide/feature-guide/user/$slug.tsx
around lines 209 - 216, Update the language menu trigger in the
DropdownMenuTrigger block to add aria-label={label} so the icon-only mobile
button has an accessible name, and mark the Languages icon with
aria-hidden="true" as decorative.

Source: Coding guidelines

web/default/src/docs/user-guide/zh/subscription.mdx-16-22 (1)

16-22: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

补充完整的订阅购买流程。

英文版本包含选择套餐、点击购买、选择支付方式和确认支付等步骤;当前中文版本只说明浏览套餐。请补充购买与支付步骤,确保用户可以完成整个流程。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide/zh/subscription.mdx` around lines 16 - 22,
完善中文订阅指南中“订阅详情”下的购买流程,参考英文版本补充选择套餐、点击购买、选择支付方式和确认支付等步骤,并保留现有套餐信息说明及相关截图,使用户能够完成完整订阅流程。
web/default/src/docs/user-guide/en/personal-setting.mdx-34-38 (1)

34-38: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use secure storage guidance for 2FA recovery codes.

Both localized guides recommend screenshots as a primary storage option. Replace that recommendation with password-manager or secure offline storage guidance.

  • web/default/src/docs/user-guide/en/personal-setting.mdx#L34-L38: update the English backup-code instructions.
  • web/default/src/docs/user-guide/zh/personal-setting.mdx#L40-L45: update the Chinese backup-code instructions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide/en/personal-setting.mdx` around lines 34 -
38, Update the backup-code instructions in
web/default/src/docs/user-guide/en/personal-setting.mdx at lines 34-38 and
web/default/src/docs/user-guide/zh/personal-setting.mdx at lines 40-45 to
recommend storing codes in a password manager or secure offline location instead
of taking screenshots, while preserving the warning that codes are shown only
once.
web/default/src/docs/user-guide/en/pricing.mdx-17-20 (1)

17-20: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use one billing-unit convention across the top-up and pricing guides.

The pricing guide divides by 1000 for prices defined per 1K tokens, while the top-up guide omits that normalization. Define the multiplier unit and make both formulas identical.

  • web/default/src/docs/user-guide/en/pricing.mdx#L17-L20: retain or clarify the per-1K formula.
  • web/default/src/docs/user-guide/en/topup.mdx#L6-L6: update the consumption formula to use the same unit convention.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide/en/pricing.mdx` around lines 17 - 20, Use one
clearly defined per-1K-token billing-unit convention in both guides. In
web/default/src/docs/user-guide/en/pricing.mdx lines 17-20, retain or clarify
the formula dividing token count by 1000; in
web/default/src/docs/user-guide/en/topup.mdx line 6, update the consumption
formula to match exactly, including the same normalization and unit wording.
web/default/src/docs/user-guide/ja/api.mdx-55-55 (1)

55-55: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document Realtime as a WebSocket endpoint.

Replace the GET /v1/realtime wording with a ws:///wss:// /v1/realtime connection URL and the required model query/auth details, matching how the relay handles Realtime requests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide/ja/api.mdx` at line 55, Update the Realtime
API row in the Japanese API documentation to describe a ws:// or wss://
/v1/realtime connection URL instead of GET, including the required model query
parameter and authentication details consistent with the relay’s Realtime
request handling.
web/default/src/components/layout/components/footer.tsx-50-59 (1)

50-59: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make guide links locale-aware.

Every fallback link targets /zh/..., so English and Japanese visitors are sent to Chinese documentation. Derive these URLs from the active locale with a supported-locale fallback.

Based on review context, localized guides are available in Chinese, English, and Japanese.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/components/layout/components/footer.tsx` around lines 50 -
59, Update USER_GUIDE_DOCS to derive each documentation URL from the active
locale instead of hardcoding /zh/. Support Chinese, English, and Japanese locale
segments, and fall back to the supported default locale for unsupported or
missing values while preserving the existing guide paths.
web/default/src/features/home/components/hero-terminal-demo.tsx-171-210 (1)

171-210: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the terminal UI labels.

routed, Request, Response, cost, and stream via sse remain English on every localized home page. Move these labels into translation keys.

As per coding guidelines, “面向用户的文案必须使用 i18n”.

Also applies to: 218-225

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/hero-terminal-demo.tsx` around lines
171 - 210, The terminal demo’s user-facing labels remain hardcoded in English.
Update the component containing `CodePanel` and the latency footer to obtain
`routed`, `Request`, `Response`, `cost`, and `stream via sse` from the existing
i18n translation mechanism, including the corresponding labels in the additional
referenced section, and add the required translation keys for every supported
locale.

Source: Coding guidelines

web/default/src/components/language-switcher.tsx-63-77 (1)

63-77: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check backend success before persisting the language change.

api.interceptors.response shows non-skipBusiness {success: false} responses still resolve via return response, so api.put can reach setUser and update local user.setting even when the backend rejected the language change. Only persist after an explicit success path, or make the PUT reject on business failures without skipBusinessError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/components/language-switcher.tsx` around lines 63 - 77, The
language change handler must not update local user settings when the backend
returns a business-level failure. In the user branch around api.put, verify the
PUT response represents explicit success before calling parseUserSetting and
setUser, or make the request reject such failures through the existing
interceptor behavior; preserve best-effort error handling and only persist
language after confirmed success.
web/default/src/i18n/locales/fr.json-114-114 (1)

114-114: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Newly added French strings are missing accents/diacritics throughout the file.

Contrasting with pre-existing, correctly-accented French strings in the same file (e.g. Line 3788), the strings newly added in this PR systematically drop French diacritics and apostrophes — e.g. "modeles" instead of "modèles", "cle" instead of "clé", "generation" instead of "génération", "lusage"/"lIA"/"lupstream" instead of "l'usage"/"l'IA"/"l'upstream", "requetes"/"requete" instead of "requêtes"/"requête". This affects most of the ~50+ new entries in this file (representative examples cited above), degrading the French UI's professional quality.

Please run a proofreading/spell-check pass on the new entries before merge; happy to draft the corrected strings for the full list if useful.

Also applies to: 586-588, 909-909, 1001-1014, 1115-1115, 1563-1563, 1867-1867, 2071-2071, 2122-2122, 2148-2148, 2231-2231, 2366-2366, 2389-2389, 2442-2443, 2742-2750, 2794-2794, 2905-2907, 2974-2974, 3198-3198, 3455-3469, 3558-3558, 3685-3685, 3787-3790, 3933-3933, 3990-3990, 4174-4174, 4241-4241, 4319-4325, 4337-4338, 4391-4391, 4436-4442

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/fr.json` at line 114, Proofread all newly added
French entries in the locale resource, including the listed ranges, and restore
correct French accents, diacritics, and apostrophes. Preserve each translation’s
meaning and keys while correcting terms such as modèles, clé, génération,
requêtes, and l’usage/l’IA/l’upstream throughout the affected entries.
web/default/src/features/home/components/sections/operations-showcase.tsx-90-99 (1)

90-99: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Inconsistent aria-hidden on decorative icons.

The icons in the "Go to Dashboard" link (Lines 94-97, 99) have no aria-hidden, while the identical decorative icons inside entries.map (Lines 131, 144-148) correctly set aria-hidden='true'. Since the icon here is adjacent to descriptive text ("Go to Dashboard"), it is purely decorative and should be hidden from assistive tech for consistency.

♿ Proposed fix
             <HugeiconsIcon
               icon={DashboardBrowsingIcon}
               data-icon='inline-start'
+              aria-hidden='true'
             />
             {t('Go to Dashboard')}
-            <HugeiconsIcon icon={ArrowRight01Icon} data-icon='inline-end' />
+            <HugeiconsIcon icon={ArrowRight01Icon} data-icon='inline-end' aria-hidden='true' />

As per coding guidelines: "装饰性图标使用 aria-hidden=\"true\",重要信息提供文本等价".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/sections/operations-showcase.tsx`
around lines 90 - 99, Mark both HugeiconsIcon instances in the “Go to Dashboard”
link with aria-hidden='true', matching the decorative icons used in entries.map
while preserving the link’s existing descriptive text.

Source: Coding guidelines

web/default/src/features/home/components/sections/operations-showcase.tsx-46-46 (1)

46-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use router links for these internal operations showcase targets

These destinations (/keys, /usage-logs, /channels, /wallet, /dashboard) are internal TanStack Router routes. Plain anchors trigger full-page loads instead of client-side navigation; split the protectedHref logic into a helper and render those paths with @tanstack/react-router <Link> rather than <a href>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/sections/operations-showcase.tsx` at
line 46, Update the operations showcase navigation to use TanStack Router
client-side links for the internal paths /keys, /usage-logs, /channels, /wallet,
and /dashboard. Extract the existing protectedHref logic into a reusable helper,
then render matching targets with `@tanstack/react-router` Link while preserving
the current anchor behavior for non-internal destinations.
web/default/src/i18n/locales/ru.json-2478-2478 (1)

2478-2478: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a grammatical adjective for Multimodal.

Мультимодально is an adverb/short neuter form, while this source string is used as a standalone label. Use Мультимодальный to match Мультимодальный анализ on Line 2479.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/ru.json` at line 2478, Update the Russian
translation for the “Multimodal” key to the grammatical adjective
“Мультимодальный,” matching the wording used in the adjacent “Мультимодальный
анализ” label.
web/default/src/i18n/locales/ru.json-4442-4442 (1)

4442-4442: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the action in View all models.

Все модели means “All models” and omits “View”. Use Просмотреть все модели, consistent with the neighboring View all currently available models translation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/ru.json` at line 4442, Update the `View all
models` translation to `Просмотреть все модели`, preserving the action wording
and matching the neighboring `View all currently available models` translation.
web/default/src/i18n/locales/ru.json-3755-3755 (1)

3755-3755: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Translate fallback consistently.

Умный fallback mixes Russian and English, while nearby fallback labels use резервный/резервирование. Prefer Умное резервирование.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/ru.json` at line 3755, Update the Russian
translation for the “Smart fallback” key to use the consistent Russian term
“Умное резервирование” instead of the English word “fallback”.
web/default/src/i18n/locales/vi.json-114-114 (1)

114-114: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace the ASCII transliterations with reviewed Vietnamese translations.

Many new values omit Vietnamese diacritics (ID ung dung, Suy luan, Dinh tuyen, etc.), and "Model marketplace": "Cho model" is mistranslated. This makes the Vietnamese UI look machine-generated and changes meaning in places. Please have the new entries reviewed by a Vietnamese speaker.

Examples
-    "Alipay App ID": "ID ung dung Alipay",
+    "Alipay App ID": "ID ứng dụng Alipay",

-    "Context": "Ngu canh",
+    "Context": "Ngữ cảnh",

-    "Model": "Model",
+    "Model": "Mô hình",

-    "Model marketplace": "Cho model",
+    "Model marketplace": "Thị trường mô hình",

Also applies to: 252-258, 371-371, 415-416, 586-588, 708-708, 789-789, 817-819, 843-843, 909-909, 930-930, 940-940, 992-992, 1001-1004, 1014-1014, 1115-1115, 1563-1563, 1707-1720, 1779-1779, 1837-1837, 1867-1867, 1957-1957, 2071-2071, 2122-2122, 2148-2148, 2203-2203, 2220-2220, 2231-2231, 2265-2265, 2366-2366, 2389-2389, 2422-2422, 2442-2443, 2478-2479, 2742-2750, 2778-2782, 2794-2794, 2905-2907, 2974-2974, 3119-3119, 3164-3164, 3198-3198, 3239-3239, 3304-3304, 3379-3379, 3426-3426, 3442-3442, 3455-3469, 3558-3558, 3661-3661, 3685-3685, 3755-3755, 3787-3790, 3933-3933, 3990-3990, 4174-4174, 4241-4241, 4319-4325, 4337-4338, 4391-4391, 4436-4442, 4555-4555

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/vi.json` at line 114, Review and replace the
Vietnamese values for all newly added entries in vi.json, including the listed
ranges, with natural, accurate translations using proper Vietnamese diacritics
and terminology. Correct mistranslations such as “Model marketplace,” while
preserving every existing key, placeholder, and JSON structure.

Source: Learnings

web/default/src/i18n/locales/zh.json-1710-1711 (1)

1710-1711: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use routing terminology for “fallback”.

“备用” reads as generic backup and loses the request-routing/failover meaning. Use a consistent term such as “回退” or “故障转移” (for example, “智能故障转移”).

Also applies to: 3755-3755

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/zh.json` around lines 1710 - 1711, Update the
Chinese translations for the “fallback” and “Fallback tier” keys in the locale
resource to use request-routing/failover terminology consistently, replacing the
generic “备用” wording with the established “回退” or “故障转移” term, including the
additional occurrence noted in the comment.
web/default/src/i18n/locales/zh.json-3788-3788 (1)

3788-3788: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use a legally accurate Chinese term for “Merchant of Record”.

“登记商户” reads as merely registered merchants and does not capture the payment entity-of-record/compliance-liability role. Use an approved MoR term such as “商户记录主体(Merchant of Record)”, “记录商家(Merchant of Record)”, or “备案商家(Merchant of Record)”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/zh.json` at line 3788, Update the translation
value in the locale entry containing “Merchant of Record” to use an approved
legally accurate Chinese term, such as “商户记录主体(Merchant of Record)”, while
preserving the surrounding payment and compliance text.
🧹 Nitpick comments (13)
relay/channel/task/dreambrand/adaptor_test.go (1)

18-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both new DreamBrand test suites use hand-rolled if ... t.Fatalf instead of testify. The shared root cause is that neither file adopts the repository's required assertion libraries; the table structures themselves are fine.

  • relay/channel/task/dreambrand/adaptor_test.go#L18-L32: convert the URL/payload/validation/response assertions to require for fatal checks and assert for non-fatal field comparisons.
  • relay/channel/dreambrand/adaptor_test.go#L22-L36: apply the same conversion across the conversion, polling, and normalization tests.

As per coding guidelines, "New or substantially rewritten Go backend tests must use testify/require for setup and fatal assertions and testify/assert for non-fatal checks".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/task/dreambrand/adaptor_test.go` around lines 18 - 32, Replace
hand-rolled t.Fatalf assertions with testify/require for setup and fatal checks,
and testify/assert for non-fatal comparisons in
relay/channel/task/dreambrand/adaptor_test.go lines 18-32 and
relay/channel/dreambrand/adaptor_test.go lines 22-36. Apply this consistently
across the URL, payload, validation, response, conversion, polling, and
normalization tests while preserving the existing table structures and test
behavior.

Source: Coding guidelines

relay/channel/dreambrand/adaptor.go (2)

212-250: 🧹 Nitpick | 🔵 Trivial

Long-lived synchronous polling holds the request goroutine.

Default pollTimeout is 180s, so each pending image generation pins a request goroutine (and its upstream client connection) for up to three minutes. Consider surfacing the interval/timeout as channel settings and confirming your proxy/ingress read timeouts exceed it, otherwise clients see gateway timeouts while the poll keeps running.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/dreambrand/adaptor.go` around lines 212 - 250, Update the
synchronous pollImageTask flow to avoid tying up the request for the full
pollTimeout, exposing the polling interval and timeout through channel settings
instead. Ensure the configured proxy/ingress read timeouts exceed the maximum
polling duration so clients do not time out while polling continues.

404-478: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

parseUpstreamError, normalizeStatus, and buildURL are duplicated in relay/channel/task/dreambrand/adaptor.go.

The task package (lines 103-105, 388-433, 499-516) re-implements the same three helpers with slightly different semantics (e.g. errorReason there types code as string while here it is json.RawMessage). Since relay/channel/task/dreambrand/constants.go already aliases into this package, exporting shared helpers here and reusing them would prevent the two copies from drifting further.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/dreambrand/adaptor.go` around lines 404 - 478, The helpers
parseUpstreamError, normalizeStatus, and buildURL are duplicated in the task
Dreambrand adaptor. Export these shared helpers from the channel Dreambrand
package, preserving the existing response-code handling, status normalization,
and URL construction semantics, then remove the task-package copies and update
its callers to use the exported helpers through the existing constants.go
aliasing.
web/default/src/docs/user-guide.ts (3)

113-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Route guide chrome through the i18n catalog.

USER_GUIDE_COPY and the hard-coded “Deeprouter Docs” label bypass t(), creating a separate localization mechanism for visible UI.

  • web/default/src/docs/user-guide.ts#L113-L129: replace display strings with translation keys or remove this UI-copy table.
  • web/default/src/routes/$locale/docs/guide/feature-guide/user/$slug.tsx#L47-L70: use useTranslation().t() for section, title, language, and brand text.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide.ts` around lines 113 - 129, Replace the
hard-coded USER_GUIDE_COPY values in web/default/src/docs/user-guide.ts lines
113-129 with translation keys or remove the table. In
web/default/src/routes/$locale/docs/guide/feature-guide/user/$slug.tsx lines
47-70, use useTranslation().t() for the section, title, language, and
“Deeprouter Docs” brand text, routing all visible guide chrome through the i18n
catalog.

Source: Coding guidelines


19-48: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Code-split localized guide bodies.

These static imports ship every article for all three locales although the page renders one guide at a time. Keep navigation metadata synchronous, but dynamically import the selected locale/slug body.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide.ts` around lines 19 - 48, Update the
localized guide loading in user-guide.ts so navigation metadata remains
synchronously available while guide bodies are loaded via dynamic import only
for the selected locale and slug. Replace the eager En*, Ja*, and Zh* markdown
imports and connect the existing guide-selection flow to the corresponding lazy
imports without changing navigation behavior.

Source: Coding guidelines


71-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the documentation asset origin to VITE_ configuration.

This deployment-specific URL is hard-coded into the client bundle. Configure it through .env (for example, VITE_DOCS_ASSET_BASE_URL) so self-hosted deployments can supply their own asset source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/docs/user-guide.ts` around lines 71 - 72, Replace the
hard-coded URL in the docs asset base configuration with the Vite-exposed
environment variable VITE_DOCS_ASSET_BASE_URL, and update the related client
configuration or environment template so deployments can provide this value
through .env. Preserve the existing asset URL behavior by using the configured
value wherever the documentation asset base URL is consumed.

Source: Coding guidelines

controller/topup.go (1)

98-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated "check-then-append pay method" pattern.

This is now the fourth copy (Stripe, WaffoPancake, Waffo, Alipay) of the identical "scan payMethods for an existing type, append if missing" loop. A small helper would remove the duplication and reduce the risk of copy/paste mistakes (e.g., wrong type key) as more providers are added.

♻️ Proposed helper
+func appendPayMethodIfMissing(methods []map[string]string, methodType, name, color, minTopup string) []map[string]string {
+	for _, m := range methods {
+		if m["type"] == methodType {
+			return methods
+		}
+	}
+	return append(methods, map[string]string{
+		"name":      name,
+		"type":      methodType,
+		"color":     color,
+		"min_topup": minTopup,
+	})
+}
-	enableAlipay := isAlipayTopUpEnabled()
-	if enableAlipay {
-		hasAlipayOfficial := false
-		for _, method := range payMethods {
-			if method["type"] == model.PaymentMethodAlipayOfficial {
-				hasAlipayOfficial = true
-				break
-			}
-		}
-		if !hasAlipayOfficial {
-			payMethods = append(payMethods, map[string]string{
-				"name":      "Alipay Official",
-				"type":      model.PaymentMethodAlipayOfficial,
-				"color":     "rgba(var(--semi-blue-5), 1)",
-				"min_topup": strconv.Itoa(operation_setting.MinTopUp),
-			})
-		}
-	}
+	enableAlipay := isAlipayTopUpEnabled()
+	if enableAlipay {
+		payMethods = appendPayMethodIfMissing(payMethods, model.PaymentMethodAlipayOfficial, "Alipay Official", "rgba(var(--semi-blue-5), 1)", strconv.Itoa(operation_setting.MinTopUp))
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup.go` around lines 98 - 119, Extract the repeated pay-method
existence check and conditional append into a shared helper, then update the
Alipay flow and the existing Stripe, WaffoPancake, and Waffo flows to use it.
The helper should scan payMethods by the "type" key and append the supplied
method only when that type is absent, preserving current method data and
ordering.
router/relay-router.go (1)

189-198: 🚀 Performance & Scalability | 🔵 Trivial

Wiring looks correct; consider throughput protection for asset upload endpoints.

The new group correctly mirrors existing relay-router conventions (RouteTag, SystemPerformanceCheck, TokenAuth, Distribute). Since asset upload/audit requests can carry large payloads, ensure the downstream relay/billing pipeline enforces adequate size/throughput limits — this group has no explicit rate limiting beyond SystemPerformanceCheck and token auth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/relay-router.go` around lines 189 - 198, Update the assetAuditRouter
group containing RelayAssetAuditUploadSync, RelayAssetAuditUploadAsync, and
RelayAssetAuditTask to apply the established rate-limiting or
request-size/throughput middleware used for comparable large-payload relay
endpoints. Preserve the existing RouteTag, SystemPerformanceCheck, TokenAuth,
and Distribute middleware while ensuring uploads are protected before reaching
the downstream relay/billing pipeline.
controller/topup_alipay.go (2)

171-190: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider validating the notified paid amount against the order before crediting.

The handler credits quota using topUp.Amount from our own record rather than the webhook's total_amount, so this isn't directly exploitable given the signature check, but there's no sanity check that Alipay's reported paid amount actually matches topUp.Money. Adding that check would catch config drift or partial-payment edge cases before crediting quota.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup_alipay.go` around lines 171 - 190, The pending-order success
path should validate Alipay’s notified total_amount against the stored order
amount before updating status or crediting quota. Add a comparison using the
webhook payment value and topUp.Money, and reject/log mismatches with the
existing failure response; only continue through topUp.Update and quota
crediting when the amounts match.

107-108: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Drop the GET Alipay notify route for page pay.

This topup flow uses alipay.trade.page.pay with notify_url, and Alipay async notifications for that flow arrive via POST. The GET handler will never process the configured async callback, so router/api-router.go line 80 should only be kept if another Alipay feature actually uses GET notifications.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup_alipay.go` around lines 107 - 108, Remove the GET route
registration for Alipay notifications in the router setup, while retaining the
POST route used by the Alipay page-pay async callback. Verify references around
AlipayNotify and the router registration, and only preserve a GET registration
if another feature explicitly depends on GET notifications.
web/default/src/features/home/components/sections/features.tsx (2)

128-168: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Feature cards use plain <a> tags for internal routes, forcing full-page reloads.

feature.href (e.g. /pricing, or protectedHref('/channels') when authenticated) is rendered via a raw <a href=...> rather than the router's Link, so clicking any feature card triggers a full page reload instead of an SPA transition — even for the common authenticated case where protectedHref returns an unmodified internal path. hero.tsx's HeroSidePanel has the identical pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/sections/features.tsx` around lines
128 - 168, Replace the raw anchor wrapping each feature card in the features
section with the application router’s Link component, preserving feature.href
and existing styling so internal routes use SPA navigation. Apply the same
change to HeroSidePanel in hero.tsx, including protectedHref-generated internal
paths, while retaining any required external-link behavior.

116-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded "Deeprouter" breaks configurable branding.

t('Why Deeprouter') bakes the brand name into the translation string, whereas other components in this cohort (public-header.tsx, auth-layout.tsx) derive the display name dynamically via useSystemConfig()/customSiteName. This will show the wrong brand on deployments with a different configured site name.

♻️ Use dynamic system name via interpolation
+import { useSystemConfig } from '`@/hooks/use-system-config`'
...
 export function Features(props: FeaturesProps) {
   const { t } = useTranslation()
+  const { systemName } = useSystemConfig()
   const protectedHref = (path: string) =>
...
-            {t('Why Deeprouter')}
+            {t('Why {{systemName}}', { systemName })}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/sections/features.tsx` around lines
116 - 127, Update the features section component around the “Why Deeprouter”
label to obtain the configured site name through useSystemConfig and its
customSiteName value, then pass that name through the translation interpolation
instead of hardcoding “Deeprouter”.
web/default/src/features/home/components/sections/hero.tsx (1)

201-266: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

HeroSidePanel also uses plain <a> tags for internal routes.

Same pattern as features.tsx: protectedHref can return an unmodified internal path (e.g. /channels, /usage-logs) for authenticated users, yet the card is always rendered as a raw <a>, forcing a full page reload instead of an SPA transition through the router's Link.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/sections/hero.tsx` around lines 201
- 266, Update HeroSidePanel to render internal preview links through the
router’s Link component instead of raw <a> elements, while preserving
protectedHref’s authenticated and sign-in redirect destinations. Pass each
preview’s href to Link and retain the existing card styling, content, and
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a7eda0e-f1cf-4fb6-9be9-605bea8be7eb

📥 Commits

Reviewing files that changed from the base of the PR and between 66ee6b8 and 38f5634.

⛔ Files ignored due to path filters (3)
  • web/default/public/home/channel-health-preview.svg is excluded by !**/*.svg
  • web/default/public/home/model-routing-preview.svg is excluded by !**/*.svg
  • web/default/public/home/usage-logs-preview.svg is excluded by !**/*.svg
📒 Files selected for processing (135)
  • Dockerfile
  • Dockerfile.dev
  • common/api_type.go
  • constant/api_type.go
  • constant/asset_audit.go
  • constant/channel.go
  • controller/asset_audit.go
  • controller/channel-test.go
  • controller/misc.go
  • controller/model.go
  • controller/payment_webhook_availability.go
  • controller/payment_webhook_availability_test.go
  • controller/topup.go
  • controller/topup_alipay.go
  • controller/video_proxy.go
  • docker-compose.dev.yml
  • docker-compose.yml
  • middleware/distributor.go
  • model/option.go
  • model/topup.go
  • relay/channel/dreambrand/adaptor.go
  • relay/channel/dreambrand/adaptor_test.go
  • relay/channel/dreambrand/constants.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/adaptor_test.go
  • relay/channel/task/doubao/constants.go
  • relay/channel/task/dreambrand/adaptor.go
  • relay/channel/task/dreambrand/adaptor_test.go
  • relay/channel/task/dreambrand/constants.go
  • relay/common/relay_info.go
  • relay/common/relay_utils.go
  • relay/relay_adaptor.go
  • relay/relay_adaptor_dreambrand_test.go
  • relay/relay_task.go
  • router/api-router.go
  • router/relay-router.go
  • service/alipay.go
  • setting/operation_setting/general_setting.go
  • setting/payment_alipay.go
  • setting/system_setting/theme.go
  • web/classic/src/components/layout/Footer.jsx
  • web/classic/src/constants/channel.constants.js
  • web/classic/src/helpers/render.jsx
  • web/default/index.html
  • web/default/rsbuild.config.ts
  • web/default/scripts/sync-i18n.mjs
  • web/default/src/components/data-table/data-table-page.tsx
  • web/default/src/components/language-switcher.tsx
  • web/default/src/components/layout/components/footer.tsx
  • web/default/src/components/layout/components/header.tsx
  • web/default/src/components/layout/components/public-header.tsx
  • web/default/src/components/layout/components/section-page-layout.tsx
  • web/default/src/components/layout/components/top-nav.tsx
  • web/default/src/components/ui/card.tsx
  • web/default/src/docs/user-guide.ts
  • web/default/src/docs/user-guide/en/api.mdx
  • web/default/src/docs/user-guide/en/auth.mdx
  • web/default/src/docs/user-guide/en/chat-apps.mdx
  • web/default/src/docs/user-guide/en/log.mdx
  • web/default/src/docs/user-guide/en/meta.json
  • web/default/src/docs/user-guide/en/personal-setting.mdx
  • web/default/src/docs/user-guide/en/pricing.mdx
  • web/default/src/docs/user-guide/en/subscription.mdx
  • web/default/src/docs/user-guide/en/task.mdx
  • web/default/src/docs/user-guide/en/token.mdx
  • web/default/src/docs/user-guide/en/topup.mdx
  • web/default/src/docs/user-guide/ja/api.mdx
  • web/default/src/docs/user-guide/ja/auth.mdx
  • web/default/src/docs/user-guide/ja/chat-apps.mdx
  • web/default/src/docs/user-guide/ja/log.mdx
  • web/default/src/docs/user-guide/ja/meta.json
  • web/default/src/docs/user-guide/ja/personal-setting.mdx
  • web/default/src/docs/user-guide/ja/pricing.mdx
  • web/default/src/docs/user-guide/ja/subscription.mdx
  • web/default/src/docs/user-guide/ja/task.mdx
  • web/default/src/docs/user-guide/ja/token.mdx
  • web/default/src/docs/user-guide/ja/topup.mdx
  • web/default/src/docs/user-guide/zh/api.mdx
  • web/default/src/docs/user-guide/zh/auth.mdx
  • web/default/src/docs/user-guide/zh/chat-apps.mdx
  • web/default/src/docs/user-guide/zh/log.mdx
  • web/default/src/docs/user-guide/zh/meta.json
  • web/default/src/docs/user-guide/zh/personal-setting.mdx
  • web/default/src/docs/user-guide/zh/pricing.mdx
  • web/default/src/docs/user-guide/zh/subscription.mdx
  • web/default/src/docs/user-guide/zh/task.mdx
  • web/default/src/docs/user-guide/zh/token.mdx
  • web/default/src/docs/user-guide/zh/topup.mdx
  • web/default/src/env.d.ts
  • web/default/src/features/auth/auth-layout.tsx
  • web/default/src/features/auth/sign-in/index.tsx
  • web/default/src/features/channels/api.ts
  • web/default/src/features/channels/components/dialogs/codex-oauth-dialog.tsx
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/constants.ts
  • web/default/src/features/channels/lib/channel-type-config.ts
  • web/default/src/features/channels/lib/channel-utils.ts
  • web/default/src/features/home/components/feature-item.tsx
  • web/default/src/features/home/components/hero-terminal-demo.tsx
  • web/default/src/features/home/components/index.ts
  • web/default/src/features/home/components/sections/cta.tsx
  • web/default/src/features/home/components/sections/featured-models.tsx
  • web/default/src/features/home/components/sections/features.tsx
  • web/default/src/features/home/components/sections/hero.tsx
  • web/default/src/features/home/components/sections/how-it-works.tsx
  • web/default/src/features/home/components/sections/operations-showcase.tsx
  • web/default/src/features/home/components/sections/stats.tsx
  • web/default/src/features/home/index.tsx
  • web/default/src/features/system-settings/billing/index.tsx
  • web/default/src/features/system-settings/billing/section-registry.tsx
  • web/default/src/features/system-settings/integrations/payment-settings-section.tsx
  • web/default/src/features/system-settings/types.ts
  • web/default/src/features/wallet/api.ts
  • web/default/src/features/wallet/components/recharge-form-card.tsx
  • web/default/src/features/wallet/components/subscription-plans-card.tsx
  • web/default/src/features/wallet/constants.ts
  • web/default/src/features/wallet/hooks/use-payment.ts
  • web/default/src/features/wallet/lib/billing.ts
  • web/default/src/features/wallet/lib/payment.ts
  • web/default/src/features/wallet/lib/ui.tsx
  • web/default/src/features/wallet/types.ts
  • web/default/src/hooks/use-top-nav-links.ts
  • web/default/src/i18n/locales/_reports/_sync-report.json
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/lib/docs-url.ts
  • web/default/src/main.tsx
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/$locale/docs/guide/feature-guide/user/$slug.tsx
  • web/default/src/styles/index.css
  • web/default/src/styles/theme.css
💤 Files with no reviewable changes (1)
  • web/default/src/features/home/components/sections/cta.tsx

Comment thread controller/asset_audit.go
Comment on lines +69 to +100
if !priceData.FreeModel {
if apiErr := service.PreConsumeBilling(c, priceData.Quota, info); apiErr != nil {
c.JSON(apiErr.StatusCode, gin.H{"error": apiErr.ToOpenAIError()})
return
}
}

resp, respBody, err := doAssetAuditRequest(c, method, upstreamPath)
if err != nil {
if info.Billing != nil {
info.Billing.Refund(c)
}
c.JSON(http.StatusInternalServerError, gin.H{
"code": "do_request_failed",
"message": err.Error(),
})
return
}
defer resp.Body.Close()

if resp.StatusCode >= http.StatusBadRequest {
if info.Billing != nil {
info.Billing.Refund(c)
}
service.IOCopyBytesGracefully(c, resp, respBody)
return
}

if err := service.SettleBilling(c, info, priceData.Quota); err != nil {
logger.LogError(c, fmt.Sprintf("asset audit settle billing failed: %v", err))
}
recordAssetAuditConsumeLog(c, info, priceData.Quota, upstreamPath)

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve quota-saturation metadata before logging.

This flow never calls attachQuotaSaturation before Line 100 writes the consume log, and no clamp result is propagated into that log. Use the checked quota result and attach its saturation metadata before recording consumption.

As per coding guidelines, “Billing paths must use checked quota helpers, propagate clamp information, and call attachQuotaSaturation before writing consume or task logs so saturation is auditable.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/asset_audit.go` around lines 69 - 100, Update the billing flow
around PreConsumeBilling and recordAssetAuditConsumeLog to use the checked quota
result, retain its clamp or saturation information, and call
attachQuotaSaturation before writing the consumption log. Ensure the resulting
metadata is propagated to recordAssetAuditConsumeLog while preserving the
existing refund and settlement behavior.

Source: Coding guidelines


func AlipayNotify(c *gin.Context) {
if !isAlipayWebhookEnabled() {
logger.LogWarn(c.Request.Context(), fmt.Sprintf("Alipay webhook disabled path=%q client_ip=%s", c.Request.RequestURI, c.ClientIP()))

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid logging raw Alipay webhook params (may contain buyer PII).

common.GetJsonString(params) is logged at Warn/Info level in several branches (signature-failure, ignored-status, order-not-found). Alipay's async-notify payload commonly includes buyer-identifying fields (e.g. buyer_logon_id, buyer_id). Persisting this verbatim in logs is a PII/compliance risk. Consider logging only the fields needed for troubleshooting (out_trade_no, trade_status, trade_no) rather than the full param map.

Also applies to: 137-141, 151-151, 161-161, 189-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup_alipay.go` at line 109, Remove raw
common.GetJsonString(params) payloads from all Alipay webhook Warn/Info logs,
including the signature-failure, ignored-status, order-not-found, and related
branches in the webhook handler. Log only the troubleshooting fields
out_trade_no, trade_status, and trade_no, preserving the existing branch
behavior and context.

Comment on lines +138 to +181
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (any, *types.NewAPIError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeReadResponseBodyFailed)
}
service.CloseResponseBodyGracefully(resp)

created, err := parseCreateResponse(responseBody)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
}
result := queryResponse{Status: created.Status, URL: created.URL, Created: created.Created}
if result.URL == "" && len(created.Data) > 0 {
result.URL = created.Data[0].URL
}

if result.URL == "" {
taskID := created.ID
if taskID == "" {
taskID = created.TaskID
}
if taskID == "" {
code, message := parseUpstreamError(responseBody)
if message == "" {
message = "DreamBrand image creation returned neither task ID nor URL"
}
if code == "" {
code = "invalid_response"
}
return nil, types.NewError(fmt.Errorf("%s", message), types.ErrorCode(code))
}

result, _, err = a.pollImageTask(c.Request.Context(), info, taskID)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeBadResponse, http.StatusGatewayTimeout, types.ErrOptionWithSkipRetry())
}
return nil, types.NewError(err, types.ErrorCodeBadResponse, types.ErrOptionWithSkipRetry())
}
}

if strings.TrimSpace(result.URL) == "" {
return nil, types.NewError(fmt.Errorf("DreamBrand image task succeeded without URL"), types.ErrorCodeBadResponse, types.ErrOptionWithSkipRetry())
}

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

DoResponse never inspects resp.StatusCode.

An upstream 401/429/5xx is treated like a success envelope: either parseCreateResponse fails (reported as ErrorCodeBadResponseBody, HTTP 500) or the error branch builds types.ErrorCode(code) from an arbitrary upstream code such as "30001". The real status is dropped, so rate limits and auth failures lose their retry/classification semantics downstream.

🛡️ Suggested status propagation
 	service.CloseResponseBodyGracefully(resp)
 
+	if resp.StatusCode != http.StatusOK {
+		code, message := parseUpstreamError(responseBody)
+		if message == "" {
+			message = fmt.Sprintf("DreamBrand returned HTTP %d: %s", resp.StatusCode, responseBody)
+		}
+		if code == "" {
+			code = string(types.ErrorCodeBadResponse)
+		}
+		return nil, types.NewErrorWithStatusCode(errors.New(message), types.ErrorCode(code), resp.StatusCode)
+	}
+
 	created, err := parseCreateResponse(responseBody)
📝 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
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (any, *types.NewAPIError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeReadResponseBodyFailed)
}
service.CloseResponseBodyGracefully(resp)
created, err := parseCreateResponse(responseBody)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
}
result := queryResponse{Status: created.Status, URL: created.URL, Created: created.Created}
if result.URL == "" && len(created.Data) > 0 {
result.URL = created.Data[0].URL
}
if result.URL == "" {
taskID := created.ID
if taskID == "" {
taskID = created.TaskID
}
if taskID == "" {
code, message := parseUpstreamError(responseBody)
if message == "" {
message = "DreamBrand image creation returned neither task ID nor URL"
}
if code == "" {
code = "invalid_response"
}
return nil, types.NewError(fmt.Errorf("%s", message), types.ErrorCode(code))
}
result, _, err = a.pollImageTask(c.Request.Context(), info, taskID)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeBadResponse, http.StatusGatewayTimeout, types.ErrOptionWithSkipRetry())
}
return nil, types.NewError(err, types.ErrorCodeBadResponse, types.ErrOptionWithSkipRetry())
}
}
if strings.TrimSpace(result.URL) == "" {
return nil, types.NewError(fmt.Errorf("DreamBrand image task succeeded without URL"), types.ErrorCodeBadResponse, types.ErrOptionWithSkipRetry())
}
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (any, *types.NewAPIError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeReadResponseBodyFailed)
}
service.CloseResponseBodyGracefully(resp)
if resp.StatusCode != http.StatusOK {
code, message := parseUpstreamError(responseBody)
if message == "" {
message = fmt.Sprintf("DreamBrand returned HTTP %d: %s", resp.StatusCode, responseBody)
}
if code == "" {
code = string(types.ErrorCodeBadResponse)
}
return nil, types.NewErrorWithStatusCode(errors.New(message), types.ErrorCode(code), resp.StatusCode)
}
created, err := parseCreateResponse(responseBody)
if err != nil {
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
}
result := queryResponse{Status: created.Status, URL: created.URL, Created: created.Created}
if result.URL == "" && len(created.Data) > 0 {
result.URL = created.Data[0].URL
}
if result.URL == "" {
taskID := created.ID
if taskID == "" {
taskID = created.TaskID
}
if taskID == "" {
code, message := parseUpstreamError(responseBody)
if message == "" {
message = "DreamBrand image creation returned neither task ID nor URL"
}
if code == "" {
code = "invalid_response"
}
return nil, types.NewError(fmt.Errorf("%s", message), types.ErrorCode(code))
}
result, _, err = a.pollImageTask(c.Request.Context(), info, taskID)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, types.NewErrorWithStatusCode(err, types.ErrorCodeBadResponse, http.StatusGatewayTimeout, types.ErrOptionWithSkipRetry())
}
return nil, types.NewError(err, types.ErrorCodeBadResponse, types.ErrOptionWithSkipRetry())
}
}
if strings.TrimSpace(result.URL) == "" {
return nil, types.NewError(fmt.Errorf("DreamBrand image task succeeded without URL"), types.ErrorCodeBadResponse, types.ErrOptionWithSkipRetry())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/dreambrand/adaptor.go` around lines 138 - 181, Update
DoResponse to inspect resp.StatusCode before treating the body as a successful
creation response, preserving the upstream HTTP status and retry/classification
semantics for 401, 429, and 5xx responses. Map non-success responses through the
existing API error/status handling rather than parseCreateResponse or arbitrary
types.ErrorCode(code), while leaving successful response parsing and polling
unchanged.

Comment on lines +382 to +392
func parseResponseTask(respBody []byte) (responseTask, error) {
var envelope responseTaskEnvelope
if err := common.Unmarshal(respBody, &envelope); err == nil && envelope.Data.ID != "" {
return envelope.Data, nil
}

var task responseTask
if err := common.Unmarshal(respBody, &task); err != nil {
return responseTask{}, err
}
return task, nil

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject failed task envelopes instead of treating them as active tasks.

An upstream {"code":"error","message":"..."} response decodes as an envelope with no task, then successfully decodes again into a zero-value responseTask. ParseTaskResult consequently reports it as in_progress, causing indefinite polling. Detect envelope responses and return an error for non-success codes or missing task data; add an error-envelope regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/task/doubao/adaptor.go` around lines 382 - 392, Update
parseResponseTask to distinguish decoded responseTaskEnvelope values from direct
task responses: when an envelope is detected, return envelope.Data only for a
successful code with a non-empty task ID, and return an error for non-success
codes or missing task data instead of falling back to a zero-value responseTask.
Add a regression test covering an error envelope and verify ParseTaskResult does
not report it as in_progress.

Comment on lines +97 to +101
type TaskAdaptor struct {
taskcommon.BaseBilling
baseURL string
apiKey string
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# BaseBilling defaults
fd -t f 'billing' relay/channel/task/taskcommon --exec cat -n
# Compare with an adaptor that prices duration
rg -nP -A20 'func \(a \*(TaskAdaptor|ZLHubTaskAdaptor)\) EstimateBilling' relay/channel/task

Repository: QuantumNous/new-api

Length of output: 7267


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== taskcommon billing files =="
fd -t f . relay/channel/task/taskcommon | sort

echo
echo "== BaseBilling implementation/usages =="
rg -n "type BaseBilling|func .*BaseBilling|EstimateBilling|AdjustBilling" relay/channel/task/taskcommon relay/task relay/common -g '*.go'

echo
echo "== dreambrand task adaptor relevant sections =="
wc -l relay/channel/task/dreambrand/adaptor.go
sed -n '1,180p' relay/channel/task/dreambrand/adaptor.go
sed -n '180,340p' relay/channel/task/dreambrand/adaptor.go

Repository: QuantumNous/new-api

Length of output: 1234


Override EstimateBilling to include Dreambrand video ratios.

TaskAdaptor relies on taskcommon.BaseBilling.EstimateBilling, which documents that it returns nil and falls back to the base model price. Since Dreambrand accepts duration/resolution inputs that affect upstream cost, add the appropriate duration/resolution quotas via the quota helpers so pricing is applied correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/task/dreambrand/adaptor.go` around lines 97 - 101, Add a
TaskAdaptor.EstimateBilling override that uses the quota helpers to include
Dreambrand’s duration and resolution inputs when calculating video billing. Do
not rely on BaseBilling.EstimateBilling’s nil result; preserve the existing base
model price fallback while applying the appropriate duration/resolution quotas.

Comment on lines +50 to +57
func NormalizeDocsLink(link string) string {
link = strings.TrimSpace(link)
if link == "" || strings.Contains(link, "docs.newapi.pro") {
return DefaultDocsLink
}
return link
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the destination when normalizing legacy documentation links.

Any URL containing docs.newapi.pro is collapsed to the default Chinese authentication page, so legacy API, pricing, locale-specific, or custom documentation links are redirected to the wrong content. Restrict replacement to known default values or explicitly map old paths and locales to their new equivalents.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@setting/operation_setting/general_setting.go` around lines 50 - 57, Update
NormalizeDocsLink so it no longer replaces every URL containing docs.newapi.pro
with DefaultDocsLink. Preserve legacy API, pricing, locale-specific, and custom
destinations by replacing only recognized old default values or explicitly
mapping known legacy paths/locales to their corresponding new links; keep
empty-link normalization unchanged.

Comment on lines +66 to +82
href='https://doc.deeprouterai.com/zh/docs/guide/feature-guide/user/auth'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
{t('关于项目')}
</a>
<a
href='https://docs.newapi.pro/support/community-interaction/'
href='https://doc.deeprouterai.com/zh/docs/guide/feature-guide/user/personal-setting'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
{t('联系我们')}
</a>
<a
href='https://docs.newapi.pro/wiki/features-introduction/'
href='https://doc.deeprouterai.com/zh/docs/guide/feature-guide/user/pricing'

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive the documentation locale instead of hard-coding /zh/.

This i18n-enabled footer now sends every user to Chinese guides, although the PR adds English and Japanese guide trees. Build these URLs from the active locale or a shared documentation URL helper.

Also applies to: 98-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/classic/src/components/layout/Footer.jsx` around lines 66 - 82, Update
the documentation links in the Footer component, including the related links
around the pricing entry, to derive the locale from the active i18n state or an
existing shared documentation URL helper instead of hard-coding `/zh/`. Preserve
each link’s existing guide path and behavior while routing users to the matching
localized documentation tree.

Comment on lines +48 to +115
request: `curl https://api.deeprouterai.com/v1/chat/completions \\
-H "Authorization: Bearer dr-********" \\
-H "Content-Type: application/json" \\
-d '{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "Plan my launch" }]
}'`,
response: `{
"choices": [{ "message": { "content": "Launch plan routed." } }],
"usage": { "total_tokens": 842 }
}`,
tokens: 842,
latency: 142,
cost: '$0.0021',
accent: 'emerald',
},
{
id: 'responses',
label: 'Responses',
method: 'POST',
endpoint: '/v1/responses',
headers: ['"Authorization: Bearer sk-••••"'],
request: ['"model": "your-model",', '"input": "..."'],
response: [
'{',
' "output": [{ "type": "output_text", "text": <text> }],',
' "usage": { "total_tokens": <tokens> }',
'}',
],
responseHighlights: ['<text>', '<tokens>'],
tokens: 31,
id: 'claude',
label: 'Claude',
endpoint: '/v1/chat/completions',
request: `curl https://api.deeprouterai.com/v1/chat/completions \\
-H "Authorization: Bearer dr-********" \\
-H "Content-Type: application/json" \\
-d '{
"model": "anthropic/claude-3-5-sonnet",
"messages": [{ "role": "user", "content": "Review this spec" }]
}'`,
response: `{
"choices": [{ "message": { "content": "Spec review routed." } }],
"usage": { "total_tokens": 1180 }
}`,
tokens: 1180,
latency: 168,
cost: '$0.0035',
accent: 'amber',
},
{
id: 'claude',
label: 'Claude',
method: 'POST',
endpoint: '/v1/messages',
headers: ['"x-api-key: sk-••••"', '"anthropic-version: 2023-06-01"'],
request: [
'"model": "your-model",',
'"max_tokens": 1024,',
'"messages": [',
' { "role": "user", "content": "..." }',
']',
],
response: [
'{',
' "content": [{ "type": "text", "text": <text> }],',
' "usage": { "input_tokens": <in>, "output_tokens": <out> }',
'}',
],
responseHighlights: ['<text>', '<in>', '<out>'],
tokens: 29,
id: 'gemini',
label: 'Gemini',
endpoint: '/v1/chat/completions',
request: `curl https://api.deeprouterai.com/v1/chat/completions \\
-H "Authorization: Bearer dr-********" \\
-H "Content-Type: application/json" \\
-d '{
"model": "google/gemini-2.5-pro",
"messages": [{ "role": "user", "content": "Summarize this file" }]
}'`,
response: `{
"choices": [{ "message": { "content": "Summary generated." } }],
"usage": { "total_tokens": 736 }
}`,
tokens: 736,
latency: 156,
cost: '$0.0018',
accent: 'blue',
},
{
id: 'gemini',
label: 'Gemini',
method: 'POST',
endpoint: '/v1beta/models/{model}:generateContent',
headers: ['"x-goog-api-key: sk-••••"'],
request: [
'"contents": [',
' { "role": "user",',
' "parts": [{ "text": "..." }] }',
']',
],
response: [
'{',
' "candidates": [{ "content": { "parts": [{ "text": <text> }] } }],',
' "usageMetadata": { "totalTokenCount": <tokens> }',
'}',
],
responseHighlights: ['<text>', '<tokens>'],
tokens: 25,
latency: 93,
id: 'seedance',
label: 'Seedance',
endpoint: '/v1/video/generations',
request: `curl https://api.deeprouterai.com/v1/video/generations \\
-H "Authorization: Bearer dr-********" \\
-H "Content-Type: application/json" \\
-d '{
"model": "doubao-seedance-2.0-fast",
"prompt": "Create a product video",
"metadata": { "duration": 5, "ratio": "16:9" }
}'`,

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not hard-code the API host in copyable requests.

A self-hosted user who replaces the placeholder token and runs these examples sends that token to api.deeprouterai.com, rather than their configured instance. Source the displayed base URL from the deployment’s VITE_ configuration.

As per coding guidelines, “环境变量使用 .env 并以 VITE_ 为前缀,代码中不得硬编码配置或密钥.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/home/components/hero-terminal-demo.tsx` around lines
48 - 115, The request examples in the demo data hard-code api.deeprouterai.com,
so update the surrounding hero-terminal demo configuration to derive the
displayed API base URL from the deployment’s VITE_ environment configuration and
interpolate it into every copyable request, including chat and video endpoints.
Remove the hard-coded host while preserving the existing paths and request
formatting.

Source: Coding guidelines

Comment on lines +1306 to +1352
<div className='grid gap-6 md:grid-cols-2'>
<FormField
control={form.control}
name='AlipayPrivateKey'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Alipay private key')}</FormLabel>
<FormControl>
<Textarea
rows={8}
placeholder={t('Paste your private key here')}
autoComplete='off'
{...field}
onChange={(event) => field.onChange(event.target.value)}
/>
</FormControl>
<FormDescription>
{t('RSA2 private key used to sign payment requests')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name='AlipayPublicKey'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Alipay public key')}</FormLabel>
<FormControl>
<Textarea
rows={8}
placeholder={t('Paste Alipay public key here')}
autoComplete='off'
{...field}
onChange={(event) => field.onChange(event.target.value)}
/>
</FormControl>
<FormDescription>
{t('Alipay public key used to verify callbacks')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how AlipayPrivateKey is populated/returned by the backend settings endpoint
fd -e go . -x rg -nP -C3 'AlipayPrivateKey' {}

Repository: QuantumNous/new-api

Length of output: 2904


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate settings endpoint/type files =="
fd -e go . | rg 'setting|common|model|server|options'

echo
echo "== search settings CommonOptionMap and getter/update endpoints =="
rg -n -C 4 'CommonOptionMap|Get\(|Set\(|AlipayPrivateKey|AlipayAppId|AlipayPublicKey|AlipayGateway' -g '*.go' .

Repository: QuantumNous/new-api

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files containing CommonOptionMap =="
rg -n -l -C 5 'CommonOptionMap\s*=' setting model common . --glob '*.go' | head -100

echo
echo "== file snippets around CommonOptionMap definitions =="
for f in $(rg -l 'CommonOptionMap\s*=' setting model common . --glob '*.go' | head -20); do
  echo "--- $f"
  rg -n -C 15 'CommonOptionMap|AlipayPrivateKey|AlipayAppId|AlipayGateway|AlipayPublicKey|EpayKey|StripeApiSecret|StripeWebhookSecret|CreemApiKey|CreemWebhookSecret' "$f" | head -220
done

echo
echo "== files containing GetOption / Get settings route patterns =="
rg -n -l -C 3 'func GetOption|GetOption|common\.OptionMap|/.*settings|Get\(\).*Option|Set\(\).*Option' --glob '*.go' . | head -100

echo
echo "== read likely model/common setting definitions =="
fd -e go model common setting | sed -n '1,200p'

Repository: QuantumNous/new-api

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all repository files containing CommonOptionMap =="
rg -n -C 12 'CommonOptionMap' . --glob '*.go' | sed -n '1,260p'

echo
echo "== all repository files containing GetOption / Get settings API =="
rg -n -C 12 'func GetOption|GetOption|GetSettings|Settings|system-settings|system_settings' --glob '*.go' . | sed -n '1,360p'

Repository: QuantumNous/new-api

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files containing CommonOptionMap =="
grep -RIn -C 12 'CommonOptionMap' . --include='*.go' | sed -n '1,260p' || true

echo
echo "== repository files containing GetOption / settings API =="
grep -RIn -C 12 'func GetOption\|GetOption\|GetSettings\|Settings\|system-settings\|system_settings\|GetSettings\|POST .*settings\|GET .*settings' . --include='*.go' | sed -n '1,360p' || true

echo
echo "== candidate Go files in likely directories =="
find . -maxdepth 3 -type f \( -path './setting/*' -o -path './model/*' -o -path './common/*' \) -name '*.go' 2>/dev/null | sed -n '1,200p'

Repository: QuantumNous/new-api

Length of output: 27523


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== model/option.go relevant sections =="
rg -n -C 20 'func .*Option|CommonOptionMap|OptionMap|AlipayPrivateKey|AlipayAppId|AlipayGateway|AlipayPublicKey|EpayKey|StripeApiSecret|StripeWebhookSecret|CreemApiKey|CreemWebhookSecret|GetOption|SetOption' model/option.go

echo
echo "== controller option implementation files =="
rg -n -l -C 4 'GetOptions|UpdateOption' --glob '*.go' .

echo
echo "== controller snippets =="
for f in $(rg -l 'GetOptions\(|UpdateOption\(' --glob '*.go' .); do
  echo "--- $f"
  rg -n -C 30 'GetOptions\(|UpdateOption\(' "$f" | sed -n '1,260p'
done

echo
echo "== frontend api call to /api/option GET path =="
rg -n -C 12 'api/option|/api/option|GET.*option|useQuery|queryKey.*option|AlipayPrivateKey|AlipayPublicKey' web --glob '*.ts' --glob '*.tsx' | sed -n '1,360p'

Repository: QuantumNous/new-api

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== common/options initialization for Alipay and sensitive options =="
rg -n -C 4 'Alipay(PrivateKey|PublicKey)|StripeApiSecret|StripeWebhookSecret|Creem(ApiKey|WebhookSecret)|Token|Secret|Key' --glob '*.go' common setting/model_setting common --glob '*.go' | sed -n '1,320p'

echo
echo "== exact OptionMap entries around Alipay in model/option.go =="
sed -n '73,98p' model/option.go | cat -n

echo
echo "== exact OptionMap exclusion logic in controller/option.go =="
sed -n '78,112p' controller/option.go | cat -n

echo
 Echo "== frontend component around Alipay fields =="
sed -n '1300,1348p' web/default/src/features/system-settings/integrations/payment-settings-section.tsx | cat -n

Repository: QuantumNous/new-api

Length of output: 19328


Treat Alipay keys as masked "update-only" secrets.

AlipayPrivateKey and AlipayPublicKey are stored in common.OptionMap and included in /api/option responses unless the key suffix is Key, so an AlipayPrivateKey value can be sent to the frontend. In this form it is displayed in plaintext via the Alipay private key section, and AlipayPublicKey is already bound to a plain Textarea. Keep both fields on the same "leave blank unless updating" pattern as the other secrets: omit/store a blank value by default, and only push a non-blank value when the admin explicitly changes it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/system-settings/integrations/payment-settings-section.tsx`
around lines 1306 - 1352, Update the AlipayPrivateKey and AlipayPublicKey fields
in the payment settings form to use the existing masked update-only secret
pattern: display blank values by default, avoid exposing stored credentials, and
submit/store them only when the administrator enters a non-blank replacement.
Reuse the established secret-field behavior and ensure both fields follow it
consistently.

Comment on lines +105 to +107
if (topupInfo.enable_alipay_topup) {
return PAYMENT_TYPES.ALIPAY_OFFICIAL
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate files =="
fd -a 'payment.ts|recharge-form-card.tsx' . | sed 's#^\./##'

echo "== payment.ts outline =="
file_payment="$(fd 'payment.ts' web/default/src/features/wallet/lib | head -n1)"
echo "PAYMENT_FILE=$file_payment"
ast-grep outline "$file_payment" || true

echo "== relevant payment.ts lines =="
wc -l "$file_payment"
sed -n '1,180p' "$file_payment" | cat -n

echo "== recharge form card outline/lines =="
file_form="$(fd 'recharge-form-card.tsx' web/default/src/features/wallet/components | head -n1)"
echo "FORM_FILE=$file_form"
wc -l "$file_form"
sed -n '1,220p' "$file_form" | cat -n

echo "== Search for pay_methods/enable_alipay_topup/usages =="
rg -n "enable_alipay_topup|enable_online_topup|pay_methods|ALIPAY_OFFICIAL|ALIPAY|payMethod|payment type|topupInfo" web/default/src/features/wallet -S

Repository: QuantumNous/new-api

Length of output: 25713


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== use-topup-info parsing/payment methods slice =="
wc -l web/default/src/features/wallet/hooks/use-topup-info.ts
sed -n '60,210p' web/default/src/features/wallet/hooks/use-topup-info.ts | cat -n

echo "== wallet index default/payment selection =="
wc -l web/default/src/features/wallet/index.tsx
sed -n '120,230p' web/default/src/features/wallet/index.tsx | cat -n
sed -n '260,330p' web/default/src/features/wallet/index.tsx | cat -n

echo "== recharge form card payment method rendering =="
sed -n '290,430p' web/default/src/features/wallet/components/recharge-form-card.tsx | cat -n

echo "== PaymentMethod and topup-info related type definitions =="
sed -n '85,160p' web/default/src/features/wallet/types.ts | cat -n
sed -n '1,40p' web/default/src/features/wallet/lib/ui.tsx | cat -n

echo "== read-only structural check: exact default/payment-flow invariants =="
python3 - <<'PY'
from pathlib import Path
p = Path('web/default/src/features/wallet/lib/payment.ts')
s = p.read_text()
checks = {
    "getDefaultPaymentType_prioritises_enable_alipay_topup": s.index('if (topupInfo.enable_alipay_topup)') < s.index('if (topupInfo.pay_methods?.length > 0)'),
    "getDefaultPaymentType_returns_alipay_official_on_enable_flag": "'ALIPAY_OFFICIAL'" in s[s.index('if (topupInfo.enable_alipay_topup)'):s.index('if (topupInfo.pay_methods'))],
}
print(checks)
PY

Repository: QuantumNous/new-api

Length of output: 25740


Align Alipay enablement with an actual renderable method.

enable_alipay_topup now makes the recharge form visible and selects ALIPAY_OFFICIAL first, but payment buttons are only rendered from pay_methods or Waffo methods. If official Alipay is not present in pay_methods and no Waffo methods are available, the UI shows an empty/Payments unavailable state while pre-selecting ALIPAY_OFFICIAL, so the selection can never be confirmed.

  • Either render an explicit official Alipay option or require an actual available method when this flag is used.
📍 Affects 2 files
  • web/default/src/features/wallet/lib/payment.ts#L105-L107 (this comment)
  • web/default/src/features/wallet/components/recharge-form-card.tsx#L127-L129
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/wallet/lib/payment.ts` around lines 105 - 107, Align
the enable_alipay_topup handling in payment.ts and recharge-form-card.tsx so
selecting PAYMENT_TYPES.ALIPAY_OFFICIAL is only possible when a corresponding
renderable payment option exists. Either add an explicit official Alipay button
to the recharge form or require the method to be present in pay_methods/Waffo
methods before showing the form and selecting it; update both referenced sites
consistently.

@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.

Caution

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

⚠️ Outside diff range comments (1)
relay/common/relay_utils.go (1)

81-119: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Multipart submissions silently drop resolution/ratio/generate_audio/watermark/content.

isKnownTaskField now marks resolution, ratio, generate_audio, watermark, and content as known (excluding them from the generic req.Metadata fallback loop), but validateMultipartTaskRequest never reads these keys into req.Resolution/req.Ratio/req.GenerateAudio/req.Watermark/req.Content. Previously these unknown keys landed in req.Metadata, which downstream code (e.g. DreamBrand's resolveVideoMetadata) falls back to; now, for multipart requests, the values are captured nowhere — neither the typed field nor Metadata — so they are silently lost.

🐛 Proposed fix to bind the new fields for multipart requests
 	if images := formData["images"]; len(images) > 0 {
 		req.Images = images
 	}
 
+	if v := formData.Get("resolution"); v != "" {
+		req.Resolution = &v
+	}
+	if v := formData.Get("ratio"); v != "" {
+		req.Ratio = &v
+	}
+	if v := formData.Get("generate_audio"); v != "" {
+		if b, err := strconv.ParseBool(v); err == nil {
+			req.GenerateAudio = &b
+		}
+	}
+	if v := formData.Get("watermark"); v != "" {
+		if b, err := strconv.ParseBool(v); err == nil {
+			req.Watermark = &b
+		}
+	}
+
 	for key, values := range formData {

Also applies to: 184-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/common/relay_utils.go` around lines 81 - 119, Update
validateMultipartTaskRequest to explicitly read the known multipart form keys
resolution, ratio, generate_audio, watermark, and content into the corresponding
TaskSubmitReq fields before the generic metadata loop. Preserve the existing
parsing behavior and ensure these values are no longer dropped when
isKnownTaskField excludes them from req.Metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@relay/common/relay_utils.go`:
- Around line 81-119: Update validateMultipartTaskRequest to explicitly read the
known multipart form keys resolution, ratio, generate_audio, watermark, and
content into the corresponding TaskSubmitReq fields before the generic metadata
loop. Preserve the existing parsing behavior and ensure these values are no
longer dropped when isKnownTaskField excludes them from req.Metadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d3ec826a-a4ad-4990-a9ab-6263b78ae10c

📥 Commits

Reviewing files that changed from the base of the PR and between 38f5634 and c689a63.

📒 Files selected for processing (6)
  • relay/channel/dreambrand/adaptor.go
  • relay/channel/dreambrand/adaptor_test.go
  • relay/channel/task/dreambrand/adaptor.go
  • relay/channel/task/dreambrand/adaptor_test.go
  • relay/common/relay_info.go
  • relay/common/relay_utils.go

@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)
relay/channel/dreambrand/adaptor_test.go (1)

38-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use testify/require for fatal assertions.

This new backend test uses t.Fatalf, but repository guidelines require testify/require for fatal assertions. Replace these comparisons with require.Equal(...) and import testify/require.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/dreambrand/adaptor_test.go` around lines 38 - 48, Update
TestInitUsesImagePollingDefaults to import testify/require and replace both
t.Fatalf comparison blocks with require.Equal assertions for pollInterval and
pollTimeout, preserving the existing expected values and failure behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@relay/channel/dreambrand/adaptor_test.go`:
- Around line 38-48: Update TestInitUsesImagePollingDefaults to import
testify/require and replace both t.Fatalf comparison blocks with require.Equal
assertions for pollInterval and pollTimeout, preserving the existing expected
values and failure behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1decb60e-bf25-44cd-ac96-4f36cbd89b92

📥 Commits

Reviewing files that changed from the base of the PR and between d97a5cb and 5f2eba1.

📒 Files selected for processing (2)
  • relay/channel/dreambrand/adaptor.go
  • relay/channel/dreambrand/adaptor_test.go

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.

1 participant