Skip to content

Codex/sync upstream 20260709 - #6055

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

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

Conversation

@Aunt-Lee

@Aunt-Lee Aunt-Lee commented Jul 10, 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 redesigned home page with service details, API address copying, model pricing comparisons, image pricing, and subscription plans.
    • Added a “View Pricing” action for visitors.
    • Public subscription plan information is now available without signing in.
  • UI Improvements

    • Refreshed homepage layout, backgrounds, colors, typography, and visual effects.
    • Simplified theme customization by removing font selection and the largest density option.
    • Updated theme presets and improved sidebar, header, and card styling.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR adds GHCR image publishing, makes subscription plans publicly accessible, introduces a pricing-based home page, updates landing-page visuals, and removes font and xl-density theme customization.

Changes

Container publishing

Layer / File(s) Summary
GHCR build and publish workflow
.github/workflows/ghcr-build.yml
Adds automated GHCR builds for main-branch pushes and manual runs, including metadata, caching, authentication, and linux/amd64 publishing.

Public subscription plans

Layer / File(s) Summary
Unauthenticated plans route
router/api-router.go
Moves the subscription plans endpoint outside the authenticated subscription route group.

Frontend home and theme experience

Layer / File(s) Summary
Theme customization contract and state
web/default/src/lib/theme-customization.ts, web/default/src/context/theme-customization-provider.tsx, web/default/src/components/config-drawer.tsx
Removes font selection, font cookies, font attributes, and xl density controls from theme state and configuration UI.
Theme tokens and landing styles
web/default/src/styles/theme.css, web/default/src/styles/theme-presets.css, web/default/src/styles/index.css
Updates palette tokens and adds revised backgrounds, surfaces, card interactions, and home-page visual styles.
Pricing configuration and table
web/default/src/features/home/model-pricing-config.ts, web/default/src/features/home/components/sections/model-pricing.tsx, web/default/src/features/home/components/index.ts
Adds model and image pricing configuration plus a reusable translated pricing table.
Home data loading and custom content
web/default/src/features/home/index.tsx, web/default/src/features/home/hooks/use-home-page-content.ts, web/default/src/features/home/api.ts
Loads status, pricing, plans, and home content, then renders custom content or the new pricing-based landing page.
Landing composition and supporting components
web/default/src/features/home/components/..., web/default/src/routes/index.tsx
Updates hero composition, terminal placement, component exports, and missing imports used by the landing UI.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant Home
  participant PublicAPI
  participant BrowserStorage
  Visitor->>Home: Open landing page
  Home->>BrowserStorage: Read cached home content
  Home->>PublicAPI: Fetch status, pricing, plans, and home content
  PublicAPI-->>Home: Return public data
  Home->>BrowserStorage: Cache home content
  Home-->>Visitor: Render custom content or pricing landing page
Loading

Possibly related PRs

Poem

A rabbit hops through pricing rows,
While GHCR packs its image shows.
Fonts and xl scales leave the den,
New home cards bloom again.
Public plans now greet the sun—
Hop, hop, the landing page is done!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% 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 matches the PR’s main purpose: syncing upstream changes into main.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (1)
web/default/src/features/home/index.tsx (1)

412-426: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Eliminate DRY violations: reuse useHomePageContent hook and extract pricing notice Alert.

  1. displayHomePageContent (lines 412–426) duplicates the logic in useHomePageContent (use-home-page-content.ts). The inline version has weaker error handling (no toast), uses a hardcoded localStorage key, and doesn't guard against data being undefined when success is true. Use the existing hook instead.

  2. Pricing notice Alert (lines 708–729 and 736–756) is duplicated verbatim. Extract a PricingNoticeAlert component to avoid drift.

