Skip to content

feat: 添加错误信息复写、请求头透传、自定义邀请码、充值协议 - #2936

Closed
QiuFFFF wants to merge 0 commit into
QuantumNous:mainfrom
QiuFFFF:feat/custom-features
Closed

feat: 添加错误信息复写、请求头透传、自定义邀请码、充值协议#2936
QiuFFFF wants to merge 0 commit into
QuantumNous:mainfrom
QiuFFFF:feat/custom-features

Conversation

@QiuFFFF

@QiuFFFF QiuFFFF commented Feb 13, 2026

Copy link
Copy Markdown

1. 自定义邀请码

  • 允许用户自定义邀请码内容
  • 将邀请码按钮独立出来,修复遮罩层 bug

2. 渠道透传请求头

  • 渠道新增 pass-through request headers 功能
  • 管理员可配置需要透传的请求头

3. 用户充值协议

  • 新增充值协议设置项
  • 用户充值前需确认协议

4. 错误信息复写(Error Mapping)

  • 渠道新增 error_mapping 字段,支持按规则重写上游错误信息
  • 支持 4 种匹配方式:包含匹配、正则匹配、精确代码、精确类型
  • 可重写 message、type、code 字段
  • 前端提供可视化编辑器 + 手动 JSON 编辑模式
  • 完整 i18n 支持(en/ja/fr/ru/vi/zh-TW/zh-CN)

Test Plan

  • 前后端编译均通过
  • 错误信息复写:配置 contains/regex/exact_code/exact_type规则后,上游错误消息被正确替换;未配置时行为与原有逻辑一致
  • 前端 ErrorMappingEditor 可视化模式增删改规则正常,手动 JSON 编辑模式双向同步正常,填入模板功能正常
  • 自定义邀请码:创建、编辑、删除自定义邀请码正常,按钮独立显示无遮罩层问题
  • 充值协议:后台设置协议内容后充值页正确显示,未设置时不显示协议区域
  • 请求头透传:渠道配置透传请求头后,上游请求中携带对应 Header;未配置时请求头与原有行为一致

Summary by CodeRabbit

  • New Features

    • Edit 4-character affiliate codes via modal with validation.
    • Top-up agreement flow: admin-editable agreement, consent checkbox/modal that gates purchases.
    • Error-mapping editor UI and runtime error mapping for normalized API errors.
    • Channel option to pass through request headers.
    • CC Switch export for tokens (generate/copy/import links).
    • Configurable invite reward description surfaced in status.
  • Localization

    • Added translations for all new UI text across locales.
  • Chores

    • Routes, settings, and UI wiring for above features.

@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds affiliate-code management, configurable error-mapping applied throughout relay handlers, a top-up agreement consent flow, request-header passthrough flag, CC Switch export/link support, UI editors/modals, many i18n entries, and option wiring across backend, middleware, relay, and frontend.

Changes

