Skip to content

Codex/sync upstream 20260709 - #6636

Closed
Aunt-Lee wants to merge 26 commits into
QuantumNous:mainfrom
Aunt-Lee:codex/sync-upstream-20260709
Closed

Codex/sync upstream 20260709#6636
Aunt-Lee wants to merge 26 commits into
QuantumNous:mainfrom
Aunt-Lee:codex/sync-upstream-20260709

Conversation

@Aunt-Lee

@Aunt-Lee Aunt-Lee commented Aug 4, 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 a public subscription plans estimator with localized plan details, pricing, capacity estimates, and purchase flows.
    • Redesigned the homepage with configurable branding, pricing information, FAQs, trust signals, and refreshed calls to action.
    • Added model pricing views, provider and ratio filtering, and API URL copy controls.
    • Added localized FAQ content and broader language support.
    • Added public subscription plan access and Docker image publishing automation.
  • Improvements

    • Simplified pricing filters and theme customization options.
    • Updated currency formatting, typography, layouts, and visual styling.
    • Removed wallet redemption-code functionality.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The pull request adds a public plans experience, restructures pricing and homepage flows, introduces localized content and currency formatting, removes font customization, updates theme styling, adds API URL displays, and publishes Docker images to GHCR.

Changes

Public plans and localized subscription flows