♻️ Proposed refactor: extract pricing notice component
function PricingNoticeAlert({ className }: { className?: string }) {
  if (!pricingNoticeConfig.enabled) return null
  return (
    <Alert className={cn('text-center backdrop-blur-sm', className)}>
      <AlertDescription>
        {pricingNoticeConfig.text}
        {pricingNoticeConfig.linkText && pricingNoticeConfig.linkUrl ? (
          <>
            {' '}
            <a
              href={pricingNoticeConfig.linkUrl}
              target='_blank'
              rel='noreferrer noopener'
              className='font-semibold'
            >
              {pricingNoticeConfig.linkText}
            </a>
          </>
        ) : null}
      </AlertDescription>
    </Alert>
  )
}

Also applies to: 708-756

🤖 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 412 - 426, Replace the
inline displayHomePageContent implementation with the existing
useHomePageContent hook, including its loading state and error handling. Extract
the duplicated pricing notice markup into a reusable PricingNoticeAlert
component accepting an optional className, then render it in both existing
locations while preserving the enabled check, configured text, and optional
link.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ghcr-build.yml:
- Around line 19-20: Add persist-credentials: false to the actions/checkout@v4
step in the workflow’s “Check out” step, ensuring checkout does not store the
GITHUB_TOKEN in .git/config before the Docker build.

In `@router/api-router.go`:
- Line 35: Update controller.GetSubscriptionPlans to map each
model.SubscriptionPlan to a dedicated public response DTO containing only
client-safe plan fields, excluding stripe_price_id, creem_product_id,
waffo_pancake_product_id, upgrade_group, and downgrade_group; return the DTO
collection from the /subscription/plans endpoint instead of exposing
model.SubscriptionPlan directly.

In `@web/default/src/features/home/components/sections/model-pricing.tsx`:
- Around line 27-33: Update formatPrice to use getCurrencyDisplay() rather than
pricingCurrencyConfig.symbol, matching the implementation in index.tsx and
ensuring the displayed currency is correctly converted and labeled. Remove the
direct pricingCurrencyConfig symbol usage while preserving the existing
validation and decimal formatting behavior.

In `@web/default/src/features/home/index.tsx`:
- Around line 140-145: Update the listed Chinese translation keys in the home
page component to English source strings while preserving their meanings, and
wrap all rendered pricingHeaderConfig and imagePricingHeaderConfig values with
t(). Refactor formatSubscriptionPrice to obtain the symbol from
getCurrencyDisplay() instead of hardcoding ¥, while retaining numeric validation
and two-decimal formatting.
- Around line 451-466: Sanitize the HTML returned as homePageContent before
rendering it via dangerouslySetInnerHTML. Add a DOMPurify (or equivalent)
dependency/import, apply it to the non-URL content branch, and pass the
sanitized result to dangerouslySetInnerHTML while preserving the existing iframe
handling for HTTPS URLs.
- Around line 389-410: Update the image pricing calculation in useMemo
imageModelPricingRows so prices represent every configured image size instead of
always using multiplier 1. Derive the multipliers from configItem.types,
calculate the USD range for each via getImagePriceRangeUSD, and combine them
into an overall range before passing it to formatPerRequestPriceRange. Preserve
the existing grouped row and displayed type labels.

In `@web/default/src/features/home/model-pricing-config.ts`:
- Around line 40-53: The pricing UI configuration contains hardcoded Chinese
text and bypasses localization. Update pricingHeaderConfig,
imagePricingHeaderConfig, and pricingNoticeConfig.text, then adjust their
consumption in index.tsx to use the i18n t() function with English source-string
keys, or move the labels into locale files and translate them at render time.
- Around line 29-34: Fix the currency/symbol mismatch in pricingCurrencyConfig
by changing the USD symbol from ¥ to $, or update the configuration to derive
the symbol through getCurrencyDisplay() if dynamic currency handling is
required; ensure ModelPricing renders the documented USD prices consistently.

In `@web/default/src/styles/index.css`:
- Around line 47-51: Fix the Stylelint declaration-empty-line-before error in
the root stylesheet rule by inserting an empty line between the `@apply`
declaration and the background-image declaration; keep the existing
background-image gradients and background-attachment declarations unchanged.
- Around line 47-51: Update the body background styling near the existing radial
gradients to provide dark-mode-specific colors: replace the hardcoded light warm
stops with theme-aware tokens or add a `.dark body` `background-image` override
using suitably muted dark gradients, while preserving the existing light-mode
appearance and fixed attachment.

