Codex/sync upstream 20260709 - #6636
Conversation
Codex/sync upstream 20260709
WalkthroughThe 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. ChangesPublic plans and localized subscription flows
Pricing and homepage flows
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Warning |
There was a problem hiding this comment.
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 winRemove unreachable filter code or restore a caller.
web/default/src/features/pricing/index.tsxno longer importsPricingToolbar,PricingSidebar, oruseFilters; the current route only usesModelDetailsDrawerandPricingModelList. 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 liftSplit this component; the file is far over the size limit.
Homenow 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 winUse 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 whenusdExchangeRateis not 1.
web/default/src/features/subscriptions/components/dialogs/user-subscriptions-dialog.tsx#L255-L277: replace bothformatLocalCurrencyAmountcalls and its import withformatSubscriptionPlanPrice.web/default/src/features/subscriptions/components/subscriptions-columns.tsx#L27-L75: replace the import and price-cell call withformatSubscriptionPlanPrice.🤖 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 winExclude hidden plans from the card availability state.
hideAvailablePlanshides the grid, butisAvailableand the early return still useplans.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
hasVisiblePlansvalue from!hideAvailablePlans && plans.length > 0. Use it inisAvailableand 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 winUse 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 winKeep “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 winTranslate “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 winUse 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 winPreserve 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 winTranslate
Tokenconsistently.These Russian translations keep
Tokenin 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 winUse one Vietnamese term for dynamic pricing.
The new
Dynamic pricingentry usesGiá động. The existingDynamic Pricingentry at Line 1478 usesGiá 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 winUse a distinct translation for
Model Square.Line 2698 maps
Model SquaretoGiá mô hình, the same value used byModel Priceat Line 2684. This hides the distinction between the two labels. The relatedmodel squaretext 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 winComplete the translation for
Direct access to official providers.
直連官方的is incomplete and omitsproviders. 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 winPreserve 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 winTranslate
Start for Freeas 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 winUse 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 winThe
ratioOptions.length > 0guard is always true.Lines 117-119 insert a default entry with ratio
1and an empty group set when no ratio qualifies.ratioOptionstherefore 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, becauseoption.groupsis empty.Return an empty array when no group ratio qualifies, and keep
activeRatioon the existing?? 1fallback.🐛 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 winTranslate 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 usest(). 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'withunit: 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 winDerive the image API URL from
baseUrland hide the row when the value is empty.
baseUrlfalls back towindow.location.originwhenserverAddressis empty.buildImageApiUrlreceivesserverAddressinstead, 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 winType
api_infoonSystemStatusand select image endpoints with a stable field.
api_infois read from/api/status, butSystemStatusdoes not declare it, so this path casts the payload shape. The image URL is chosen from operator-authoreddescriptionandroutetext, so non-English or differently named entries fall back tobuildImageApiUrl(serverAddress)'s hostname-based guess. Addapi_infoto the status contract and use a stabletype/keyfield 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 winMark the copy icon as decorative and report copy failures.
The
Copyicon on line 93 has noaria-hidden="true". The same icon inweb/default/src/features/pricing/components/pricing-model-list.tsxline 195 sets it. Screen readers announce the icon in addition to the button label.
handleCopyalso shows a toast only whencopyToClipboardreturnstrue. 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 winAvoid rendering
Markdowndirectly inAccordionTrigger.
AccordionTriggeris the accordion button, whileMarkdownrenders 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 moveMarkdownout 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 winRemove the misleading currency note for
pricingCurrencyConfig.
featuredModelNamesis already used bysubscription-plan-estimator.tsx, butpricingCurrencyConfighas no consumers inweb/. 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 winAdd the currency config to
modelPricingRowsdependencies.
formatPricereads the current currency display/settings viagetCurrencyDisplay()every time it is called, but thisuseMemoonly depends onpricingData. WhenpricingDatadoes not change after a currency config update, this table keeps the old formatted prices. Store the currency value produced bygetCurrencyDisplay()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 winTranslate the
PayandSavelabels.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 winUpdate the no-top-up message when redemption is hidden.
hideRedemptionremoves the redemption controls. The no-top-up alert still tells the user to use a redemption code.Walletenables this flag, so the alert can direct the user to an unavailable action.When
hideRedemptionis 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 winLocalize the token-capacity suffix.
formatTokenCapacityalways appends the English textToken. Pass a translated label into this helper or format the complete value throught().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 winFix the Stylelint error.
Stylelint reports
declaration-empty-line-beforefor this declaration group. Add the required empty line beforebackground-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 winAdd a dark-mode
bodygradient override.
web/default/src/styles/index.css:46-51always applies the light radial backgrounds tobody; the dark-themed.darkcolor 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 winRestore 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 winFollow the component props and return-type rules.
Use
props.valueandprops.onValueChangeinstead 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 liftRegister translated fields with React Hook Form and Zod.
The localized branches bypass
formand write directly totranslationsstate. Zod does not validate these values, and the inputs cannot report validation errors through the existing form flow. Add translated fields toFAQFormValuesand the schema, then normalizevalues.translationsduring 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 winInert token-unit and price-display controls. The filter hook now hardcodes
tokenUnitandshowRechargePriceinstead of deriving them fromfilterState, so the matching toolbar controls have one option each and never change state.
web/default/src/features/pricing/hooks/use-filters.ts#L62-L64: removetokenUnitandrechargePricefromFilterState, from the initial state read of the route search params, and from the returnedsetTokenUnitandsetShowRechargePricesetters, or restore the derivation fromfilterState.web/default/src/features/pricing/components/pricing-toolbar.tsx#L190-L207: remove both single-optionSegmentedControlblocks and thehandleTokenUnitChangeandhandleRechargePriceChangecallbacks, 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 winExtract 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
usePricingSelectionnext touse-pricing-data.ts. The component then only renders. The duplicated model-order comparator invendorModelsandvisibleModelscan 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 winReuse this helper in the pricing page.
web/default/src/features/pricing/index.tsxlines 100-114 repeat the same qualification rules: skipEXCLUDED_GROUPS, require the group inusableGroup, 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
ratioOptionsand 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 valueRemove
classNamefromHeroPropsor apply it.
HeroPropsdeclaresclassName, but the component never uses it. A caller that passesclassNamegets no effect. Either drop the prop or merge it into the rootsectionclass list withcn().♻️ 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 winUse a semantic
tablefor this pricing grid.The header row and data rows are
div/spanelements. Screen readers cannot associate the "Model" and "Cache Hit" headers with each cell. This content is tabular. Usetable,thead,th scope='col', andtbodyand 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 valueDrop the unused props or apply
className.
ModelPricingPropscontains onlyclassName, and_propsis never read. The prop has no effect for callers. Remove the interface and the parameter, or mergeprops.classNameinto the rootsection.♻️ 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 valueRemove or wire up unused
ModelPricing.
ModelPricingis only declared and exported; it is not rendered inweb/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 winDo not nest
ButtoninsideLink.Wrap these links with
Button render={<Link ... />}instead. This pattern is already supported byButtonPrimitiveand matches the home section usage. Applies atweb/default/src/features/home/index.tsx:518andweb/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 winUse
PricingDatafor the pricing query response.
getPricingreturnsPromise<PricingData>, but this query is typed asHomePricingResponse. ImportPricingDatainstead 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 winMake
CardTitlerender the correct heading element instead of using ARIA overrides at each call site.
CardTitlerenders adiv; these call sites passrole='heading'to add heading semantics while changing the heading level witharia-level. Move the heading semantics intoCardTitle, either by having it render<h>elements or by forwarding arender/asprop 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 liftUse theme variables for the home-page palette.
The new
home-claude,blur-ball-*, andshine-textrules hardcode light and dark colors. Activedata-theme-presetvalues 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
📒 Files selected for processing (63)
.github/workflows/ghcr-build.ymlrouter/api-router.goweb/default/src/components/config-drawer.tsxweb/default/src/components/content-language-select.tsxweb/default/src/components/data-table/layout/card-row-content.tsxweb/default/src/components/layout/components/authenticated-layout.tsxweb/default/src/components/layout/components/nav-group.tsxweb/default/src/components/layout/components/public-header.tsxweb/default/src/components/ui/button.tsxweb/default/src/context/theme-customization-provider.tsxweb/default/src/features/dashboard/hooks/use-status-data.tsweb/default/src/features/dashboard/types.tsweb/default/src/features/home/api.tsweb/default/src/features/home/components/gateway-card.tsxweb/default/src/features/home/components/hero-buttons.tsxweb/default/src/features/home/components/hero-terminal-demo.tsxweb/default/src/features/home/components/index.tsweb/default/src/features/home/components/scrolling-icons.tsxweb/default/src/features/home/components/sections/cta.tsxweb/default/src/features/home/components/sections/features.tsxweb/default/src/features/home/components/sections/hero.tsxweb/default/src/features/home/components/sections/how-it-works.tsxweb/default/src/features/home/components/sections/model-pricing.tsxweb/default/src/features/home/hooks/use-home-page-content.tsweb/default/src/features/home/index.tsxweb/default/src/features/home/model-pricing-config.tsweb/default/src/features/keys/index.tsxweb/default/src/features/pricing/components/index.tsweb/default/src/features/pricing/components/pricing-model-list.tsxweb/default/src/features/pricing/components/pricing-sidebar.tsxweb/default/src/features/pricing/components/pricing-toolbar.tsxweb/default/src/features/pricing/hooks/use-filters.tsweb/default/src/features/pricing/index.tsxweb/default/src/features/pricing/lib/filters.tsweb/default/src/features/pricing/lib/model-helpers.tsweb/default/src/features/subscription-plan-estimator.tsxweb/default/src/features/subscriptions/components/dialogs/subscription-purchase-dialog.tsxweb/default/src/features/subscriptions/components/dialogs/user-subscriptions-dialog.tsxweb/default/src/features/subscriptions/components/subscriptions-columns.tsxweb/default/src/features/system-settings/content/faq-section.tsxweb/default/src/features/wallet/components/recharge-form-card.tsxweb/default/src/features/wallet/components/subscription-plans-card.tsxweb/default/src/features/wallet/hooks/use-topup-info.tsweb/default/src/features/wallet/index.tsxweb/default/src/hooks/use-top-nav-links.tsweb/default/src/i18n/config.tsweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh-TW.jsonweb/default/src/i18n/locales/zh.jsonweb/default/src/i18n/static-keys.tsweb/default/src/lib/currency.tsweb/default/src/lib/localized-content.tsweb/default/src/lib/theme-customization.tsweb/default/src/routeTree.gen.tsweb/default/src/routes/index.tsxweb/default/src/routes/plans.tsxweb/default/src/styles/index.cssweb/default/src/styles/theme-presets.cssweb/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
| 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() | ||
| }, []) |
There was a problem hiding this comment.
📐 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: deletedisplayHomePageContent, thehomePageContentandhomePageContentLoadedstate, and theuseEffect. CalluseHomePageContent()and readcontent,isLoaded, andisUrlfrom it. This also removes the swallowed error at Line 424, the hardcoded Chineseconsole.errorstring, and the repeated'home_page_content'literal.web/default/src/features/home/index.tsx#L456-L467: replacehomePageContent.startsWith('https://')with the hook'sisUrlflag. The current check sends anhttp://URL to thedangerouslySetInnerHTMLbranch, 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
| <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> |
There was a problem hiding this comment.
📐 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.
| <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
| const pricingQuery = useQuery({ | ||
| queryKey: ['pricing'], | ||
| queryFn: getPricing, | ||
| staleTime: 5 * 60 * 1000, | ||
| }) |
There was a problem hiding this comment.
🎯 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.
| {p.plan.title} ( | ||
| {formatLocalCurrencyAmount( | ||
| Number(p.plan.price_amount || 0) | ||
| )} | ||
| ) |
There was a problem hiding this comment.
🎯 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' |
There was a problem hiding this comment.
🎯 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.
| /** 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) |
There was a problem hiding this comment.
🎯 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.tsxRepository: 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])
PYRepository: 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.
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
Summary by CodeRabbit
New Features
Improvements