diff --git a/web/scripts/heroui-v3-startend-content-codemod.mjs b/web/scripts/heroui-v3-startend-content-codemod.mjs new file mode 100644 index 00000000000..56b9ce526bd --- /dev/null +++ b/web/scripts/heroui-v3-startend-content-codemod.mjs @@ -0,0 +1,236 @@ +#!/usr/bin/env bun +/* +Copyright (C) 2025 QuantumNous + +HeroUI v3 startContent / endContent codemod. + +Phase 5 of web/TODO.md. v3 Button & Chip removed the startContent / +endContent props — icons must live in `children` and rely on the +component's built-in `gap-2` for spacing. Without this rewrite the +icon prop was silently ignored and ~135 buttons across the app shipped +without their leading / trailing icon. + +What it does +------------ +Walks every .jsx / .tsx file via @babel/parser, finds every +JSXAttribute named `startContent` or `endContent`, drops the attribute, +and injects the attribute's expression as the first / last child of +the surrounding JSX element. Source-level edits — preserves the rest +of the file as-is. Run `bun run lint:fix` after for prettier polish. + +What it does NOT do +------------------- +- It does not assume v3 Chip — Chip still gets the same children + treatment, but the v3 docs ask you to wrap the icon side-by-side + with text, which is exactly what this codemod produces. +- It does not run on @heroui/styles internals or node_modules. + +Usage +----- + bun scripts/heroui-v3-startend-content-codemod.mjs # apply + bun scripts/heroui-v3-startend-content-codemod.mjs --dry # report only +*/ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { execSync } from 'node:child_process'; +import { parse } from '@babel/parser'; +import _traverse from '@babel/traverse'; + +const traverse = _traverse.default || _traverse; + +const DRY_RUN = process.argv.includes('--dry'); +const ROOT = new URL('../src', import.meta.url).pathname; + +const files = execSync(`git ls-files '${ROOT}/**/*.jsx' '${ROOT}/**/*.tsx'`, { + encoding: 'utf8', +}) + .split('\n') + .filter(Boolean); + +let touched = 0; +let edits = 0; +const skipped = []; + +for (const file of files) { + let src; + try { + src = readFileSync(file, 'utf8'); + } catch { + continue; + } + if (!src.includes('startContent=') && !src.includes('endContent=')) { + continue; + } + + let ast; + try { + ast = parse(src, { + sourceType: 'module', + plugins: ['jsx', 'classProperties'], + errorRecovery: true, + }); + } catch (err) { + skipped.push(`${file}: parse error — ${err.message}`); + continue; + } + + // Collect edits as { start, end, replacement } in *file* coordinates. + // Apply them right-to-left so earlier offsets stay valid. + const fileEdits = []; + + traverse(ast, { + JSXAttribute(path) { + const name = path.node.name?.name; + if (name !== 'startContent' && name !== 'endContent') return; + + const value = path.node.value; + if (!value || value.type !== 'JSXExpressionContainer') { + // We only know how to migrate {expr} forms, not bare strings. + skipped.push( + `${file}:${path.node.loc?.start.line}: non-expression ${name} — leave for manual fix`, + ); + return; + } + + const exprNode = value.expression; + const rawExprSource = src.slice(exprNode.start, exprNode.end); + // JSXElement / JSXFragment children render as themselves (no + // surrounding `{}`). Anything else (CallExpression, Identifier, + // ConditionalExpression, …) needs to live inside a JSX expression + // container or it would be parsed as plain text. + // Examples: + // ({foo: 1}) → no `{}` + // getIcon(p) → `{getIcon(p)}` + // isReady ? : → `{isReady ? : }` + const isJsxValue = + exprNode.type === 'JSXElement' || exprNode.type === 'JSXFragment'; + const exprSource = isJsxValue ? rawExprSource : `{${rawExprSource}}`; + + // Walk back over leading whitespace so the deletion swallows the + // attribute's own line. This avoids leaving a blank gap between + // surviving siblings. + let attrStart = path.node.start; + while (attrStart > 0 && /[ \t]/.test(src[attrStart - 1])) { + attrStart--; + } + let attrEnd = path.node.end; + // Also swallow the trailing newline so we don't leave a blank line. + if (src[attrEnd] === '\n') attrEnd++; + else if (src[attrEnd] === '\r' && src[attrEnd + 1] === '\n') attrEnd += 2; + // If we ate the newline, we should *not* also swallow the leading + // newline of the previous line. + if (attrStart > 0 && src[attrStart - 1] === '\n') { + // good — this attribute had its own line; the deletion now spans + // [start-of-line .. end-of-line]. + } else { + // Inline attribute (e.g. ` } diff --git a/web/src/components/auth/RegisterForm.jsx b/web/src/components/auth/RegisterForm.jsx index f58af29e3ed..fe7784cd5d9 100644 --- a/web/src/components/auth/RegisterForm.jsx +++ b/web/src/components/auth/RegisterForm.jsx @@ -129,12 +129,12 @@ const RegisterForm = () => { (status.custom_oauth_providers || []).length > 0; const hasOAuthRegisterOptions = Boolean( status.github_oauth || - status.discord_oauth || - status.oidc_enabled || - status.wechat_login || - status.linuxdo_oauth || - status.telegram_oauth || - hasCustomOAuthProviders, + status.discord_oauth || + status.oidc_enabled || + status.wechat_login || + status.linuxdo_oauth || + status.telegram_oauth || + hasCustomOAuthProviders, ); const [showEmailVerification, setShowEmailVerification] = useState(false); @@ -396,109 +396,107 @@ const RegisterForm = () => { subtitle={t('先选择一种便捷方式,或继续使用用户名注册。')} >
- {status.wechat_login && ( - - } - onPress={onWeChatLoginClicked} - isPending={wechatLoading} - > - {t('使用 微信 继续')} - - )} - - {status.github_oauth && ( - } - onPress={handleGitHubClick} - isPending={githubLoading} - isDisabled={githubButtonDisabled} - > - {githubButtonText} - - )} - - {status.discord_oauth && ( - - } - onPress={handleDiscordClick} - isPending={discordLoading} - > - {t('使用 Discord 继续')} - - )} - - {status.oidc_enabled && ( - } - onPress={handleOIDCClick} - isPending={oidcLoading} - > - {t('使用 OIDC 继续')} - - )} - - {status.linuxdo_oauth && ( - - } - onPress={handleLinuxDOClick} - isPending={linuxdoLoading} - > - {t('使用 LinuxDO 继续')} - - )} - - {status.custom_oauth_providers && - status.custom_oauth_providers.map((provider) => ( - handleCustomOAuthClick(provider)} - isPending={customOAuthLoading[provider.slug]} - > - {t('使用 {{name}} 继续', { name: provider.name })} - - ))} - - {status.telegram_oauth && ( -
- -
- )} - - {t('或')} - - } - onPress={handleEmailRegisterClick} - isPending={emailRegisterLoading} - className='bg-foreground text-background' + {status.wechat_login && ( + + + {t('使用 微信 继续')} + + )} + + {status.github_oauth && ( + + + {githubButtonText} + + )} + + {status.discord_oauth && ( + + + {t('使用 Discord 继续')} + + )} + + {status.oidc_enabled && ( + + + {t('使用 OIDC 继续')} + + )} + + {status.linuxdo_oauth && ( + + + {t('使用 LinuxDO 继续')} + + )} + + {status.custom_oauth_providers && + status.custom_oauth_providers.map((provider) => ( + handleCustomOAuthClick(provider)} + isPending={customOAuthLoading[provider.slug]} > - {t('使用 用户名 注册')} - + {getOAuthProviderIcon(provider.icon || '', 20)} + {t('使用 {{name}} 继续', { name: provider.name })} + + ))} + + {status.telegram_oauth && ( +
+
- - + )} + + {t('或')} + + + + {t('使用 用户名 注册')} + +
+ + ); @@ -512,125 +510,125 @@ const RegisterForm = () => { title={t('注 册')} subtitle={t('创建一个新账户,稍后可以继续使用第三方登录。')} > -
{ - event.preventDefault(); - handleSubmit(); - }} - > + { + event.preventDefault(); + handleSubmit(); + }} + > + handleChange('username', event.target.value)} + icon={} + /> + + handleChange('password', event.target.value)} + icon={} + /> + + + handleChange('password2', event.target.value) + } + icon={} + /> + + {showEmailVerification && ( + <> - handleChange('username', event.target.value) + handleChange('email', event.target.value) } - icon={} - /> - - - handleChange('password', event.target.value) + icon={} + action={ + } - icon={} /> - - handleChange('password2', event.target.value) + handleChange('verification_code', event.target.value) } - icon={} - /> - - {showEmailVerification && ( - <> - - handleChange('email', event.target.value) - } - icon={} - action={ - - } - /> - - handleChange('verification_code', event.target.value) - } - icon={} - /> - - )} - - } /> + + )} + + + +
+ + {t('注册')} + +
+ + + {hasOAuthRegisterOptions && ( + <> + {t('或')} + +
+ + {t('其他注册选项')} + +
+ + )} -
- - {t('注册')} - -
- - - {hasOAuthRegisterOptions && ( - <> - {t('或')} - -
- - {t('其他注册选项')} - -
- - )} - - + ); diff --git a/web/src/components/common/ErrorBoundary.jsx b/web/src/components/common/ErrorBoundary.jsx index 2aff10b2351..aee4e3335be 100644 --- a/web/src/components/common/ErrorBoundary.jsx +++ b/web/src/components/common/ErrorBoundary.jsx @@ -65,9 +65,9 @@ class ErrorBoundary extends React.Component { diff --git a/web/src/components/common/modals/SecureVerificationModal.jsx b/web/src/components/common/modals/SecureVerificationModal.jsx index 236a54f18a4..412a8eeb4b2 100644 --- a/web/src/components/common/modals/SecureVerificationModal.jsx +++ b/web/src/components/common/modals/SecureVerificationModal.jsx @@ -131,90 +131,101 @@ const SecureVerificationModal = ({ {title || t('安全验证')} -
- {/* 描述信息 */} - {description && ( -

- {description} -

- )} - - {/* 验证方式选择 */} - onMethodSwitch(String(key))} - size='default' - variant='underlined' - > - {has2FA && ( - -
-
- onCodeChange(event.target.value)} - size='large' - maxLength={8} - onKeyDown={handleKeyDown} - autoFocus={method === '2fa'} - isDisabled={loading} - startContent={} - /> -
- -

- {t('从认证器应用中获取验证码,或使用备用码')} -

- -
- - -
-
-
- )} - - {hasPasskey && passkeySupported && ( - -
-
-
- -
-

- {t('使用 Passkey 验证')} -

-

- {t('点击验证按钮,使用您的生物特征或安全密钥')} +

+ {/* 描述信息 */} + {description && ( +

+ {description}

-
- -
- - -
+ )} + + {/* 验证方式选择 */} + onMethodSwitch(String(key))} + size='default' + variant='underlined' + > + {has2FA && ( + +
+
+ + onCodeChange(event.target.value) + } + size='large' + maxLength={8} + onKeyDown={handleKeyDown} + autoFocus={method === '2fa'} + isDisabled={loading} + > + + +
+ +

+ {t('从认证器应用中获取验证码,或使用备用码')} +

+ +
+ + +
+
+
+ )} + + {hasPasskey && passkeySupported && ( + +
+
+
+ +
+

+ {t('使用 Passkey 验证')} +

+

+ {t('点击验证按钮,使用您的生物特征或安全密钥')} +

+
+ +
+ + +
+
+
+ )} +
- - )} - -
diff --git a/web/src/components/common/ui/CardPro.jsx b/web/src/components/common/ui/CardPro.jsx index 7f0b9b06b29..2dfc151628e 100644 --- a/web/src/components/common/ui/CardPro.jsx +++ b/web/src/components/common/ui/CardPro.jsx @@ -98,11 +98,11 @@ const CardPro = ({
diff --git a/web/src/components/common/ui/CardTable.jsx b/web/src/components/common/ui/CardTable.jsx index f40a5eddb38..883f0c77dbf 100644 --- a/web/src/components/common/ui/CardTable.jsx +++ b/web/src/components/common/ui/CardTable.jsx @@ -19,11 +19,7 @@ For commercial licensing, please contact support@quantumnous.com import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { - Skeleton, - Pagination, - Button, -} from '@heroui/react'; +import { Skeleton, Pagination, Button } from '@heroui/react'; import { ChevronDown, ChevronUp, Inbox } from 'lucide-react'; import PropTypes from 'prop-types'; import { useIsMobile } from '../../../hooks/common/useIsMobile'; @@ -189,7 +185,10 @@ const CardTable = ({ if (showSkeleton) { const renderSkeletonCard = (key) => { return ( -
+
{visibleColumns.map((col, idx) => { if (!col.title) { return ( @@ -234,9 +233,11 @@ const CardTable = ({ (!tableProps.rowExpandable || tableProps.rowExpandable(record)); return ( -
+
{visibleColumns.map((col, colIdx) => { - const title = col.title; const cellContent = renderCell(col, record, index); @@ -271,11 +272,15 @@ const CardTable = ({ variant='tertiary' size='sm' className='w-full flex justify-center mt-2' - startContent={showDetails ? : } onPress={() => { setShowDetails(!showDetails); }} > + {showDetails ? ( + + ) : ( + + )} {showDetails ? t('收起') : t('详情')} {showDetails && ( @@ -291,11 +296,7 @@ const CardTable = ({ if (isEmpty) { if (tableProps.empty) return tableProps.empty; - return ( -
- {renderEmpty()} -
- ); + return
{renderEmpty()}
; } return ( @@ -308,9 +309,7 @@ const CardTable = ({ /> ))} {!hidePagination && tableProps.pagination && dataSource.length > 0 && ( -
- {renderPagination()} -
+
{renderPagination()}
)}
); diff --git a/web/src/components/common/ui/ChannelKeyDisplay.jsx b/web/src/components/common/ui/ChannelKeyDisplay.jsx index 071e622e7d1..f9986a1d54a 100644 --- a/web/src/components/common/ui/ChannelKeyDisplay.jsx +++ b/web/src/components/common/ui/ChannelKeyDisplay.jsx @@ -129,11 +129,7 @@ const ChannelKeyDisplay = ({ {t('共 {{count}} 个密钥', { count: parsedKeys.length })} -
@@ -160,9 +156,9 @@ const ChannelKeyDisplay = ({
diff --git a/web/src/components/model-deployments/DeploymentAccessGuard.jsx b/web/src/components/model-deployments/DeploymentAccessGuard.jsx index 9bf0f0e4d63..4ebcd72b4e0 100644 --- a/web/src/components/model-deployments/DeploymentAccessGuard.jsx +++ b/web/src/components/model-deployments/DeploymentAccessGuard.jsx @@ -45,9 +45,7 @@ const DeploymentAccessGuard = ({
- - {t('加载设置中...')} - + {t('加载设置中...')}
@@ -101,11 +99,8 @@ const DeploymentAccessGuard = ({
-
@@ -156,22 +151,15 @@ const DeploymentAccessGuard = ({

{title}

-

- {description} -

+

{description}

{detail ? ( -

- {detail} -

+

{detail}

) : null}
- {onRetry ? ( diff --git a/web/src/components/settings/CustomOAuthSetting.jsx b/web/src/components/settings/CustomOAuthSetting.jsx index 3a3e5fc1c53..ab1a4117e4a 100644 --- a/web/src/components/settings/CustomOAuthSetting.jsx +++ b/web/src/components/settings/CustomOAuthSetting.jsx @@ -248,8 +248,10 @@ const ACCESS_POLICY_TEMPLATES = { }; const ACCESS_DENIED_TEMPLATES = { - level_hint: '需要等级 {{required}},你当前等级 {{current}}(字段:{{field}})', - org_hint: '仅限指定组织或角色访问。组织={{current.org}},角色={{current.roles}}', + level_hint: + '需要等级 {{required}},你当前等级 {{current}}(字段:{{field}})', + org_hint: + '仅限指定组织或角色访问。组织={{current.org}},角色={{current.roles}}', }; // ----------------------------- main ----------------------------- @@ -405,7 +407,9 @@ const CustomOAuthSetting = ({ serverAddress }) => { if (selectedPreset && !baseUrl) { showError(t('请先填写 Issuer URL,以自动生成完整的端点 URL')); } else { - showError(t('端点 URL 必须是完整地址(以 http:// 或 https:// 开头)')); + showError( + t('端点 URL 必须是完整地址(以 http:// 或 https:// 开头)'), + ); } return; } @@ -484,8 +488,8 @@ const CustomOAuthSetting = ({ serverAddress }) => { ? data.scopes_supported : []; if (scopesSupported.length > 0 && !formValues.scopes) { - const preferredScopes = ['openid', 'profile', 'email'].filter( - (scope) => scopesSupported.includes(scope), + const preferredScopes = ['openid', 'profile', 'email'].filter((scope) => + scopesSupported.includes(scope), ); discoveredValues.scopes = preferredScopes.length > 0 @@ -562,8 +566,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { newValues.authorization_endpoint = cleanUrl + presetConfig.authorization_endpoint; newValues.token_endpoint = cleanUrl + presetConfig.token_endpoint; - newValues.user_info_endpoint = - cleanUrl + presetConfig.user_info_endpoint; + newValues.user_info_endpoint = cleanUrl + presetConfig.user_info_endpoint; } mergeFormValues(newValues); }; @@ -574,8 +577,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { const presetConfig = OAUTH_PRESETS[selectedPreset]; const cleanUrl = normalizeBaseUrl(url); mergeFormValues({ - authorization_endpoint: - cleanUrl + presetConfig.authorization_endpoint, + authorization_endpoint: cleanUrl + presetConfig.authorization_endpoint, token_endpoint: cleanUrl + presetConfig.token_endpoint, user_info_endpoint: cleanUrl + presetConfig.user_info_endpoint, }); @@ -619,11 +621,8 @@ const CustomOAuthSetting = ({ serverAddress }) => {
-
@@ -641,13 +640,9 @@ const CustomOAuthSetting = ({ serverAddress }) => { {t('图标')} - - {t('名称')} - + {t('名称')} Slug - - {t('状态')} - + {t('状态')} {t('Client ID')} @@ -698,17 +693,17 @@ const CustomOAuthSetting = ({ serverAddress }) => {
@@ -742,9 +737,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { {t('Configuration')}
- {t( - '先填写配置,再自动填充 OAuth 端点,能显著减少手工输入', - )} + {t('先填写配置,再自动填充 OAuth 端点,能显著减少手工输入')}
{discoveryInfo && ( @@ -784,7 +777,9 @@ const CustomOAuthSetting = ({ serverAddress }) => { onSelectionChange={(key) => { // Map synthetic 'custom' id back to '' so the // existing handler logic is preserved. - handlePresetChange(key === 'custom' ? '' : String(key ?? '')); + handlePresetChange( + key === 'custom' ? '' : String(key ?? ''), + ); }} > @@ -800,24 +795,24 @@ const CustomOAuthSetting = ({ serverAddress }) => { {t('自定义')} - {Object.entries(OAUTH_PRESETS).map(([key, config]) => ( - - {config.name} - - - ))} + {Object.entries(OAUTH_PRESETS).map( + ([key, config]) => ( + + {config.name} + + + ), + )}
- - {t('发行者 URL(Issuer URL)')} - + {t('发行者 URL(Issuer URL)')} {
@@ -875,9 +870,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { - setField('name')(event.target.value) - } + onChange={(event) => setField('name')(event.target.value)} placeholder={t('例如:GitHub Enterprise')} className={inputClass} /> @@ -887,9 +880,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { - setField('slug')(event.target.value) - } + onChange={(event) => setField('slug')(event.target.value)} placeholder={t('例如:github-enterprise')} className={inputClass} /> @@ -906,9 +897,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { - setField('icon')(event.target.value) - } + onChange={(event) => setField('icon')(event.target.value)} placeholder={t( '例如:github / si:google / https://example.com/logo.png / 🐱', )} @@ -1004,9 +993,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { />
- - {t('User Info Endpoint')} - + {t('User Info Endpoint')} { }} > - - {t('认证方式')} - + {t('认证方式')} @@ -1224,9 +1209,7 @@ const CustomOAuthSetting = ({ serverAddress }) => { type='text' value={formValues.access_denied_message || ''} onChange={(event) => - setField('access_denied_message')( - event.target.value, - ) + setField('access_denied_message')(event.target.value) } placeholder={t( '例如:需要等级 {{required}},你当前等级 {{current}}', @@ -1260,9 +1243,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
- - {t('启用供应商')} - + {t('启用供应商')} diff --git a/web/src/components/settings/personal/cards/CheckinCalendar.jsx b/web/src/components/settings/personal/cards/CheckinCalendar.jsx index c700f06d597..5d0d530a66c 100644 --- a/web/src/components/settings/personal/cards/CheckinCalendar.jsx +++ b/web/src/components/settings/personal/cards/CheckinCalendar.jsx @@ -92,17 +92,31 @@ function MonthCalendar({ yearMonth, onMonthChange, dateRender, t }) {
- -
-
{monthLabel}
+
+ {monthLabel} +
@@ -132,9 +146,7 @@ function MonthCalendar({ yearMonth, onMonthChange, dateRender, t }) {
{ @@ -364,9 +359,7 @@ const TwoFASetting = ({ t }) => { {t('未启用')} )} {status.locked && ( - - {t('账户已锁定')} - + {t('账户已锁定')} )}
@@ -388,27 +381,27 @@ const TwoFASetting = ({ t }) => { ) : ( <> @@ -495,8 +488,7 @@ const TwoFASetting = ({ t }) => { codes={setupData.backup_codes} title={t('备用恢复代码')} onCopy={() => { - const codesText = - setupData.backup_codes.join('\n'); + const codesText = setupData.backup_codes.join('\n'); copyTextToClipboard( codesText, t('备用码已复制到剪贴板'), @@ -510,9 +502,7 @@ const TwoFASetting = ({ t }) => { value={verificationCode} onValueChange={setVerificationCode} maxLength={6} - placeholder={t( - '输入认证器应用显示的6位数字验证码', - )} + placeholder={t('输入认证器应用显示的6位数字验证码')} size='lg' > @@ -566,11 +556,7 @@ const TwoFASetting = ({ t }) => { {/* Disable 2FA modal */} - +
@@ -605,9 +591,7 @@ const TwoFASetting = ({ t }) => {
  • - - {t('永久删除所有备用码(包括未使用的)')} - + {t('永久删除所有备用码(包括未使用的)')}
  • @@ -669,11 +653,7 @@ const TwoFASetting = ({ t }) => { {/* Regenerate backup codes modal */} - +
    diff --git a/web/src/components/table/channels/ChannelsActions.jsx b/web/src/components/table/channels/ChannelsActions.jsx index 2ebe2b9ff5a..005bb5032cc 100644 --- a/web/src/components/table/channels/ChannelsActions.jsx +++ b/web/src/components/table/channels/ChannelsActions.jsx @@ -59,10 +59,10 @@ function ClickDropdown({ label, items }) { variant='tertiary' size='sm' className='w-full md:w-auto' - endContent={} onPress={() => setOpen((prev) => !prev)} > {label} + {open ? (
    ask({ title: t('确定是否要修复数据库一致性?'), - content: t('进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用'), + content: t( + '进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用', + ), onConfirm: fixChannelsAbilities, }), }, diff --git a/web/src/components/table/channels/modals/CodexUsageModal.jsx b/web/src/components/table/channels/modals/CodexUsageModal.jsx index ecc552cb50c..e8e0faf99cb 100644 --- a/web/src/components/table/channels/modals/CodexUsageModal.jsx +++ b/web/src/components/table/channels/modals/CodexUsageModal.jsx @@ -349,12 +349,8 @@ function CodexUsageView({ t, record, payload, onCopy, onRefresh }) {
    -
    @@ -510,12 +506,8 @@ function CodexUsageLoader({ t, record, initialPayload, onCopy }) {
    {tt('获取用量失败')}
    -
    @@ -553,11 +545,7 @@ const CodexUsageModal = ({ return ( - + {tt('Codex 帐号与用量')} @@ -573,11 +561,8 @@ const CodexUsageModal = ({ ) : null} - diff --git a/web/src/components/table/channels/modals/EditTagModal.jsx b/web/src/components/table/channels/modals/EditTagModal.jsx index 7b86299054f..c0c2a789b6d 100644 --- a/web/src/components/table/channels/modals/EditTagModal.jsx +++ b/web/src/components/table/channels/modals/EditTagModal.jsx @@ -15,14 +15,7 @@ For commercial licensing, please contact support@quantumnous.com import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Card, Spinner } from '@heroui/react'; -import { - Bookmark, - Code2, - Save, - Settings, - User, - X, -} from 'lucide-react'; +import { Bookmark, Code2, Save, Settings, User, X } from 'lucide-react'; import { API, showError, @@ -245,12 +238,8 @@ function MultiSelectChips({ selected ? 'text-primary' : 'text-foreground' }`} > - - {opt.label ?? String(v)} - - {selected ? ( - {'✓'} - ) : null} + {opt.label ?? String(v)} + {selected ? {'✓'} : null} ); @@ -394,10 +383,7 @@ const EditTagModal = ({ visible, tag, handleClose, refresh }) => { return; } const trimmedHeaderOverride = formVals.header_override.trim(); - if ( - trimmedHeaderOverride !== '' && - !verifyJSON(trimmedHeaderOverride) - ) { + if (trimmedHeaderOverride !== '' && !verifyJSON(trimmedHeaderOverride)) { showInfo('请求头覆盖必须是合法的 JSON 格式!'); setLoading(false); return; @@ -605,9 +591,7 @@ const EditTagModal = ({ visible, tag, handleClose, refresh }) => { setField('models', v)} @@ -819,8 +803,7 @@ const EditTagModal = ({ visible, tag, handleClose, refresh }) => {
    - {t('支持变量:')} - {' '} + {t('支持变量:')}{' '} {t('渠道密钥')}: {'{api_key}'} @@ -851,9 +834,7 @@ const EditTagModal = ({ visible, tag, handleClose, refresh }) => { setField('groups', v)} /> @@ -867,19 +848,12 @@ const EditTagModal = ({ visible, tag, handleClose, refresh }) => {
    - -
    diff --git a/web/src/components/table/channels/modals/ModelTestModal.jsx b/web/src/components/table/channels/modals/ModelTestModal.jsx index ec4ec6ffd57..4a6aede976f 100644 --- a/web/src/components/table/channels/modals/ModelTestModal.jsx +++ b/web/src/components/table/channels/modals/ModelTestModal.jsx @@ -215,9 +215,7 @@ const ModelTestModal = ({ return; } if (checked) { - setSelectedModelKeys( - Array.from(new Set([...selectedModelKeys, key])), - ); + setSelectedModelKeys(Array.from(new Set([...selectedModelKeys, key]))); } else { setSelectedModelKeys(selectedModelKeys.filter((item) => item !== key)); } @@ -265,12 +263,12 @@ const ModelTestModal = ({ )} @@ -318,8 +316,7 @@ const ModelTestModal = ({ {currentTestChannel.name} {t('渠道的模型测试')} - {t('共')}{' '} - {currentTestChannel.models.split(',').length}{' '} + {t('共')} {currentTestChannel.models.split(',').length}{' '} {t('个模型')}
    @@ -344,7 +341,10 @@ const ModelTestModal = ({ className='h-9 w-full min-w-0 rounded-xl border border-border bg-background px-3 text-sm outline-none transition focus:border-primary' > {endpointTypeOptions.map((option) => ( - ))} @@ -381,7 +381,10 @@ const ModelTestModal = ({ {/* Search + actions */}
    - + { {manualDisabledCount + autoDisabledCount > 0 && ( @@ -555,10 +553,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { className='px-4 py-10 text-center' >
    - +
    {t('暂无密钥数据')}
    @@ -602,9 +597,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { )} > - {timestamp2string( - record.disabled_time, - )} + {timestamp2string(record.disabled_time)} )} @@ -679,9 +672,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { {
    @@ -1589,9 +1564,9 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => {
    @@ -1652,10 +1627,7 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => { isDisabled={envVariables.length === 1} aria-label={t('删除')} onPress={() => - handleRemoveEnvVariable( - index, - 'env', - ) + handleRemoveEnvVariable(index, 'env') } > @@ -1666,11 +1638,9 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => {
    @@ -1744,11 +1714,9 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => {
    @@ -1807,7 +1775,8 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => { {t('预估总费用')}
    - {typeof priceEstimation.estimated_cost === 'number' + {typeof priceEstimation.estimated_cost === + 'number' ? `${priceEstimation.estimated_cost.toFixed(4)} ${currencyLabel}` : '--'}
    @@ -1817,8 +1786,8 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => { {t('小时费率')} - {typeof priceEstimation.price_breakdown?.hourly_rate === - 'number' + {typeof priceEstimation.price_breakdown + ?.hourly_rate === 'number' ? `${priceEstimation.price_breakdown.hourly_rate.toFixed(4)} ${currencyLabel}/h` : '--'} @@ -1828,8 +1797,8 @@ const CreateDeploymentModal = ({ visible, onCancel, onSuccess, t }) => { {t('计算成本')} - {typeof priceEstimation.price_breakdown?.compute_cost === - 'number' + {typeof priceEstimation.price_breakdown + ?.compute_cost === 'number' ? `${priceEstimation.price_breakdown.compute_cost.toFixed(4)} ${currencyLabel}` : '--'} diff --git a/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx b/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx index 2861cb65227..5cde641d6fe 100644 --- a/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx +++ b/web/src/components/table/model-deployments/modals/EditDeploymentModal.jsx @@ -185,16 +185,16 @@ const EditDeploymentModal = ({ variant='tertiary' onPress={handleClose} isDisabled={loading} - startContent={} > + {t('取消')}
    diff --git a/web/src/components/table/model-deployments/modals/UpdateConfigModal.jsx b/web/src/components/table/model-deployments/modals/UpdateConfigModal.jsx index 1ac6f1e56d4..8162c1f703c 100644 --- a/web/src/components/table/model-deployments/modals/UpdateConfigModal.jsx +++ b/web/src/components/table/model-deployments/modals/UpdateConfigModal.jsx @@ -59,7 +59,13 @@ function StatusChip({ tone = 'grey', children }) { // Replaces Semi `` / `` with a `
    ` // element so accordion state is native (no extra hook + accessible by // default). -function CollapseSection({ icon, title, suffix, defaultOpen = false, children }) { +function CollapseSection({ + icon, + title, + suffix, + defaultOpen = false, + children, +}) { return (
    {children}
    ; } -const UpdateConfigModal = ({ - visible, - onCancel, - deployment, - onSuccess, - t, -}) => { +const UpdateConfigModal = ({ visible, onCancel, deployment, onSuccess, t }) => { const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ image_url: '', @@ -439,15 +439,18 @@ const UpdateConfigModal = ({ {envVars.map((envVar, index) => ( -
    +
    } onPress={addSecretEnvVar} > + {t('添加')}
    {secretEnvVars.map((envVar, index) => ( -
    +
    {items.map((item, idx) => ( -
    - {item.key} -
    +
    {item.key}
    {item.value}
    ))} @@ -330,8 +328,7 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { {details.total_gpus} - {t('总计')} {details.total_gpus}{' '} - {t('个GPU')} + {t('总计')} {details.total_gpus} {t('个GPU')}
    ), @@ -341,7 +338,8 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { value: (
    - {t('每容器GPU数')}: {details.gpus_per_container} + {t('每容器GPU数')}:{' '} + {details.gpus_per_container}
    {t('容器总数')}: {details.total_containers} @@ -397,14 +395,17 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { }, { key: t('流量端口'), - value: details.container_config.traffic_port || 'N/A', + value: + details.container_config.traffic_port || 'N/A', }, { key: t('启动命令'), value: ( {details.container_config.entrypoint - ? details.container_config.entrypoint.join(' ') + ? details.container_config.entrypoint.join( + ' ', + ) : 'N/A'} ), @@ -491,7 +492,6 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { @@ -546,11 +547,7 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { >
    {details.locations.map((location) => ( - + 🌍 {location.name} ({location.iso2}) @@ -581,9 +578,7 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => {
    - - {t('计费开始')}: - + {t('计费开始')}: {details.started_at ? timestamp2string(details.started_at) @@ -591,9 +586,7 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => {
    - - {t('预计结束')}: - + {t('预计结束')}: {details.finished_at ? timestamp2string(details.finished_at) @@ -651,10 +644,10 @@ const ViewDetailsModal = ({ visible, onCancel, deployment, t }) => { diff --git a/web/src/components/table/models/modals/EditModelModal.jsx b/web/src/components/table/models/modals/EditModelModal.jsx index e51839a33e8..2d56471419b 100644 --- a/web/src/components/table/models/modals/EditModelModal.jsx +++ b/web/src/components/table/models/modals/EditModelModal.jsx @@ -15,13 +15,7 @@ For commercial licensing, please contact support@quantumnous.com import React, { useEffect, useState, useMemo } from 'react'; import JSONEditor from '../../../common/ui/JSONEditor'; import { Button, Card, Spinner, Switch } from '@heroui/react'; -import { - AlertTriangle, - ExternalLink, - FileText, - Save, - X, -} from 'lucide-react'; +import { AlertTriangle, ExternalLink, FileText, Save, X } from 'lucide-react'; import { API, showError, showSuccess } from '../../../../helpers'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; @@ -631,9 +625,7 @@ const EditModelModal = (props) => { {t('参与官方同步')}
    - {t( - '关闭后,此模型将不会被"同步官方"自动覆盖或创建', - )} + {t('关闭后,此模型将不会被"同步官方"自动覆盖或创建')}
    {
    - -
    diff --git a/web/src/components/table/models/modals/EditPrefillGroupModal.jsx b/web/src/components/table/models/modals/EditPrefillGroupModal.jsx index 4d8e38ebc0e..aafd3bbf15a 100644 --- a/web/src/components/table/models/modals/EditPrefillGroupModal.jsx +++ b/web/src/components/table/models/modals/EditPrefillGroupModal.jsx @@ -93,11 +93,7 @@ function TagInput({ value = [], onChange, placeholder, ariaLabel }) { if (event.key === 'Enter' || event.key === ',') { event.preventDefault(); commit(draft); - } else if ( - event.key === 'Backspace' && - !draft && - items.length > 0 - ) { + } else if (event.key === 'Backspace' && !draft && items.length > 0) { remove(items.length - 1); } }} @@ -367,19 +363,12 @@ const EditPrefillGroupModal = ({
    - -
    diff --git a/web/src/components/table/models/modals/PrefillGroupManagement.jsx b/web/src/components/table/models/modals/PrefillGroupManagement.jsx index f31febab9c6..125213f1abe 100644 --- a/web/src/components/table/models/modals/PrefillGroupManagement.jsx +++ b/web/src/components/table/models/modals/PrefillGroupManagement.jsx @@ -152,7 +152,9 @@ const PrefillGroupManagement = ({ visible, onClose }) => { : items || {}; const keys = Object.keys(obj); if (keys.length === 0) { - return {t('暂无项目')}; + return ( + {t('暂无项目')} + ); } return renderLimitedItems({ items: keys, @@ -171,7 +173,9 @@ const PrefillGroupManagement = ({ visible, onClose }) => { maxDisplay: 3, }); } catch { - return {t('数据格式错误')}; + return ( + {t('数据格式错误')} + ); } }, }, @@ -182,7 +186,11 @@ const PrefillGroupManagement = ({ visible, onClose }) => { width: 140, render: (_, record) => (
    -
    -
    diff --git a/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx b/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx index 1b88b251f21..7293d376046 100644 --- a/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx +++ b/web/src/components/table/redemptions/modals/EditRedemptionModal.jsx @@ -39,7 +39,17 @@ import ConfirmDialog from '@/components/common/ui/ConfirmDialog'; const inputClass = 'h-10 w-full rounded-lg border border-[color:var(--app-border)] bg-background px-3 text-sm text-foreground outline-none transition focus:border-primary disabled:opacity-50'; -function NumberField({ label, value, onChange, min, step, prefix, helper, error, placeholder }) { +function NumberField({ + label, + value, + onChange, + min, + step, + prefix, + helper, + error, + placeholder, +}) { return (
    {label}
    @@ -352,10 +362,7 @@ const EditRedemptionModal = (props) => { value={toLocalDateTimeInputValue(values.expired_time)} onChange={(event) => { const v = event.target.value; - setField( - 'expired_time', - v ? new Date(v) : null, - ); + setField('expired_time', v ? new Date(v) : null); }} aria-label={t('过期时间')} className={inputClass} @@ -449,19 +456,16 @@ const EditRedemptionModal = (props) => {
    -
    diff --git a/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx b/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx index be5d5307dfe..20182fdf05a 100644 --- a/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx +++ b/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx @@ -227,10 +227,7 @@ const AddEditSubscriptionModal = ({ if (!values.custom_seconds || Number(values.custom_seconds) < 1) { next.custom_seconds = t('请输入秒数'); } - } else if ( - !values.duration_value || - Number(values.duration_value) < 1 - ) { + } else if (!values.duration_value || Number(values.duration_value) < 1) { next.duration_value = t('请输入数值'); } if ( @@ -620,9 +617,7 @@ const AddEditSubscriptionModal = ({
    - + {t('自定义秒数')}
    - -
    diff --git a/web/src/components/table/task-logs/modals/AudioPreviewModal.jsx b/web/src/components/table/task-logs/modals/AudioPreviewModal.jsx index 3abed63a63c..713803c985d 100644 --- a/web/src/components/table/task-logs/modals/AudioPreviewModal.jsx +++ b/web/src/components/table/task-logs/modals/AudioPreviewModal.jsx @@ -78,31 +78,25 @@ const AudioClipCard = ({ clip }) => { )}
    - {tags && ( -
    - {tags} -
    - )} + {tags &&
    {tags}
    } {hasError ? (
    - - {t('音频无法播放')} - + {t('音频无法播放')}
    @@ -141,13 +135,14 @@ const AudioPreviewModal = ({ isModalOpen, setIsModalOpen, audioClips }) => { {clips.length === 0 ? ( - - {t('无')} - + {t('无')} ) : (
    {clips.map((clip, idx) => ( - + ))}
    )} diff --git a/web/src/components/table/task-logs/modals/ContentModal.jsx b/web/src/components/table/task-logs/modals/ContentModal.jsx index 7d13fa54b20..a786a8a6740 100644 --- a/web/src/components/table/task-logs/modals/ContentModal.jsx +++ b/web/src/components/table/task-logs/modals/ContentModal.jsx @@ -86,25 +86,15 @@ const ContentModal = ({

    {t('• 需要特定的请求头或认证')}

    -

    - {t('• 防盗链保护机制')} -

    +

    {t('• 防盗链保护机制')}

    - -
    @@ -143,7 +133,9 @@ const ContentModal = ({ {isVideo ? t('视频预览') : t('内容预览')} - + {isVideo ? ( renderVideoContent() ) : ( diff --git a/web/src/components/table/tokens/modals/EditTokenModal.jsx b/web/src/components/table/tokens/modals/EditTokenModal.jsx index 5cd88144594..1c73dd99bf7 100644 --- a/web/src/components/table/tokens/modals/EditTokenModal.jsx +++ b/web/src/components/table/tokens/modals/EditTokenModal.jsx @@ -14,13 +14,7 @@ For commercial licensing, please contact support@quantumnous.com import React, { useEffect, useState, useContext } from 'react'; import { Button, Card, Spinner, Switch } from '@heroui/react'; -import { - CreditCard, - KeyRound, - Link as LinkIcon, - Save, - X, -} from 'lucide-react'; +import { CreditCard, KeyRound, Link as LinkIcon, Save, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { API, @@ -513,9 +507,7 @@ const EditTokenModal = (props) => { - setField('name')(event.target.value) - } + onChange={(event) => setField('name')(event.target.value)} placeholder={t('请输入名称')} className={inputClass} /> @@ -853,19 +845,12 @@ const EditTokenModal = (props) => {
    - -
    diff --git a/web/src/components/table/usage-logs/modals/ParamOverrideModal.jsx b/web/src/components/table/usage-logs/modals/ParamOverrideModal.jsx index eb79a7b14c1..bceaac1133b 100644 --- a/web/src/components/table/usage-logs/modals/ParamOverrideModal.jsx +++ b/web/src/components/table/usage-logs/modals/ParamOverrideModal.jsx @@ -161,12 +161,12 @@ const ParamOverrideModal = ({
    diff --git a/web/src/components/table/users/modals/AddUserModal.jsx b/web/src/components/table/users/modals/AddUserModal.jsx index 41c6bf92834..b57cc333987 100644 --- a/web/src/components/table/users/modals/AddUserModal.jsx +++ b/web/src/components/table/users/modals/AddUserModal.jsx @@ -155,13 +155,17 @@ const AddUserModal = (props) => { setField('username')(event.target.value)} + onChange={(event) => + setField('username')(event.target.value) + } placeholder={t('请输入用户名')} aria-label={t('用户名')} className={inputClass} /> {errors.username ? ( -
    {errors.username}
    +
    + {errors.username} +
    ) : null} @@ -189,13 +193,17 @@ const AddUserModal = (props) => { setField('password')(event.target.value)} + onChange={(event) => + setField('password')(event.target.value) + } placeholder={t('请输入密码')} aria-label={t('密码')} className={inputClass} /> {errors.password ? ( -
    {errors.password}
    +
    + {errors.password} +
    ) : null} @@ -218,19 +226,12 @@ const AddUserModal = (props) => {
    - -
    diff --git a/web/src/components/table/users/modals/EditUserModal.jsx b/web/src/components/table/users/modals/EditUserModal.jsx index 3af8a6c4c86..a15c5e1ef06 100644 --- a/web/src/components/table/users/modals/EditUserModal.jsx +++ b/web/src/components/table/users/modals/EditUserModal.jsx @@ -27,14 +27,7 @@ import { Spinner, useOverlayState, } from '@heroui/react'; -import { - Edit3, - Link as LinkIcon, - Save, - User, - Users, - X, -} from 'lucide-react'; +import { Edit3, Link as LinkIcon, Save, User, Users, X } from 'lucide-react'; import { API, showError, @@ -325,9 +318,7 @@ const EditUserModal = (props) => { >
    - - {t(isEdit ? '编辑' : '新建')} - + {t(isEdit ? '编辑' : '新建')}

    {isEdit ? t('编辑用户') : t('创建用户')}

    @@ -482,9 +473,9 @@ const EditUserModal = (props) => { {t('调整额度')}
    @@ -532,9 +523,7 @@ const EditUserModal = (props) => { {t('绑定信息')}
    - {t( - '管理用户已绑定的第三方账户,支持筛选与解绑', - )} + {t('管理用户已绑定的第三方账户,支持筛选与解绑')}
    @@ -552,19 +541,12 @@ const EditUserModal = (props) => {
    - -
    @@ -675,9 +657,7 @@ const EditUserModal = (props) => { quota === '' ? '' : adjustMode === 'override' - ? Number( - quotaToDisplayAmount(quota).toFixed(6), - ) + ? Number(quotaToDisplayAmount(quota).toFixed(6)) : Number( quotaToDisplayAmount( Math.abs(quota), diff --git a/web/src/components/table/users/modals/UserBindingManagementModal.jsx b/web/src/components/table/users/modals/UserBindingManagementModal.jsx index 1a97acd173f..80c9792975f 100644 --- a/web/src/components/table/users/modals/UserBindingManagementModal.jsx +++ b/web/src/components/table/users/modals/UserBindingManagementModal.jsx @@ -153,9 +153,7 @@ const UserBindingManagementModal = ({ return; } setCustomOAuthBindings((prev) => - prev.filter( - (item) => Number(item.provider_id) !== Number(provider.id), - ), + prev.filter((item) => Number(item.provider_id) !== Number(provider.id)), ); showSuccess(t('解绑成功')); } catch (error) { @@ -198,9 +196,7 @@ const UserBindingManagementModal = ({ name: 'Discord', enabled: Boolean(statusInfo.discord_oauth), value: getBuiltInBindingValue('discord_id'), - icon: ( - - ), + icon: , }, { key: 'oidc', @@ -208,9 +204,7 @@ const UserBindingManagementModal = ({ name: 'OIDC', enabled: Boolean(statusInfo.oidc_enabled), value: getBuiltInBindingValue('oidc_id'), - icon: ( - - ), + icon: , }, { key: 'wechat', @@ -218,9 +212,7 @@ const UserBindingManagementModal = ({ name: t('微信'), enabled: Boolean(statusInfo.wechat_login), value: getBuiltInBindingValue('wechat_id'), - icon: ( - - ), + icon: , }, { key: 'telegram', @@ -228,9 +220,7 @@ const UserBindingManagementModal = ({ name: 'Telegram', enabled: Boolean(statusInfo.telegram_oauth), value: getBuiltInBindingValue('telegram_id'), - icon: ( - - ), + icon: , }, { key: 'linuxdo', @@ -238,9 +228,7 @@ const UserBindingManagementModal = ({ name: 'LinuxDO', enabled: Boolean(statusInfo.linuxdo_oauth), value: getBuiltInBindingValue('linux_do_id'), - icon: ( - - ), + icon: , }, ]; @@ -306,10 +294,7 @@ const UserBindingManagementModal = ({ <> - +
    @@ -394,7 +379,6 @@ const UserBindingManagementModal = ({ color='danger' variant='tertiary' size='sm' - startContent={} isDisabled={!isBound} isPending={Boolean( bindingActionLoading[loadingKey], @@ -418,6 +402,7 @@ const UserBindingManagementModal = ({ } }} > + {t('解绑')} @@ -445,7 +430,8 @@ const UserBindingManagementModal = ({ const target = pendingUnbind; setPendingUnbind(null); if (!target) return; - if (target.kind === 'builtin') performUnbindBuiltIn(target.bindingItem); + if (target.kind === 'builtin') + performUnbindBuiltIn(target.bindingItem); else performUnbindCustom(target.provider); }} > diff --git a/web/src/components/table/users/modals/UserSubscriptionsModal.jsx b/web/src/components/table/users/modals/UserSubscriptionsModal.jsx index 66e76160a6d..db6f4b8a880 100644 --- a/web/src/components/table/users/modals/UserSubscriptionsModal.jsx +++ b/web/src/components/table/users/modals/UserSubscriptionsModal.jsx @@ -63,13 +63,7 @@ function renderStatusTag(sub, t) { return {t('已过期')}; } -const UserSubscriptionsModal = ({ - visible, - onCancel, - user, - t, - onSuccess, -}) => { +const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { const isMobile = useIsMobile(); const [loading, setLoading] = useState(false); const [creating, setCreating] = useState(false); @@ -421,10 +415,10 @@ const UserSubscriptionsModal = ({
    diff --git a/web/src/components/topup/RechargeCard.jsx b/web/src/components/topup/RechargeCard.jsx index ef59da83c59..a8546e2784a 100644 --- a/web/src/components/topup/RechargeCard.jsx +++ b/web/src/components/topup/RechargeCard.jsx @@ -193,14 +193,10 @@ const RechargeCard = ({ const minTopupVal = Number(payMethod.min_topup) || 0; const isStripe = payMethod.type === 'stripe'; const isWaffo = - typeof payMethod.type === 'string' && - payMethod.type.startsWith('waffo:'); + typeof payMethod.type === 'string' && payMethod.type.startsWith('waffo:'); const isWaffoPancake = payMethod.type === 'waffo_pancake'; const disabled = - (!enableOnlineTopUp && - !isStripe && - !isWaffo && - !isWaffoPancake) || + (!enableOnlineTopUp && !isStripe && !isWaffo && !isWaffoPancake) || (!enableStripeTopUp && isStripe) || (!enableWaffoTopUp && isWaffo) || (!enableWaffoPancakeTopUp && isWaffoPancake) || @@ -234,12 +230,12 @@ const RechargeCard = ({ ); @@ -254,9 +250,7 @@ const RechargeCard = ({ ); } - return ( - {buttonEl} - ); + return {buttonEl}; }; const renderPresetGrid = () => ( @@ -278,9 +272,7 @@ const RechargeCard = ({
    {presetAmounts.map((preset, index) => { const discount = - preset.discount || - topupInfo?.discount?.[preset.value] || - 1.0; + preset.discount || topupInfo?.discount?.[preset.value] || 1.0; const originalPrice = preset.value * priceRatio; const discountedPrice = originalPrice * discount; const hasDiscount = discount < 1.0; @@ -324,9 +316,7 @@ const RechargeCard = ({ setTopUpCount(preset.value); }} className={`flex w-full cursor-pointer flex-col items-center gap-1 rounded-xl bg-background px-3 py-3 text-center transition-colors hover:border-primary ${ - selected - ? 'border-2 border-primary' - : 'border border-border' + selected ? 'border-2 border-primary' : 'border border-border' }`} >
    @@ -551,11 +541,8 @@ const RechargeCard = ({
    - diff --git a/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx b/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx index 3b43a90de0b..ec005e77cd8 100644 --- a/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx +++ b/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx @@ -96,17 +96,24 @@ const SubscriptionPurchaseModal = ({
    - + - + {formatSubscriptionDuration(plan, t)} } /> - {formatSubscriptionResetPeriod(plan, t) !== t('不重置') && ( + {formatSubscriptionResetPeriod(plan, t) !== + t('不重置') && ( {totalAmount > 0 ? ( - + {renderQuota(totalAmount)} ) : ( @@ -164,11 +173,11 @@ const SubscriptionPurchaseModal = ({ )} @@ -176,11 +185,11 @@ const SubscriptionPurchaseModal = ({ )} @@ -208,7 +217,9 @@ const SubscriptionPurchaseModal = ({ color='primary' onPress={onPayEpay} isPending={paying} - isDisabled={!selectedEpayMethod || purchaseLimitReached} + isDisabled={ + !selectedEpayMethod || purchaseLimitReached + } > {t('支付')} diff --git a/web/src/components/topup/modals/TopupHistoryModal.jsx b/web/src/components/topup/modals/TopupHistoryModal.jsx index 4ddfa2a03a3..2bfcae4176c 100644 --- a/web/src/components/topup/modals/TopupHistoryModal.jsx +++ b/web/src/components/topup/modals/TopupHistoryModal.jsx @@ -32,7 +32,13 @@ import { useOverlayState, } from '@heroui/react'; import { Coins, Search } from 'lucide-react'; -import { API, copy, showError, showSuccess, timestamp2string } from '../../../helpers'; +import { + API, + copy, + showError, + showSuccess, + timestamp2string, +} from '../../../helpers'; import { isAdmin } from '../../../helpers/utils'; import { useIsMobile } from '../../../hooks/common/useIsMobile'; import ConfirmDialog from '../../common/ui/ConfirmDialog'; @@ -145,7 +151,11 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => { primary: 'primary', }; return ( - + {t(config.key)} ); @@ -218,7 +228,9 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => { dataIndex: 'money', key: 'money', render: (money) => ( - ¥{Number(money || 0).toFixed(2)} + + ¥{Number(money || 0).toFixed(2)} + ), }, { @@ -239,13 +251,13 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => { if (record.status === 'pending') { actions.push( + , ); } return actions.length > 0 ? <>{actions} : null; @@ -286,13 +298,14 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => {
    } placeholder={t('订单号')} value={keyword} onValueChange={handleKeywordChange} isClearable size='sm' - /> + > + +
    {loading ? ( @@ -336,7 +349,10 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => { {columns.map((column) => ( - + {column.title} ))} @@ -349,7 +365,10 @@ const TopupHistoryModal = ({ visible, onCancel, t }) => { className='bg-background transition hover:bg-surface-secondary/60' > {columns.map((column) => ( - + {renderCell(record, column) || '-'} ))} diff --git a/web/src/pages/Home/index.jsx b/web/src/pages/Home/index.jsx index d55b6d237a9..dd3c4160375 100644 --- a/web/src/pages/Home/index.jsx +++ b/web/src/pages/Home/index.jsx @@ -211,7 +211,6 @@ const Home = () => { size={isMobile ? 'md' : 'lg'} variant='tertiary' className='rounded-full px-6 py-2 font-semibold' - startContent={} onPress={() => window.open( 'https://github.com/QuantumNous/new-api', @@ -219,6 +218,7 @@ const Home = () => { ) } > + {statusState.status.version} ) : ( @@ -227,9 +227,9 @@ const Home = () => { size={isMobile ? 'md' : 'lg'} variant='tertiary' className='rounded-full px-6 py-2 font-semibold' - startContent={} onPress={() => window.open(docsLink, '_blank')} > + {t('文档')} ) diff --git a/web/src/pages/Setting/Chat/SettingsChats.jsx b/web/src/pages/Setting/Chat/SettingsChats.jsx index c7f3dfc6bf9..efa3ed7750b 100644 --- a/web/src/pages/Setting/Chat/SettingsChats.jsx +++ b/web/src/pages/Setting/Chat/SettingsChats.jsx @@ -176,8 +176,7 @@ export default function SettingsChats(props) { return; } const updateArray = compareObjects(inputs, inputsRow); - if (!updateArray.length) - return showWarning(t('你似乎并没有修改什么')); + if (!updateArray.length) return showWarning(t('你似乎并没有修改什么')); const requestQueue = updateArray.map((item) => { let value = ''; if (typeof inputs[item.key] === 'boolean') { @@ -297,9 +296,7 @@ export default function SettingsChats(props) { syncConfigsToJson(newConfigs); } else { const maxId = - chatConfigs.length > 0 - ? Math.max(...chatConfigs.map((c) => c.id)) - : -1; + chatConfigs.length > 0 ? Math.max(...chatConfigs.map((c) => c.id)) : -1; const newConfig = { id: maxId + 1, ...values }; const newConfigs = [...chatConfigs, newConfig]; setChatConfigs(newConfigs); @@ -335,10 +332,7 @@ export default function SettingsChats(props) { const pageStart = (page - 1) * PAGE_SIZE; const pageItems = filteredConfigs.slice(pageStart, pageStart + PAGE_SIZE); - const pageCount = Math.max( - 1, - Math.ceil(filteredConfigs.length / PAGE_SIZE), - ); + const pageCount = Math.max(1, Math.ceil(filteredConfigs.length / PAGE_SIZE)); const highlightKeywords = (text) => { if (!text) return text; @@ -429,31 +423,23 @@ export default function SettingsChats(props) { {editMode === 'visual' ? (
    - } - endContent={} - > + } /> -
    @@ -515,17 +501,17 @@ export default function SettingsChats(props) {
    @@ -565,9 +551,7 @@ export default function SettingsChats(props) { size='sm' variant='tertiary' isDisabled={page >= pageCount} - onPress={() => - setPage((p) => Math.min(pageCount, p + 1)) - } + onPress={() => setPage((p) => Math.min(pageCount, p + 1))} > {t('下一页')} @@ -603,11 +587,8 @@ export default function SettingsChats(props) { {editMode === 'json' && (
    -
    @@ -618,9 +599,7 @@ export default function SettingsChats(props) { - - {isEdit ? t('编辑聊天配置') : t('添加聊天配置')} - + {isEdit ? t('编辑聊天配置') : t('添加聊天配置')}
    diff --git a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.jsx b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.jsx index ee6d279f9a4..33c56746a12 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.jsx +++ b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.jsx @@ -355,30 +355,30 @@ const SettingsAPIInfo = ({ options, refresh }) => {
    @@ -486,17 +486,17 @@ const SettingsAPIInfo = ({ options, refresh }) => {
    @@ -545,9 +545,7 @@ const SettingsAPIInfo = ({ options, refresh }) => { size='sm' variant='tertiary' isDisabled={currentPage >= totalPages} - onPress={() => - setCurrentPage((p) => Math.min(totalPages, p + 1)) - } + onPress={() => setCurrentPage((p) => Math.min(totalPages, p + 1))} > {t('下一页')} @@ -662,7 +660,10 @@ const SettingsAPIInfo = ({ options, refresh }) => {
    -
    @@ -441,18 +433,14 @@ const SettingsAnnouncements = ({ options, refresh }) => { ariaLabel={t('选择当前页')} /> - - {t('内容')} - + {t('内容')} {t('发布时间')} {t('类型')} - - {t('说明')} - + {t('说明')} {t('操作')} @@ -525,17 +513,17 @@ const SettingsAnnouncements = ({ options, refresh }) => {
    @@ -566,7 +554,9 @@ const SettingsAnnouncements = ({ options, refresh }) => { ))} - {t('共 {{total}} 条', { total: announcementsList.length })} + + {t('共 {{total}} 条', { total: announcementsList.length })} +
    @@ -610,9 +598,9 @@ const SettingsAnnouncements = ({ options, refresh }) => {
    @@ -644,7 +632,9 @@ const SettingsAnnouncements = ({ options, refresh }) => { { const v = event.target.value; setAnnouncementForm((prev) => ({ @@ -748,7 +738,10 @@ const SettingsAnnouncements = ({ options, refresh }) => { /> - @@ -405,17 +399,17 @@ const SettingsFAQ = ({ options, refresh }) => { @@ -464,9 +458,7 @@ const SettingsFAQ = ({ options, refresh }) => { size='sm' variant='tertiary' isDisabled={currentPage >= totalPages} - onPress={() => - setCurrentPage((p) => Math.min(totalPages, p + 1)) - } + onPress={() => setCurrentPage((p) => Math.min(totalPages, p + 1))} > {t('下一页')} @@ -534,7 +526,10 @@ const SettingsFAQ = ({ options, refresh }) => { - @@ -419,17 +420,17 @@ const SettingsUptimeKuma = ({ options, refresh }) => { @@ -460,7 +461,9 @@ const SettingsUptimeKuma = ({ options, refresh }) => { ))} - {t('共 {{total}} 条', { total: uptimeGroupsList.length })} + + {t('共 {{total}} 条', { total: uptimeGroupsList.length })} +
    @@ -567,7 +568,9 @@ const SettingsUptimeKuma = ({ options, refresh }) => { className={inputClass} /> {formErrors.slug ? ( -
    {formErrors.slug}
    +
    + {formErrors.slug} +
    ) : null}
    diff --git a/web/src/pages/Setting/Model/SettingModelDeployment.jsx b/web/src/pages/Setting/Model/SettingModelDeployment.jsx index dadacbd5e54..449db02296a 100644 --- a/web/src/pages/Setting/Model/SettingModelDeployment.jsx +++ b/web/src/pages/Setting/Model/SettingModelDeployment.jsx @@ -183,9 +183,7 @@ export default function SettingModelDeployment(props) { onChange={(e) => setField('model_deployment.ionet.api_key')(e.target.value) } - placeholder={t( - '请输入 io.net API Key(敏感信息不显示)', - )} + placeholder={t('请输入 io.net API Key(敏感信息不显示)')} disabled={!enabled} aria-label={t('API Key')} className={inputClass} @@ -199,11 +197,11 @@ export default function SettingModelDeployment(props) { @@ -223,13 +221,13 @@ export default function SettingModelDeployment(props) { diff --git a/web/src/pages/Setting/Operation/SettingsChannelAffinity.jsx b/web/src/pages/Setting/Operation/SettingsChannelAffinity.jsx index 806dcdb8f3b..5119ac00dd1 100644 --- a/web/src/pages/Setting/Operation/SettingsChannelAffinity.jsx +++ b/web/src/pages/Setting/Operation/SettingsChannelAffinity.jsx @@ -239,7 +239,8 @@ const tryParseRulesJsonArray = (jsonString) => { const parseOptionalObjectJson = (jsonString, label) => { const raw = (jsonString || '').trim(); if (!raw) return { ok: true, value: null }; - if (!verifyJSON(raw)) return { ok: false, message: `${label} JSON 格式不正确` }; + if (!verifyJSON(raw)) + return { ok: false, message: `${label} JSON 格式不正确` }; try { const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { @@ -583,7 +584,9 @@ export default function SettingsChannelAffinity(props) { return showError(t(keySourcesValidation.message)); } - const userAgentInclude = normalizeStringList(values.user_agent_include_text); + const userAgentInclude = normalizeStringList( + values.user_agent_include_text, + ); const paramTemplateValidation = parseOptionalObjectJson( paramTemplateDraft, '参数覆盖模板', @@ -743,12 +746,8 @@ export default function SettingsChannelAffinity(props) { {t('名称')} - - {t('模型正则')} - - - {t('路径正则')} - + {t('模型正则')} + {t('路径正则')} {t('Key 来源')} {t('TTL(秒)')} @@ -798,10 +797,7 @@ export default function SettingsChannelAffinity(props) { {(record.model_regex || []).length === 0 ? '-' : (record.model_regex || []).slice(0, 3).map((v, idx) => ( - + {v} ))} @@ -810,10 +806,7 @@ export default function SettingsChannelAffinity(props) { {(record.path_regex || []).length === 0 ? '-' : (record.path_regex || []).slice(0, 2).map((v, idx) => ( - + {v} ))} @@ -821,18 +814,20 @@ export default function SettingsChannelAffinity(props) { {(record.key_sources || []).length === 0 ? '-' - : (record.key_sources || []).slice(0, 3).map((src, idx) => { - const s = normalizeKeySource(src); - const detail = s.type === 'gjson' ? s.path : s.key; - return ( - - {s.type}:{detail} - - ); - })} + : (record.key_sources || []) + .slice(0, 3) + .map((src, idx) => { + const s = normalizeKeySource(src); + const detail = s.type === 'gjson' ? s.path : s.key; + return ( + + {s.type}:{detail} + + ); + })} {Number(record.ttl_seconds || 0) || '-'} @@ -849,9 +844,9 @@ export default function SettingsChannelAffinity(props) { ) : ( @@ -1042,17 +1037,13 @@ export default function SettingsChannelAffinity(props) { value={inputs[KEY_MAX_ENTRIES] ?? 0} onChange={(event) => { const raw = event.target.value; - setInputsField(KEY_MAX_ENTRIES)( - raw === '' ? 0 : Number(raw), - ); + setInputsField(KEY_MAX_ENTRIES)(raw === '' ? 0 : Number(raw)); }} placeholder='例如 100000…' className={inputClass} /> - {t( - '内存缓存最大条目数。0 表示使用后端默认容量:100000。', - )} + {t('内存缓存最大条目数。0 表示使用后端默认容量:100000。')}
    @@ -1063,9 +1054,7 @@ export default function SettingsChannelAffinity(props) { value={inputs[KEY_DEFAULT_TTL] ?? 0} onChange={(event) => { const raw = event.target.value; - setInputsField(KEY_DEFAULT_TTL)( - raw === '' ? 0 : Number(raw), - ); + setInputsField(KEY_DEFAULT_TTL)(raw === '' ? 0 : Number(raw)); }} placeholder='例如 3600…' className={inputClass} @@ -1121,11 +1110,8 @@ export default function SettingsChannelAffinity(props) { -
    @@ -1312,9 +1296,7 @@ export default function SettingsChannelAffinity(props) {
    - - {t('TTL(秒,0 表示默认)')} - + {t('TTL(秒,0 表示默认)')} } onPress={() => setParamTemplateEditorVisible(true) } > + {t('可视化编辑')}
    @@ -1501,10 +1479,9 @@ export default function SettingsChannelAffinity(props) { onConfirm={async () => { setConfirmClearAll(false); try { - const res = await API.delete( - '/api/option/channel_affinity_cache', - { params: { all: true } }, - ); + const res = await API.delete('/api/option/channel_affinity_cache', { + params: { all: true }, + }); const { success, message } = res.data; if (!success) return showError(t(message)); showSuccess(t('已清空')); @@ -1535,10 +1512,9 @@ export default function SettingsChannelAffinity(props) { return; } try { - const res = await API.delete( - '/api/option/channel_affinity_cache', - { params: { rule_name: target.name } }, - ); + const res = await API.delete('/api/option/channel_affinity_cache', { + params: { rule_name: target.name }, + }); const { success, message } = res.data; if (!success) return showError(t(message)); showSuccess(t('已清空')); @@ -1548,9 +1524,7 @@ export default function SettingsChannelAffinity(props) { } }} > - {confirmClearRule?.name - ? `${t('规则')}: ${confirmClearRule.name}` - : ''} + {confirmClearRule?.name ? `${t('规则')}: ${confirmClearRule.name}` : ''}
    {label}
    @@ -288,12 +296,8 @@ export default function SettingsPaymentGatewayCreem(props) {
    {t('产品配置')}
    - diff --git a/web/src/pages/Setting/Ratio/GroupRatioSettings.jsx b/web/src/pages/Setting/Ratio/GroupRatioSettings.jsx index c996c6efd2c..07d0ef8dde1 100644 --- a/web/src/pages/Setting/Ratio/GroupRatioSettings.jsx +++ b/web/src/pages/Setting/Ratio/GroupRatioSettings.jsx @@ -12,7 +12,13 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Spinner, Switch } from '@heroui/react'; import { ChevronDown, HelpCircle, X } from 'lucide-react'; @@ -174,14 +180,11 @@ export default function GroupRatioSettings(props) { if ( !validateJSON(inputs['group_ratio_setting.group_special_usable_group']) ) { - next['group_ratio_setting.group_special_usable_group'] = t( - '不是合法的 JSON 字符串', - ); + next['group_ratio_setting.group_special_usable_group'] = + t('不是合法的 JSON 字符串'); } if (!validateAutoGroups(inputs.AutoGroups)) { - next.AutoGroups = t( - '必须是有效的 JSON 字符串数组,例如:["g1","g2"]', - ); + next.AutoGroups = t('必须是有效的 JSON 字符串数组,例如:["g1","g2"]'); } setErrors(next); return Object.keys(next).length === 0; @@ -281,7 +284,9 @@ export default function GroupRatioSettings(props) {
    - {t('倍率用于计费乘数,勾选「用户可选」后用户可在创建令牌时选择该分组')} + {t( + '倍率用于计费乘数,勾选「用户可选」后用户可在创建令牌时选择该分组', + )}

    - {t('用户分组')}{' — '} + {t('用户分组')} + {' — '} {t('由管理员分配,决定用户身份等级(如 default、vip)。')}

    - {t('令牌分组')}{' — '} + {t('令牌分组')} + {' — '} {t( '用户创建令牌时选择的分组,决定该令牌的实际计费倍率。一个用户可以创建多个令牌,使用不同分组。', )}

    - {t('倍率')}{' — '} + {t('倍率')} + {' — '} {t('计费乘数,倍率越低费用越低。例如倍率 0.5 表示半价。')}

    - {t('用户可选')}{' — '} + {t('用户可选')} + {' — '} {t( '勾选后,该分组会出现在用户创建令牌时的下拉菜单中。未勾选的分组只能由管理员分配,用户自己无法选择。', )}

    - {t('自动分组')}{' — '} + {t('自动分组')} + {' — '} {t( '令牌分组设为 auto 时,系统按优先级顺序自动选择一个可用分组。', )} @@ -621,7 +631,9 @@ export default function GroupRatioSettings(props) { {`${t('分组名')} ${t('倍率')} ${t('用户可选')} ${t('说明')}\n──────────────────────────────────────\nstandard 1.0 ${t('是')} ${t('标准价格')}\npremium 0.5 ${t('是')} ${t('高级套餐,半价优惠')}`}

    - {t('两个分组都勾选了「用户可选」,所以用户创建令牌时可以看到这两个选项:')} + {t( + '两个分组都勾选了「用户可选」,所以用户创建令牌时可以看到这两个选项:', + )}

    {t('用户创建令牌 → 选择分组下拉框:')} @@ -631,7 +643,9 @@ export default function GroupRatioSettings(props) { {` └─ premium (${t('高级套餐,半价优惠')})`}

    - {t('选择 premium 创建的令牌,调用 API 时费用为 standard 的 50%。')} + {t( + '选择 premium 创建的令牌,调用 API 时费用为 standard 的 50%。', + )}

    {t('对比:不勾选「用户可选」的场景')} @@ -663,18 +677,22 @@ export default function GroupRatioSettings(props) { {t('用户分组的联动作用')}

    - {t('管理员给用户分配的分组(如 vip)不仅决定用户身份,还会影响后续两个功能:')} + {t( + '管理员给用户分配的分组(如 vip)不仅决定用户身份,还会影响后续两个功能:', + )}

    {'1. '} - {t('特殊倍率')}{' — '} + {t('特殊倍率')} + {' — '} {t( '可以根据用户分组设置不同的计费倍率。例如 vip 用户使用 standard 令牌时倍率从 1.0 降为 0.8。', )}

    {'2. '} - {t('可用分组')}{' — '} + {t('可用分组')} + {' — '} {t( '可以根据用户分组增减令牌可选的分组范围。例如 vip 用户额外开放 premium 分组,或移除某个分组的选择权。', )} @@ -728,7 +746,9 @@ export default function GroupRatioSettings(props) { {`1. default ${t('最高优先级')}\n2. vip`}

    - {t('开启「默认使用 auto 分组」后,新建令牌和初始令牌都会自动设为 auto。')} + {t( + '开启「默认使用 auto 分组」后,新建令牌和初始令牌都会自动设为 auto。', + )}

    @@ -756,7 +776,9 @@ export default function GroupRatioSettings(props) { )}

    - {t('简单来说:同一个令牌分组,不同等级的用户可以享受不同的价格。')} + {t( + '简单来说:同一个令牌分组,不同等级的用户可以享受不同的价格。', + )}

    @@ -777,14 +799,14 @@ export default function GroupRatioSettings(props) { {`${t('用户分组')} ${t('使用分组')} ${t('倍率')}\n────────────────────────────\nvip standard 0.8\nvip premium 0.3`} -

    - {t('配置后的效果:')} -

    +

    {t('配置后的效果:')}

    {`${t('普通用户')} + standard ${t('令牌')} → ${t('倍率')} 1.0 (${t('不变')})\nvip ${t('用户')} + standard ${t('令牌')} → ${t('倍率')} 0.8 (${t('享受 8 折')})\nvip ${t('用户')} + premium ${t('令牌')} → ${t('倍率')} 0.3 (${t('从 0.5 降到 0.3')})`}

    - {t('只有配置了规则的组合才会覆盖,未配置的组合仍使用令牌分组的基础倍率。')} + {t( + '只有配置了规则的组合才会覆盖,未配置的组合仍使用令牌分组的基础倍率。', + )}

    @@ -812,7 +834,9 @@ export default function GroupRatioSettings(props) { )}

    - {t('通过此功能,可以根据用户所在分组,为不同等级的用户展示不同的可选列表。')} + {t( + '通过此功能,可以根据用户所在分组,为不同等级的用户展示不同的可选列表。', + )}

    @@ -822,7 +846,9 @@ export default function GroupRatioSettings(props) { )}

    - {t('不配置规则时,所有用户看到的下拉框一样:')} + + {t('不配置规则时,所有用户看到的下拉框一样:')} +

    {`${t('所有用户')} → ${t('创建令牌可选')}:\n ├─ standard\n └─ premium`} @@ -833,9 +859,7 @@ export default function GroupRatioSettings(props) { {`${t('用户分组')} ${t('操作')} ${t('目标分组')} ${t('描述')}\n──────────────────────────────────────────\nvip ${t('添加')} (+:) exclusive ${t('专属分组')}\nvip ${t('移除')} (-:) standard -`} -

    - {t('配置后的效果:')} -

    +

    {t('配置后的效果:')}

    {`${t('普通用户')} → ${t('创建令牌可选')}:\n ├─ standard\n └─ premium\n\nvip ${t('用户')} → ${t('创建令牌可选')}:\n ├─ premium (${t('保留')})\n └─ exclusive (${t('新增')})\n\n ${t('standard 已被移除,vip 用户看不到')}`} @@ -851,12 +875,16 @@ export default function GroupRatioSettings(props) {

    - group_special_usable_group + + group_special_usable_group +

    {`{\n "vip": {\n "+:exclusive": "${t('专属分组')}",\n "-:standard": "remove"\n }\n}`}

    - {t('键的前缀 +: 表示添加,-: 表示移除,无前缀表示追加。值为分组描述(移除时填 "remove")。')} + {t( + '键的前缀 +: 表示添加,-: 表示移除,无前缀表示追加。值为分组描述(移除时填 "remove")。', + )}

    @@ -897,12 +925,8 @@ export default function GroupRatioSettings(props) { ); })} - diff --git a/web/src/pages/Setting/Ratio/UpstreamRatioSync.jsx b/web/src/pages/Setting/Ratio/UpstreamRatioSync.jsx index f1d618fcfcd..ac26d9c2bd8 100644 --- a/web/src/pages/Setting/Ratio/UpstreamRatioSync.jsx +++ b/web/src/pages/Setting/Ratio/UpstreamRatioSync.jsx @@ -57,13 +57,7 @@ import ChannelSelectorModal from '../../../components/settings/ChannelSelectorMo const inputClass = 'h-10 w-full rounded-xl border border-border bg-background px-3 text-sm text-foreground outline-none transition focus:border-primary disabled:opacity-50'; -function StatusChip({ - tone = 'grey', - bg, - color, - prefixIcon, - children, -}) { +function StatusChip({ tone = 'grey', bg, color, prefixIcon, children }) { if (bg) { return ( { Object.keys(ratios).forEach((ratioType) => { - if ( - newDifferences[model] && - newDifferences[model][ratioType] - ) { + if (newDifferences[model] && newDifferences[model][ratioType]) { delete newDifferences[model][ratioType]; if (Object.keys(newDifferences[model]).length === 0) { delete newDifferences[model]; @@ -566,9 +557,7 @@ export default function UpstreamRatioSync(props) { return dataSource.filter((item) => { const matchesKeyword = !searchKeyword.trim() || - item.model - .toLowerCase() - .includes(searchKeyword.toLowerCase().trim()); + item.model.toLowerCase().includes(searchKeyword.toLowerCase().trim()); const matchesRatioType = !ratioTypeFilter || item.ratioType === ratioTypeFilter; return matchesKeyword && matchesRatioType; @@ -604,10 +593,8 @@ export default function UpstreamRatioSync(props) { map[upName] = { selectableCount, selectedCount, - allSelected: - selectableCount > 0 && selectedCount === selectableCount, - partiallySelected: - selectedCount > 0 && selectedCount < selectableCount, + allSelected: selectableCount > 0 && selectedCount === selectableCount, + partiallySelected: selectedCount > 0 && selectedCount < selectableCount, hasSelectableItems: selectableCount > 0, }; }); @@ -624,10 +611,7 @@ export default function UpstreamRatioSync(props) { ); const startIndex = filteredDataSource.length === 0 ? 0 : (currentPage - 1) * pageSize + 1; - const endIndex = Math.min( - currentPage * pageSize, - filteredDataSource.length, - ); + const endIndex = Math.min(currentPage * pageSize, filteredDataSource.length); const handleBulkSelect = (upName, checked) => { if (checked) { @@ -726,22 +710,22 @@ export default function UpstreamRatioSync(props) {
    @@ -754,18 +738,14 @@ export default function UpstreamRatioSync(props) { - setSearchKeyword(event.target.value) - } + onChange={(event) => setSearchKeyword(event.target.value)} placeholder={t('搜索模型名称')} className={`${inputClass} pl-8`} />
    updateRow(record._id, 'name', event.target.value)} + onChange={(event) => + updateRow(record._id, 'name', event.target.value) + } aria-label={t('分组名称')} className={`${baseInputClass} ${ isDup ? 'border-amber-400 focus:border-amber-500' : '' @@ -158,7 +163,11 @@ export default function GroupTable({ groupRatio, userUsableGroups, onChange }) { render: (_, record) => ( { @@ -244,12 +253,8 @@ export default function GroupTable({ groupRatio, userUsableGroups, onChange }) { } />
    -
    diff --git a/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx b/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx index 60a492e3a19..34615f10965 100644 --- a/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx +++ b/web/src/pages/Setting/Ratio/components/ModelPricingEditor.jsx @@ -88,9 +88,7 @@ function InfoBanner({ tone = 'warning', title, description }) { } />
    - {title ? ( -
    {title}
    - ) : null} + {title ?
    {title}
    : null} {description ?
    {description}
    : null}
    @@ -212,15 +210,9 @@ export default function ModelPricingEditor({ } }; - const totalPages = Math.max( - 1, - Math.ceil(filteredModels.length / PAGE_SIZE), - ); + const totalPages = Math.max(1, Math.ceil(filteredModels.length / PAGE_SIZE)); const startIndex = (currentPage - 1) * PAGE_SIZE + 1; - const endIndex = Math.min( - currentPage * PAGE_SIZE, - filteredModels.length, - ); + const endIndex = Math.min(currentPage * PAGE_SIZE, filteredModels.length); const allOnPageSelected = pagedData.length > 0 && @@ -276,27 +268,25 @@ export default function ModelPricingEditor({ {allowAddModel ? ( ) : null} -
    +
    - setConflictOnly(event.target.checked) - } + onChange={(event) => setConflictOnly(event.target.checked)} className='h-4 w-4 accent-primary' /> {t('仅显示矛盾倍率')} @@ -396,8 +381,7 @@ export default function ModelPricingEditor({ const isChecked = selectedModelNames.includes( record.name, ); - const isFocused = - record.name === selectedModelName; + const isFocused = record.name === selectedModelName; const rowBg = isChecked ? 'bg-success/10' : isFocused @@ -412,9 +396,7 @@ export default function ModelPricingEditor({ - setSelectedModelName(record.name) - } + onClick={() => setSelectedModelName(record.name)} > - toggleRow( - record.name, - event.target.checked, - ) + toggleRow(record.name, event.target.checked) } aria-label={record.name} className='h-4 w-4 accent-primary' @@ -530,9 +509,7 @@ export default function ModelPricingEditor({ variant='tertiary' isDisabled={currentPage >= totalPages} onPress={() => - setCurrentPage( - Math.min(totalPages, currentPage + 1), - ) + setCurrentPage(Math.min(totalPages, currentPage + 1)) } > {t('下一页')} @@ -550,9 +527,7 @@ export default function ModelPricingEditor({
    - {selectedModel - ? selectedModel.name - : t('模型计费编辑器')} + {selectedModel ? selectedModel.name : t('模型计费编辑器')}
    {selectedModel ? ( @@ -680,10 +655,7 @@ export default function ModelPricingEditor({ value={selectedModel.completionPrice} placeholder={t('输入 $/1M tokens')} onChange={(value) => - handleNumericFieldChange( - 'completionPrice', - value, - ) + handleNumericFieldChange('completionPrice', value) } headerAction={ handleOptionalFieldToggle( 'completionPrice', @@ -885,10 +855,7 @@ export default function ModelPricingEditor({ value={selectedModel.audioInputPrice} placeholder={t('输入 $/1M tokens')} onChange={(value) => - handleNumericFieldChange( - 'audioInputPrice', - value, - ) + handleNumericFieldChange('audioInputPrice', value) } headerAction={