---

Nitpick comments:
In `@web/default/src/features/home/index.tsx`:
- Around line 412-426: Replace the inline displayHomePageContent implementation
with the existing useHomePageContent hook, including its loading state and error
handling. Extract the duplicated pricing notice markup into a reusable
PricingNoticeAlert component accepting an optional className, then render it in
both existing locations while preserving the enabled check, configured text, and
optional link.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a7127d6-88f3-4eac-9ae5-47d8147294cf

📥 Commits

Reviewing files that changed from the base of the PR and between 246d62a and 859dc87.

📒 Files selected for processing (23)
  • .github/workflows/ghcr-build.yml
  • router/api-router.go
  • web/default/src/components/config-drawer.tsx
  • web/default/src/context/theme-customization-provider.tsx
  • 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/lib/theme-customization.ts
  • web/default/src/routes/index.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 (7)
  • web/default/src/features/home/components/gateway-card.tsx
  • web/default/src/features/home/api.ts
  • web/default/src/features/home/components/hero-buttons.tsx
  • web/default/src/features/home/components/scrolling-icons.tsx
  • web/default/src/features/home/components/sections/features.tsx
  • web/default/src/features/home/components/sections/how-it-works.tsx
  • web/default/src/routes/index.tsx

Comment on lines +19 to +20
- name: Check out
uses: actions/checkout@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Set persist-credentials: false on the checkout step.

actions/checkout@v4 defaults to persist-credentials: true, which stores the GITHUB_TOKEN in .git/config on the runner. Since the Dockerfile copies the entire build context (COPY . .), these credentials could leak into the image if .git is not excluded by .dockerignore. Adding persist-credentials: false is a simple hardening measure since this workflow only needs to read the source, not push back to the repo.

🔒 Proposed fix
       - name: Check out
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 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
- name: Check out
uses: actions/checkout@v4
- name: Check out
uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 19-20: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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 @.github/workflows/ghcr-build.yml around lines 19 - 20, Add
persist-credentials: false to the actions/checkout@v4 step in the workflow’s
“Check out” step, ensuring checkout does not store the GITHUB_TOKEN in
.git/config before the Docker build.

Source: Linters/SAST tools

Comment thread router/api-router.go
//apiRouter.GET("/midjourney", controller.GetMidjourney)
apiRouter.GET("/home_page_content", controller.GetHomePageContent)
apiRouter.GET("/pricing", middleware.HeaderNavModuleAuth("pricing"), controller.GetPricing)
apiRouter.GET("/subscription/plans", controller.GetSubscriptionPlans)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the SubscriptionPlan model definition and its JSON tags
fd -t f 'subscription' model/ | head
rg -nP -C2 'type\s+SubscriptionPlan\s+struct' --type=go
# Show full struct with json tags to audit exposed fields
ast-grep run --pattern 'type SubscriptionPlan struct { $$$ }' --lang go
# Check NormalizeDefaults to confirm it does not populate sensitive data
rg -nP -C3 'func .*NormalizeDefaults' --type=go

Repository: QuantumNous/new-api

Length of output: 471


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- model/subscription.go (lines) ---'
wc -l model/subscription.go
cat -n model/subscription.go | sed -n '1,260p'

echo '--- search GetSubscriptionPlans / DTO ---'
rg -n -C 3 'GetSubscriptionPlans|SubscriptionPlanDTO|type .*SubscriptionPlan.*DTO|subscription/plans' controller router model

Repository: QuantumNous/new-api

Length of output: 12692


Hide internal plan metadata from the public plans endpoint

controller.GetSubscriptionPlans returns model.SubscriptionPlan directly, so unauthenticated callers receive fields like stripe_price_id, creem_product_id, waffo_pancake_product_id, upgrade_group, and downgrade_group. Map this to a public DTO and omit internal/provider-specific fields.

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