Cohort / File(s) Summary
Backend: global options & status
common/constants.go, model/option.go, controller/misc.go, router/api-router.go
New global options wired (InviteRewardDescription, TopupAgreement, CCSwitchEnabled); GetStatus now exposes invite_reward_description, topup_agreement, enable_ccswitch; new PUT route for updating aff code.
Backend: affiliate flow
controller/user.go, model/user.go, i18n/keys.go, i18n/locales/*.yaml
Add UpdateAffCode handler + request type; aff-code validation, existence check, update logic; i18n keys and locale strings added for aff-code errors/success.
Error-mapping core
service/error.go, constant/context_key.go, model/channel.go
New ErrorMapping types and ApplyErrorMapping; context key added; Channel gained ErrorMapping field and accessor. Review regex/replace logic and pattern precedence.
Relay: mapping applied
relay/*.go, relay/channel/gemini/relay-gemini.go, relay/chat_completions_via_responses.go, relay/*_handler.go, relay/websocket.go
Many relay handlers now read error_mapping from request context and call service.ApplyErrorMapping on produced API errors across failure paths — ensure mapping is used consistently and nil-safe.
Channel settings & middleware
dto/channel_settings.go, relay/common/relay_info.go, middleware/distributor.go, web/src/components/table/channels/modals/EditChannelModal.jsx
Add error_mapping field and pass_through_headers_enabled; middleware stores channel error_mapping into context; relay meta initializes wildcard passthrough header; modal integrates ErrorMappingEditor and passthrough switch.
Frontend: ErrorMapping editor & helper
web/src/components/common/ui/ErrorMappingEditor.jsx, web/src/helpers/ccswitch.js
New visual/manual JSON editor for error-mapping rules; new helper generateCCSwitchLink for CC Switch URLs. Large UI component — review parsing/serialization and onChange contract.
Frontend: top-up & invite UI
web/src/components/topup/..., web/src/components/topup/modals/*, web/src/components/topup/index.jsx
Top-up agreement content + consent checkbox/modal gating payment actions until consent; EditAffCode modal; InvitationCard accepts inviteRewardDescription and onEditAffCode. Verify prop propagation and modal flows.
Frontend: tokens / CC Switch
web/src/components/table/tokens/TokensColumnDefs.jsx, web/src/components/table/tokens/TokensTable.jsx, web/src/helpers/data.js, web/src/components/settings/OperationSetting.jsx
Add CC Switch export/copy UI, generateCCSwitchLink usage, localStorage flag enable_ccswitch, settings expose CCSwitchEnabled; update column APIs to accept enableCCSwitch.
i18n & deps
web/src/i18n/locales/*, web/package.json
Many new locale keys across languages for aff-code, top-up agreement, passthrough headers, error-mapping UI, CC Switch; added dompurify dependency.
Misc
.env.example
.env.example content removed (file cleared).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant DB
    participant Relay
    participant Service
    participant Upstream

    User->>Frontend: open edit-aff / start top-up / trigger channel request
    Frontend->>API: PUT /api/user/aff or payment request
    API->>DB: ValidateAffCode / IsAffCodeExists / UpdateUserAffCode
    DB-->>API: result
    API-->>Frontend: success/failure (i18n)

    Frontend->>Relay: channel request (context includes error_mapping, status_code_mapping)
    Relay->>Upstream: forward request
    Upstream-->>Relay: error response
    Relay->>Service: ApplyErrorMapping(newAPIError, error_mapping)
    Service-->>Relay: mapped error
    Relay-->>Frontend: normalized error response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • Calcium-Ion

Poem

🐰 I hopped through code with twinkling eyes,
Four-letter codes now uppercase and wise,
Errors don fresh cloaks that gently mend,
Agreements checked before the coins descend,
Headers leap the gates — hooray, new ties!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the four main features added: error message remapping, request header pass-through, custom invitation codes, and recharge agreement. It is specific, clear, and directly reflects the primary changes in the changeset.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

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

Caution

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

⚠️ Outside diff range comments (1)
web/src/components/topup/modals/SubscriptionPurchaseModal.jsx (1)

20-99: ⚠️ Potential issue | 🟠 Major

Sanitize agreement HTML before injecting into the DOM.

marked.parse(...) with dangerouslySetInnerHTML renders raw HTML without sanitization. Since topupAgreement is server-provided, malicious HTML injection is possible. Sanitize using DOMPurify or equivalent before rendering.

🛡️ Suggested hardening
-import { marked } from 'marked';
+import { marked } from 'marked';
+import DOMPurify from 'dompurify';
@@
   const purchaseLimitReached =
     purchaseLimit > 0 && purchaseCount >= purchaseLimit;
+  const agreementHtml = DOMPurify.sanitize(marked.parse(topupAgreement || ''));
@@
-      <div dangerouslySetInnerHTML={{ __html: marked.parse(topupAgreement || '') }} />
+      <div dangerouslySetInnerHTML={{ __html: agreementHtml }} />
🤖 Fix all issues with AI agents
In `@model/user.go`:
- Around line 332-339: The UpdateUserAffCode function currently returns raw DB
errors and can surface a duplicate-key error during a race; after performing the
Update in UpdateUserAffCode check the returned result.Error for a
unique-constraint/duplicate-key error (e.g., Postgres pq/error Code "23505" or
MySQL duplicate entry) and translate that into a domain error like
errors.New("aff_code_already_taken") before returning; keep the existing
not-found check on result.RowsAffected and only map DB-specific duplicate-key
errors to the friendly aff_code_already_taken value so callers get a stable,
non-DB-specific error.
- Around line 312-329: ValidateAffCode uppercases codes but IsAffCodeExists
queries the raw aff_code which can cause case-collisions; change IsAffCodeExists
to compare normalized values by using SQL UPPER on the column and passing the
uppercased affCode (e.g. use WHERE UPPER(aff_code) = ? AND id != ? with
strings.ToUpper(affCode) as the parameter) so uniqueness checks are
case-insensitive and consistent with ValidateAffCode (update IsAffCodeExists
function accordingly).

In `@service/error.go`:
- Around line 215-241: The exact_code/exact_type comparisons in matchesPattern
use the internal values passed as errCode/errType (from
newApiErr.GetErrorCode/GetErrorType) which miss upstream provider fields; change
matchesPattern (or its caller) to prefer upstream RelayError values when
present: detect a RelayError on the API error (RelayError struct/field on the
error object), extract its upstream code and type (e.g., relay.Code / relay.Type
or equivalent fields) and use those for the "exact_code" and "exact_type"
branches (fall back to the internal errCode/errType if no RelayError), while
leaving "contains" and "regex" semantics unchanged; update references in
matchesPattern and any call sites that construct errCode/errType to pass the
upstream values so exact_* rules match provider codes/types.

In `@web/src/components/common/ui/ErrorMappingEditor.jsx`:
- Around line 112-126: The effect in useEffect reads the current patterns state
and calls patternsToJson(patterns) while only listing [value] in its dependency
array, causing a stale-closure bug; fix it by introducing a ref (e.g.,
lastEmittedJsonRef) to store the last emitted JSON representation and use that
ref for comparison instead of reading patterns directly, update the ref whenever
you call setPatterns (or after successfully syncing), and keep the effect
dependencies to [value] while also clearing setJsonError('') as before;
reference parsePatterns, patternsToJson, value, patterns, setPatterns, and
setJsonError when locating where to add the ref and update logic.

In `@web/src/components/topup/modals/PaymentConfirmModal.jsx`:
- Around line 20-24: The agreement HTML is being injected via
dangerouslySetInnerHTML using marked.parse() without sanitization (e.g., in
PaymentConfirmModal.jsx where marked.parse is used), which risks XSS; install
and import DOMPurify (add dompurify to web/package.json), run
DOMPurify.sanitize(marked.parse(...)) and pass the sanitized string to
dangerouslySetInnerHTML, and apply the same change to
SubscriptionPurchaseModal.jsx and RechargeCard.jsx (update their imports and
replace raw marked.parse() uses with DOMPurify.sanitize(marked.parse(...))
before injection).

In `@web/src/components/topup/RechargeCard.jsx`:
- Line 560: The payment method buttons are not visually disabled like the
redemption code button even though the PaymentConfirmModal prevents proceeding
via its confirm button using disabled={topupAgreement &&
!agreedToTopupAgreement}; update the payment method button rendering (the
components that trigger opening PaymentConfirmModal) to also respect the same
gating by disabling them when topupAgreement && !agreedToTopupAgreement (use the
same agreedToTopupAgreement and topupAgreement flags), so both the immediate
payment options and the PaymentConfirmModal follow the same UX/guarding
behavior.

In `@web/src/i18n/locales/fr.json`:
- Around line 2526-2536: Remove the duplicate JSON key "匹配类型": "Type de
correspondance" from the fr.json block where it appears alongside "错误信息复写",
"匹配内容", "替换为" (the later occurrence), leaving only the original definition (the
earlier occurrence). Ensure the surrounding entries and trailing
comma/formatting remain valid JSON after removing that single key-value pair.

In `@web/src/i18n/locales/ja.json`:
- Around line 2509-2519: The JSON contains a duplicate key "匹配类型" (previously
defined as "マッチングタイプ") which is being overridden by the later "匹配类型": "マッチタイプ";
remove the duplicate or rename the new key to a distinct flat-key such as
"错误匹配类型" to preserve the original translation; if you rename the key, update all
references/usages in the codebase that expect the error-mapping context to use
the new key ("错误匹配类型") and ensure no other duplicate Chinese-source keys remain
in this locale file.

In `@web/src/i18n/locales/vi.json`:
- Line 2959: The JSON contains a duplicate key "确认修改" with a different
translation; remove the new duplicate entry or rename it to a distinct key and
update its usage in the invite-code confirmation dialog. Specifically, either
delete the second "确认修改": "Xác nhận" entry so the original "确认修改": "Xác nhận sửa
đổi" remains, or create a new key (e.g., "确认修改_invite_code" or similar) and
change the component that renders the invite-code confirmation to use that new
key so translations remain unambiguous.
🧹 Nitpick comments (4)
web/src/components/common/ui/ErrorMappingEditor.jsx (2)

256-272: fillTemplate bypasses emitChange helper, duplicating emit logic.

Other handlers (handleVisualChange, handleManualChange) route through emitChange, but fillTemplate manually calls formApi.setValue and onChange separately. If emitChange gains additional logic (e.g., validation, analytics), this path will silently miss it.

Proposed fix
  const fillTemplate = useCallback(() => {
    const templateString = JSON.stringify(TEMPLATE, null, 2);
-   if (formApi && field) {
-     formApi.setValue(field, templateString);
-   }
    setManualText(templateString);
    setPatterns(
      TEMPLATE.patterns.map((p) => ({
        id: generateUniqueId(),
        match: p.match,
        match_type: p.match_type,
        replace_message: p.replace_message,
      })),
    );
-   onChange?.(templateString);
+   emitChange(templateString);
    setJsonError('');
- }, [onChange, formApi, field]);
+ }, [emitChange]);

354-361: Tab onChange partially duplicates toggleEditMode logic.

Switching to manual mode (lines 355-358) inlines the same logic that toggleEditMode performs for the visual→manual path (lines 198-201), while the manual→visual path delegates to toggleEditMode. Consider using toggleEditMode for both directions to keep a single source of truth.

web/src/components/topup/modals/EditAffCodeModal.jsx (1)

28-37: Redundant regex validation on line 33.

Since handleChange (line 23) already strips non-alphanumeric characters and uppercases the input, after passing the length check on line 29, the regex on line 33 will always match. This second check is dead code.

♻️ Suggested simplification
   const handleOk = () => {
     if (affCode.length !== 4) {
       setError(t('邀请码必须为4位字母或数字'));
       return;
     }
-    if (!/^[A-Za-z0-9]{4}$/.test(affCode)) {
-      setError(t('邀请码只能包含字母和数字'));
-      return;
-    }
     onOk(affCode);
   };
web/src/components/topup/RechargeCard.jsx (1)

614-627: Add HTML sanitization for Markdown output to prevent XSS.

marked.parse() does not sanitize HTML — raw <script> tags or event-handler attributes in Markdown will pass through. While topupAgreement is admin-configured, using dangerouslySetInnerHTML with unsanitized output is a defense-in-depth gap. Consider using DOMPurify:

 import { marked } from 'marked';
+import DOMPurify from 'dompurify';
-      <div dangerouslySetInnerHTML={{ __html: marked.parse(topupAgreement || '') }} />
+      <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(marked.parse(topupAgreement || '')) }} />

Note: This pattern appears in multiple components (SubscriptionPurchaseModal, PaymentConfirmModal, AnnouncementsPanel, FaqPanel, NoticeModal). Consider applying sanitization consistently across the codebase.

Comment thread model/user.go Outdated
Comment thread model/user.go Outdated
Comment thread service/error.go Outdated
Comment on lines +112 to +126
useEffect(() => {
const newPatterns = parsePatterns(value);
// Compare serialized to avoid unnecessary updates
const currentJson = patternsToJson(patterns);
if (typeof value === 'string' && value.trim()) {
if (value !== currentJson) {
setPatterns(newPatterns);
}
} else if (!value) {
if (patterns.length > 0) {
setPatterns([]);
}
}
setJsonError('');
}, [value]);

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.

⚠️ Potential issue | 🟡 Minor

Stale closure: patterns read inside effect but missing from dependency array.

This effect reads patterns (line 115) and calls patternsToJson (line 115) but the dependency array only includes [value]. When value changes, the comparison value !== currentJson uses a potentially stale patterns snapshot, which can cause the sync logic to either skip a necessary update or apply a redundant one.

Proposed fix

Consider using a ref to track the "last emitted value" instead of comparing against the current patterns state:

+ const lastEmittedRef = useRef(value);
+
  useEffect(() => {
-   const newPatterns = parsePatterns(value);
-   const currentJson = patternsToJson(patterns);
-   if (typeof value === 'string' && value.trim()) {
-     if (value !== currentJson) {
-       setPatterns(newPatterns);
-     }
-   } else if (!value) {
-     if (patterns.length > 0) {
-       setPatterns([]);
-     }
+   if (value === lastEmittedRef.current) return;
+   lastEmittedRef.current = value;
+   if (typeof value === 'string' && value.trim()) {
+     setPatterns(parsePatterns(value));
+   } else if (!value) {
+     setPatterns([]);
    }
    setJsonError('');
  }, [value]);

This avoids reading stale patterns and correctly detects only externally-driven changes.

📝 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
useEffect(() => {
const newPatterns = parsePatterns(value);
// Compare serialized to avoid unnecessary updates
const currentJson = patternsToJson(patterns);
if (typeof value === 'string' && value.trim()) {
if (value !== currentJson) {
setPatterns(newPatterns);
}
} else if (!value) {
if (patterns.length > 0) {
setPatterns([]);
}
}
setJsonError('');
}, [value]);
const lastEmittedRef = useRef(value);
useEffect(() => {
if (value === lastEmittedRef.current) return;
lastEmittedRef.current = value;
if (typeof value === 'string' && value.trim()) {
setPatterns(parsePatterns(value));
} else if (!value) {
setPatterns([]);
}
setJsonError('');
}, [value]);
🤖 Prompt for AI Agents
In `@web/src/components/common/ui/ErrorMappingEditor.jsx` around lines 112 - 126,
The effect in useEffect reads the current patterns state and calls
patternsToJson(patterns) while only listing [value] in its dependency array,
causing a stale-closure bug; fix it by introducing a ref (e.g.,
lastEmittedJsonRef) to store the last emitted JSON representation and use that
ref for comparison instead of reading patterns directly, update the ref whenever
you call setPatterns (or after successfully syncing), and keep the effect
dependencies to [value] while also clearing setJsonError('') as before;
reference parsePatterns, patternsToJson, value, patterns, setPatterns, and
setJsonError when locating where to add the ref and update logic.

Comment on lines +20 to +24
import React, { useState } from 'react';
import { Modal, Typography, Card, Skeleton, Checkbox } from '@douyinfe/semi-ui';
import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si';
import { CreditCard } from 'lucide-react';
import { marked } from 'marked';

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's read the PaymentConfirmModal.jsx file to see the actual code
cat -n web/src/components/topup/modals/PaymentConfirmModal.jsx

Repository: QuantumNous/new-api

Length of output: 10821


🏁 Script executed:

# Check if DOMPurify is already in the project dependencies
cat web/package.json | grep -A 50 '"dependencies"' | head -60

Repository: QuantumNous/new-api

Length of output: 1677


🏁 Script executed:

# Also check the root package.json in case it's there
cat package.json | grep -i 'dompurify' || echo "Not found in root"

Repository: QuantumNous/new-api

Length of output: 142


🏁 Script executed:

# Search for all marked.parse usage in the web directory
rg 'marked\.parse' web/

Repository: QuantumNous/new-api

Length of output: 1371


🏁 Script executed:

# Check if marked has any built-in sanitization or configuration
rg 'marked\.(setOptions|defaults|configure)' web/ || echo "No marked config found"

Repository: QuantumNous/new-api

Length of output: 85


🏁 Script executed:

# Verify the source of topupAgreement - check parent components that use PaymentConfirmModal
rg -B 3 -A 3 'PaymentConfirmModal' web/src --type jsx | head -100

Repository: QuantumNous/new-api

Length of output: 90


🏁 Script executed:

# Check where PaymentConfirmModal is imported/used
rg 'PaymentConfirmModal' web/src --type js

Repository: QuantumNous/new-api

Length of output: 402


🏁 Script executed:

# Also check without type filter
rg 'topupAgreement' web/src | grep -v 'PaymentConfirmModal.jsx'

Repository: QuantumNous/new-api

Length of output: 1773


Sanitize agreement HTML before injecting into the DOM to prevent XSS attacks.
Line 67 uses dangerouslySetInnerHTML with unsanitized output from marked.parse(). If the agreement content is compromised, this enables stored XSS.

🛡️ Suggested fix
 import React, { useState } from 'react';
 import { Modal, Typography, Card, Skeleton, Checkbox } from '@douyinfe/semi-ui';
 import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si';
 import { CreditCard } from 'lucide-react';
 import { marked } from 'marked';
+import DOMPurify from 'dompurify';
@@
-      <div dangerouslySetInnerHTML={{ __html: marked.parse(topupAgreement || '') }} />
+      <div
+        dangerouslySetInnerHTML={{
+          __html: DOMPurify.sanitize(marked.parse(topupAgreement || '')),
+        }}
+      />

Also add dompurify to web/package.json dependencies.

Also applies to: SubscriptionPurchaseModal.jsx and RechargeCard.jsx

🤖 Prompt for AI Agents
In `@web/src/components/topup/modals/PaymentConfirmModal.jsx` around lines 20 -
24, The agreement HTML is being injected via dangerouslySetInnerHTML using
marked.parse() without sanitization (e.g., in PaymentConfirmModal.jsx where
marked.parse is used), which risks XSS; install and import DOMPurify (add
dompurify to web/package.json), run DOMPurify.sanitize(marked.parse(...)) and
pass the sanitized string to dangerouslySetInnerHTML, and apply the same change
to SubscriptionPurchaseModal.jsx and RechargeCard.jsx (update their imports and
replace raw marked.parse() uses with DOMPurify.sanitize(marked.parse(...))
before injection).

theme='solid'
onClick={topUp}
loading={isSubmitting}
disabled={topupAgreement && !agreedToTopupAgreement}

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "agreedToTopupAgreement" --type=js --type=jsx --type=tsx --type=ts

Repository: QuantumNous/new-api

Length of output: 90


🏁 Script executed:

rg -n "agreedToTopupAgreement"

Repository: QuantumNous/new-api

Length of output: 2012


🏁 Script executed:

wc -l web/src/components/topup/RechargeCard.jsx

Repository: QuantumNous/new-api

Length of output: 108


🏁 Script executed:

sed -n '280,370p' web/src/components/topup/RechargeCard.jsx

Repository: QuantumNous/new-api

Length of output: 4296


🏁 Script executed:

sed -n '1,100p' web/src/components/topup/RechargeCard.jsx | grep -A 30 "preTopUp"

Repository: QuantumNous/new-api

Length of output: 475


🏁 Script executed:

rg -n "const preTopUp|function preTopUp" web/src/components/topup/

Repository: QuantumNous/new-api

Length of output: 141


🏁 Script executed:

sed -n '159,200p' web/src/components/topup/index.jsx

Repository: QuantumNous/new-api

Length of output: 946


🏁 Script executed:

rg -n "setOpen" web/src/components/topup/index.jsx | head -20

Repository: QuantumNous/new-api

Length of output: 703


🏁 Script executed:

sed -n '730,760p' web/src/components/topup/index.jsx

Repository: QuantumNous/new-api

Length of output: 920


🏁 Script executed:

rg -n "PaymentConfirmModal" web/src/components/topup/index.jsx

Repository: QuantumNous/new-api

Length of output: 160


🏁 Script executed:

sed -n '718,735p' web/src/components/topup/index.jsx

Repository: QuantumNous/new-api

Length of output: 662


🏁 Script executed:

sed -n '75,95p' web/src/components/topup/modals/PaymentConfirmModal.jsx

Repository: QuantumNous/new-api

Length of output: 831


Payment flow is protected downstream, but direct button gating differs from redemption code button.

The payment method buttons (lines 300-360) are not directly gated by agreedToTopupAgreement, unlike the redemption code button (line 560). However, the PaymentConfirmModal confirms button is gated with disabled: topupAgreement && !agreedToTopupAgreement, preventing users from completing payment without accepting terms. The flow is consistent and protected—users cannot proceed with payment without agreeing—though the pattern differs from the immediately-disabled redemption button. Consider whether the payment buttons should also be directly disabled for consistent UX.

🤖 Prompt for AI Agents
In `@web/src/components/topup/RechargeCard.jsx` at line 560, The payment method
buttons are not visually disabled like the redemption code button even though
the PaymentConfirmModal prevents proceeding via its confirm button using
disabled={topupAgreement && !agreedToTopupAgreement}; update the payment method
button rendering (the components that trigger opening PaymentConfirmModal) to
also respect the same gating by disabling them when topupAgreement &&
!agreedToTopupAgreement (use the same agreedToTopupAgreement and topupAgreement
flags), so both the immediate payment options and the PaymentConfirmModal follow
the same UX/guarding behavior.

Comment thread web/src/i18n/locales/fr.json Outdated
Comment thread web/src/i18n/locales/ja.json Outdated
Comment thread web/src/i18n/locales/vi.json Outdated

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

🤖 Fix all issues with AI agents
In `@service/error.go`:
- Around line 192-198: The visual editor in ErrorMappingEditor.jsx is dropping
ErrorMappingPattern.replace_type and replace_code because the parser/serializer
only preserves match, match_type, and replace_message; update the visual editor
to either (A) include input controls and serialization for replace_type and
replace_code (ensure the parser that builds ErrorMappingPattern instances
includes these fields) or (B) prevent silent loss by detecting non-empty
replace_type/replace_code when toggling modes and showing a confirmation/warning
modal before switching to visual mode; locate the pattern parsing/serialization
routine in ErrorMappingEditor.jsx (the functions that parse patterns at ~lines
72–73 and the mode switch logic at ~96–100) and implement one of these fixes so
ErrorMappingPattern.replace_type and replace_code are preserved or the user is
warned.

In `@web/src/components/common/ui/ErrorMappingEditor.jsx`:
- Around line 63-79: parsePatterns currently only preserves match, match_type
and replace_message causing loss of replace_type/replace_code on round-trip;
update parsePatterns (and ensure patternsToJson) to retain all original fields
by mapping parsed.patterns into objects that include existing properties (e.g.,
spread the original pattern p) while still assigning id via generateUniqueId()
and providing defaults for match, match_type and replace_message when missing;
ensure patternsToJson emits those preserved fields when serializing so advanced
fields like replace_type and replace_code survive editor round-trips.

In `@web/src/i18n/locales/vi.json`:
- Around line 3081-3091: Remove the duplicate JSON key "匹配类型" in the
translations block (the second occurrence near the added rules) so only the
original definition remains; update the block that contains "匹配内容", "匹配类型",
"替换为", etc. by deleting the later "匹配类型": "Loại khớp" entry, then validate the
vi.json file (JSON parse/lint) to ensure no trailing commas or syntax errors
after removal.
🧹 Nitpick comments (6)
web/src/components/topup/RechargeCard.jsx (2)

614-628: Agreement modal is rendered even when topupAgreement is falsy.

The <Modal> is always in the DOM regardless of whether topupAgreement is configured. While it won't visually appear (since showAgreementModal is false by default), it's unnecessary overhead. Consider guarding with {topupAgreement && <Modal ...>} for consistency with how the checkbox block (line 586) is guarded.

♻️ Proposed fix
     <>
+    {topupAgreement && (
     <Modal
       title={t('充值协议')}
       visible={showAgreementModal}
       onCancel={() => setShowAgreementModal(false)}
       onOk={() => {
         setAgreedToTopupAgreement(true);
         setShowAgreementModal(false);
       }}
       okText={t('同意')}
       cancelText={t('取消')}
       centered
     >
       <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(marked.parse(topupAgreement || '')) }} />
     </Modal>
+    )}
     <Card className='!rounded-2xl shadow-sm border-0'>

586-608: Agreement checkbox + modal logic is duplicated across three components.

The identical pattern — showAgreementModal state, the agreement <Modal> with DOMPurify.sanitize(marked.parse(...)), and the <Checkbox> with agreement link — appears in RechargeCard.jsx, PaymentConfirmModal.jsx, and SubscriptionPurchaseModal.jsx. Consider extracting a shared TopupAgreementBlock component (checkbox + modal) to reduce duplication and ensure consistent behavior when the agreement flow changes.

Also applies to: 614-628

web/src/components/topup/modals/SubscriptionPurchaseModal.jsx (1)

86-100: Same unconditional modal rendering; guard with topupAgreement.

Same as in RechargeCard.jsx — the agreement modal is rendered even when topupAgreement is falsy. Wrap with {topupAgreement && <Modal ...>}.

Additionally, this modal opens on top of the already-open purchase modal. Verify that Semi UI stacks modals correctly with proper z-index ordering so the agreement modal isn't hidden behind the purchase modal.

service/error.go (1)

246-251: Regex compiled on every error — consider caching or adding a timeout guard.

regexp.Compile is invoked on every error that reaches this path. While the pattern count is typically small, a maliciously crafted regex (e.g., catastrophic backtracking) could cause significant latency on the request path. Go's regexp uses RE2 semantics which prevents catastrophic backtracking, but the per-call compilation overhead is still wasteful for hot paths.

For a future improvement, consider caching compiled regexes (e.g., sync.Map keyed by pattern string) or compiling them once when the channel config is loaded.

web/src/components/topup/modals/PaymentConfirmModal.jsx (1)

55-69: Same unconditional modal rendering — guard with topupAgreement.

Consistent with the other components, wrap the agreement modal with {topupAgreement && <Modal ...>} to avoid rendering when no agreement is configured.

web/src/components/common/ui/ErrorMappingEditor.jsx (1)

347-421: Tab switching has asymmetric behavior — visual→manual and manual→visual use different code paths.

In the Tabs.onChange handler (lines 355-362), switching to manual mode directly sets state and syncs manualText, while switching to visual calls toggleEditMode() which has its own try/catch and state management. This asymmetry could lead to subtle inconsistencies. Consider unifying both directions through toggleEditMode or extracting the sync logic into a shared helper.

Comment thread service/error.go Outdated
Comment on lines +192 to +198
type ErrorMappingPattern struct {
Match string `json:"match"`
MatchType string `json:"match_type"`
ReplaceMessage string `json:"replace_message"`
ReplaceType string `json:"replace_type"`
ReplaceCode string `json:"replace_code"`
}

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.

⚠️ Potential issue | 🟡 Minor

Visual editor silently drops replace_type and replace_code fields.

The ErrorMappingPattern struct supports replace_type and replace_code, but the visual editor in ErrorMappingEditor.jsx (line 72-73) only preserves match, match_type, and replace_message when parsing patterns. Switching from manual to visual mode (or any round-trip through the visual editor) will silently discard replace_type/replace_code values.

This is partially mitigated by the editor auto-selecting manual mode when advanced fields are detected (lines 96-100 in ErrorMappingEditor.jsx), but a user can still switch to visual mode and lose data.

Consider either:

  1. Adding replace_type/replace_code fields to the visual editor, or
  2. Showing a warning when switching to visual mode if advanced fields are present.
🤖 Prompt for AI Agents
In `@service/error.go` around lines 192 - 198, The visual editor in
ErrorMappingEditor.jsx is dropping ErrorMappingPattern.replace_type and
replace_code because the parser/serializer only preserves match, match_type, and
replace_message; update the visual editor to either (A) include input controls
and serialization for replace_type and replace_code (ensure the parser that
builds ErrorMappingPattern instances includes these fields) or (B) prevent
silent loss by detecting non-empty replace_type/replace_code when toggling modes
and showing a confirmation/warning modal before switching to visual mode; locate
the pattern parsing/serialization routine in ErrorMappingEditor.jsx (the
functions that parse patterns at ~lines 72–73 and the mode switch logic at
~96–100) and implement one of these fixes so ErrorMappingPattern.replace_type
and replace_code are preserved or the user is warned.

Comment on lines +63 to +79
const parsePatterns = useCallback((val) => {
if (!val || typeof val !== 'string' || !val.trim()) return [];
try {
const parsed = JSON.parse(val);
if (parsed && Array.isArray(parsed.patterns)) {
return parsed.patterns.map((p) => ({
id: generateUniqueId(),
match: p.match || '',
match_type: p.match_type || 'contains',
replace_message: p.replace_message || '',
}));
}
} catch {
// ignore
}
return [];
}, []);

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.

⚠️ Potential issue | 🟡 Minor

parsePatterns silently strips replace_type and replace_code — data loss on round-trip.

When patterns with advanced fields (replace_type, replace_code) are parsed, only match, match_type, and replace_message are preserved (lines 70-72). Combined with patternsToJson (lines 136-146) also only emitting these three fields, any round-trip through the visual editor drops the advanced fields. The auto-detection of manual mode (lines 96-100) mitigates this partially, but users can still switch modes manually.

At minimum, preserve all fields in parsePatterns even if the visual editor doesn't expose them, so they survive a round-trip through patternsToJson.

Proposed fix — preserve advanced fields
  const parsePatterns = useCallback((val) => {
    if (!val || typeof val !== 'string' || !val.trim()) return [];
    try {
      const parsed = JSON.parse(val);
      if (parsed && Array.isArray(parsed.patterns)) {
        return parsed.patterns.map((p) => ({
          id: generateUniqueId(),
          match: p.match || '',
          match_type: p.match_type || 'contains',
          replace_message: p.replace_message || '',
+         replace_type: p.replace_type || '',
+         replace_code: p.replace_code || '',
        }));
      }
    } catch {
      // ignore
    }
    return [];
  }, []);

And in patternsToJson:

  const patternsToJson = (pats) => {
    const cleaned = pats
      .filter((p) => p.match || p.replace_message)
-     .map(({ match, match_type, replace_message }) => {
+     .map(({ match, match_type, replace_message, replace_type, replace_code }) => {
        const item = { match, match_type: match_type || 'contains' };
        if (replace_message) item.replace_message = replace_message;
+       if (replace_type) item.replace_type = replace_type;
+       if (replace_code) item.replace_code = replace_code;
        return item;
      });
    if (cleaned.length === 0) return '';
    return JSON.stringify({ patterns: cleaned }, null, 2);
  };
🤖 Prompt for AI Agents
In `@web/src/components/common/ui/ErrorMappingEditor.jsx` around lines 63 - 79,
parsePatterns currently only preserves match, match_type and replace_message
causing loss of replace_type/replace_code on round-trip; update parsePatterns
(and ensure patternsToJson) to retain all original fields by mapping
parsed.patterns into objects that include existing properties (e.g., spread the
original pattern p) while still assigning id via generateUniqueId() and
providing defaults for match, match_type and replace_message when missing;
ensure patternsToJson emits those preserved fields when serializing so advanced
fields like replace_type and replace_code survive editor round-trips.

Comment thread web/src/i18n/locales/vi.json Outdated
Comment on lines +3081 to +3091
"错误信息复写": "Ghi đè thông báo lỗi",
"匹配内容": "Nội dung khớp",
"匹配类型": "Loại khớp",
"替换为": "Thay thế bằng",
"添加规则": "Thêm quy tắc",
"包含匹配": "Chứa",
"正则匹配": "Regex",
"精确代码": "Mã chính xác",
"精确类型": "Loại chính xác",
"第一个匹配的规则生效,支持重写 message、type、code 字段": "Quy tắc khớp đầu tiên được áp dụng. Hỗ trợ ghi đè các trường message, type và code",
"暂无规则,点击下方按钮添加": "Chưa có quy tắc. Nhấp bên dưới để thêm.",

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.

⚠️ Potential issue | 🟡 Minor

Duplicate key "匹配类型" — already defined at Line 608 with the same value.

Line 608 already defines "匹配类型": "Loại khớp", and Line 3083 re-declares it with the identical value. While there's no behavioral difference (JSON last-key-wins), this is a maintenance hazard — if someone later changes only one occurrence, they'll get a silent mismatch. Remove the duplicate at Line 3083.

Proposed fix
     "错误信息复写": "Ghi đè thông báo lỗi",
     "匹配内容": "Nội dung khớp",
-    "匹配类型": "Loại khớp",
     "替换为": "Thay thế bằng",
📝 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
"错误信息复写": "Ghi đè thông báo lỗi",
"匹配内容": "Nội dung khớp",
"匹配类型": "Loại khớp",
"替换为": "Thay thế bằng",
"添加规则": "Thêm quy tắc",
"包含匹配": "Chứa",
"正则匹配": "Regex",
"精确代码": "Mã chính xác",
"精确类型": "Loại chính xác",
"第一个匹配的规则生效,支持重写 message、type、code 字段": "Quy tắc khớp đầu tiên được áp dụng. Hỗ trợ ghi đè các trường message, type và code",
"暂无规则,点击下方按钮添加": "Chưa có quy tắc. Nhấp bên dưới để thêm.",
"错误信息复写": "Ghi đè thông báo lỗi",
"匹配内容": "Nội dung khớp",
"替换为": "Thay thế bằng",
"添加规则": "Thêm quy tắc",
"包含匹配": "Chứa",
"正则匹配": "Regex",
"精确代码": "Mã chính xác",
"精确类型": "Loại chính xác",
"第一个匹配的规则生效,支持重写 message、type、code 字段": "Quy tắc khớp đầu tiên được áp dụng. Hỗ trợ ghi đè các trường message, type và code",
"暂无规则,点击下方按钮添加": "Chưa có quy tắc. Nhấp bên dưới để thêm.",
🤖 Prompt for AI Agents
In `@web/src/i18n/locales/vi.json` around lines 3081 - 3091, Remove the duplicate
JSON key "匹配类型" in the translations block (the second occurrence near the added
rules) so only the original definition remains; update the block that contains
"匹配内容", "匹配类型", "替换为", etc. by deleting the later "匹配类型": "Loại khớp" entry,
then validate the vi.json file (JSON parse/lint) to ensure no trailing commas or
syntax errors after removal.

@lanfunoe

Copy link
Copy Markdown
Contributor

不考虑拆开交吗?这不是review灾难吗?

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

🤖 Fix all issues with AI agents
In `@web/src/i18n/locales/fr.json`:
- Around line 2760-2762: The three translation keys "已复制 CC Switch 链接", "CC
Switch 导入", and "开启后令牌管理页面显示 CCS 一键导入按钮" were added but belong to a separate CC
Switch/CCS feature; remove these keys from this PR (revert their additions in
fr.json) or move them into a dedicated PR for the CC Switch feature and update
the PR description to document that addition, ensuring the keys only land with
the feature-specific change set.

In `@web/src/i18n/locales/ru.json`:
- Around line 2774-2776: The three Russian translation keys ("已复制 CC Switch 链接",
"CC Switch 导入", "开启后令牌管理页面显示 CCS 一键导入按钮") are unrelated to this PR's scope;
remove these entries from web/src/i18n/locales/ru.json (or move them to a
separate commit/branch dedicated to the CC Switch/CCS feature) and update the PR
to only include translations relevant to this change; reference the exact keys
above when making the edit and ensure the PR description is updated to reflect
the separation.
- Around line 2540-2541: The ru.json file defines the JSON key "匹配类型" twice
(existing value "Тип соответствия" and a new value "Тип совпадения"), causing a
silent override; locate both occurrences of the key "匹配类型" in
web/src/i18n/locales/ru.json and either remove the duplicate entry or rename one
key to a distinct, context-scoped key (e.g., "匹配类型.contextName") and update all
consumers to use the new key (search for usages of "匹配类型" in code to ensure you
change callers to the renamed key if you choose to split translations).

In `@web/src/i18n/locales/zh-CN.json`:
- Line 2551: Remove the duplicate JSON key "匹配类型" from zh-CN.json by deleting
the later occurrence (the one at the end of the file) and keep the original
definition earlier in the file; after removal, run a JSON/locale linter or
search for other duplicate keys to ensure no other repeated keys exist across
this locale.

In `@web/src/i18n/locales/zh-TW.json`:
- Around line 750-751: Update the Traditional Chinese (zh-TW) values for the
recharge/agreement keys to use "儲值" instead of "充值" and make the wording
consistent: replace in the value for "填写充值协议内容后,用户充值时将被要求勾选已阅读充值协议" → use
"填寫充值協議內容後,使用者儲值時將被要求勾選已閱讀儲值協議"; for "在此输入充值协议内容,支持 Markdown & HTML 代码" change
to use "在此輸入儲值協議內容,支援 Markdown & HTML 程式碼"; and update the other affected
strings referenced in the comment (the values for the keys containing "充值协议",
"設定充值協議", "充值協議已更新", "充值協議更新失敗") to use "儲值協議" and "儲值" consistently. Ensure you
only change the zh-TW (right-hand) translations and keep Simplified Chinese keys
unchanged.
🧹 Nitpick comments (1)
web/src/i18n/locales/zh-CN.json (1)

2549-2559: New error-mapping and CCS keys are appended at the end rather than in sorted position.

The file generally maintains a sorted key order. The error-mapping block (lines 2549–2559) and CCS block (lines 2827–2830) are appended near the file's end instead of being inserted at their sorted positions. Consider running the i18n sort tooling (bun run i18n:*) to normalize key order across all locale files and keep diffs clean for future changes.

Also applies to: 2827-2830

Comment thread web/src/i18n/locales/fr.json Outdated
Comment on lines +2760 to +2762
"已复制 CC Switch 链接": "Lien CC Switch copié",
"CC Switch 导入": "Importation CC Switch",
"开启后令牌管理页面显示 CCS 一键导入按钮": "Afficher le bouton d'import CCS sur la page des jetons",

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.

⚠️ Potential issue | 🟡 Minor

CC Switch entries are outside the stated PR scope.

These three keys ("已复制 CC Switch 链接", "CC Switch 导入", "开启后令牌管理页面显示 CCS 一键导入按钮") appear to belong to a separate "CC Switch / CCS" feature mentioned only in the commit messages, not in the PR description. Consider splitting them into a dedicated PR or at minimum documenting them in the PR objectives to keep review scope clear.

🤖 Prompt for AI Agents
In `@web/src/i18n/locales/fr.json` around lines 2760 - 2762, The three translation
keys "已复制 CC Switch 链接", "CC Switch 导入", and "开启后令牌管理页面显示 CCS 一键导入按钮" were added
but belong to a separate CC Switch/CCS feature; remove these keys from this PR
(revert their additions in fr.json) or move them into a dedicated PR for the CC
Switch feature and update the PR description to document that addition, ensuring
the keys only land with the feature-specific change set.

Comment thread web/src/i18n/locales/ru.json Outdated
Comment on lines +2540 to +2541
"匹配内容": "Содержание совпадения",
"匹配类型": "Тип совпадения",

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.

⚠️ Potential issue | 🟠 Major

Duplicate JSON key "匹配类型" — second definition silently overrides the first.

Line 622 already defines "匹配类型": "Тип соответствия". This new entry at line 2541 ("Тип совпадения") will silently win in JSON parsing, changing the translation for all existing consumers of that key. If both usages need distinct translations, one key must be renamed (e.g., scoped with a context prefix). If they should share the same translation, remove one of the duplicates.

#!/bin/bash
# Verify duplicate "匹配类型" keys in ru.json and check usage in source files
echo "=== Occurrences in ru.json ==="
rg -n '匹配类型' web/src/i18n/locales/ru.json

echo ""
echo "=== Usage of '匹配类型' in TSX/TS source files ==="
rg -n '匹配类型' --type=ts --type-add 'ts:*.tsx' -g '!web/src/i18n/locales/*'
🤖 Prompt for AI Agents
In `@web/src/i18n/locales/ru.json` around lines 2540 - 2541, The ru.json file
defines the JSON key "匹配类型" twice (existing value "Тип соответствия" and a new
value "Тип совпадения"), causing a silent override; locate both occurrences of
the key "匹配类型" in web/src/i18n/locales/ru.json and either remove the duplicate
entry or rename one key to a distinct, context-scoped key (e.g.,
"匹配类型.contextName") and update all consumers to use the new key (search for
usages of "匹配类型" in code to ensure you change callers to the renamed key if you
choose to split translations).

Comment thread web/src/i18n/locales/ru.json Outdated
Comment on lines +2774 to +2776
"已复制 CC Switch 链接": "Ссылка CC Switch скопирована",
"CC Switch 导入": "Импорт CC Switch",
"开启后令牌管理页面显示 CCS 一键导入按钮": "Показать кнопку импорта CCS на странице токенов",

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.

⚠️ Potential issue | 🟡 Minor

CC Switch entries not mentioned in PR description.

These three keys ("已复制 CC Switch 链接", "CC Switch 导入", "开启后令牌管理页面显示 CCS 一键导入按钮") appear to be from a different feature (CC Switch / CCS) that isn't listed in the PR objectives. This aligns with the reviewer comment about the PR bundling too many changes.

🤖 Prompt for AI Agents
In `@web/src/i18n/locales/ru.json` around lines 2774 - 2776, The three Russian
translation keys ("已复制 CC Switch 链接", "CC Switch 导入", "开启后令牌管理页面显示 CCS 一键导入按钮")
are unrelated to this PR's scope; remove these entries from
web/src/i18n/locales/ru.json (or move them to a separate commit/branch dedicated
to the CC Switch/CCS feature) and update the PR to only include translations
relevant to this change; reference the exact keys above when making the edit and
ensure the PR description is updated to reflect the separation.

Comment thread web/src/i18n/locales/zh-CN.json Outdated
"错误": "错误",
"错误信息复写": "错误信息复写",
"匹配内容": "匹配内容",
"匹配类型": "匹配类型",

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for duplicate keys in zh-CN.json and inspect specific lines
echo "=== Checking for duplicate keys in zh-CN.json ==="
python3 << 'EOF'
import json
import re
import sys

filepath = "web/src/i18n/locales/zh-CN.json"

try:
    # Read the file to extract keys manually
    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()
        
    # Find all key-value pairs with line numbers
    lines = content.split('\n')
    key_positions = {}
    
    for i, line in enumerate(lines, 1):
        # Match pattern "key": "value"
        match = re.search(r'"([^"]+)"\s*:\s*', line)
        if match:
            key = match.group(1)
            if key not in key_positions:
                key_positions[key] = []
            key_positions[key].append(i)
    
    # Find duplicates
    duplicates = {k: v for k, v in key_positions.items() if len(v) > 1}
    
    if duplicates:
        print("Duplicate keys found:")
        for key in sorted(duplicates.keys()):
            lines_str = ", ".join(map(str, duplicates[key]))
            print(f'  "{key}": lines {lines_str}')
    else:
        print("No duplicate keys found")
        
    # Specifically check for "匹配类型"
    print("\n=== Checking for '匹配类型' ===")
    if "匹配类型" in key_positions:
        print(f'"匹配类型" found at lines: {key_positions["匹配类型"]}')
    else:
        print('"匹配类型" not found')

except Exception as e:
    print(f"Error: {e}")
    sys.exit(1)
EOF

Repository: QuantumNous/new-api

Length of output: 476


🏁 Script executed:

#!/bin/bash
# Inspect lines around 606 and 2551 in zh-CN.json
echo "=== Inspecting line 606 area ==="
sed -n '604,608p' web/src/i18n/locales/zh-CN.json | cat -n

echo ""
echo "=== Inspecting line 2551 area ==="
sed -n '2549,2553p' web/src/i18n/locales/zh-CN.json | cat -n

echo ""
echo "=== Checking total lines in file ==="
wc -l web/src/i18n/locales/zh-CN.json

Repository: QuantumNous/new-api

Length of output: 573


Duplicate JSON key "匹配类型" — already defined at line 606.

This key already exists earlier in the file. In JSON, duplicate keys cause the last occurrence to silently override earlier ones. While both values are identical here so there's no immediate data loss, this creates maintenance issues and will cause confusion in other locale files if translators provide different translations for the two occurrences. Remove the duplicate at line 2551.

Proposed fix
     "错误信息复写": "错误信息复写",
     "匹配内容": "匹配内容",
-    "匹配类型": "匹配类型",
     "替换为": "替换为",
🤖 Prompt for AI Agents
In `@web/src/i18n/locales/zh-CN.json` at line 2551, Remove the duplicate JSON key
"匹配类型" from zh-CN.json by deleting the later occurrence (the one at the end of
the file) and keep the original definition earlier in the file; after removal,
run a JSON/locale linter or search for other duplicate keys to ensure no other
repeated keys exist across this locale.

Comment thread web/src/i18n/locales/zh-TW.json Outdated
Comment on lines +750 to +751
"填写充值协议内容后,用户充值时将被要求勾选已阅读充值协议": "填寫充值協議內容後,使用者充值時將被要求勾選已閱讀充值協議",
"在此输入充值协议内容,支持 Markdown & HTML 代码": "在此輸入充值協議內容,支援 Markdown & HTML 代碼",

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.

⚠️ Potential issue | 🟡 Minor

Inconsistent zh-TW translation: "充值" should be "儲值"

Throughout this file, the Simplified Chinese term "充值" is consistently translated to the Traditional Chinese "儲值" (e.g., line 428: "充值": "儲值", and many others in lines 429–443). However, the newly added recharge-agreement translations retain "充值" in the zh-TW values:

  • Line 750: 使用者充值時 → should be 使用者儲值時; 充值協議儲值協議
  • Line 751: 充值協議內容儲值協議內容
  • Line 1072: 充值協議儲值協議
  • Line 2047: 設定充值協議設定儲值協議
  • Line 2579: 充值協議已更新儲值協議已更新
  • Line 2580: 充值協議更新失敗儲值協議更新失敗

Additionally, line 751 uses "代碼" for "代码", but similar strings (e.g., line 729, 730, 734) use "程式碼". Should be 支援 Markdown & HTML 程式碼 for consistency.

Also applies to: 1072-1072, 2047-2047, 2579-2580

🤖 Prompt for AI Agents
In `@web/src/i18n/locales/zh-TW.json` around lines 750 - 751, Update the
Traditional Chinese (zh-TW) values for the recharge/agreement keys to use "儲值"
instead of "充值" and make the wording consistent: replace in the value for
"填写充值协议内容后,用户充值时将被要求勾选已阅读充值协议" → use "填寫充值協議內容後,使用者儲值時將被要求勾選已閱讀儲值協議"; for
"在此输入充值协议内容,支持 Markdown & HTML 代码" change to use "在此輸入儲值協議內容,支援 Markdown & HTML
程式碼"; and update the other affected strings referenced in the comment (the
values for the keys containing "充值协议", "設定充值協議", "充值協議已更新", "充值協議更新失敗") to use
"儲值協議" and "儲值" consistently. Ensure you only change the zh-TW (right-hand)
translations and keep Simplified Chinese keys unchanged.

@QiuFFFF
QiuFFFF force-pushed the feat/custom-features branch from 7332e00 to 76d6ba5 Compare February 13, 2026 05:47

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

🤖 Fix all issues with AI agents
In `@web/src/components/table/tokens/TokensColumnDefs.jsx`:
- Around line 311-318: In handleCCSwitchExport, await copyText(url) can throw
and prevent window.open(url, '_blank') from running; wrap the copyText call in a
try/catch (or perform window.open before awaiting copyText) so that
generateCCSwitchLink(app, record.name, fullKey) and window.open(url, '_blank')
always execute regardless of clipboard failures, call Toast.success(t('已复制 CC
Switch 链接')) only on successful copy and show a fallback Toast.error or
console.error in the catch, and ensure you still compute fullKey and url using
fullKey and generateCCSwitchLink before the try/catch so the identifiers remain
available.
- Around line 384-413: The CC Switch dropdown menu items lack React keys,
causing potential list-key warnings; update the menu array passed to the
Dropdown (the menu objects in the enableCCSwitch block rendering Dropdown) to
include a unique key property for each item (e.g., use the name or a prefixed
identifier like "cc-claude", "cc-codex", "cc-gemini") while keeping the existing
node, name, and onClick that call handleCCSwitchExport so React can properly
track the items.

In `@web/src/i18n/locales/zh-TW.json`:
- Around line 2548-2549: Remove the duplicate JSON key "匹配类型": find the second
occurrence of the key "匹配类型": "匹配類型" (the one added around line 2549) and delete
that entry so the file keeps only the original definition (the earlier "匹配类型":
"匹配類型") to avoid key overriding and redundancy.
🧹 Nitpick comments (3)
controller/misc.go (1)

344-344: Pre-existing: direct encoding/json usage violates coding guidelines.

Line 344 uses json.NewDecoder(c.Request.Body).Decode(&req) directly instead of the wrapper functions from common/json.go. This is pre-existing code (not introduced by this PR), but worth noting for a future cleanup. As per coding guidelines, "Do NOT directly import or call encoding/json for marshal/unmarshal in business code."

web/src/components/table/tokens/TokensTable.jsx (1)

52-53: enableCCSwitch won't update without a re-render trigger.

localStorage.getItem is read on every render, but nothing triggers a re-render when the value changes (e.g., from another tab or a settings page). If the flag is only expected to take effect after a page refresh, this is fine. If it should be reactive, consider a useState + storage event listener or a shared context.

Given this is a feature flag that's likely toggled infrequently, the current approach is acceptable.

web/src/components/table/tokens/TokensColumnDefs.jsx (1)

320-332: renderOperations has grown to 10 positional parameters.

This function now takes 10 positional arguments, making call sites fragile and hard to read. The parent getTokensColumns already uses a destructured object pattern. Consider aligning renderOperations to the same pattern for consistency and maintainability.

Comment on lines +311 to +318
// Handle CC Switch export
const handleCCSwitchExport = async (app, record, copyText, t) => {
const fullKey = 'sk-' + record.key;
const url = generateCCSwitchLink(app, record.name, fullKey);
await copyText(url);
Toast.success(t('已复制 CC Switch 链接'));
window.open(url, '_blank');
};

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.

⚠️ Potential issue | 🟡 Minor

window.open won't fire if copyText throws.

If await copyText(url) rejects (e.g., clipboard permissions denied), the window.open call on Line 317 is never reached. Consider a try/catch or rearranging so the navigation isn't blocked by clipboard failures.

Proposed fix
 const handleCCSwitchExport = async (app, record, copyText, t) => {
   const fullKey = 'sk-' + record.key;
   const url = generateCCSwitchLink(app, record.name, fullKey);
-  await copyText(url);
-  Toast.success(t('已复制 CC Switch 链接'));
-  window.open(url, '_blank');
+  try {
+    await copyText(url);
+    Toast.success(t('已复制 CC Switch 链接'));
+  } catch {
+    Toast.warning(t('复制失败,请手动复制链接'));
+  }
+  window.open(url, '_blank');
 };
🤖 Prompt for AI Agents
In `@web/src/components/table/tokens/TokensColumnDefs.jsx` around lines 311 - 318,
In handleCCSwitchExport, await copyText(url) can throw and prevent
window.open(url, '_blank') from running; wrap the copyText call in a try/catch
(or perform window.open before awaiting copyText) so that
generateCCSwitchLink(app, record.name, fullKey) and window.open(url, '_blank')
always execute regardless of clipboard failures, call Toast.success(t('已复制 CC
Switch 链接')) only on successful copy and show a fallback Toast.error or
console.error in the catch, and ensure you still compute fullKey and url using
fullKey and generateCCSwitchLink before the try/catch so the identifiers remain
available.

Comment on lines +384 to +413
{enableCCSwitch && (
<Dropdown
trigger='click'
position='bottomRight'
menu={[
{
node: 'item',
name: 'Claude',
onClick: () =>
handleCCSwitchExport('claude', record, copyText, t),
},
{
node: 'item',
name: 'Codex',
onClick: () =>
handleCCSwitchExport('codex', record, copyText, t),
},
{
node: 'item',
name: 'Gemini',
onClick: () =>
handleCCSwitchExport('gemini', record, copyText, t),
},
]}
>
<Button size='small' type='tertiary'>
CCS
</Button>
</Dropdown>
)}

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.

⚠️ Potential issue | 🟡 Minor

Missing key on CC Switch dropdown menu items.

The chatsArray items (Line 344) include a key property, but the CC Switch menu items do not. This may cause React key warnings when rendering the dropdown list.

Proposed fix
          menu={[
            {
              node: 'item',
+             key: 'claude',
              name: 'Claude',
              onClick: () =>
                handleCCSwitchExport('claude', record, copyText, t),
            },
            {
              node: 'item',
+             key: 'codex',
              name: 'Codex',
              onClick: () =>
                handleCCSwitchExport('codex', record, copyText, t),
            },
            {
              node: 'item',
+             key: 'gemini',
              name: 'Gemini',
              onClick: () =>
                handleCCSwitchExport('gemini', record, copyText, t),
            },
          ]}
📝 Committable suggestion

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

Suggested change
{enableCCSwitch && (
<Dropdown
trigger='click'
position='bottomRight'
menu={[
{
node: 'item',
name: 'Claude',
onClick: () =>
handleCCSwitchExport('claude', record, copyText, t),
},
{
node: 'item',
name: 'Codex',
onClick: () =>
handleCCSwitchExport('codex', record, copyText, t),
},
{
node: 'item',
name: 'Gemini',
onClick: () =>
handleCCSwitchExport('gemini', record, copyText, t),
},
]}
>
<Button size='small' type='tertiary'>
CCS
</Button>
</Dropdown>
)}
{enableCCSwitch && (
<Dropdown
trigger='click'
position='bottomRight'
menu={[
{
node: 'item',
key: 'claude',
name: 'Claude',
onClick: () =>
handleCCSwitchExport('claude', record, copyText, t),
},
{
node: 'item',
key: 'codex',
name: 'Codex',
onClick: () =>
handleCCSwitchExport('codex', record, copyText, t),
},
{
node: 'item',
key: 'gemini',
name: 'Gemini',
onClick: () =>
handleCCSwitchExport('gemini', record, copyText, t),
},
]}
>
<Button size='small' type='tertiary'>
CCS
</Button>
</Dropdown>
)}
🤖 Prompt for AI Agents
In `@web/src/components/table/tokens/TokensColumnDefs.jsx` around lines 384 - 413,
The CC Switch dropdown menu items lack React keys, causing potential list-key
warnings; update the menu array passed to the Dropdown (the menu objects in the
enableCCSwitch block rendering Dropdown) to include a unique key property for
each item (e.g., use the name or a prefixed identifier like "cc-claude",
"cc-codex", "cc-gemini") while keeping the existing node, name, and onClick that
call handleCCSwitchExport so React can properly track the items.

Comment thread web/src/i18n/locales/zh-TW.json Outdated
Comment on lines +2548 to +2549
"匹配内容": "匹配內容",
"匹配类型": "匹配類型",

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and display all occurrences of the duplicate key
fd 'zh-TW.json' web/src/i18n/locales --exec grep -n '"匹配类型"' {}

Repository: QuantumNous/new-api

Length of output: 111


🏁 Script executed:

#!/bin/bash
# Show context around line 606
echo "=== Context around line 606 ==="
sed -n '603,609p' web/src/i18n/locales/zh-TW.json

echo ""
echo "=== Context around line 2549 ==="
sed -n '2546,2552p' web/src/i18n/locales/zh-TW.json

# Also check for any other duplicate keys in the file
echo ""
echo "=== Total occurrences of each key (showing keys with >1 occurrence) ==="
grep -o '"[^"]*": ' web/src/i18n/locales/zh-TW.json | sort | uniq -d | head -20

Repository: QuantumNous/new-api

Length of output: 889


Remove duplicate JSON key "匹配类型"

Line 606 already defines "匹配类型": "匹配類型". Line 2549 adds the same key with the same value. In JSON, the duplicate key definition overrides the first, making line 2549 redundant. Remove it.

Proposed fix
     "错误信息复写": "錯誤訊息複寫",
     "匹配内容": "匹配內容",
-    "匹配类型": "匹配類型",
     "替换为": "替換為",
📝 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
"匹配内容": "匹配內容",
"匹配类型": "匹配類型",
"匹配内容": "匹配內容",
🤖 Prompt for AI Agents
In `@web/src/i18n/locales/zh-TW.json` around lines 2548 - 2549, Remove the
duplicate JSON key "匹配类型": find the second occurrence of the key "匹配类型": "匹配類型"
(the one added around line 2549) and delete that entry so the file keeps only
the original definition (the earlier "匹配类型": "匹配類型") to avoid key overriding and
redundancy.

@QiuFFFF

QiuFFFF commented Feb 13, 2026

Copy link
Copy Markdown
Author

不考虑拆开交吗?这不是review灾难吗?

给忘记了.......下次一定

@QiuFFFF QiuFFFF closed this Feb 13, 2026
@QiuFFFF
QiuFFFF force-pushed the feat/custom-features branch from 327ffe7 to f77381c Compare February 13, 2026 13:49
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