Layer / File(s) Summary
Public plans route and estimator
router/api-router.go, web/default/src/routes/plans.tsx, web/default/src/features/subscription-plan-estimator.tsx, web/default/src/routeTree.gen.ts
Adds unauthenticated plan retrieval and a /plans route with plan selection, capacity estimates, payment handling, and post-purchase refreshes.
Localized content and translations
web/default/src/lib/localized-content.ts, web/default/src/components/content-language-select.tsx, web/default/src/features/system-settings/content/faq-section.tsx, web/default/src/features/dashboard/*, web/default/src/i18n/*
Adds localized content contracts, FAQ translation editing, localized FAQ rendering, language selection, and new translations across supported locales.
Billing and wallet presentation
web/default/src/lib/currency.ts, web/default/src/features/subscriptions/*, web/default/src/features/wallet/*
Uses shared currency formatting, localizes plan fields, adds wallet display controls, and hides redemption and available-plan sections where configured.

Pricing and homepage flows

Layer / File(s) Summary
Pricing page and model list
web/default/src/features/pricing/*
Simplifies filters to vendor and model criteria, adds provider and group-ratio tabs, and renders calculated model pricing cards.
Homepage and API key displays
web/default/src/features/home/*, web/default/src/features/keys/index.tsx
Replaces the homepage composition with custom content, model pricing, plans, FAQs, trust signals, and copyable server URLs.
Theme and interface updates
web/default/src/lib/theme-customization.ts, web/default/src/context/theme-customization-provider.tsx, web/default/src/styles/*, web/default/src/components/*
Removes font customization and legacy density options, updates theme tokens and gradients, and adjusts shared typography and layout.
Build and supporting integration
.github/workflows/ghcr-build.yml, web/default/src/hooks/use-top-nav-links.ts, web/default/src/features/home/components/*
Adds GHCR publishing on main-branch pushes or manual dispatch, adds the plans navigation link, and applies small homepage component updates.

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

Possibly related PRs

Suggested reviewers: calcium-ion

Poem

A rabbit hops through plans so bright,
With prices sorted just right.
Local words bloom, themes softly glow,
GHCR images build and go.
“Click your plan,” the bunny sings,
While fresh UI grows tiny wings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.42% 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 identifies the main purpose as synchronizing upstream changes, which matches the broad frontend and build updates in the pull request.
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/sync-upstream-20260709
🧪 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.

Warning

⚠️ This pull request shows signs of AI-generated slop (description_diff_mismatch, ai_padded_prose). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@seefs001 seefs001 closed this Aug 4, 2026

@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: 6

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 (4)
web/default/src/features/pricing/components/pricing-toolbar.tsx (1)

262-282: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove unreachable filter code or restore a caller.

web/default/src/features/pricing/index.tsx no longer imports PricingToolbar, PricingSidebar, or useFilters; the current route only uses ModelDetailsDrawer and PricingModelList. As long as the route remains this way, these exports are unreachable.

🤖 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/pricing/components/pricing-toolbar.tsx` around lines
262 - 282, Remove the unreachable filter implementation centered on
PricingToolbar, including its PricingSidebar and useFilters dependencies, or
restore a caller from the pricing route so these exports are used. Keep the
current ModelDetailsDrawer and PricingModelList flow unchanged if the filter UI
is removed.
web/default/src/features/home/index.tsx (1)

259-830: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split this component; the file is far over the size limit.

Home now spans roughly 570 lines inside one component and mixes three data queries, content loading, clipboard handling, and five distinct UI sections. Extract the price comparison table, the subscription plan tabs, and the FAQ accordion into sibling components, and move the query and content loading into hooks.

As per coding guidelines: "组件文件超过约 200 行时,应考虑拆分子组件或提取自定义 Hook".

🤖 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/index.tsx` around lines 259 - 830, Split the
oversized Home component by extracting the model pricing table, subscription
plan tabs, and FAQ accordion into sibling components, preserving their current
rendering and behavior. Move the home status, pricing, subscription-plan, and
custom home-page content loading logic into focused custom hooks, then keep Home
responsible only for composing sections and shared actions such as URL copying.
Use the existing symbols modelPricingRows, subscriptionPlanGroups, faqItems, and
displayHomePageContent as extraction anchors.

Source: Coding guidelines

web/default/src/features/subscriptions/components/dialogs/user-subscriptions-dialog.tsx (1)

255-277: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the subscription-specific formatter for every price_amount.

Both admin views bypass the exchange-rate normalization in formatSubscriptionPlanPrice. This causes subscription prices to disagree with the purchase flow when usdExchangeRate is not 1.

  • web/default/src/features/subscriptions/components/dialogs/user-subscriptions-dialog.tsx#L255-L277: replace both formatLocalCurrencyAmount calls and its import with formatSubscriptionPlanPrice.
  • web/default/src/features/subscriptions/components/subscriptions-columns.tsx#L27-L75: replace the import and price-cell call with formatSubscriptionPlanPrice.
🤖 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/subscriptions/components/dialogs/user-subscriptions-dialog.tsx`
around lines 255 - 277, Update
web/default/src/features/subscriptions/components/dialogs/user-subscriptions-dialog.tsx
lines 255-277 by replacing both formatLocalCurrencyAmount calls and its import
with formatSubscriptionPlanPrice. Also update
web/default/src/features/subscriptions/components/subscriptions-columns.tsx
lines 27-75 by replacing the formatter import and price-cell call with
formatSubscriptionPlanPrice.
web/default/src/features/wallet/components/subscription-plans-card.tsx (1)

516-640: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exclude hidden plans from the card availability state.

hideAvailablePlans hides the grid, but isAvailable and the early return still use plans.length. When the user has no subscriptions, the wallet can show an empty subscription panel and retain the two-column layout only because hidden plans exist.

Derive a hasVisiblePlans value from !hideAvailablePlans && plans.length > 0. Use it in isAvailable and the early-return condition.

🤖 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/components/subscription-plans-card.tsx`
around lines 516 - 640, Derive a hasVisiblePlans value from !hideAvailablePlans
&& plans.length > 0, then use it wherever availability is determined, including
isAvailable and the early-return condition. Keep the existing plans rendering
logic unchanged while ensuring hidden plans do not count toward the wallet’s
available subscription state or layout.
🟡 Minor comments (26)
web/default/src/i18n/locales/fr.json-692-692 (1)

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

Use explicit and consistent cache-hit terminology.

The current translations do not clearly distinguish cache hits from general cache operations.

  • web/default/src/i18n/locales/fr.json#L692-L692: translate "Cache Hit" as "Hit de cache" or "Accès au cache".
  • web/default/src/i18n/locales/fr.json#L944-L944: translate "Cache Hit Rate" as "Taux de succès du cache" or the project’s chosen cache-hit term.
🤖 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 692, Update the French
translations for "Cache Hit" and "Cache Hit Rate" in
web/default/src/i18n/locales/fr.json at lines 692 and 944 to use explicit,
consistent cache-hit terminology, such as "Hit de cache" and the corresponding
"Taux de succès du cache" phrasing.
web/default/src/i18n/locales/fr.json-4581-4581 (1)

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

Keep “prompt words” in the translation.

The source says that simulated prompt words are added to requests. "invites simulées" means simulated prompts and omits the word-level detail. Use a translation such as "des mots de prompt simulés" or "des termes simulés dans les prompts".

🤖 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 4581, Update the French
translation for the key containing “To avoid account bans…” so it explicitly
preserves the word-level meaning of “prompt words,” using wording such as “des
mots de prompt simulés” or an equivalent phrase rather than “invites simulées.”
web/default/src/i18n/locales/ja.json-2911-2911 (1)

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

Translate “substitutions” accurately.

偽装 means falsification or impersonation. It does not mean model substitution. Use wording such as モデルのダウングレードや置き換えなし to preserve the source meaning.

Proposed fix
-    "No model downgrades or substitutions": "モデルの劣化・偽装なし",
+    "No model downgrades or substitutions": "モデルのダウングレードや置き換えなし",
🤖 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/ja.json` at line 2911, Update the Japanese
translation for the “No model downgrades or substitutions” key to accurately
express model downgrades and replacements, replacing 偽装 with wording such as
ダウングレードや置き換えなし while preserving the source meaning.
web/default/src/i18n/locales/ru.json-1374-1374 (1)

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

Use a natural percentage status message.

Результаты проверки не на 100 % is awkward Russian. Use Результаты проверки не достигают 100 %.

🤖 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 1374, Update the Russian
translation for the “Detection Results Are Not 100%” key to use the natural
phrasing “Результаты проверки не достигают 100 %”, leaving the key and other
translations unchanged.
web/default/src/i18n/locales/ru.json-944-944 (1)

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

Preserve the “rate” metric.

The source contains Cache Hit Rate. The current translation means cache hits, not the hit rate.

Use Параллельность и доля попаданий в кэш.

🤖 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 944, Update the Russian
translation for the “Concurrency & Cache Hit Rate” locale key to use
“Параллельность и доля попаданий в кэш”, preserving the metric’s meaning as a
hit rate rather than cache hits.
web/default/src/i18n/locales/ru.json-68-68 (1)

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

Translate Token consistently.

These Russian translations keep Token in English. This locale already uses Токен and токены for the same term.

Proposed wording
-    "{{plan}} Token capacity": "Лимит Token для {{plan}}",
+    "{{plan}} Token capacity": "Лимит токенов для {{plan}}",

-    "Maximum input Tokens": "Максимум входных Token",
+    "Maximum input Tokens": "Максимум входных токенов",

-    "Maximum output Tokens": "Максимум выходных Token",
+    "Maximum output Tokens": "Максимум выходных токенов",

-    "Select a plan to see the maximum standalone input or output Token capacity for each model.": "Выберите план, чтобы увидеть максимум входных или выходных Token при отдельном использовании каждой модели.",
+    "Select a plan to see the maximum standalone input or output Token capacity for each model.": "Выберите план, чтобы увидеть максимальный лимит входных или выходных токенов при отдельном использовании каждой модели.",

Also applies to: 2591-2591, 2595-2595, 4009-4009

🤖 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 68, Update the Russian locale
entries for “{{plan}} Token capacity” and the additional referenced Token
strings to use the established Russian translations “Токен” or “токены”
consistently, replacing the remaining English “Token” while preserving each
message’s existing placeholders and meaning.
web/default/src/i18n/locales/vi.json-1477-1477 (1)

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

Use one Vietnamese term for dynamic pricing.

The new Dynamic pricing entry uses Giá động. The existing Dynamic Pricing entry at Line 1478 uses Giá linh hoạt. These keys differ only by capitalization and can render the same concept. Use one Vietnamese term for both entries.

🤖 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 1477, Use a single Vietnamese
translation for the “Dynamic pricing” and “Dynamic Pricing” entries in the
locale object, updating the new “Dynamic pricing” value to match the existing
“Dynamic Pricing” translation “Giá linh hoạt”.
web/default/src/i18n/locales/vi.json-2698-2698 (1)

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

Use a distinct translation for Model Square.

Line 2698 maps Model Square to Giá mô hình, the same value used by Model Price at Line 2684. This hides the distinction between the two labels. The related model square text at Line 903 also uses different terminology. Choose one dedicated Vietnamese label and use it consistently.

🤖 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 2698, Update the `Model Square`
translation in the Vietnamese locale to use a distinct Vietnamese label from the
`Model Price` value, matching the terminology used by the related `model square`
translation elsewhere in the file.
web/default/src/i18n/locales/zh-TW.json-1382-1382 (1)

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

Complete the translation for Direct access to official providers.

直連官方的 is incomplete and omits providers. It can render as unfinished UI text.

-    "Direct access to official providers": "直連官方的",
+    "Direct access to official 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/i18n/locales/zh-TW.json` at line 1382, Update the zh-TW
translation value for the “Direct access to official providers” key to include a
complete Traditional Chinese equivalent for “providers,” replacing the
incomplete “直連官方的” text while preserving the key and JSON structure.
web/default/src/i18n/locales/zh-TW.json-2911-2911 (1)

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

Preserve the model-routing claim.

不降智、不造假 does not directly state “no model downgrades or substitutions.” Use wording that preserves the source meaning.

-    "No model downgrades or substitutions": "不降智、不造假",
+    "No model downgrades or substitutions": "不降級、不替換模型",
🤖 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-TW.json` at line 2911, Update the zh-TW
translation for “No model downgrades or substitutions” to explicitly convey that
models are neither downgraded nor substituted, replacing “不降智、不造假” while
preserving the source meaning.
web/default/src/i18n/locales/zh-TW.json-4242-4242 (1)

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

Translate Start for Free as an action.

免費接入 means free access or connection. It does not clearly tell the user to start using the service.

-    "Start for Free": "免費接入",
+    "Start for Free": "免費開始使用",
🤖 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-TW.json` at line 4242, Update the `Start for
Free` translation in the locale resource to use Traditional Chinese wording that
clearly expresses an action to begin using the service for free, rather than
free access or connection.
web/default/src/i18n/locales/zh-TW.json-68-68 (1)

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

Use a capacity term for {{plan}} Token capacity.

用量 means usage. It does not clearly describe the plan’s available capacity. Use 容量 or 上限.

Proposed fix
-    "{{plan}} Token capacity": "{{plan}} Token 用量",
+    "{{plan}} Token capacity": "{{plan}} Token 容量",
🤖 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-TW.json` at line 68, Update the zh-TW
translation for "{{plan}} Token capacity" to use a capacity term such as "容量" or
"上限" instead of "用量", while preserving the {{plan}} placeholder.
web/default/src/features/pricing/index.tsx-96-132 (1)

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

The ratioOptions.length > 0 guard is always true.

Lines 117-119 insert a default entry with ratio 1 and an empty group set when no ratio qualifies. ratioOptions therefore never has length 0, so the guard on line 215 always passes. The Group section then renders one tab that shows only (1x) with no group name, because option.groups is empty.

Return an empty array when no group ratio qualifies, and keep activeRatio on the existing ?? 1 fallback.

🐛 Proposed change
-    if (groupsByRatio.size === 0) {
-      groupsByRatio.set(1, new Set())
-    }
-
     return [...groupsByRatio.entries()]
🤖 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/pricing/index.tsx` around lines 96 - 132, Update the
ratioOptions useMemo to return an empty array when groupsByRatio has no
qualifying entries instead of inserting the default ratio-1 option. Preserve the
existing sorting and mapping for valid groups, and keep activeRatio’s
availableRatios[0] ?? 1 fallback unchanged so the Group section does not render
an empty tab.
web/default/src/features/pricing/components/pricing-model-list.tsx-93-93 (1)

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

Translate the token unit label.

'/M Token' is user-facing text and it is hardcoded in five places (lines 93, 109, 123, 140, 155). The request unit on line 169 already uses t(). Move the token unit to an i18n key and reuse it.

As per coding guidelines: "面向用户的文案必须使用 i18n;React 组件使用 useTranslation()t()".

🌐 Proposed change
+const TOKEN_UNIT_KEY = '/M Token'
+
 export function PricingModelList(props: PricingModelListProps) {
   const { t } = useTranslation()
+  const tokenUnitLabel = t(TOKEN_UNIT_KEY)

Then replace each unit: '/M Token' with unit: tokenUnitLabel, and add the key to every locale file.

🤖 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/pricing/components/pricing-model-list.tsx` at line
93, In the pricing model list component, use the existing React i18n pattern via
useTranslation() and t() to create a shared token unit label, then replace all
five hardcoded unit: '/M Token' values with that label. Add the corresponding
translation key and localized value to every locale file.

Source: Coding guidelines

web/default/src/features/keys/index.tsx-103-119 (1)

103-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Derive the image API URL from baseUrl and hide the row when the value is empty.

baseUrl falls back to window.location.origin when serverAddress is empty. buildImageApiUrl receives serverAddress instead, so it returns '' in that case. The second row then shows an empty <code> element next to an enabled copy button, and the button copies an empty string. Self-hosted deployments that do not set the server address see this.

🐛 Proposed change
   const imageApiUrl = useMemo(
-    () => getImageApiUrl(status) || buildImageApiUrl(serverAddress),
-    [serverAddress, status]
+    () => getImageApiUrl(status) || buildImageApiUrl(baseUrl),
+    [baseUrl, status]
   )
 
   return (
     <div className='mb-3 flex flex-col gap-2 px-1 sm:flex-row sm:items-center'>
       <ApiUrlRow label='BaseUrl' value={baseUrl} />
-      <div className='bg-border hidden h-5 w-px sm:block' />
-      <ApiUrlRow label={t('Image API URL')} value={imageApiUrl} />
+      {imageApiUrl && (
+        <>
+          <div className='bg-border hidden h-5 w-px sm:block' />
+          <ApiUrlRow label={t('Image API URL')} value={imageApiUrl} />
+        </>
+      )}
     </div>
   )
🤖 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/keys/index.tsx` around lines 103 - 119, Update the
imageApiUrl useMemo to build the URL from the already-resolved baseUrl rather
than serverAddress, preserving any window.location.origin fallback. Render the
image API ApiUrlRow only when imageApiUrl is non-empty, including its separator
only when the row is shown, so empty values cannot be copied.
web/default/src/features/keys/index.tsx-53-67 (1)

53-67: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Type api_info on SystemStatus and select image endpoints with a stable field.

api_info is read from /api/status, but SystemStatus does not declare it, so this path casts the payload shape. The image URL is chosen from operator-authored description and route text, so non-English or differently named entries fall back to buildImageApiUrl(serverAddress)'s hostname-based guess. Add api_info to the status contract and use a stable type/key field for the image API instead of string keywords.

🤖 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/keys/index.tsx` around lines 53 - 67, Update the
SystemStatus contract to declare the api_info entries, including the stable
type/key field used to identify endpoints. In getImageApiUrl, remove the
description/route keyword matching and select the image endpoint by its stable
type/key value, then normalize its url; preserve the existing empty-string
fallback when no matching endpoint or URL exists.
web/default/src/features/keys/index.tsx-85-94 (1)

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

Mark the copy icon as decorative and report copy failures.

The Copy icon on line 93 has no aria-hidden="true". The same icon in web/default/src/features/pricing/components/pricing-model-list.tsx line 195 sets it. Screen readers announce the icon in addition to the button label.

handleCopy also shows a toast only when copyToClipboard returns true. A failed copy gives no feedback. Add a translated error toast.

As per coding guidelines: "装饰性图标使用 aria-hidden="true"" and "错误提示使用 i18n,统一通过 toast.error 等方式展示".

♿ Proposed change
   const handleCopy = async () => {
     const ok = await copyToClipboard(props.value)
-    if (ok) toast.success(t('Copied'))
+    if (!ok) {
+      toast.error(t('Copy failed'))
+      return
+    }
+    toast.success(t('Copied'))
   }
-        <Copy className='size-3.5' />
+        <Copy className='size-3.5' 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/keys/index.tsx` around lines 85 - 94, The Copy icon
rendered by the copy Button should be marked decorative with aria-hidden="true".
Update handleCopy to show a translated error toast via toast.error when
copyToClipboard fails, while preserving the existing success toast behavior.

Source: Coding guidelines

web/default/src/features/home/index.tsx-805-809 (1)

805-809: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid rendering Markdown directly in AccordionTrigger.

AccordionTrigger is the accordion button, while Markdown renders a wrapper <div> and normal Markdown can emit block <p>, headings, lists, and other flow nodes. This creates invalid nesting inside the button. If the FAQ question contains only inline emphasis, escape it first or render only inline formatting; otherwise move Markdown out of the trigger.

🤖 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/index.tsx` around lines 805 - 809, Update the
FAQ question rendering in the AccordionTrigger so the button contains only valid
inline content: escape plain question text or use an inline-only formatter for
emphasis, and do not render the block-producing Markdown component inside the
trigger. For questions requiring block Markdown, restructure the accordion so
that content is rendered outside AccordionTrigger while preserving the existing
question styling and behavior.
web/default/src/features/home/model-pricing-config.ts-36-62 (1)

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

Remove the misleading currency note for pricingCurrencyConfig.

featuredModelNames is already used by subscription-plan-estimator.tsx, but pricingCurrencyConfig has no consumers in web/. Keep or remove it; currently it should not be documented as controlling the displayed currency.

🤖 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/model-pricing-config.ts` around lines 36 - 62,
Remove the misleading currency-type comment above pricingCurrencyConfig, since
this configuration is not used to control displayed currency; leave the
pricingCurrencyConfig values and featuredModelNames unchanged.
web/default/src/features/home/index.tsx-347-411 (1)

347-411: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the currency config to modelPricingRows dependencies.

formatPrice reads the current currency display/settings via getCurrencyDisplay() every time it is called, but this useMemo only depends on pricingData. When pricingData does not change after a currency config update, this table keeps the old formatted prices. Store the currency value produced by getCurrencyDisplay() in this rendering path and include it in the dependency list.

🤖 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/index.tsx` around lines 347 - 411, Update the
modelPricingRows useMemo to read and retain the currency value from
getCurrencyDisplay(), then include that currency value in the memo dependency
array alongside pricingData so formatted prices recompute after currency
settings change.
web/default/src/features/wallet/components/recharge-form-card.tsx-273-277 (1)

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

Translate the Pay and Save labels.

These new labels bypass t(). They remain English when the interface language changes.

As per coding guidelines, user-facing text must use i18n. <coding_guidelines>

🤖 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/components/recharge-form-card.tsx` around
lines 273 - 277, Update the user-facing “Pay” and “Save” labels in the recharge
form’s payment display to use the existing i18n translation function t().
Preserve the current currency formatting, discount condition, and visual
separator while ensuring both labels are localized.

Source: Coding guidelines

web/default/src/features/wallet/components/recharge-form-card.tsx-506-559 (1)

506-559: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the no-top-up message when redemption is hidden.

hideRedemption removes the redemption controls. The no-top-up alert still tells the user to use a redemption code. Wallet enables this flag, so the alert can direct the user to an unavailable action.

When hideRedemption is true, show only the administrator-contact guidance.

🤖 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/components/recharge-form-card.tsx` around
lines 506 - 559, Update the fallback alert in the redemption section of the
recharge form so that when hideRedemption is true it displays only
administrator-contact guidance instead of redemption-code instructions. Preserve
the existing redemption-disabled message when hideRedemption is false and
redemptionEnabled is false, using the existing hideRedemption and
redemptionEnabled conditions.
web/default/src/features/subscription-plan-estimator.tsx-59-63 (1)

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

Localize the token-capacity suffix.

formatTokenCapacity always appends the English text Token. Pass a translated label into this helper or format the complete value through t().

As per coding guidelines, user-facing text must use i18n. <coding_guidelines>

🤖 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/subscription-plan-estimator.tsx` around lines 59 -
63, Update formatTokenCapacity so the user-facing “Token” suffix is localized
through the existing i18n mechanism: either accept a translated label as an
argument or translate the complete formatted value with t(). Preserve the
existing handling for non-finite and non-positive values.

Source: Coding guidelines

web/default/src/styles/index.css-47-50 (1)

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

Fix the Stylelint error.

Stylelint reports declaration-empty-line-before for this declaration group. Add the required empty line before background-image.

As per coding guidelines, “完成代码改动前必须对涉及文件执行 lint,并修复所有 lint error。”

🤖 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/styles/index.css` around lines 47 - 50, Add the required
empty line between the existing `@apply` declaration and the background-image
declaration group in the stylesheet, then run lint on the modified file and
resolve any resulting lint errors.

Sources: Coding guidelines, Linters/SAST tools

web/default/src/styles/index.css-47-51 (1)

47-51: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a dark-mode body gradient override.

web/default/src/styles/index.css:46-51 always applies the light radial backgrounds to body; the dark-themed .dark color variables do not remove them. Add a .dark body { background-image: none; } or a darker dark-mode gradient so opaque content does not render over light decorations.

🤖 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/styles/index.css` around lines 47 - 51, Update the body
styling near the existing radial background declarations to add a dark-mode
override targeting .dark body. Remove the light gradients in dark mode by
setting the background image to none, or replace them with explicitly
dark-compatible gradients while preserving the current light-theme behavior.

Source: Coding guidelines

web/default/src/styles/index.css-479-491 (1)

479-491: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore the data-card-hover="false" exclusion from the card hover styles.

Multiple consumers still set data-card-hover='false', including click-target cards in the wallet product grid. These cards will now lift and gain a shadow under @media (min-width: 641px) [data-slot='card']:hover. Add back an exclusion so static cards keep the original hover behavior.

🤖 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/styles/index.css` around lines 479 - 491, Update the desktop
hover selectors for [data-slot='card'] so cards with data-card-hover="false" are
excluded from the transform and box-shadow rules. Preserve the existing hover
styling for cards without that attribute or with hover enabled.
🧹 Nitpick comments (13)
web/default/src/components/content-language-select.tsx (1)

42-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Follow the component props and return-type rules.

Use props.value and props.onValueChange instead of destructuring component props. Add an explicit component return type. Run the configured Bun type check and lint after this TypeScript change.

As per coding guidelines, “对象非必要不要解构,尤其是组件 props;优先通过 props.xxx 访问属性。” and “参数和返回值应显式标注类型。”

🤖 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/content-language-select.tsx` around lines 42 - 45,
Update ContentLanguageSelect to accept a named props object and access its
fields through props.value and props.onValueChange instead of destructuring; add
the explicit component return type required by the project’s TypeScript
conventions, then run the configured Bun type check and lint.

Source: Coding guidelines

web/default/src/features/system-settings/content/faq-section.tsx (1)

405-470: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Register translated fields with React Hook Form and Zod.

The localized branches bypass form and write directly to translations state. Zod does not validate these values, and the inputs cannot report validation errors through the existing form flow. Add translated fields to FAQFormValues and the schema, then normalize values.translations during submission.

As per coding guidelines, “表单使用 React Hook Form 与 Zod。”

🤖 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/content/faq-section.tsx` around
lines 405 - 470, Update the localized question and answer inputs in the FAQ form
to use React Hook Form fields backed by Zod, adding the translated values to
FAQFormValues and its validation schema so they participate in the existing
error flow. Replace direct translations-state updates in the non-default editing
branches with registered field bindings, and normalize values.translations
during submission before persisting the FAQ.

Source: Coding guidelines

web/default/src/features/pricing/hooks/use-filters.ts (1)

62-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Inert token-unit and price-display controls. The filter hook now hardcodes tokenUnit and showRechargePrice instead of deriving them from filterState, so the matching toolbar controls have one option each and never change state.

  • web/default/src/features/pricing/hooks/use-filters.ts#L62-L64: remove tokenUnit and rechargePrice from FilterState, from the initial state read of the route search params, and from the returned setTokenUnit and setShowRechargePrice setters, or restore the derivation from filterState.
  • web/default/src/features/pricing/components/pricing-toolbar.tsx#L190-L207: remove both single-option SegmentedControl blocks and the handleTokenUnitChange and handleRechargePriceChange callbacks, or restore the removed options.
🤖 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/pricing/hooks/use-filters.ts` around lines 62 - 64,
The pricing filter controls are inert because tokenUnit and showRechargePrice
are hardcoded. In web/default/src/features/pricing/hooks/use-filters.ts lines
62-64, either restore their derivation from filterState or remove
tokenUnit/rechargePrice from FilterState, route-search initialization, and
returned setters; in
web/default/src/features/pricing/components/pricing-toolbar.tsx lines 190-207,
remove the corresponding single-option SegmentedControl blocks and
handleTokenUnitChange/handleRechargePriceChange callbacks if choosing removal.
web/default/src/features/pricing/index.tsx (1)

61-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the derivation block into a hook.

This file is now about 296 lines, and lines 61-165 hold all vendor, ratio, and model derivation. Move that block into a hook such as usePricingSelection next to use-pricing-data.ts. The component then only renders. The duplicated model-order comparator in vendorModels and visibleModels can also become one named comparator.

As per coding guidelines: "组件文件超过约 200 行时,应考虑拆分子组件或提取自定义 Hook".

🤖 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/pricing/index.tsx` around lines 61 - 165, The
pricing component’s vendor, ratio, and model derivation is too large and
duplicates sorting logic. Extract the block beginning with visibleVendors into a
nearby usePricingSelection hook, returning the derived selection values needed
by the component, and replace the duplicated vendorModels/visibleModels sorting
callbacks with one named model-order comparator reused by both.

Source: Coding guidelines

web/default/src/features/pricing/lib/model-helpers.ts (1)

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

Reuse this helper in the pricing page.

web/default/src/features/pricing/index.tsx lines 100-114 repeat the same qualification rules: skip EXCLUDED_GROUPS, require the group in usableGroup, and keep only finite positive ratios. Two copies of these rules can diverge and then the ratio tabs and the visible model list disagree.

Export a helper that returns the qualifying group-ratio pairs, and build both ratioOptions and this function on top of 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/pricing/lib/model-helpers.ts` around lines 53 - 72,
Refactor getModelUsableGroupRatios to use an exported helper that returns
qualifying group-ratio pairs after applying the existing EXCLUDED_GROUPS,
usableGroup membership, finite, and positive-ratio checks. Update both
getModelUsableGroupRatios and the pricing page’s ratioOptions construction in
the pricing page component to consume this shared helper, keeping ratio tabs and
visible model filtering aligned.
web/default/src/features/home/components/sections/hero.tsx (1)

26-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove className from HeroProps or apply it.

HeroProps declares className, but the component never uses it. A caller that passes className gets no effect. Either drop the prop or merge it into the root section class list with cn().

♻️ Proposed fix
 interface HeroProps {
-  className?: string
   isAuthenticated?: boolean
 }

Alternative, if callers need it:

-    <section className='relative z-10 flex flex-col items-center overflow-hidden px-6 pt-28 pb-16 md:pt-36 md:pb-24'>
+    <section
+      className={cn(
+        'relative z-10 flex flex-col items-center overflow-hidden px-6 pt-28 pb-16 md:pt-36 md:pb-24',
+        props.className
+      )}
+    >
🤖 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 26 -
36, Remove the unused className field from HeroProps, unless Hero callers
require custom styling; if it is needed, apply props.className to the root
section in Hero using the existing cn() utility. Keep the isAuthenticated prop
behavior unchanged.
web/default/src/features/home/components/sections/model-pricing.tsx (3)

45-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a semantic table for this pricing grid.

The header row and data rows are div/span elements. Screen readers cannot associate the "Model" and "Cache Hit" headers with each cell. This content is tabular. Use table, thead, th scope='col', and tbody and keep the same Tailwind classes.

As per coding guidelines: "使用语义化 HTML".

🤖 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/model-pricing.tsx` around
lines 45 - 63, Replace the div/span pricing grid with a semantic table in the
model pricing section: use table, thead, and tbody, with each header represented
by th scope="col" and each data row using tr and td. Preserve the existing
Tailwind classes and modelPricingConfig mapping, including the cacheHit
fallback.

Source: Coding guidelines


23-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unused props or apply className.

ModelPricingProps contains only className, and _props is never read. The prop has no effect for callers. Remove the interface and the parameter, or merge props.className into the root section.

♻️ Proposed fix
-interface ModelPricingProps {
-  className?: string
-}
-
-export function ModelPricing(_props: ModelPricingProps) {
+export function ModelPricing() {
   const { t } = useTranslation()
🤖 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/model-pricing.tsx` around
lines 23 - 27, Update the ModelPricing component to remove the unused
ModelPricingProps interface and parameter, unless the component’s root section
should apply the provided className; if retaining the prop, read it from props
and merge it into that root element’s existing classes.

27-67: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove or wire up unused ModelPricing.

ModelPricing is only declared and exported; it is not rendered in web/default/src/features/home. Keep it only if the duplicated price section should be used; otherwise remove the component/export.

🤖 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/model-pricing.tsx` around
lines 27 - 67, Remove the unused ModelPricing component and its export from the
home feature, including its now-unneeded imports and supporting code; do not add
a new render path unless this section is explicitly intended to replace an
existing price section.
web/default/src/features/home/index.tsx (3)

518-523: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not nest Button inside Link.

Wrap these links with Button render={<Link ... />} instead. This pattern is already supported by ButtonPrimitive and matches the home section usage. Applies at web/default/src/features/home/index.tsx:518 and web/default/src/features/home/index.tsx:757.

🤖 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/index.tsx` around lines 518 - 523, Replace the
nested Link/Button structure in the home feature’s “Start for Free” links with
Button’s render prop, using render={<Link ... />} while preserving the existing
destinations, classes, size, icon, and translated label. Apply the same change
to both affected occurrences, including the home section usage around the second
link.

288-292: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use PricingData for the pricing query response.

getPricing returns Promise<PricingData>, but this query is typed as HomePricingResponse. Import PricingData instead and remove the local response type if it is only used here.

🤖 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/index.tsx` around lines 288 - 292, Update the
pricing query in the home component to use PricingData as the useQuery response
type, matching getPricing’s return value. Import PricingData from its existing
module and remove the local HomePricingResponse type if it is only referenced by
this query.

Source: Coding guidelines


562-568: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make CardTitle render the correct heading element instead of using ARIA overrides at each call site.

CardTitle renders a div; these call sites pass role='heading' to add heading semantics while changing the heading level with aria-level. Move the heading semantics into CardTitle, either by having it render <h> elements or by forwarding a render/as prop so this usage does not depend on aria overrides.

🤖 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/index.tsx` around lines 562 - 568, Update the
CardTitle component to render a semantic heading element or accept an as/render
prop for selecting the heading level, then revise this usage to use that API
instead of role='heading' and aria-level. Preserve the intended level-2 heading
semantics and update other equivalent CardTitle call sites as needed.
web/default/src/styles/index.css (1)

609-686: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Use theme variables for the home-page palette.

The new home-claude, blur-ball-*, and shine-text rules hardcode light and dark colors. Active data-theme-preset values cannot modify these surfaces. Define the palette from CSS variables or preset-aware variables.

As per coding guidelines, “主题使用 CSS 变量与 dark:。”

🤖 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/styles/index.css` around lines 609 - 686, Update the
home-claude, blur-ball-indigo, blur-ball-teal, and shine-text rules to use the
existing theme or preset-aware CSS variables instead of hardcoded light and dark
color values. Remove the duplicated .dark-specific palette overrides where
variables can provide both modes, while preserving the current gradients,
opacity, and text clipping behavior so active data-theme-preset values control
these surfaces.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bbeb54c-4fa6-4528-8b03-d9ed2c58647a

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab0202 and 43a5848.

📒 Files selected for processing (63)
  • .github/workflows/ghcr-build.yml
  • router/api-router.go
  • web/default/src/components/config-drawer.tsx
  • web/default/src/components/content-language-select.tsx
  • web/default/src/components/data-table/layout/card-row-content.tsx
  • web/default/src/components/layout/components/authenticated-layout.tsx
  • web/default/src/components/layout/components/nav-group.tsx
  • web/default/src/components/layout/components/public-header.tsx
  • web/default/src/components/ui/button.tsx
  • web/default/src/context/theme-customization-provider.tsx
  • web/default/src/features/dashboard/hooks/use-status-data.ts
  • web/default/src/features/dashboard/types.ts
  • web/default/src/features/home/api.ts
  • web/default/src/features/home/components/gateway-card.tsx
  • web/default/src/features/home/components/hero-buttons.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/scrolling-icons.tsx
  • web/default/src/features/home/components/sections/cta.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/model-pricing.tsx
  • web/default/src/features/home/hooks/use-home-page-content.ts
  • web/default/src/features/home/index.tsx
  • web/default/src/features/home/model-pricing-config.ts
  • web/default/src/features/keys/index.tsx
  • web/default/src/features/pricing/components/index.ts
  • web/default/src/features/pricing/components/pricing-model-list.tsx
  • web/default/src/features/pricing/components/pricing-sidebar.tsx
  • web/default/src/features/pricing/components/pricing-toolbar.tsx
  • web/default/src/features/pricing/hooks/use-filters.ts
  • web/default/src/features/pricing/index.tsx
  • web/default/src/features/pricing/lib/filters.ts
  • web/default/src/features/pricing/lib/model-helpers.ts
  • web/default/src/features/subscription-plan-estimator.tsx
  • web/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsx
  • web/default/src/features/subscriptions/components/dialogs/user-subscriptions-dialog.tsx
  • web/default/src/features/subscriptions/components/subscriptions-columns.tsx
  • web/default/src/features/system-settings/content/faq-section.tsx
  • 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/hooks/use-topup-info.ts
  • web/default/src/features/wallet/index.tsx
  • web/default/src/hooks/use-top-nav-links.ts
  • web/default/src/i18n/config.ts
  • 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-TW.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/static-keys.ts
  • web/default/src/lib/currency.ts
  • web/default/src/lib/localized-content.ts
  • web/default/src/lib/theme-customization.ts
  • web/default/src/routeTree.gen.ts
  • web/default/src/routes/index.tsx
  • web/default/src/routes/plans.tsx
  • web/default/src/styles/index.css
  • web/default/src/styles/theme-presets.css
  • web/default/src/styles/theme.css
💤 Files with no reviewable changes (8)
  • web/default/src/features/home/components/sections/features.tsx
  • web/default/src/features/pricing/lib/filters.ts
  • web/default/src/features/home/api.ts
  • web/default/src/features/home/components/gateway-card.tsx
  • web/default/src/routes/index.tsx
  • web/default/src/features/home/components/sections/how-it-works.tsx
  • web/default/src/features/home/components/scrolling-icons.tsx
  • web/default/src/features/home/components/hero-buttons.tsx

Comment on lines +413 to +440
const displayHomePageContent = async () => {
const cached = localStorage.getItem('home_page_content') || ''
setHomePageContent(cached)
try {
const res = await api.get('/api/home_page_content')
const { success, data } = res.data
if (success) {
setHomePageContent(data)
localStorage.setItem('home_page_content', data)
}
} catch (error) {
console.error('加载首页内容失败:', error)
}
setHomePageContentLoaded(true)
}

const handleCopyBaseURL = async () => {
try {
await navigator.clipboard.writeText(serverAddress)
toast.success(t('Copied to clipboard'))
} catch {
// Cross-origin frames may reject access while navigating.
toast.error(t('Copy failed'))
}
}, [i18n.language, resolvedTheme])
}

useEffect(() => {
if (isUrl) {
syncIframePreferences()
}
}, [isUrl, syncIframePreferences])
displayHomePageContent()
}, [])

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Home reimplements the homepage-content hook, and the copy is weaker. web/default/src/features/home/hooks/use-home-page-content.ts already loads /api/home_page_content, caches it in local storage, exposes isLoaded, and derives isUrl from a parsed protocol allowlist. Home duplicates that logic inline and loses the error toast and the URL parsing. Adopt the hook at both sites.

  • web/default/src/features/home/index.tsx#L413-L440: delete displayHomePageContent, the homePageContent and homePageContentLoaded state, and the useEffect. Call useHomePageContent() and read content, isLoaded, and isUrl from it. This also removes the swallowed error at Line 424, the hardcoded Chinese console.error string, and the repeated 'home_page_content' literal.
  • web/default/src/features/home/index.tsx#L456-L467: replace homePageContent.startsWith('https://') with the hook's isUrl flag. The current check sends an http:// URL to the dangerouslySetInnerHTML branch, so the URL is injected as markup instead of being framed. Sanitize the remaining HTML branch as described in the separate comment on Line 456.
📍 Affects 1 file
  • web/default/src/features/home/index.tsx#L413-L440 (this comment)
  • web/default/src/features/home/index.tsx#L456-L467
🤖 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/index.tsx` around lines 413 - 440, The Home
component should reuse useHomePageContent instead of duplicating homepage
loading state and effects. In web/default/src/features/home/index.tsx lines
413-440, remove displayHomePageContent, its related state, and the useEffect,
then call useHomePageContent() and use its content and isLoaded values. In lines
456-467, replace the startsWith URL check with the hook’s isUrl flag; sanitize
the remaining HTML branch according to the separate review comment.

Source: Coding guidelines

Comment on lines +497 to +513
<div className='relative w-full flex-1'>
<Input
readOnly
value={serverAddress}
className='rounded-full pr-24'
/>
<div className='absolute top-1/2 right-1 flex -translate-y-1/2 items-center gap-1'>
<Button
variant='ghost'
size='icon-sm'
className='rounded-full'
onClick={handleCopyBaseURL}
>
<Copy />
</Button>
</div>
</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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Give the server address field and the copy button an accessible name.

The Input at Line 498 has no associated label and no aria-label. The icon-only Button at Line 504 contains only the Copy icon, so it has no accessible name. A screen-reader user cannot tell what the field holds or what the button does, which blocks the copy task.

As per coding guidelines: "表单控件必须与 label 关联" and "装饰性图标使用 aria-hidden=\"true\",重要信息提供文本等价".

♿ Proposed fix
                       <Input
                         readOnly
+                        aria-label={t('API Base URL')}
                         value={serverAddress}
                         className='rounded-full pr-24'
                       />
                       <div className='absolute top-1/2 right-1 flex -translate-y-1/2 items-center gap-1'>
                         <Button
                           variant='ghost'
                           size='icon-sm'
                           className='rounded-full'
+                          aria-label={t('Copy')}
                           onClick={handleCopyBaseURL}
                         >
-                          <Copy />
+                          <Copy aria-hidden='true' />
                         </Button>
📝 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
<div className='relative w-full flex-1'>
<Input
readOnly
value={serverAddress}
className='rounded-full pr-24'
/>
<div className='absolute top-1/2 right-1 flex -translate-y-1/2 items-center gap-1'>
<Button
variant='ghost'
size='icon-sm'
className='rounded-full'
onClick={handleCopyBaseURL}
>
<Copy />
</Button>
</div>
</div>
<div className='relative w-full flex-1'>
<Input
readOnly
aria-label={t('API Base URL')}
value={serverAddress}
className='rounded-full pr-24'
/>
<div className='absolute top-1/2 right-1 flex -translate-y-1/2 items-center gap-1'>
<Button
variant='ghost'
size='icon-sm'
className='rounded-full'
aria-label={t('Copy')}
onClick={handleCopyBaseURL}
>
<Copy aria-hidden='true' />
</Button>
</div>
</div>
🤖 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/index.tsx` around lines 497 - 513, Update the
server address field and copy control in the component rendering this block to
provide accessible names: associate the read-only Input with a visible or
visually hidden label, and add an explicit accessible name describing the copy
action to the icon-only Button. Mark the Copy icon as decorative with
aria-hidden="true" so it is not used as the button’s name.

Source: Coding guidelines

Comment on lines +82 to +86
const pricingQuery = useQuery({
queryKey: ['pricing'],
queryFn: getPricing,
staleTime: 5 * 60 * 1000,
})

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

Do not fail public plan discovery when pricing requires authentication.

The /api/pricing route uses HeaderNavModuleAuth("pricing"). An anonymous request can fail when pricing requires authentication. Lines 245-303 then hide the public plans and purchase flow because hasError includes pricingQuery.isError.

Treat only plansQuery failure as a page-level error. If pricing fails, keep the plans visible and show unavailable capacity estimates.

Also applies to: 245-303

🤖 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/subscription-plan-estimator.tsx` around lines 82 -
86, Update the page-level error handling around pricingQuery and plansQuery so
only plansQuery.isError triggers the error state. Preserve rendering of public
plans and the purchase flow when pricing fails, and make the pricing-dependent
capacity estimates display as unavailable when pricingQuery errors.

Comment on lines +255 to +259
{p.plan.title} (
{formatLocalCurrencyAmount(
Number(p.plan.price_amount || 0)
)}
)

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

Use formatSubscriptionPlanPrice for subscription plan prices.

formatLocalCurrencyAmount skips the exchange-rate normalization used by the purchase flow. When usdExchangeRate is not 1, this dialog displays a different amount for the same price_amount.

Replace both calls with formatSubscriptionPlanPrice.

Also applies to: 273-277

🤖 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/subscriptions/components/dialogs/user-subscriptions-dialog.tsx`
around lines 255 - 259, Replace both formatLocalCurrencyAmount calls rendering
subscription plan prices in the user subscriptions dialog with
formatSubscriptionPlanPrice, preserving the existing Number(p.plan.price_amount
|| 0) input so the dialog uses the same exchange-rate normalization as the
purchase flow.

import { GroupBadge } from '@/components/group-badge'
import { StatusBadge } from '@/components/status-badge'
import { TableId } from '@/components/table-id'
import { formatLocalCurrencyAmount } from '@/lib/currency'

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

Use formatSubscriptionPlanPrice for subscription plan prices.

This column formats price_amount as an already-local amount. The purchase flow first applies subscription exchange-rate normalization. A non-default exchange rate therefore produces inconsistent prices.

Import and call formatSubscriptionPlanPrice instead.

Also applies to: 73-75

🤖 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/subscriptions/components/subscriptions-columns.tsx`
at line 27, Replace formatLocalCurrencyAmount with formatSubscriptionPlanPrice
in the subscription plan price column, including the usage around the referenced
price rendering logic. Update the import and call so prices use the same
exchange-rate normalization as the purchase flow.

Comment on lines +619 to +629
/** Format a locally configured subscription amount as its USD-equivalent price. */
export function formatSubscriptionPlanPrice(
amount: number | null | undefined,
options?: CurrencyFormatOptions
): string {
if (amount == null || Number.isNaN(amount)) return '-'

const { config } = getCurrencyDisplay()
const exchangeRate = config.usdExchangeRate > 0 ? config.usdExchangeRate : 1

return formatLocalCurrencyAmount(amount / exchangeRate, options)

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

ast-grep outline web/default/src/lib/currency.ts --items all

rg -n -C 8 \
  'getCurrencyDisplay|function getBillingDisplayMeta|formatLocalCurrencyAmount|formatSubscriptionPlanPrice|usdExchangeRate|quotaDisplayType' \
  web/default/src/lib/currency.ts

rg -n -C 8 \
  'effectiveUsdExchangeRate|quotaDisplayType|usdExchangeRate' \
  web/default/src/features/wallet/index.tsx

Repository: QuantumNous/new-api

Length of output: 18391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation and exact call sites of formatSubscriptionPlanPrice.
sed -n '606,645p' web/default/src/lib/currency.ts
sed -n '330,395p' web/default/src/lib/currency.ts
sed -n '438,468p' web/default/src/lib/currency.ts

rg -n -C 6 \
  'formatSubscriptionPlanPrice\b|formatBillingCurrencyFromUSD\b|formatCurrencyFromUSD\b|usdExchangeRate=' \
  web/default/src/app web/default/src/features web/default/src | head -n 240

python3 - <<'PY'
from pathlib import Path
p = Path('web/default/src/lib/currency.ts')
text = p.read_text()
print('uses config.usdExchangeRate in formatter:', 'const exchangeRate = config.usdExchangeRate > 0 ? config.usdExchangeRate : 1' in text)
print('uses exchangeRate in billing format:', 'amountUSD * meta.exchangeRate' in text)
print('USD display quota show usdExchangeRate: ' in str(p.read_text()) and 'config.usdExchangeRate' in text.split("case 'USD'")[1].split('case')[0])
PY

Repository: QuantumNous/new-api

Length of output: 29532


Avoid double-converting subscription prices.

formatSubscriptionPlanPrice() divides the passed price by config.usdExchangeRate, while USD display still keeps usdExchangeRate as the configured rate and formats that value as $. For USD display, this shows an underpriced subscription (for example, a $10 rate can appear around $1.43 with usdExchangeRate=7). Use an amount that is already in the target currency, or normalize the formatter for USD display before dividing.

🤖 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/currency.ts` around lines 619 - 629, Update
formatSubscriptionPlanPrice to avoid applying usdExchangeRate twice when the
target display currency is USD. Ensure the amount passed to
formatLocalCurrencyAmount is already normalized for the selected currency,
preserving correct conversion for non-USD displays and the existing null/NaN
fallback.

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.

2 participants