In `@router/api-router.go` at line 35, Update controller.GetSubscriptionPlans to
map each model.SubscriptionPlan to a dedicated public response DTO containing
only client-safe plan fields, excluding stripe_price_id, creem_product_id,
waffo_pancake_product_id, upgrade_group, and downgrade_group; return the DTO
collection from the /subscription/plans endpoint instead of exposing
model.SubscriptionPlan directly.

Comment on lines +27 to +33
function formatPrice(value?: number): string {
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
return '-'
}
const { symbol } = pricingCurrencyConfig
return `${symbol}${value.toFixed(value >= 1 ? 2 : 4).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')}`
}

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 getCurrencyDisplay() instead of pricingCurrencyConfig.symbol for currency formatting.

formatPrice here uses pricingCurrencyConfig.symbol (currently '¥') to format USD-denominated prices, producing incorrect output like ¥70. The formatPrice in index.tsx uses getCurrencyDisplay() which properly handles currency conversion and symbols. Align this implementation to avoid divergence and incorrect display.

🔧 Proposed fix
+import { getCurrencyDisplay } from '`@/lib/currency`'
+
-function formatPrice(value?: number): string {
-  if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
-    return '-'
-  }
-  const { symbol } = pricingCurrencyConfig
-  return `${symbol}${value.toFixed(value >= 1 ? 2 : 4).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')}`
-}
+function formatPrice(value?: number): string {
+  if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
+    return '-'
+  }
+  const { meta } = getCurrencyDisplay()
+  if (meta.kind === 'custom') {
+    return `${meta.symbol}${(value * meta.exchangeRate).toFixed(2)}`
+  }
+  if (meta.kind === 'currency') {
+    return new Intl.NumberFormat(undefined, {
+      style: 'currency',
+      currency: meta.currencyCode,
+      currencyDisplay: 'narrowSymbol',
+      minimumFractionDigits: 0,
+      maximumFractionDigits: 2,
+    }).format(value * meta.exchangeRate)
+  }
+  return `$${value.toFixed(2)}`
+}
📝 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
function formatPrice(value?: number): string {
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
return '-'
}
const { symbol } = pricingCurrencyConfig
return `${symbol}${value.toFixed(value >= 1 ? 2 : 4).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')}`
}
import { getCurrencyDisplay } from '`@/lib/currency`'
function formatPrice(value?: number): string {
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
return '-'
}
const { meta } = getCurrencyDisplay()
if (meta.kind === 'custom') {
return `${meta.symbol}${(value * meta.exchangeRate).toFixed(2)}`
}
if (meta.kind === 'currency') {
return new Intl.NumberFormat(undefined, {
style: 'currency',
currency: meta.currencyCode,
currencyDisplay: 'narrowSymbol',
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(value * meta.exchangeRate)
}
return `$${value.toFixed(2)}`
}
🤖 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 - 33, Update formatPrice to use getCurrencyDisplay() rather than
pricingCurrencyConfig.symbol, matching the implementation in index.tsx and
ensuring the displayed currency is correctly converted and labeled. Remove the
direct pricingCurrencyConfig symbol usage while preserving the existing
validation and decimal formatting behavior.

Comment on lines +140 to +145
function formatSubscriptionPrice(amount: number | string): string {
const numeric =
typeof amount === 'number' ? amount : Number.parseFloat(String(amount))
if (!Number.isFinite(numeric)) return '-'
return `¥${numeric.toFixed(2)}`
}

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 | 🏗️ Heavy lift

Fix i18n violations: use English t() keys, wrap config values in t(), and avoid hardcoded currency symbols.

Multiple i18n issues in this file:

  1. Chinese t() keys: t('已复制到剪切板'), t('复制失败'), t('直连官方的'), t('企业级接口网关'), t('获取密钥'), t('模型价格对比'), t('暂无价格数据'), t('图像模型') — As per coding guidelines, use English source strings as keys.
  2. Config values without t(): pricingHeaderConfig.* and imagePricingHeaderConfig.* are Chinese strings rendered directly in JSX (lines 556–571, 658–664) without t(), so they won't translate.
  3. formatSubscriptionPrice hardcodes ¥ (lines 140–145): Ignores the system currency config (getCurrencyDisplay()), producing incorrect symbols for non-CNY deployments.
🔧 Proposed fixes

Fix Chinese t() keys to English:

-        toast.success(t('已复制到剪切板'))
+        toast.success(t('Copied to clipboard'))
-        toast.error(t('复制失败'))
+        toast.error(t('Copy failed'))

Fix formatSubscriptionPrice to use currency config:

-function formatSubscriptionPrice(amount: number | string): string {
-  const numeric =
-    typeof amount === 'number' ? amount : Number.parseFloat(String(amount))
-  if (!Number.isFinite(numeric)) return '-'
-  return `¥${numeric.toFixed(2)}`
-}
+function formatSubscriptionPrice(amount: number | string): string {
+  const numeric =
+    typeof amount === 'number' ? amount : Number.parseFloat(String(amount))
+  if (!Number.isFinite(numeric)) return '-'
+  const { meta } = getCurrencyDisplay()
+  const converted = numeric * meta.exchangeRate
+  return `${meta.symbol}${converted.toFixed(2)}`
+}

Also applies to: 431-433, 488-495, 556-571

🤖 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 140 - 145, Update the
listed Chinese translation keys in the home page component to English source
strings while preserving their meanings, and wrap all rendered
pricingHeaderConfig and imagePricingHeaderConfig values with t(). Refactor
formatSubscriptionPrice to obtain the symbol from getCurrencyDisplay() instead
of hardcoding ¥, while retaining numeric validation and two-decimal formatting.

Source: Coding guidelines

Comment on lines +389 to +410
const imageModelPricingRows = useMemo<ImageModelPricingRow[]>(() => {
const pricingModels = pricingData?.data || []
const groupRatios = pricingData?.group_ratio || {}
const usableGroups = pricingData?.usable_group || {}
const modelMap = new Map(
pricingModels.map((model) => [model.model_name, model])
)

return imageModelPricingConfig.map((configItem) => {
const typeLabels = configItem.types.map((typeItem) => typeItem.type)
const model = findPricingModel(modelMap, [configItem.name])
const priceRange = model
? getImagePriceRangeUSD(model, groupRatios, usableGroups, 1)
: null

return {
name: configItem.name,
types: typeLabels.join('、'),
price: formatPerRequestPriceRange(priceRange),
}
})
}, [pricingData])

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

Image model pricing only computes price for 1K multiplier, misleading users.

getImagePriceRangeUSD is called with multiplier: 1 (line 401), but imageModelPricingConfig defines multipliers 1, 4, and 16 for 1K, 2K, and 4K respectively. The displayed types field shows "1K、2K、4K" but the price only reflects the 1K variant. Users see all three types listed but a price that doesn't cover 2K or 4K.

Either compute a price range across all multipliers, or render separate rows per type with correct prices.

🔧 Proposed fix: compute range across all multipliers
   return imageModelPricingConfig.map((configItem) => {
     const typeLabels = configItem.types.map((typeItem) => typeItem.type)
     const model = findPricingModel(modelMap, [configItem.name])
-    const priceRange = model
-      ? getImagePriceRangeUSD(model, groupRatios, usableGroups, 1)
-      : null
+    const priceRanges = model
+      ? configItem.types.map((typeItem) =>
+          getImagePriceRangeUSD(model, groupRatios, usableGroups, typeItem.multiplier)
+        ).filter((r): r is { min: number; max: number } => r !== null)
+      : []
+    const priceRange = priceRanges.length > 0
+      ? {
+          min: Math.min(...priceRanges.map((r) => r.min)),
+          max: Math.max(...priceRanges.map((r) => r.max)),
+        }
+      : null

     return {
       name: configItem.name,
       types: typeLabels.join('、'),
       price: formatPerRequestPriceRange(priceRange),
     }
   })
📝 Committable suggestion

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

Suggested change
const imageModelPricingRows = useMemo<ImageModelPricingRow[]>(() => {
const pricingModels = pricingData?.data || []
const groupRatios = pricingData?.group_ratio || {}
const usableGroups = pricingData?.usable_group || {}
const modelMap = new Map(
pricingModels.map((model) => [model.model_name, model])
)
return imageModelPricingConfig.map((configItem) => {
const typeLabels = configItem.types.map((typeItem) => typeItem.type)
const model = findPricingModel(modelMap, [configItem.name])
const priceRange = model
? getImagePriceRangeUSD(model, groupRatios, usableGroups, 1)
: null
return {
name: configItem.name,
types: typeLabels.join('、'),
price: formatPerRequestPriceRange(priceRange),
}
})
}, [pricingData])
const imageModelPricingRows = useMemo<ImageModelPricingRow[]>(() => {
const pricingModels = pricingData?.data || []
const groupRatios = pricingData?.group_ratio || {}
const usableGroups = pricingData?.usable_group || {}
const modelMap = new Map(
pricingModels.map((model) => [model.model_name, model])
)
return imageModelPricingConfig.map((configItem) => {
const typeLabels = configItem.types.map((typeItem) => typeItem.type)
const model = findPricingModel(modelMap, [configItem.name])
const priceRanges = model
? configItem.types
.map((typeItem) =>
getImagePriceRangeUSD(
model,
groupRatios,
usableGroups,
typeItem.multiplier
)
)
.filter(
(r): r is { min: number; max: number } => r !== null
)
: []
const priceRange =
priceRanges.length > 0
? {
min: Math.min(...priceRanges.map((r) => r.min)),
max: Math.max(...priceRanges.map((r) => r.max)),
}
: null
return {
name: configItem.name,
types: typeLabels.join('、'),
price: formatPerRequestPriceRange(priceRange),
}
})
}, [pricingData])
🤖 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 389 - 410, Update the
image pricing calculation in useMemo imageModelPricingRows so prices represent
every configured image size instead of always using multiplier 1. Derive the
multipliers from configItem.types, calculate the USD range for each via
getImagePriceRangeUSD, and combine them into an overall range before passing it
to formatPerRequestPriceRange. Preserve the existing grouped row and displayed
type labels.

Comment on lines +451 to +466
if (homePageContent) {
return (
<PublicLayout>
<div className='mx-auto max-w-6xl px-4 py-8'>
<RichContent
mode='markdown'
content={content}
className='custom-home-content'
/>
</div>
<PublicLayout showMainContainer={false}>
<main className='w-full overflow-x-hidden'>
{homePageContent.startsWith('https://') ? (
<iframe
src={homePageContent}
className='h-screen w-full border-none'
title={t('Custom Home Page')}
/>
) : (
<div
className='mt-[60px]'
dangerouslySetInnerHTML={{ __html: homePageContent }}
/>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize dangerouslySetInnerHTML content to prevent XSS.

Content from /api/home_page_content is injected directly into the DOM without sanitization. If the endpoint is accessible to non-admin users or the backend content can be manipulated, this exposes the page to stored XSS. Use DOMPurify (or equivalent) to sanitize the HTML before injection.

🔒 Proposed fix
+import DOMPurify from 'dompurify'
+
           ) : (
             <div
               className='mt-[60px]'
-              dangerouslySetInnerHTML={{ __html: homePageContent }}
+              dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(homePageContent) }}
             />
           )}
📝 Committable suggestion

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

Suggested change
if (homePageContent) {
return (
<PublicLayout>
<div className='mx-auto max-w-6xl px-4 py-8'>
<RichContent
mode='markdown'
content={content}
className='custom-home-content'
/>
</div>
<PublicLayout showMainContainer={false}>
<main className='w-full overflow-x-hidden'>
{homePageContent.startsWith('https://') ? (
<iframe
src={homePageContent}
className='h-screen w-full border-none'
title={t('Custom Home Page')}
/>
) : (
<div
className='mt-[60px]'
dangerouslySetInnerHTML={{ __html: homePageContent }}
/>
)}
if (homePageContent) {
return (
<PublicLayout showMainContainer={false}>
<main className='w-full overflow-x-hidden'>
{homePageContent.startsWith('https://') ? (
<iframe
src={homePageContent}
className='h-screen w-full border-none'
title={t('Custom Home Page')}
/>
) : (
<div
className='mt-[60px]'
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(homePageContent) }}
/>
)}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 463-463: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation

(react-unsafe-html-injection)

🤖 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 451 - 466, Sanitize the
HTML returned as homePageContent before rendering it via
dangerouslySetInnerHTML. Add a DOMPurify (or equivalent) dependency/import,
apply it to the non-URL content branch, and pass the sanitized result to
dangerouslySetInnerHTML while preserving the existing iframe handling for HTTPS
URLs.

Comment on lines +29 to +34
export const pricingCurrencyConfig = {
// 货币类型:'USD' 或 'CNY'
currency: 'USD' as 'USD' | 'CNY',
// 显示符号
symbol: '¥',
}

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

Fix pricingCurrencyConfig currency/symbol mismatch.

currency is set to 'USD' but symbol is '¥' (CNY symbol). This causes ModelPricing (in model-pricing.tsx) to render USD prices with a ¥ symbol, e.g., ¥70 for a $70 price. The modelPricingConfig entries are documented as USD ("单位 USD"), so the symbol should be '$' or this should use getCurrencyDisplay() for proper currency handling.

🔧 Proposed fix
 export const pricingCurrencyConfig = {
   // 货币类型:'USD' 或 'CNY'
   currency: 'USD' as 'USD' | 'CNY',
   // 显示符号
-  symbol: '¥',
+  symbol: '$',
 }
📝 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
export const pricingCurrencyConfig = {
// 货币类型:'USD' 或 'CNY'
currency: 'USD' as 'USD' | 'CNY',
// 显示符号
symbol: '¥',
}
export const pricingCurrencyConfig = {
// 货币类型:'USD' 或 'CNY'
currency: 'USD' as 'USD' | 'CNY',
// 显示符号
symbol: '$',
}
🤖 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 29 - 34,
Fix the currency/symbol mismatch in pricingCurrencyConfig by changing the USD
symbol from ¥ to $, or update the configuration to derive the symbol through
getCurrencyDisplay() if dynamic currency handling is required; ensure
ModelPricing renders the documented USD prices consistently.

Comment on lines +40 to +53
export const pricingHeaderConfig = {
model: '模型',
input: '输入(1M)',
output: '输出(1M)',
official: '官方输入/输出(1M)',
discount: '折扣',
cacheHit: '缓存命中',
}

export const imagePricingHeaderConfig = {
model: '模型名称',
type: '图像类型',
price: '价格',
}

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 | 🏗️ Heavy lift

Make config text i18n-ready.

pricingHeaderConfig, imagePricingHeaderConfig, and pricingNoticeConfig.text contain hardcoded Chinese strings. These are rendered directly in index.tsx without t(), so non-Chinese users see untranslated text. As per coding guidelines, frontend UI text must support i18n with English source strings as keys.

Either move these to i18n locale files and use t() at the consumption site, or wrap the values in t() calls.

Also applies to: 153-160

🤖 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 40 - 53,
The pricing UI configuration contains hardcoded Chinese text and bypasses
localization. Update pricingHeaderConfig, imagePricingHeaderConfig, and
pricingNoticeConfig.text, then adjust their consumption in index.tsx to use the
i18n t() function with English source-string keys, or move the labels into
locale files and translate them at render time.

Source: Coding guidelines

Comment on lines +47 to +51
@apply bg-background text-foreground has-[div[data-variant='inset']]:bg-sidebar min-h-svh w-full font-sans;
background-image:
radial-gradient(circle at 14% 8%, oklch(0.96 0.025 68 / 65%), transparent 38%),
radial-gradient(circle at 86% 0%, oklch(0.93 0.04 48 / 35%), transparent 42%);
background-attachment: fixed;

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 | 🟡 Minor | ⚡ Quick win

Fix Stylelint error: add empty line before background-image declaration.

Stylelint reports declaration-empty-line-before at lines 48-50. Per coding guidelines, lint errors in changed files must be fixed before completing code changes.

🔧 Proposed fix
     `@apply` bg-background text-foreground has-[div[data-variant='inset']]:bg-sidebar min-h-svh w-full font-sans;
+
     background-image:
       radial-gradient(circle at 14% 8%, oklch(0.96 0.025 68 / 65%), transparent 38%),
       radial-gradient(circle at 86% 0%, oklch(0.93 0.04 48 / 35%), transparent 42%);
📝 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
@apply bg-background text-foreground has-[div[data-variant='inset']]:bg-sidebar min-h-svh w-full font-sans;
background-image:
radial-gradient(circle at 14% 8%, oklch(0.96 0.025 68 / 65%), transparent 38%),
radial-gradient(circle at 86% 0%, oklch(0.93 0.04 48 / 35%), transparent 42%);
background-attachment: fixed;
`@apply` bg-background text-foreground has-[div[data-variant='inset']]:bg-sidebar min-h-svh w-full font-sans;
background-image:
radial-gradient(circle at 14% 8%, oklch(0.96 0.025 68 / 65%), transparent 38%),
radial-gradient(circle at 86% 0%, oklch(0.93 0.04 48 / 35%), transparent 42%);
background-attachment: fixed;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 48-50: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 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, Fix the Stylelint
declaration-empty-line-before error in the root stylesheet rule by inserting an
empty line between the `@apply` declaration and the background-image declaration;
keep the existing background-image gradients and background-attachment
declarations unchanged.

Sources: Coding guidelines, Linters/SAST tools


🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for a dark-mode body background override in index.css.
rg -nU -A3 '\.dark\s+body\b|\.dark\s*\{[^}]*background-image' web/default/src/styles/index.css

# Also check theme.css for any body background override.
rg -nU -A3 '\.dark\s+body\b|body\s*\{[^}]*background' web/default/src/styles/theme.css

Repository: QuantumNous/new-api

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant stylesheet files.
git ls-files 'web/default/src/styles/*' 'web/default/AGENTS.md'

# Show outlines/lengths before reading.
for f in web/default/src/styles/index.css web/default/src/styles/theme.css web/default/AGENTS.md; do
  [ -f "$f" ] && { echo "== $f =="; wc -l "$f"; }
done

# Read the relevant sections with line numbers.
echo '== index.css =='
cat -n web/default/src/styles/index.css | sed -n '1,220p'

echo '== theme.css =='
cat -n web/default/src/styles/theme.css | sed -n '1,220p'

echo '== AGENTS.md =='
cat -n web/default/AGENTS.md | sed -n '1,220p'

Repository: QuantumNous/new-api

Length of output: 25738


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus on background-related declarations and dark-mode selectors in the styles tree.
rg -n --hidden --glob 'web/default/src/styles/**' \
  '(background-image|background-attachment|body\b|\.dark\b|color-scheme|theme)' \
  web/default/src/styles

Repository: QuantumNous/new-api

Length of output: 5378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the body background gradient is defined using CSS variables elsewhere.
rg -n --hidden --glob 'web/default/src/styles/**' \
  'oklch\(|radial-gradient|--.*background|--.*foreground|bg-background' \
  web/default/src/styles

Repository: QuantumNous/new-api

Length of output: 32929


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact section in web/default/src/styles/index.css around the reported lines.
cat -n web/default/src/styles/index.css | sed -n '1,140p'

# Search the entire web/default/src tree for dark body/background overrides.
rg -n --hidden --glob 'web/default/src/**' '\.dark\s+body\b|body\s*\{[^}]*background|background-image' web/default/src

Repository: QuantumNous/new-api

Length of output: 5329


Add a dark-mode override for the body gradients. The hardcoded warm gradients stay bright in .dark; switch them to theme-aware tokens or add a .dark body background-image variant.

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 48-50: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 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
background styling near the existing radial gradients to provide
dark-mode-specific colors: replace the hardcoded light warm stops with
theme-aware tokens or add a `.dark body` `background-image` override using
suitably muted dark gradients, while preserving the existing light-mode
appearance and fixed attachment.

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