diff --git a/controller/misc.go b/controller/misc.go index 6219676e7..e76ca51bb 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -115,7 +115,7 @@ func GetStatus(c *gin.Context) { "user_agreement_enabled": legalSetting.UserAgreement != "", "privacy_policy_enabled": legalSetting.PrivacyPolicy != "", "checkin_enabled": operation_setting.GetCheckinSetting().Enabled, - "_qn": "new-api", + "_qn": "new-api", } // 根据启用状态注入可选内容 diff --git a/controller/performance.go b/controller/performance.go index c7e853548..a6fedc46c 100644 --- a/controller/performance.go +++ b/controller/performance.go @@ -91,11 +91,11 @@ func GetPerformanceStats(c *gin.Context) { // 获取配置信息 diskConfig := common.GetDiskCacheConfig() config := PerformanceConfig{ - DiskCacheEnabled: diskConfig.Enabled, - DiskCacheThresholdMB: diskConfig.ThresholdMB, - DiskCacheMaxSizeMB: diskConfig.MaxSizeMB, - DiskCachePath: diskConfig.Path, - IsRunningInContainer: common.IsRunningInContainer(), + DiskCacheEnabled: diskConfig.Enabled, + DiskCacheThresholdMB: diskConfig.ThresholdMB, + DiskCacheMaxSizeMB: diskConfig.MaxSizeMB, + DiskCachePath: diskConfig.Path, + IsRunningInContainer: common.IsRunningInContainer(), } // 获取磁盘空间信息 @@ -199,4 +199,3 @@ func getDiskCacheInfo() DiskCacheInfo { return info } - diff --git a/controller/subscription.go b/controller/subscription.go index 5a6f88d66..b4561fe45 100644 --- a/controller/subscription.go +++ b/controller/subscription.go @@ -15,8 +15,8 @@ import ( // ---- Shared types ---- type SubscriptionPlanDTO struct { - Plan model.SubscriptionPlan `json:"plan"` - Items []model.SubscriptionPlanItem `json:"items"` + Plan model.SubscriptionPlan `json:"plan"` + Items []model.SubscriptionPlanItem `json:"items"` } type BillingPreferenceRequest struct { diff --git a/controller/subscription_payment_creem.go b/controller/subscription_payment_creem.go index 5927a7002..f5888b8e9 100644 --- a/controller/subscription_payment_creem.go +++ b/controller/subscription_payment_creem.go @@ -97,4 +97,3 @@ func SubscriptionRequestCreemPay(c *gin.Context) { }, }) } - diff --git a/controller/subscription_payment_epay.go b/controller/subscription_payment_epay.go index 0e287a211..fc24337b1 100644 --- a/controller/subscription_payment_epay.go +++ b/controller/subscription_payment_epay.go @@ -7,12 +7,12 @@ import ( "strconv" "time" + "github.com/Calcium-Ion/go-epay/epay" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/QuantumNous/new-api/setting/system_setting" - "github.com/Calcium-Ion/go-epay/epay" "github.com/gin-gonic/gin" "github.com/samber/lo" ) @@ -151,4 +151,3 @@ func SubscriptionEpayReturn(c *gin.Context) { } c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=pending") } - diff --git a/controller/subscription_payment_stripe.go b/controller/subscription_payment_stripe.go index 367fca9dc..b7ffde46e 100644 --- a/controller/subscription_payment_stripe.go +++ b/controller/subscription_payment_stripe.go @@ -116,4 +116,3 @@ func genStripeSubscriptionLink(referenceId string, customerId string, email stri } return result.URL, nil } - diff --git a/controller/topup_stripe.go b/controller/topup_stripe.go index 3fa605f17..4a4c4102c 100644 --- a/controller/topup_stripe.go +++ b/controller/topup_stripe.go @@ -169,10 +169,10 @@ func sessionCompleted(event stripe.Event) { // Subscription order takes precedence if model.GetSubscriptionOrderByTradeNo(referenceId) != nil { payload := map[string]any{ - "customer": customerId, + "customer": customerId, "amount_total": event.GetObjectValue("amount_total"), - "currency": strings.ToUpper(event.GetObjectValue("currency")), - "event_type": string(event.Type), + "currency": strings.ToUpper(event.GetObjectValue("currency")), + "event_type": string(event.Type), } if err := model.CompleteSubscriptionOrder(referenceId, jsonString(payload)); err != nil { log.Println("complete subscription order failed:", err.Error(), referenceId) diff --git a/main.go b/main.go index ae391ac3c..23953b877 100644 --- a/main.go +++ b/main.go @@ -19,8 +19,8 @@ import ( "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/router" "github.com/QuantumNous/new-api/service" - "github.com/QuantumNous/new-api/setting/ratio_setting" _ "github.com/QuantumNous/new-api/setting/performance_setting" // 注册性能设置 + "github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/bytedance/gopkg/util/gopool" "github.com/gin-contrib/sessions" diff --git a/model/subscription.go b/model/subscription.go index 26518512a..fcbd4d00b 100644 --- a/model/subscription.go +++ b/model/subscription.go @@ -36,7 +36,7 @@ type SubscriptionPlan struct { Enabled bool `json:"enabled" gorm:"default:true"` SortOrder int `json:"sort_order" gorm:"type:int;default:0"` - StripePriceId string `json:"stripe_price_id" gorm:"type:varchar(128);default:''"` + StripePriceId string `json:"stripe_price_id" gorm:"type:varchar(128);default:''"` CreemProductId string `json:"creem_product_id" gorm:"type:varchar(128);default:''"` CreatedAt int64 `json:"created_at" gorm:"bigint"` @@ -69,9 +69,9 @@ type SubscriptionPlanItem struct { // Subscription order (payment -> webhook -> create UserSubscription) type SubscriptionOrder struct { - Id int `json:"id"` - UserId int `json:"user_id" gorm:"index"` - PlanId int `json:"plan_id" gorm:"index"` + Id int `json:"id"` + UserId int `json:"user_id" gorm:"index"` + PlanId int `json:"plan_id" gorm:"index"` Money float64 `json:"money"` TradeNo string `json:"trade_no" gorm:"unique;type:varchar(255);index"` @@ -134,8 +134,8 @@ func (s *UserSubscription) BeforeUpdate(tx *gorm.DB) error { } type UserSubscriptionItem struct { - Id int `json:"id"` - UserSubscriptionId int `json:"user_subscription_id" gorm:"index"` + Id int `json:"id"` + UserSubscriptionId int `json:"user_subscription_id" gorm:"index"` ModelName string `json:"model_name" gorm:"type:varchar(128);index"` QuotaType int `json:"quota_type" gorm:"type:int;index"` AmountTotal int64 `json:"amount_total" gorm:"type:bigint;not null;default:0"` @@ -211,14 +211,14 @@ func CreateUserSubscriptionFromPlanTx(tx *gorm.DB, userId int, plan *Subscriptio return nil, err } sub := &UserSubscription{ - UserId: userId, - PlanId: plan.Id, - StartTime: now.Unix(), - EndTime: endUnix, - Status: "active", - Source: source, - CreatedAt: common.GetTimestamp(), - UpdatedAt: common.GetTimestamp(), + UserId: userId, + PlanId: plan.Id, + StartTime: now.Unix(), + EndTime: endUnix, + Status: "active", + Source: source, + CreatedAt: common.GetTimestamp(), + UpdatedAt: common.GetTimestamp(), } if err := tx.Create(sub).Error; err != nil { return nil, err @@ -586,4 +586,3 @@ func PostConsumeUserSubscriptionDelta(itemId int, delta int64) error { return tx.Save(&item).Error }) } - diff --git a/relay/channel/ali/adaptor.go b/relay/channel/ali/adaptor.go index 23ef5f4be..17869a06d 100644 --- a/relay/channel/ali/adaptor.go +++ b/relay/channel/ali/adaptor.go @@ -13,8 +13,8 @@ import ( "github.com/QuantumNous/new-api/relay/channel/openai" relaycommon "github.com/QuantumNous/new-api/relay/common" "github.com/QuantumNous/new-api/relay/constant" - "github.com/QuantumNous/new-api/setting/model_setting" "github.com/QuantumNous/new-api/service" + "github.com/QuantumNous/new-api/setting/model_setting" "github.com/QuantumNous/new-api/types" "github.com/gin-gonic/gin" diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 8363b6040..7a3aad333 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -113,7 +113,7 @@ type RelayInfo struct { UserQuota int RelayFormat types.RelayFormat SendResponseCount int - FinalPreConsumedQuota int // 最终预消耗的配额 + FinalPreConsumedQuota int // 最终预消耗的配额 // BillingSource indicates whether this request is billed from wallet quota or subscription. // "" or "wallet" => wallet; "subscription" => subscription BillingSource string @@ -130,10 +130,10 @@ type RelayInfo struct { SubscriptionPlanId int SubscriptionPlanTitle string // SubscriptionAmountTotal / SubscriptionAmountUsedAfterPreConsume are used to compute remaining in logs. - SubscriptionAmountTotal int64 + SubscriptionAmountTotal int64 SubscriptionAmountUsedAfterPreConsume int64 - IsClaudeBetaQuery bool // /v1/messages?beta=true - IsChannelTest bool // channel test request + IsClaudeBetaQuery bool // /v1/messages?beta=true + IsChannelTest bool // channel test request PriceData types.PriceData diff --git a/service/billing.go b/service/billing.go index 7a7be40fc..84b329f72 100644 --- a/service/billing.go +++ b/service/billing.go @@ -114,4 +114,3 @@ func PreConsumeBilling(c *gin.Context, preConsumedQuota int, relayInfo *relaycom return nil } } - diff --git a/web/i18next.config.js b/web/i18next.config.js index 4e138bdfc..40808629b 100644 --- a/web/i18next.config.js +++ b/web/i18next.config.js @@ -21,77 +21,66 @@ import { defineConfig } from 'i18next-cli'; /** @type {import('i18next-cli').I18nextToolkitConfig} */ export default defineConfig({ - locales: [ - "zh", - "en", - "fr", - "ru", - "ja", - "vi" - ], + locales: ['zh', 'en', 'fr', 'ru', 'ja', 'vi'], extract: { - input: [ - "src/**/*.{js,jsx,ts,tsx}" - ], - ignore: [ - "src/i18n/**/*" - ], - output: "src/i18n/locales/{{language}}.json", + input: ['src/**/*.{js,jsx,ts,tsx}'], + ignore: ['src/i18n/**/*'], + output: 'src/i18n/locales/{{language}}.json', ignoredAttributes: [ - "accept", - "align", - "aria-label", - "autoComplete", - "className", - "clipRule", - "color", - "crossOrigin", - "data-index", - "data-name", - "data-testid", - "data-type", - "defaultActiveKey", - "direction", - "editorType", - "field", - "fill", - "fillRule", - "height", - "hoverStyle", - "htmlType", - "id", - "itemKey", - "key", - "keyPrefix", - "layout", - "margin", - "maxHeight", - "mode", - "name", - "overflow", - "placement", - "position", - "rel", - "role", - "rowKey", - "searchPosition", - "selectedStyle", - "shape", - "size", - "style", - "theme", - "trigger", - "uploadTrigger", - "validateStatus", - "value", - "viewBox", - "width" + 'accept', + 'align', + 'aria-label', + 'autoComplete', + 'className', + 'clipRule', + 'color', + 'crossOrigin', + 'data-index', + 'data-name', + 'data-testid', + 'data-type', + 'defaultActiveKey', + 'direction', + 'editorType', + 'field', + 'fill', + 'fillRule', + 'height', + 'hoverStyle', + 'htmlType', + 'id', + 'itemKey', + 'key', + 'keyPrefix', + 'layout', + 'margin', + 'maxHeight', + 'mode', + 'name', + 'overflow', + 'placement', + 'position', + 'rel', + 'role', + 'rowKey', + 'searchPosition', + 'selectedStyle', + 'shape', + 'size', + 'style', + 'theme', + 'trigger', + 'uploadTrigger', + 'validateStatus', + 'value', + 'viewBox', + 'width', ], sort: true, disablePlurals: false, removeUnusedKeys: false, nsSeparator: false, keySeparator: false, - mergeNamespaces: true - } -}); \ No newline at end of file + mergeNamespaces: true, + }, +}); diff --git a/web/src/components/auth/LoginForm.jsx b/web/src/components/auth/LoginForm.jsx index 5111f1f68..134451ec3 100644 --- a/web/src/components/auth/LoginForm.jsx +++ b/web/src/components/auth/LoginForm.jsx @@ -39,7 +39,15 @@ import { isPasskeySupported, } from '../../helpers'; import Turnstile from 'react-turnstile'; -import { Button, Card, Checkbox, Divider, Form, Icon, Modal } from '@douyinfe/semi-ui'; +import { + Button, + Card, + Checkbox, + Divider, + Form, + Icon, + Modal, +} from '@douyinfe/semi-ui'; import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import TelegramLoginButton from 'react-telegram-login'; @@ -55,7 +63,7 @@ import WeChatIcon from '../common/logo/WeChatIcon'; import LinuxDoIcon from '../common/logo/LinuxDoIcon'; import TwoFAVerification from './TwoFAVerification'; import { useTranslation } from 'react-i18next'; -import { SiDiscord }from 'react-icons/si'; +import { SiDiscord } from 'react-icons/si'; const LoginForm = () => { let navigate = useNavigate(); @@ -126,7 +134,7 @@ const LoginForm = () => { setTurnstileEnabled(true); setTurnstileSiteKey(status.turnstile_site_key); } - + // 从 status 获取用户协议和隐私政策的启用状态 setHasUserAgreement(status?.user_agreement_enabled || false); setHasPrivacyPolicy(status?.privacy_policy_enabled || false); @@ -514,7 +522,15 @@ const LoginForm = () => { theme='outline' className='w-full h-12 flex items-center justify-center !rounded-full border border-gray-200 hover:bg-gray-50 transition-colors' type='tertiary' - icon={} + icon={ + + } onClick={handleDiscordClick} loading={discordLoading} > @@ -626,11 +642,11 @@ const LoginForm = () => { {t('隐私政策')} - )} - - - - )} + )} + + + + )} {!status.self_use_mode_enabled && (
@@ -746,7 +762,9 @@ const LoginForm = () => { htmlType='submit' onClick={handleSubmit} loading={loginLoading} - disabled={(hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms} + disabled={ + (hasUserAgreement || hasPrivacyPolicy) && !agreedToTerms + } > {t('继续')} diff --git a/web/src/components/common/DocumentRenderer/index.jsx b/web/src/components/common/DocumentRenderer/index.jsx index 383afc11d..68e868c51 100644 --- a/web/src/components/common/DocumentRenderer/index.jsx +++ b/web/src/components/common/DocumentRenderer/index.jsx @@ -41,7 +41,7 @@ const isUrl = (content) => { // 检查是否为 HTML 内容 const isHtmlContent = (content) => { if (!content || typeof content !== 'string') return false; - + // 检查是否包含HTML标签 const htmlTagRegex = /<\/?[a-z][\s\S]*>/i; return htmlTagRegex.test(content); @@ -52,16 +52,16 @@ const sanitizeHtml = (html) => { // 创建一个临时元素来解析HTML const tempDiv = document.createElement('div'); tempDiv.innerHTML = html; - + // 提取样式 const styles = Array.from(tempDiv.querySelectorAll('style')) - .map(style => style.innerHTML) + .map((style) => style.innerHTML) .join('\n'); - + // 提取body内容,如果没有body标签则使用全部内容 const bodyContent = tempDiv.querySelector('body'); const content = bodyContent ? bodyContent.innerHTML : html; - + return { content, styles }; }; @@ -129,7 +129,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => { // 处理HTML样式注入 useEffect(() => { const styleId = `document-renderer-styles-${cacheKey}`; - + if (htmlStyles) { let styleEl = document.getElementById(styleId); if (!styleEl) { @@ -165,8 +165,12 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
} - darkModeImage={} + image={ + + } + darkModeImage={ + + } className='p-8' />
@@ -179,7 +183,9 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
- {title} + + {title} +

{t('管理员设置了外部链接,点击下方按钮访问')}

@@ -202,20 +208,22 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => { // 如果是 HTML 内容,直接渲染 if (isHtmlContent(content)) { const { content: htmlContent, styles } = sanitizeHtml(content); - + // 设置样式(如果有的话) useEffect(() => { if (styles && styles !== htmlStyles) { setHtmlStyles(styles); } }, [content, styles, htmlStyles]); - + return (
- {title} -
+ {title} + +
@@ -230,7 +238,9 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
- {title} + + {title} +
@@ -240,4 +250,4 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => { ); }; -export default DocumentRenderer; \ No newline at end of file +export default DocumentRenderer; diff --git a/web/src/components/layout/SiderBar.jsx b/web/src/components/layout/SiderBar.jsx index d544408a7..34971c397 100644 --- a/web/src/components/layout/SiderBar.jsx +++ b/web/src/components/layout/SiderBar.jsx @@ -51,7 +51,7 @@ const routerMap = { personal: '/console/personal', }; -const SiderBar = ({ onNavigate = () => { } }) => { +const SiderBar = ({ onNavigate = () => {} }) => { const { t } = useTranslation(); const [collapsed, toggleCollapsed] = useSidebarCollapsed(); const { diff --git a/web/src/components/layout/components/SkeletonWrapper.jsx b/web/src/components/layout/components/SkeletonWrapper.jsx index eec4be9a7..3d4981668 100644 --- a/web/src/components/layout/components/SkeletonWrapper.jsx +++ b/web/src/components/layout/components/SkeletonWrapper.jsx @@ -136,9 +136,7 @@ const SkeletonWrapper = ({ loading={true} active placeholder={ - + } />
@@ -186,7 +184,9 @@ const SkeletonWrapper = ({ loading={true} active placeholder={ - + } />
@@ -221,9 +221,7 @@ const SkeletonWrapper = ({ loading={true} active placeholder={ - + } /> ); diff --git a/web/src/components/playground/CustomInputRender.jsx b/web/src/components/playground/CustomInputRender.jsx index f83d6bcbf..c995773a7 100644 --- a/web/src/components/playground/CustomInputRender.jsx +++ b/web/src/components/playground/CustomInputRender.jsx @@ -30,64 +30,67 @@ const CustomInputRender = (props) => { detailProps; const containerRef = useRef(null); - const handlePaste = useCallback(async (e) => { - const items = e.clipboardData?.items; - if (!items) return; + const handlePaste = useCallback( + async (e) => { + const items = e.clipboardData?.items; + if (!items) return; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - - if (item.type.indexOf('image') !== -1) { - e.preventDefault(); - const file = item.getAsFile(); - - if (file) { - try { - if (!imageEnabled) { - Toast.warning({ - content: t('请先在设置中启用图片功能'), - duration: 3, - }); - return; - } + for (let i = 0; i < items.length; i++) { + const item = items[i]; - const reader = new FileReader(); - reader.onload = (event) => { - const base64 = event.target.result; - - if (onPasteImage) { - onPasteImage(base64); - Toast.success({ - content: t('图片已添加'), - duration: 2, - }); - } else { - Toast.error({ - content: t('无法添加图片'), - duration: 2, + if (item.type.indexOf('image') !== -1) { + e.preventDefault(); + const file = item.getAsFile(); + + if (file) { + try { + if (!imageEnabled) { + Toast.warning({ + content: t('请先在设置中启用图片功能'), + duration: 3, }); + return; } - }; - reader.onerror = () => { - console.error('Failed to read image file:', reader.error); + + const reader = new FileReader(); + reader.onload = (event) => { + const base64 = event.target.result; + + if (onPasteImage) { + onPasteImage(base64); + Toast.success({ + content: t('图片已添加'), + duration: 2, + }); + } else { + Toast.error({ + content: t('无法添加图片'), + duration: 2, + }); + } + }; + reader.onerror = () => { + console.error('Failed to read image file:', reader.error); + Toast.error({ + content: t('粘贴图片失败'), + duration: 2, + }); + }; + reader.readAsDataURL(file); + } catch (error) { + console.error('Failed to paste image:', error); Toast.error({ content: t('粘贴图片失败'), duration: 2, }); - }; - reader.readAsDataURL(file); - } catch (error) { - console.error('Failed to paste image:', error); - Toast.error({ - content: t('粘贴图片失败'), - duration: 2, - }); + } } + break; } - break; } - } - }, [onPasteImage, imageEnabled, t]); + }, + [onPasteImage, imageEnabled, t], + ); useEffect(() => { const container = containerRef.current; diff --git a/web/src/components/playground/CustomRequestEditor.jsx b/web/src/components/playground/CustomRequestEditor.jsx index 786ccdc4a..68f0ae871 100644 --- a/web/src/components/playground/CustomRequestEditor.jsx +++ b/web/src/components/playground/CustomRequestEditor.jsx @@ -140,7 +140,9 @@ const CustomRequestEditor = ({ {/* 提示信息 */} } className='!rounded-lg' closeIcon={null} @@ -201,7 +203,9 @@ const CustomRequestEditor = ({ )} - {t('请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。')} + {t( + '请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。', + )}
diff --git a/web/src/components/playground/DebugPanel.jsx b/web/src/components/playground/DebugPanel.jsx index ee4a0b60d..28de0153b 100644 --- a/web/src/components/playground/DebugPanel.jsx +++ b/web/src/components/playground/DebugPanel.jsx @@ -191,10 +191,7 @@ const DebugPanel = ({ itemKey='response' > {debugData.sseMessages && debugData.sseMessages.length > 0 ? ( - + ) : ( { const stats = useMemo(() => { const total = parsedSSEData.length; - const errors = parsedSSEData.filter(item => item.error).length; - const done = parsedSSEData.filter(item => item.isDone).length; + const errors = parsedSSEData.filter((item) => item.error).length; + const done = parsedSSEData.filter((item) => item.isDone).length; const valid = total - errors - done; return { total, errors, done, valid }; }, [parsedSSEData]); const handleToggleAll = useCallback(() => { - setExpandedKeys(prev => { + setExpandedKeys((prev) => { if (prev.length === parsedSSEData.length) { return []; } else { - return parsedSSEData.map(item => item.key); + return parsedSSEData.map((item) => item.key); } }); }, [parsedSSEData]); @@ -87,7 +101,9 @@ const SSEViewer = ({ sseData }) => { const handleCopyAll = useCallback(async () => { try { const allData = parsedSSEData - .map(item => (item.parsed ? JSON.stringify(item.parsed, null, 2) : item.raw)) + .map((item) => + item.parsed ? JSON.stringify(item.parsed, null, 2) : item.raw, + ) .join('\n\n'); await copy(allData); @@ -100,15 +116,20 @@ const SSEViewer = ({ sseData }) => { } }, [parsedSSEData, t]); - const handleCopySingle = useCallback(async (item) => { - try { - const textToCopy = item.parsed ? JSON.stringify(item.parsed, null, 2) : item.raw; - await copy(textToCopy); - Toast.success(t('已复制')); - } catch (err) { - Toast.error(t('复制失败')); - } - }, [t]); + const handleCopySingle = useCallback( + async (item) => { + try { + const textToCopy = item.parsed + ? JSON.stringify(item.parsed, null, 2) + : item.raw; + await copy(textToCopy); + Toast.success(t('已复制')); + } catch (err) { + Toast.error(t('复制失败')); + } + }, + [t], + ); const renderSSEItem = (item) => { if (item.isDone) { @@ -158,18 +179,24 @@ const SSEViewer = ({ sseData }) => { {item.parsed?.choices?.[0] && (
{item.parsed.choices[0].delta?.content && ( - + )} {item.parsed.choices[0].delta?.reasoning_content && ( )} {item.parsed.choices[0].finish_reason && ( - + )} {item.parsed.usage && ( - )}
@@ -194,7 +221,9 @@ const SSEViewer = ({ sseData }) => { {t('SSE数据流')} - {stats.errors > 0 && } + {stats.errors > 0 && ( + + )}
@@ -208,14 +237,28 @@ const SSEViewer = ({ sseData }) => { {copied ? t('已复制') : t('复制全部')} - +
@@ -242,11 +285,16 @@ const SSEViewer = ({ sseData }) => { ) : ( <> - {item.parsed?.id || item.parsed?.object || t('SSE 事件')} + {item.parsed?.id || + item.parsed?.object || + t('SSE 事件')} {item.parsed?.choices?.[0]?.delta && ( - • {Object.keys(item.parsed.choices[0].delta).filter(k => item.parsed.choices[0].delta[k]).join(', ')} + •{' '} + {Object.keys(item.parsed.choices[0].delta) + .filter((k) => item.parsed.choices[0].delta[k]) + .join(', ')} )} diff --git a/web/src/components/settings/HttpStatusCodeRulesInput.jsx b/web/src/components/settings/HttpStatusCodeRulesInput.jsx index 361bc19e6..5faf93af9 100644 --- a/web/src/components/settings/HttpStatusCodeRulesInput.jsx +++ b/web/src/components/settings/HttpStatusCodeRulesInput.jsx @@ -68,4 +68,3 @@ export default function HttpStatusCodeRulesInput(props) { ); } - diff --git a/web/src/components/settings/ModelDeploymentSetting.jsx b/web/src/components/settings/ModelDeploymentSetting.jsx index 941f640a4..c872f631d 100644 --- a/web/src/components/settings/ModelDeploymentSetting.jsx +++ b/web/src/components/settings/ModelDeploymentSetting.jsx @@ -40,7 +40,7 @@ const ModelDeploymentSetting = () => { 'model_deployment.ionet.api_key': '', 'model_deployment.ionet.enabled': false, }; - + data.forEach((item) => { if (item.key.endsWith('Enabled') || item.key.endsWith('enabled')) { newInputs[item.key] = toBoolean(item.value); @@ -82,4 +82,4 @@ const ModelDeploymentSetting = () => { ); }; -export default ModelDeploymentSetting; \ No newline at end of file +export default ModelDeploymentSetting; diff --git a/web/src/components/settings/OperationSetting.jsx b/web/src/components/settings/OperationSetting.jsx index 9ee5fd007..171c29f26 100644 --- a/web/src/components/settings/OperationSetting.jsx +++ b/web/src/components/settings/OperationSetting.jsx @@ -71,7 +71,8 @@ const OperationSetting = () => { AutomaticEnableChannelEnabled: false, AutomaticDisableKeywords: '', AutomaticDisableStatusCodes: '401', - AutomaticRetryStatusCodes: '100-199,300-399,401-407,409-499,500-503,505-523,525-599', + AutomaticRetryStatusCodes: + '100-199,300-399,401-407,409-499,500-503,505-523,525-599', 'monitor_setting.auto_test_channel_enabled': false, 'monitor_setting.auto_test_channel_minutes': 10 /* 签到设置 */, 'checkin_setting.enabled': false, diff --git a/web/src/components/settings/OtherSetting.jsx b/web/src/components/settings/OtherSetting.jsx index 646d21e74..f8e0b5375 100644 --- a/web/src/components/settings/OtherSetting.jsx +++ b/web/src/components/settings/OtherSetting.jsx @@ -378,13 +378,15 @@ const OtherSetting = () => { - @@ -141,7 +160,9 @@ const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => { /> - {t('说明:生成结果是可直接粘贴到渠道密钥里的 JSON(包含 access_token / refresh_token / account_id)。')} + {t( + '说明:生成结果是可直接粘贴到渠道密钥里的 JSON(包含 access_token / refresh_token / account_id)。', + )} diff --git a/web/src/components/table/channels/modals/CodexUsageModal.jsx b/web/src/components/table/channels/modals/CodexUsageModal.jsx index 16ad07610..5e1317ac6 100644 --- a/web/src/components/table/channels/modals/CodexUsageModal.jsx +++ b/web/src/components/table/channels/modals/CodexUsageModal.jsx @@ -18,7 +18,14 @@ For commercial licensing, please contact support@quantumnous.com */ import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { Modal, Button, Progress, Tag, Typography, Spin } from '@douyinfe/semi-ui'; +import { + Modal, + Button, + Progress, + Tag, + Typography, + Spin, +} from '@douyinfe/semi-ui'; import { API, showError } from '../../../../helpers'; const { Text } = Typography; @@ -134,7 +141,12 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
{statusTag} -
@@ -243,7 +255,12 @@ const CodexUsageLoader = ({ t, record, initialPayload, onCopy }) => {
{tt('获取用量失败')}
-
diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 683362c32..141cd5626 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -2000,171 +2000,180 @@ const EditChannelModal = (props) => { autoComplete='new-password' onChange={(value) => handleInputChange('key', value)} disabled={isIonetLocked} - extraText={ -
- {isEdit && - isMultiKeyChannel && - keyMode === 'append' && ( - - {t( - '追加模式:新密钥将添加到现有密钥列表的末尾', - )} - + extraText={ +
+ {isEdit && + isMultiKeyChannel && + keyMode === 'append' && ( + + {t( + '追加模式:新密钥将添加到现有密钥列表的末尾', + )} + + )} + {isEdit && ( + )} - {isEdit && ( - - )} - {batchExtra} -
- } - showClear - /> - ) - ) : ( - <> - {inputs.type === 57 ? ( - <> - handleInputChange('key', value)} - disabled={isIonetLocked} - extraText={ -
- - {t( - '仅支持 JSON 对象,必须包含 access_token 与 account_id', - )} - + {batchExtra} +
+ } + showClear + /> + ) + ) : ( + <> + {inputs.type === 57 ? ( + <> + + handleInputChange('key', value) + } + disabled={isIonetLocked} + extraText={ +
+ + {t( + '仅支持 JSON 对象,必须包含 access_token 与 account_id', + )} + - + + + {isEdit && ( + + )} + + {isEdit && ( + + )} + {batchExtra} + +
+ } + autosize + showClear + /> + + setCodexOAuthModalVisible(false)} + onSuccess={handleCodexOAuthGenerated} + /> + + ) : inputs.type === 41 && + (inputs.vertex_key_type || 'json') === 'json' ? ( + <> + {!batch && ( +
+ + {t('密钥输入方式')} + + - {isEdit && ( - - )} - {isEdit && ( - - )} - {batchExtra}
- } - autosize - showClear - /> - - setCodexOAuthModalVisible(false)} - onSuccess={handleCodexOAuthGenerated} - /> - - ) : inputs.type === 41 && - (inputs.vertex_key_type || 'json') === 'json' ? ( - <> - {!batch && ( -
- - {t('密钥输入方式')} - - - - - -
- )} + )} {batch && ( { {/* Header: Advanced Settings */}
- +
@@ -549,9 +553,7 @@ const EditTagModal = (props) => { field='param_override' label={t('参数覆盖')} placeholder={ - t( - '此项可选,用于覆盖请求参数。不支持覆盖 stream 参数', - ) + + t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数') + '\n' + t('旧格式(直接覆盖):') + '\n{\n "temperature": 0,\n "max_tokens": 1000\n}' + diff --git a/web/src/components/table/channels/modals/ModelSelectModal.jsx b/web/src/components/table/channels/modals/ModelSelectModal.jsx index eda7f80b5..b38580b66 100644 --- a/web/src/components/table/channels/modals/ModelSelectModal.jsx +++ b/web/src/components/table/channels/modals/ModelSelectModal.jsx @@ -104,7 +104,9 @@ const ModelSelectModal = ({ }, [normalizedRedirectModels, normalizedSelectedSet]); const filteredModels = models.filter((m) => - String(m || '').toLowerCase().includes(keyword.toLowerCase()), + String(m || '') + .toLowerCase() + .includes(keyword.toLowerCase()), ); // 分类模型:新获取的模型和已有模型 diff --git a/web/src/components/table/model-deployments/modals/ConfirmationDialog.jsx b/web/src/components/table/model-deployments/modals/ConfirmationDialog.jsx index f462292a3..5e90b153b 100644 --- a/web/src/components/table/model-deployments/modals/ConfirmationDialog.jsx +++ b/web/src/components/table/model-deployments/modals/ConfirmationDialog.jsx @@ -30,7 +30,7 @@ const ConfirmationDialog = ({ type = 'danger', deployment, t, - loading = false + loading = false, }) => { const [confirmText, setConfirmText] = useState(''); @@ -66,17 +66,17 @@ const ConfirmationDialog = ({ okButtonProps={{ disabled: !isConfirmed, type: type === 'danger' ? 'danger' : 'primary', - loading + loading, }} width={480} > -
- +
+ {t('此操作具有风险,请确认要继续执行')}。 {t('请输入部署名称以完成二次确认')}: - + {requiredText || t('未知部署')} @@ -87,7 +87,7 @@ const ConfirmationDialog = ({ autoFocus /> {!isConfirmed && confirmText && ( - + {t('部署名称不匹配,请检查后重新输入')} )} diff --git a/web/src/components/table/model-deployments/modals/ExtendDurationModal.jsx b/web/src/components/table/model-deployments/modals/ExtendDurationModal.jsx index 3b357bc94..e4e9b7bb9 100644 --- a/web/src/components/table/model-deployments/modals/ExtendDurationModal.jsx +++ b/web/src/components/table/model-deployments/modals/ExtendDurationModal.jsx @@ -130,9 +130,7 @@ const ExtendDurationModal = ({ ? details.locations .map((location) => Number( - location?.id ?? - location?.location_id ?? - location?.locationId, + location?.id ?? location?.location_id ?? location?.locationId, ), ) .filter((id) => Number.isInteger(id) && id > 0) @@ -181,9 +179,7 @@ const ExtendDurationModal = ({ } else { const message = response.data.message || ''; setPriceEstimation(null); - setPriceError( - t('价格计算失败') + (message ? `: ${message}` : ''), - ); + setPriceError(t('价格计算失败') + (message ? `: ${message}` : '')); } } catch (error) { if (costRequestIdRef.current !== requestId) { @@ -192,9 +188,7 @@ const ExtendDurationModal = ({ const message = error?.response?.data?.message || error.message || ''; setPriceEstimation(null); - setPriceError( - t('价格计算失败') + (message ? `: ${message}` : ''), - ); + setPriceError(t('价格计算失败') + (message ? `: ${message}` : '')); } finally { if (costRequestIdRef.current === requestId) { setCostLoading(false); @@ -269,11 +263,8 @@ const ExtendDurationModal = ({ const newTotalTime = `${currentRemainingTime} + ${durationHours}${t('小时')}`; const priceData = priceEstimation || {}; - const breakdown = - priceData.price_breakdown || priceData.PriceBreakdown || {}; - const currencyLabel = ( - priceData.currency || priceData.Currency || 'USDC' - ) + const breakdown = priceData.price_breakdown || priceData.PriceBreakdown || {}; + const currencyLabel = (priceData.currency || priceData.Currency || 'USDC') .toString() .toUpperCase(); @@ -316,7 +307,10 @@ const ExtendDurationModal = ({ confirmLoading={loading} okButtonProps={{ disabled: - !deployment?.id || detailsLoading || !durationHours || durationHours < 1, + !deployment?.id || + detailsLoading || + !durationHours || + durationHours < 1, }} width={600} className='extend-duration-modal' @@ -357,9 +351,7 @@ const ExtendDurationModal = ({

{t('延长容器时长将会产生额外费用,请确认您有足够的账户余额。')}

-

- {t('延长操作一旦确认无法撤销,费用将立即扣除。')} -

+

{t('延长操作一旦确认无法撤销,费用将立即扣除。')}

} /> @@ -370,7 +362,9 @@ const ExtendDurationModal = ({ onValueChange={(values) => { if (values.duration_hours !== undefined) { const numericValue = Number(values.duration_hours); - setDurationHours(Number.isFinite(numericValue) ? numericValue : 0); + setDurationHours( + Number.isFinite(numericValue) ? numericValue : 0, + ); } }} > diff --git a/web/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx b/web/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx index ee27a690c..3a430c94c 100644 --- a/web/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx +++ b/web/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx @@ -81,7 +81,9 @@ const renderModels = (text, record, t) => {
))} {items.length > 3 && ( -
...{t('共')} {items.length} {t('个模型')}
+
+ ...{t('共')} {items.length} {t('个模型')} +
)}
); diff --git a/web/src/components/table/subscriptions/SubscriptionsTable.jsx b/web/src/components/table/subscriptions/SubscriptionsTable.jsx index ad6bfa6b5..cc54b9dc0 100644 --- a/web/src/components/table/subscriptions/SubscriptionsTable.jsx +++ b/web/src/components/table/subscriptions/SubscriptionsTable.jsx @@ -27,14 +27,8 @@ import { import { getSubscriptionsColumns } from './SubscriptionsColumnDefs'; const SubscriptionsTable = (subscriptionsData) => { - const { - plans, - loading, - compactMode, - openEdit, - disablePlan, - t, - } = subscriptionsData; + const { plans, loading, compactMode, openEdit, disablePlan, t } = + subscriptionsData; const columns = useMemo(() => { return getSubscriptionsColumns({ @@ -47,12 +41,12 @@ const SubscriptionsTable = (subscriptionsData) => { const tableColumns = useMemo(() => { return compactMode ? columns.map((col) => { - if (col.dataIndex === 'operate') { - const { fixed, ...rest } = col; - return rest; - } - return col; - }) + if (col.dataIndex === 'operate') { + const { fixed, ...rest } = col; + return rest; + } + return col; + }) : columns; }, [compactMode, columns]); diff --git a/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx b/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx index 6d2f8eb2a..cb0f24565 100644 --- a/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx +++ b/web/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx @@ -169,7 +169,10 @@ const AddEditSubscriptionModal = ({ next.push({ model_name: modelName, quota_type: modelMeta.quota_type, - amount_total: Number.isFinite(defaultAmount) && defaultAmount >= 0 ? defaultAmount : 0, + amount_total: + Number.isFinite(defaultAmount) && defaultAmount >= 0 + ? defaultAmount + : 0, }); }); setItems(next); @@ -216,7 +219,9 @@ const AddEditSubscriptionModal = ({ return; } const keySet = new Set(selectedRowKeys); - const next = (items || []).filter((it) => !keySet.has(`${it.model_name}-${it.quota_type}`)); + const next = (items || []).filter( + (it) => !keySet.has(`${it.model_name}-${it.quota_type}`), + ); setItems(next); setSelectedRowKeys([]); showSuccess(t('已删除选中项')); @@ -417,7 +422,9 @@ const AddEditSubscriptionModal = ({ field='title' label={t('套餐标题')} placeholder={t('例如:基础套餐')} - rules={[{ required: true, message: t('请输入套餐标题') }]} + rules={[ + { required: true, message: t('请输入套餐标题') }, + ]} showClear /> @@ -585,7 +592,9 @@ const AddEditSubscriptionModal = ({
- {t('模型权益')} + + {t('模型权益')} +
{t('配置套餐可使用的模型及额度')}
@@ -646,7 +655,9 @@ const AddEditSubscriptionModal = ({ diff --git a/web/src/components/table/tokens/modals/EditTokenModal.jsx b/web/src/components/table/tokens/modals/EditTokenModal.jsx index cc9f51b0e..fce482014 100644 --- a/web/src/components/table/tokens/modals/EditTokenModal.jsx +++ b/web/src/components/table/tokens/modals/EditTokenModal.jsx @@ -378,7 +378,12 @@ const EditTokenModal = (props) => { /> )} - + { placeholder={t('允许的IP,一行一个,不填写则不限制')} autosize rows={1} - extraText={t('请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用')} + extraText={t( + '请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用', + )} showClear style={{ width: '100%' }} /> diff --git a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx index f28d06a1b..96f2214a6 100644 --- a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx +++ b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx @@ -203,7 +203,7 @@ function renderModelName(record, copyText, t) { if (!modelMapped) { return renderModelTag(record.model_name, { onClick: (event) => { - copyText(event, record.model_name).then((r) => { }); + copyText(event, record.model_name).then((r) => {}); }, }); } else { @@ -220,7 +220,7 @@ function renderModelName(record, copyText, t) { {renderModelTag(record.model_name, { onClick: (event) => { - copyText(event, record.model_name).then((r) => { }); + copyText(event, record.model_name).then((r) => {}); }, })}
@@ -231,7 +231,7 @@ function renderModelName(record, copyText, t) { {renderModelTag(other.upstream_model_name, { onClick: (event) => { copyText(event, other.upstream_model_name).then( - (r) => { }, + (r) => {}, ); }, })} @@ -242,7 +242,7 @@ function renderModelName(record, copyText, t) { > {renderModelTag(record.model_name, { onClick: (event) => { - copyText(event, record.model_name).then((r) => { }); + copyText(event, record.model_name).then((r) => {}); }, suffixIcon: ( - + {t('优选')} @@ -653,45 +657,45 @@ export const getLogsColumns = ({ let content = other?.claude ? renderModelPriceSimple( - other.model_ratio, - other.model_price, - other.group_ratio, - other?.user_group_ratio, - other.cache_tokens || 0, - other.cache_ratio || 1.0, - other.cache_creation_tokens || 0, - other.cache_creation_ratio || 1.0, - other.cache_creation_tokens_5m || 0, - other.cache_creation_ratio_5m || - other.cache_creation_ratio || - 1.0, - other.cache_creation_tokens_1h || 0, - other.cache_creation_ratio_1h || - other.cache_creation_ratio || - 1.0, - false, - 1.0, - other?.is_system_prompt_overwritten, - 'claude', - ) + other.model_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + other.cache_creation_tokens || 0, + other.cache_creation_ratio || 1.0, + other.cache_creation_tokens_5m || 0, + other.cache_creation_ratio_5m || + other.cache_creation_ratio || + 1.0, + other.cache_creation_tokens_1h || 0, + other.cache_creation_ratio_1h || + other.cache_creation_ratio || + 1.0, + false, + 1.0, + other?.is_system_prompt_overwritten, + 'claude', + ) : renderModelPriceSimple( - other.model_ratio, - other.model_price, - other.group_ratio, - other?.user_group_ratio, - other.cache_tokens || 0, - other.cache_ratio || 1.0, - 0, - 1.0, - 0, - 1.0, - 0, - 1.0, - false, - 1.0, - other?.is_system_prompt_overwritten, - 'openai', - ); + other.model_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + 0, + 1.0, + 0, + 1.0, + 0, + 1.0, + false, + 1.0, + other?.is_system_prompt_overwritten, + 'openai', + ); // Do not add billing source here; keep details clean. const summary = [content, text ? `${t('详情')}:${text}` : null] .filter(Boolean) diff --git a/web/src/components/table/users/modals/BindSubscriptionModal.jsx b/web/src/components/table/users/modals/BindSubscriptionModal.jsx index cff91e4fd..baa71d1fb 100644 --- a/web/src/components/table/users/modals/BindSubscriptionModal.jsx +++ b/web/src/components/table/users/modals/BindSubscriptionModal.jsx @@ -121,4 +121,3 @@ const BindSubscriptionModal = ({ visible, onCancel, user, t, onSuccess }) => { }; export default BindSubscriptionModal; - diff --git a/web/src/components/table/users/modals/UserSubscriptionsModal.jsx b/web/src/components/table/users/modals/UserSubscriptionsModal.jsx index 0b47c8b44..da4c881b3 100644 --- a/web/src/components/table/users/modals/UserSubscriptionsModal.jsx +++ b/web/src/components/table/users/modals/UserSubscriptionsModal.jsx @@ -129,7 +129,9 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { if (!user?.id) return; setLoading(true); try { - const res = await API.get(`/api/subscription/admin/users/${user.id}/subscriptions`); + const res = await API.get( + `/api/subscription/admin/users/${user.id}/subscriptions`, + ); if (res.data?.success) { const next = res.data.data || []; setSubs(next); @@ -167,9 +169,12 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { } setCreating(true); try { - const res = await API.post(`/api/subscription/admin/users/${user.id}/subscriptions`, { - plan_id: selectedPlanId, - }); + const res = await API.post( + `/api/subscription/admin/users/${user.id}/subscriptions`, + { + plan_id: selectedPlanId, + }, + ); if (res.data?.success) { showSuccess(t('新增成功')); setSelectedPlanId(null); @@ -217,7 +222,9 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { okType: 'danger', onOk: async () => { try { - const res = await API.delete(`/api/subscription/admin/user_subscriptions/${subId}`); + const res = await API.delete( + `/api/subscription/admin/user_subscriptions/${subId}`, + ); if (res.data?.success) { showSuccess(t('已删除')); await loadUserSubscriptions(); @@ -247,7 +254,8 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { render: (_, record) => { const sub = record?.subscription; const planId = sub?.plan_id; - const title = planTitleMap.get(planId) || (planId ? `#${planId}` : '-'); + const title = + planTitleMap.get(planId) || (planId ? `#${planId}` : '-'); return (
{title}
@@ -292,7 +300,10 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { const content = (
{items.map((it) => ( -
+
{it.model_name} {it.amount_used}/{it.amount_total} @@ -319,7 +330,8 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { render: (_, record) => { const sub = record?.subscription; const now = Date.now() / 1000; - const isExpired = (sub?.end_time || 0) > 0 && (sub?.end_time || 0) < now; + const isExpired = + (sub?.end_time || 0) > 0 && (sub?.end_time || 0) < now; const isActive = sub?.status === 'active' && !isExpired; const isCancelled = sub?.status === 'cancelled'; return ( @@ -412,7 +424,9 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { }} empty={ } + image={ + + } darkModeImage={ } @@ -428,4 +442,3 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => { }; export default UserSubscriptionsModal; - diff --git a/web/src/components/topup/RechargeCard.jsx b/web/src/components/topup/RechargeCard.jsx index 15c37dffb..264c965b7 100644 --- a/web/src/components/topup/RechargeCard.jsx +++ b/web/src/components/topup/RechargeCard.jsx @@ -87,7 +87,12 @@ const RechargeCard = ({ const onlineFormApiRef = useRef(null); const redeemFormApiRef = useRef(null); const showAmountSkeleton = useMinimumLoadingTime(amountLoading); - console.log(' enabled screem ?', enableCreemTopUp, ' products ?', creemProducts); + console.log( + ' enabled screem ?', + enableCreemTopUp, + ' products ?', + creemProducts, + ); return ( {/* 卡片头部 */} @@ -503,7 +508,8 @@ const RechargeCard = ({ {t('充值额度')}: {product.quota}
- {product.currency === 'EUR' ? '€' : '$'}{product.price} + {product.currency === 'EUR' ? '€' : '$'} + {product.price}
))} diff --git a/web/src/components/topup/SubscriptionPlansCard.jsx b/web/src/components/topup/SubscriptionPlansCard.jsx index 11b4950e7..0395cffb9 100644 --- a/web/src/components/topup/SubscriptionPlansCard.jsx +++ b/web/src/components/topup/SubscriptionPlansCard.jsx @@ -240,9 +240,7 @@ const SubscriptionPlansCard = ({
- - {t('订阅套餐')} - + {t('订阅套餐')}
{t('购买订阅获得模型额度/次数')}
@@ -276,13 +274,27 @@ const SubscriptionPlansCard = ({
{[1, 2, 3].map((i) => ( - - + +
- +
- +
))}
@@ -299,18 +311,26 @@ const SubscriptionPlansCard = ({ {activeSubscriptions.length} {t('个生效中')} ) : ( - {t('无生效')} + + {t('无生效')} + )} {allSubscriptions.length > activeSubscriptions.length && ( - {allSubscriptions.length - activeSubscriptions.length} {t('个已过期')} + {allSubscriptions.length - activeSubscriptions.length}{' '} + {t('个已过期')} )}
- {isActive ? t('至') : t('过期于')} {new Date((subscription?.end_time || 0) * 1000).toLocaleString()} + {isActive ? t('至') : t('过期于')}{' '} + {new Date( + (subscription?.end_time || 0) * 1000, + ).toLocaleString()}
{/* 权益列表 */} {items.length > 0 && ( @@ -360,23 +389,36 @@ const SubscriptionPlansCard = ({ const used = Number(it.amount_used || 0); const total = Number(it.amount_total || 0); const remain = total - used; - const percent = total > 0 ? Math.round((used / total) * 100) : 0; + const percent = + total > 0 ? Math.round((used / total) * 100) : 0; const label = it.quota_type === 1 ? t('次') : ''; return ( 80 ? 'red' : 'blue') : 'grey'} + color={ + isActive + ? percent > 80 + ? 'red' + : 'blue' + : 'grey' + } type='light' shape='circle' > - {it.model_name}: {remain}{label} + {it.model_name}: {remain} + {label} ); })} {items.length > 4 && ( - + +{items.length - 4} )} @@ -406,8 +448,9 @@ const SubscriptionPlansCard = ({ return (
@@ -462,16 +505,20 @@ const SubscriptionPlansCard = ({ {/* 权益列表 */}
{planItems.slice(0, 5).map((it, idx) => ( -
+
- {it.model_name} - + + {it.model_name} + + {it.amount_total} {it.quota_type === 1 ? t('次') : ''} @@ -495,7 +542,9 @@ const SubscriptionPlansCard = ({ type='primary' block onClick={() => openBuy(p)} - className={isPopular ? '!bg-purple-600 hover:!bg-purple-700' : ''} + className={ + isPopular ? '!bg-purple-600 hover:!bg-purple-700' : '' + } > {t('立即订阅')} @@ -534,4 +583,3 @@ const SubscriptionPlansCard = ({ }; export default SubscriptionPlansCard; - diff --git a/web/src/components/topup/index.jsx b/web/src/components/topup/index.jsx index 0cfc431af..04ef8935a 100644 --- a/web/src/components/topup/index.jsx +++ b/web/src/components/topup/index.jsx @@ -91,7 +91,8 @@ const TopUp = () => { // 订阅相关 const [subscriptionPlans, setSubscriptionPlans] = useState([]); const [subscriptionLoading, setSubscriptionLoading] = useState(true); - const [billingPreference, setBillingPreference] = useState('subscription_first'); + const [billingPreference, setBillingPreference] = + useState('subscription_first'); const [activeSubscriptions, setActiveSubscriptions] = useState([]); const [allSubscriptions, setAllSubscriptions] = useState([]); @@ -339,7 +340,9 @@ const TopUp = () => { try { const res = await API.get('/api/subscription/self'); if (res.data?.success) { - setBillingPreference(res.data.data?.billing_preference || 'subscription_first'); + setBillingPreference( + res.data.data?.billing_preference || 'subscription_first', + ); // Active subscriptions const activeSubs = res.data.data?.subscriptions || []; setActiveSubscriptions(activeSubs); @@ -708,7 +711,8 @@ const TopUp = () => { {t('产品名称')}:{selectedCreemProduct.name}

- {t('价格')}:{selectedCreemProduct.currency === 'EUR' ? '€' : '$'}{selectedCreemProduct.price} + {t('价格')}:{selectedCreemProduct.currency === 'EUR' ? '€' : '$'} + {selectedCreemProduct.price}

{t('充值额度')}:{selectedCreemProduct.quota} diff --git a/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx b/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx index f81f399bb..a9adcec6e 100644 --- a/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx +++ b/web/src/components/topup/modals/SubscriptionPurchaseModal.jsx @@ -18,7 +18,16 @@ For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; -import { Banner, Modal, Typography, Card, Tag, Button, Select, Divider } from '@douyinfe/semi-ui'; +import { + Banner, + Modal, + Typography, + Card, + Tag, + Button, + Select, + Divider, +} from '@douyinfe/semi-ui'; import { Crown, CalendarClock, Package, Check } from 'lucide-react'; import { SiStripe } from 'react-icons/si'; import { IconCreditCard } from '@douyinfe/semi-icons'; @@ -137,7 +146,8 @@ const SubscriptionPurchaseModal = ({ {t('应付金额')}: - {currency}{price.toFixed(price % 1 === 0 ? 0 : 2)} + {currency} + {price.toFixed(price % 1 === 0 ? 0 : 2)}

@@ -146,12 +156,21 @@ const SubscriptionPurchaseModal = ({ {/* 权益列表 */} {items.length > 0 && (
- {t('权益明细')}: + + {t('权益明细')}: +
{items.slice(0, 6).map((it, idx) => ( - + - {it.model_name}: {it.amount_total}{it.quota_type === 1 ? t('次') : ''} + {it.model_name}: {it.amount_total} + {it.quota_type === 1 ? t('次') : ''} ))} {items.length > 6 && ( @@ -166,7 +185,9 @@ const SubscriptionPurchaseModal = ({ {/* 支付方式 */} {hasAnyPayment ? (
- {t('选择支付方式')}: + + {t('选择支付方式')}: + {/* Stripe / Creem */} {(hasStripe || hasCreem) && ( diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index 6e09bf43c..666d6b7b0 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -236,9 +236,7 @@ async function prepareOAuthState(options = {}) { if (shouldLogout) { try { await API.get('/api/user/logout', { skipErrorHandler: true }); - } catch (err) { - - } + } catch (err) {} localStorage.removeItem('user'); updateAPI(); } diff --git a/web/src/helpers/dashboard.jsx b/web/src/helpers/dashboard.jsx index 8df375f11..d93d04619 100644 --- a/web/src/helpers/dashboard.jsx +++ b/web/src/helpers/dashboard.jsx @@ -261,7 +261,7 @@ export const processRawData = ( }; // 检查数据是否跨年 - const showYear = isDataCrossYear(data.map(item => item.created_at)); + const showYear = isDataCrossYear(data.map((item) => item.created_at)); data.forEach((item) => { result.uniqueModels.add(item.model_name); @@ -269,7 +269,11 @@ export const processRawData = ( result.totalQuota += item.quota; result.totalTimes += item.count; - const timeKey = timestamp2string1(item.created_at, dataExportDefaultTime, showYear); + const timeKey = timestamp2string1( + item.created_at, + dataExportDefaultTime, + showYear, + ); if (!result.timePoints.includes(timeKey)) { result.timePoints.push(timeKey); } @@ -328,10 +332,14 @@ export const aggregateDataByTimeAndModel = (data, dataExportDefaultTime) => { const aggregatedData = new Map(); // 检查数据是否跨年 - const showYear = isDataCrossYear(data.map(item => item.created_at)); + const showYear = isDataCrossYear(data.map((item) => item.created_at)); data.forEach((item) => { - const timeKey = timestamp2string1(item.created_at, dataExportDefaultTime, showYear); + const timeKey = timestamp2string1( + item.created_at, + dataExportDefaultTime, + showYear, + ); const modelKey = item.model_name; const key = `${timeKey}-${modelKey}`; @@ -372,7 +380,7 @@ export const generateChartTimePoints = ( ); const showYear = isDataCrossYear(generatedTimestamps); - chartTimePoints = generatedTimestamps.map(ts => + chartTimePoints = generatedTimestamps.map((ts) => timestamp2string1(ts, dataExportDefaultTime, showYear), ); } diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index d3a7350a9..5c58ff0e4 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -170,21 +170,21 @@ export const getModelCategories = (() => { gemini: { label: 'Gemini', icon: , - filter: (model) => - model.model_name.toLowerCase().includes('gemini') || + filter: (model) => + model.model_name.toLowerCase().includes('gemini') || model.model_name.toLowerCase().includes('gemma') || - model.model_name.toLowerCase().includes('learnlm') || + model.model_name.toLowerCase().includes('learnlm') || model.model_name.toLowerCase().startsWith('embedding-') || model.model_name.toLowerCase().includes('text-embedding-004') || - model.model_name.toLowerCase().includes('imagen-4') || - model.model_name.toLowerCase().includes('veo-') || - model.model_name.toLowerCase().includes('aqa') , + model.model_name.toLowerCase().includes('imagen-4') || + model.model_name.toLowerCase().includes('veo-') || + model.model_name.toLowerCase().includes('aqa'), }, moonshot: { label: 'Moonshot', icon: , - filter: (model) => - model.model_name.toLowerCase().includes('moonshot') || + filter: (model) => + model.model_name.toLowerCase().includes('moonshot') || model.model_name.toLowerCase().includes('kimi'), }, zhipu: { @@ -192,8 +192,8 @@ export const getModelCategories = (() => { icon: , filter: (model) => model.model_name.toLowerCase().includes('chatglm') || - model.model_name.toLowerCase().includes('glm-') || - model.model_name.toLowerCase().includes('cogview') || + model.model_name.toLowerCase().includes('glm-') || + model.model_name.toLowerCase().includes('cogview') || model.model_name.toLowerCase().includes('cogvideo'), }, qwen: { @@ -209,8 +209,8 @@ export const getModelCategories = (() => { minimax: { label: 'MiniMax', icon: , - filter: (model) => - model.model_name.toLowerCase().includes('abab') || + filter: (model) => + model.model_name.toLowerCase().includes('abab') || model.model_name.toLowerCase().includes('minimax'), }, baidu: { @@ -236,7 +236,7 @@ export const getModelCategories = (() => { cohere: { label: 'Cohere', icon: , - filter: (model) => + filter: (model) => model.model_name.toLowerCase().includes('command') || model.model_name.toLowerCase().includes('c4ai-') || model.model_name.toLowerCase().includes('embed-'), @@ -259,7 +259,7 @@ export const getModelCategories = (() => { mistral: { label: 'Mistral AI', icon: , - filter: (model) => + filter: (model) => model.model_name.toLowerCase().includes('mistral') || model.model_name.toLowerCase().includes('codestral') || model.model_name.toLowerCase().includes('pixtral') || diff --git a/web/src/helpers/statusCodeRules.js b/web/src/helpers/statusCodeRules.js index a0d5e75f9..5e681e8b8 100644 --- a/web/src/helpers/statusCodeRules.js +++ b/web/src/helpers/statusCodeRules.js @@ -35,7 +35,9 @@ export function parseHttpStatusCodeRules(input) { } const merged = mergeRanges(ranges); - const tokens = merged.map((r) => (r.start === r.end ? `${r.start}` : `${r.start}-${r.end}`)); + const tokens = merged.map((r) => + r.start === r.end ? `${r.start}` : `${r.start}-${r.end}`, + ); const normalized = tokens.join(','); return { @@ -78,7 +80,9 @@ function isNumber(s) { function mergeRanges(ranges) { if (!Array.isArray(ranges) || ranges.length === 0) return []; - const sorted = [...ranges].sort((a, b) => (a.start !== b.start ? a.start - b.start : a.end - b.end)); + const sorted = [...ranges].sort((a, b) => + a.start !== b.start ? a.start - b.start : a.end - b.end, + ); const merged = [sorted[0]]; for (let i = 1; i < sorted.length; i += 1) { diff --git a/web/src/helpers/utils.jsx b/web/src/helpers/utils.jsx index a54676e47..5ce83e678 100644 --- a/web/src/helpers/utils.jsx +++ b/web/src/helpers/utils.jsx @@ -217,7 +217,11 @@ export function timestamp2string(timestamp) { ); } -export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour', showYear = false) { +export function timestamp2string1( + timestamp, + dataExportDefaultTime = 'hour', + showYear = false, +) { let date = new Date(timestamp * 1000); let year = date.getFullYear(); let month = (date.getMonth() + 1).toString(); @@ -248,7 +252,9 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour', sho nextDay = '0' + nextDay; } // 周视图结束日期也仅在跨年时显示年份 - let nextStr = showYear ? nextWeekYear + '-' + nextMonth + '-' + nextDay : nextMonth + '-' + nextDay; + let nextStr = showYear + ? nextWeekYear + '-' + nextMonth + '-' + nextDay + : nextMonth + '-' + nextDay; str += ' - ' + nextStr; } return str; @@ -257,7 +263,9 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour', sho // 检查时间戳数组是否跨年 export function isDataCrossYear(timestamps) { if (!timestamps || timestamps.length === 0) return false; - const years = new Set(timestamps.map(ts => new Date(ts * 1000).getFullYear())); + const years = new Set( + timestamps.map((ts) => new Date(ts * 1000).getFullYear()), + ); return years.size > 1; } diff --git a/web/src/hooks/model-deployments/useModelDeploymentSettings.js b/web/src/hooks/model-deployments/useModelDeploymentSettings.js index c53fe55b8..e3578006b 100644 --- a/web/src/hooks/model-deployments/useModelDeploymentSettings.js +++ b/web/src/hooks/model-deployments/useModelDeploymentSettings.js @@ -55,13 +55,20 @@ export const useModelDeploymentSettings = () => { const isIoNetEnabled = settings['model_deployment.ionet.enabled']; - const buildConnectionError = (rawMessage, fallbackMessage = 'Connection failed') => { + const buildConnectionError = ( + rawMessage, + fallbackMessage = 'Connection failed', + ) => { const message = (rawMessage || fallbackMessage).trim(); const normalized = message.toLowerCase(); if (normalized.includes('expired') || normalized.includes('expire')) { return { type: 'expired', message }; } - if (normalized.includes('invalid') || normalized.includes('unauthorized') || normalized.includes('api key')) { + if ( + normalized.includes('invalid') || + normalized.includes('unauthorized') || + normalized.includes('api key') + ) { return { type: 'invalid', message }; } if (normalized.includes('network') || normalized.includes('timeout')) { @@ -85,7 +92,11 @@ export const useModelDeploymentSettings = () => { } const message = response?.data?.message || 'Connection failed'; - setConnectionState({ loading: false, ok: false, error: buildConnectionError(message) }); + setConnectionState({ + loading: false, + ok: false, + error: buildConnectionError(message), + }); } catch (error) { if (error?.code === 'ERR_NETWORK') { setConnectionState({ @@ -95,8 +106,13 @@ export const useModelDeploymentSettings = () => { }); return; } - const rawMessage = error?.response?.data?.message || error?.message || 'Unknown error'; - setConnectionState({ loading: false, ok: false, error: buildConnectionError(rawMessage, 'Connection failed') }); + const rawMessage = + error?.response?.data?.message || error?.message || 'Unknown error'; + setConnectionState({ + loading: false, + ok: false, + error: buildConnectionError(rawMessage, 'Connection failed'), + }); } }, []); diff --git a/web/src/hooks/playground/useApiRequest.jsx b/web/src/hooks/playground/useApiRequest.jsx index 12db9f5ca..8ec50cf45 100644 --- a/web/src/hooks/playground/useApiRequest.jsx +++ b/web/src/hooks/playground/useApiRequest.jsx @@ -231,7 +231,10 @@ export const useApiRequest = ( if (data.choices?.[0]) { const choice = data.choices[0]; let content = choice.message?.content || ''; - let reasoningContent = choice.message?.reasoning_content || choice.message?.reasoning || ''; + let reasoningContent = + choice.message?.reasoning_content || + choice.message?.reasoning || + ''; const processed = processThinkTags(content, reasoningContent); @@ -318,8 +321,8 @@ export const useApiRequest = ( isStreamComplete = true; // 标记流正常完成 source.close(); sseSourceRef.current = null; - setDebugData((prev) => ({ - ...prev, + setDebugData((prev) => ({ + ...prev, response: responseData, sseMessages: [...(prev.sseMessages || []), '[DONE]'], // 添加 DONE 标记 isStreaming: false, diff --git a/web/src/hooks/playground/usePlaygroundState.js b/web/src/hooks/playground/usePlaygroundState.js index 9574a4c3c..79be10134 100644 --- a/web/src/hooks/playground/usePlaygroundState.js +++ b/web/src/hooks/playground/usePlaygroundState.js @@ -36,18 +36,23 @@ import { processIncompleteThinkTags } from '../../helpers'; export const usePlaygroundState = () => { const { t } = useTranslation(); - + // 使用惰性初始化,确保只在组件首次挂载时加载配置和消息 const [savedConfig] = useState(() => loadConfig()); const [initialMessages] = useState(() => { const loaded = loadMessages(); // 检查是否是旧的中文默认消息,如果是则清除 - if (loaded && loaded.length === 2 && loaded[0].id === '2' && loaded[1].id === '3') { - const hasOldChinese = - loaded[0].content === '你好' || + if ( + loaded && + loaded.length === 2 && + loaded[0].id === '2' && + loaded[1].id === '3' + ) { + const hasOldChinese = + loaded[0].content === '你好' || loaded[1].content === '你好,请问有什么可以帮助您的吗?' || loaded[1].content === '你好!很高兴见到你。有什么我可以帮助你的吗?'; - + if (hasOldChinese) { // 清除旧的默认消息 localStorage.removeItem('playground_messages'); @@ -81,8 +86,10 @@ export const usePlaygroundState = () => { const [status, setStatus] = useState({}); // 消息相关状态 - 使用加载的消息或默认消息初始化 - const [message, setMessage] = useState(() => initialMessages || getDefaultMessages(t)); - + const [message, setMessage] = useState( + () => initialMessages || getDefaultMessages(t), + ); + // 当语言改变时,如果是默认消息则更新 useEffect(() => { // 只在没有保存的消息时才更新默认消息 diff --git a/web/src/hooks/usage-logs/useUsageLogsData.jsx b/web/src/hooks/usage-logs/useUsageLogsData.jsx index d44ed6ecf..3b12b8daa 100644 --- a/web/src/hooks/usage-logs/useUsageLogsData.jsx +++ b/web/src/hooks/usage-logs/useUsageLogsData.jsx @@ -364,32 +364,36 @@ export const useLogsData = () => { key: t('日志详情'), value: other?.claude ? renderClaudeLogContent( - other?.model_ratio, - other.completion_ratio, - other.model_price, - other.group_ratio, - other?.user_group_ratio, - other.cache_ratio || 1.0, - other.cache_creation_ratio || 1.0, - other.cache_creation_tokens_5m || 0, - other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0, - other.cache_creation_tokens_1h || 0, - other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0, - ) + other?.model_ratio, + other.completion_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_ratio || 1.0, + other.cache_creation_ratio || 1.0, + other.cache_creation_tokens_5m || 0, + other.cache_creation_ratio_5m || + other.cache_creation_ratio || + 1.0, + other.cache_creation_tokens_1h || 0, + other.cache_creation_ratio_1h || + other.cache_creation_ratio || + 1.0, + ) : renderLogContent( - other?.model_ratio, - other.completion_ratio, - other.model_price, - other.group_ratio, - other?.user_group_ratio, - other.cache_ratio || 1.0, - false, - 1.0, - other.web_search || false, - other.web_search_call_count || 0, - other.file_search || false, - other.file_search_call_count || 0, - ), + other?.model_ratio, + other.completion_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_ratio || 1.0, + false, + 1.0, + other.web_search || false, + other.web_search_call_count || 0, + other.file_search || false, + other.file_search_call_count || 0, + ), }); if (logs[i]?.content) { expandDataLocal.push({ @@ -458,12 +462,12 @@ export const useLogsData = () => { other.cache_creation_ratio || 1.0, other.cache_creation_tokens_5m || 0, other.cache_creation_ratio_5m || - other.cache_creation_ratio || - 1.0, + other.cache_creation_ratio || + 1.0, other.cache_creation_tokens_1h || 0, other.cache_creation_ratio_1h || - other.cache_creation_ratio || - 1.0, + other.cache_creation_ratio || + 1.0, ); } else { content = renderModelPrice( @@ -519,7 +523,8 @@ export const useLogsData = () => { const pre = other?.subscription_pre_consumed ?? 0; const postDelta = other?.subscription_post_delta ?? 0; const finalConsumed = - other?.subscription_consumed ?? (quotaType === 1 ? 1 : pre + postDelta); + other?.subscription_consumed ?? + (quotaType === 1 ? 1 : pre + postDelta); const remain = other?.subscription_remain; const total = other?.subscription_total; // Use multiple Description items to avoid an overlong single line. @@ -549,7 +554,9 @@ export const useLogsData = () => { .join('\n'); expandDataLocal.push({ key: t('订阅结算'), - value:
{settlementLines}
, + value: ( +
{settlementLines}
+ ), }); if (remain !== undefined && total !== undefined) { expandDataLocal.push({ @@ -638,7 +645,7 @@ export const useLogsData = () => { // Page handlers const handlePageChange = (page) => { setActivePage(page); - loadLogs(page, pageSize).then((r) => { }); + loadLogs(page, pageSize).then((r) => {}); }; const handlePageSizeChange = async (size) => { diff --git a/web/src/pages/Playground/index.jsx b/web/src/pages/Playground/index.jsx index 6b7f8d16a..68a97c335 100644 --- a/web/src/pages/Playground/index.jsx +++ b/web/src/pages/Playground/index.jsx @@ -438,14 +438,17 @@ const Playground = () => { }, [setMessage, saveMessagesImmediately]); // 处理粘贴图片 - const handlePasteImage = useCallback((base64Data) => { - if (!inputs.imageEnabled) { - return; - } - // 添加图片到 imageUrls 数组 - const newUrls = [...(inputs.imageUrls || []), base64Data]; - handleInputChange('imageUrls', newUrls); - }, [inputs.imageEnabled, inputs.imageUrls, handleInputChange]); + const handlePasteImage = useCallback( + (base64Data) => { + if (!inputs.imageEnabled) { + return; + } + // 添加图片到 imageUrls 数组 + const newUrls = [...(inputs.imageUrls || []), base64Data]; + handleInputChange('imageUrls', newUrls); + }, + [inputs.imageEnabled, inputs.imageUrls, handleInputChange], + ); // Playground Context 值 const playgroundContextValue = { @@ -457,10 +460,10 @@ const Playground = () => { return (
- - {(showSettings || !isMobile) && ( - + {(showSettings || !isMobile) && ( + { : 'relative z-[1] w-80 h-[calc(100vh-66px)]' } `} - width={isMobile ? '100%' : 320} - > - setShowSettings(false)} - onConfigImport={handleConfigImport} - onConfigReset={handleConfigReset} - onCustomRequestModeChange={setCustomRequestMode} - onCustomRequestBodyChange={setCustomRequestBody} - previewPayload={previewPayload} - messages={message} - /> - - )} - - -
-
- + setShowDebugPanel(!showDebugPanel)} - renderCustomChatContent={renderCustomChatContent} - renderChatBoxAction={renderChatBoxAction} + customRequestMode={customRequestMode} + customRequestBody={customRequestBody} + onInputChange={handleInputChange} + onParameterToggle={handleParameterToggle} + onCloseSettings={() => setShowSettings(false)} + onConfigImport={handleConfigImport} + onConfigReset={handleConfigReset} + onCustomRequestModeChange={setCustomRequestMode} + onCustomRequestBodyChange={setCustomRequestBody} + previewPayload={previewPayload} + messages={message} /> + + )} + + +
+
+ setShowDebugPanel(!showDebugPanel)} + renderCustomChatContent={renderCustomChatContent} + renderChatBoxAction={renderChatBoxAction} + /> +
+ + {/* 调试面板 - 桌面端 */} + {showDebugPanel && !isMobile && ( +
+ +
+ )}
- {/* 调试面板 - 桌面端 */} - {showDebugPanel && !isMobile && ( -
+ {/* 调试面板 - 移动端覆盖层 */} + {showDebugPanel && isMobile && ( +
setShowDebugPanel(false)} customRequestMode={customRequestMode} />
)} -
- {/* 调试面板 - 移动端覆盖层 */} - {showDebugPanel && isMobile && ( -
- setShowDebugPanel(false)} - customRequestMode={customRequestMode} - /> -
- )} - - {/* 浮动按钮 */} - setShowSettings(!showSettings)} - onToggleDebugPanel={() => setShowDebugPanel(!showDebugPanel)} - /> -
- -
+ {/* 浮动按钮 */} + setShowSettings(!showSettings)} + onToggleDebugPanel={() => setShowDebugPanel(!showDebugPanel)} + /> + + +
); }; diff --git a/web/src/pages/PrivacyPolicy/index.jsx b/web/src/pages/PrivacyPolicy/index.jsx index 026290b18..d2c4a872d 100644 --- a/web/src/pages/PrivacyPolicy/index.jsx +++ b/web/src/pages/PrivacyPolicy/index.jsx @@ -26,12 +26,12 @@ const PrivacyPolicy = () => { return ( ); }; -export default PrivacyPolicy; \ No newline at end of file +export default PrivacyPolicy; diff --git a/web/src/pages/Setting/Model/SettingGlobalModel.jsx b/web/src/pages/Setting/Model/SettingGlobalModel.jsx index 9878875c7..4b8f9f4d9 100644 --- a/web/src/pages/Setting/Model/SettingGlobalModel.jsx +++ b/web/src/pages/Setting/Model/SettingGlobalModel.jsx @@ -199,9 +199,9 @@ export default function SettingGlobalModel(props) { 'global.pass_through_request_enabled': value, }) } - extraText={ - t('开启后,所有请求将直接透传给上游,不会进行任何处理(重定向和渠道适配也将失效),请谨慎开启') - } + extraText={t( + '开启后,所有请求将直接透传给上游,不会进行任何处理(重定向和渠道适配也将失效),请谨慎开启', + )} /> @@ -210,11 +210,7 @@ export default function SettingGlobalModel(props) { - diff --git a/web/src/pages/Setting/Model/SettingGrokModel.jsx b/web/src/pages/Setting/Model/SettingGrokModel.jsx index 3fdf2ca09..3d721c798 100644 --- a/web/src/pages/Setting/Model/SettingGrokModel.jsx +++ b/web/src/pages/Setting/Model/SettingGrokModel.jsx @@ -49,8 +49,7 @@ export default function SettingGrokModel(props) { .validate() .then(() => { const updateArray = compareObjects(inputs, inputsRow); - if (!updateArray.length) - return showWarning(t('你似乎并没有修改什么')); + if (!updateArray.length) return showWarning(t('你似乎并没有修改什么')); const requestQueue = updateArray.map((item) => { const value = String(inputs[item.key]); diff --git a/web/src/pages/Setting/Model/SettingModelDeployment.jsx b/web/src/pages/Setting/Model/SettingModelDeployment.jsx index 88a043b10..fdfbb448e 100644 --- a/web/src/pages/Setting/Model/SettingModelDeployment.jsx +++ b/web/src/pages/Setting/Model/SettingModelDeployment.jsx @@ -18,7 +18,15 @@ For commercial licensing, please contact support@quantumnous.com */ import React, { useEffect, useState, useRef } from 'react'; -import { Button, Col, Form, Row, Spin, Card, Typography } from '@douyinfe/semi-ui'; +import { + Button, + Col, + Form, + Row, + Spin, + Card, + Typography, +} from '@douyinfe/semi-ui'; import { compareObjects, API, @@ -88,9 +96,7 @@ export default function SettingModelDeployment(props) { showError(t('网络连接失败,请检查网络设置或稍后重试')); } else { const rawMessage = - error?.response?.data?.message || - error?.message || - ''; + error?.response?.data?.message || error?.message || ''; const localizedMessage = rawMessage ? getLocalizedMessage(rawMessage) : t('未知错误'); @@ -104,7 +110,7 @@ export default function SettingModelDeployment(props) { function onSubmit() { const updateArray = compareObjects(inputs, inputsRow); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么')); - + const requestQueue = updateArray.map((item) => { let value = String(inputs[item.key]); return API.put('/api/option/', { @@ -112,7 +118,7 @@ export default function SettingModelDeployment(props) { value, }); }); - + setLoading(true); Promise.all(requestQueue) .then((res) => { @@ -141,7 +147,7 @@ export default function SettingModelDeployment(props) { 'model_deployment.ionet.api_key': '', 'model_deployment.ionet.enabled': false, }; - + const currentInputs = {}; for (let key in defaultInputs) { if (props.options.hasOwnProperty(key)) { @@ -150,7 +156,7 @@ export default function SettingModelDeployment(props) { currentInputs[key] = defaultInputs[key]; } } - + setInputs(currentInputs); setInputsRow(structuredClone(currentInputs)); refForm.current?.setValues(currentInputs); @@ -165,9 +171,11 @@ export default function SettingModelDeployment(props) { getFormApi={(formAPI) => (refForm.current = formAPI)} style={{ marginBottom: 15 }} > - +
{t('模型部署设置')}
} @@ -186,7 +194,9 @@ export default function SettingModelDeployment(props) { +
io.net
@@ -226,18 +236,16 @@ export default function SettingModelDeployment(props) { } disabled={!inputs['model_deployment.ionet.enabled']} extraText={t('请使用 Project 为 io.cloud 的密钥')} - mode="password" + mode='password' />
diff --git a/web/src/pages/Setting/Operation/SettingsCreditLimit.jsx b/web/src/pages/Setting/Operation/SettingsCreditLimit.jsx index a5208a502..52476932b 100644 --- a/web/src/pages/Setting/Operation/SettingsCreditLimit.jsx +++ b/web/src/pages/Setting/Operation/SettingsCreditLimit.jsx @@ -172,7 +172,9 @@ export default function SettingsCreditLimit(props) { setInputs({ ...inputs, diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.jsx b/web/src/pages/Setting/Operation/SettingsMonitoring.jsx index 6e1743478..29b55e56c 100644 --- a/web/src/pages/Setting/Operation/SettingsMonitoring.jsx +++ b/web/src/pages/Setting/Operation/SettingsMonitoring.jsx @@ -18,13 +18,7 @@ For commercial licensing, please contact support@quantumnous.com */ import React, { useEffect, useState, useRef } from 'react'; -import { - Button, - Col, - Form, - Row, - Spin, -} from '@douyinfe/semi-ui'; +import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui'; import { compareObjects, API, @@ -46,7 +40,8 @@ export default function SettingsMonitoring(props) { AutomaticEnableChannelEnabled: false, AutomaticDisableKeywords: '', AutomaticDisableStatusCodes: '401', - AutomaticRetryStatusCodes: '100-199,300-399,401-407,409-499,500-503,505-523,525-599', + AutomaticRetryStatusCodes: + '100-199,300-399,401-407,409-499,500-503,505-523,525-599', 'monitor_setting.auto_test_channel_enabled': false, 'monitor_setting.auto_test_channel_minutes': 10, }); diff --git a/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx b/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx index 5f351c105..817bca51a 100644 --- a/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx +++ b/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx @@ -252,7 +252,11 @@ export default function SettingsSidebarModulesAdmin(props) { modules: [ { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') }, { key: 'models', title: t('模型管理'), description: t('AI模型配置') }, - { key: 'deployment', title: t('模型部署'), description: t('模型部署管理') }, + { + key: 'deployment', + title: t('模型部署'), + description: t('模型部署管理'), + }, { key: 'redemption', title: t('兑换码管理'), diff --git a/web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.jsx b/web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.jsx index 32e2e6fbc..18e8192d8 100644 --- a/web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.jsx +++ b/web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.jsx @@ -1,385 +1,404 @@ import React, { useEffect, useState, useRef } from 'react'; import { - Banner, - Button, - Form, - Row, - Col, - Typography, - Spin, - Table, - Modal, - Input, - InputNumber, - Select, + Banner, + Button, + Form, + Row, + Col, + Typography, + Spin, + Table, + Modal, + Input, + InputNumber, + Select, } from '@douyinfe/semi-ui'; const { Text } = Typography; -import { - API, - showError, - showSuccess, -} from '../../../helpers'; +import { API, showError, showSuccess } from '../../../helpers'; import { useTranslation } from 'react-i18next'; import { Plus, Trash2 } from 'lucide-react'; export default function SettingsPaymentGatewayCreem(props) { - const { t } = useTranslation(); - const [loading, setLoading] = useState(false); - const [inputs, setInputs] = useState({ - CreemApiKey: '', - CreemWebhookSecret: '', - CreemProducts: '[]', - CreemTestMode: false, - }); - const [originInputs, setOriginInputs] = useState({}); - const [products, setProducts] = useState([]); - const [showProductModal, setShowProductModal] = useState(false); - const [editingProduct, setEditingProduct] = useState(null); - const [productForm, setProductForm] = useState({ + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [inputs, setInputs] = useState({ + CreemApiKey: '', + CreemWebhookSecret: '', + CreemProducts: '[]', + CreemTestMode: false, + }); + const [originInputs, setOriginInputs] = useState({}); + const [products, setProducts] = useState([]); + const [showProductModal, setShowProductModal] = useState(false); + const [editingProduct, setEditingProduct] = useState(null); + const [productForm, setProductForm] = useState({ + name: '', + productId: '', + price: 0, + quota: 0, + currency: 'USD', + }); + const formApiRef = useRef(null); + + useEffect(() => { + if (props.options && formApiRef.current) { + const currentInputs = { + CreemApiKey: props.options.CreemApiKey || '', + CreemWebhookSecret: props.options.CreemWebhookSecret || '', + CreemProducts: props.options.CreemProducts || '[]', + CreemTestMode: props.options.CreemTestMode === 'true', + }; + setInputs(currentInputs); + setOriginInputs({ ...currentInputs }); + formApiRef.current.setValues(currentInputs); + + // Parse products + try { + const parsedProducts = JSON.parse(currentInputs.CreemProducts); + setProducts(parsedProducts); + } catch (e) { + setProducts([]); + } + } + }, [props.options]); + + const handleFormChange = (values) => { + setInputs(values); + }; + + const submitCreemSetting = async () => { + setLoading(true); + try { + const options = []; + + if (inputs.CreemApiKey && inputs.CreemApiKey !== '') { + options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey }); + } + + if (inputs.CreemWebhookSecret && inputs.CreemWebhookSecret !== '') { + options.push({ + key: 'CreemWebhookSecret', + value: inputs.CreemWebhookSecret, + }); + } + + // Save test mode setting + options.push({ + key: 'CreemTestMode', + value: inputs.CreemTestMode ? 'true' : 'false', + }); + + // Save products as JSON string + options.push({ key: 'CreemProducts', value: JSON.stringify(products) }); + + // 发送请求 + const requestQueue = options.map((opt) => + API.put('/api/option/', { + key: opt.key, + value: opt.value, + }), + ); + + const results = await Promise.all(requestQueue); + + // 检查所有请求是否成功 + const errorResults = results.filter((res) => !res.data.success); + if (errorResults.length > 0) { + errorResults.forEach((res) => { + showError(res.data.message); + }); + } else { + showSuccess(t('更新成功')); + // 更新本地存储的原始值 + setOriginInputs({ ...inputs }); + props.refresh?.(); + } + } catch (error) { + showError(t('更新失败')); + } + setLoading(false); + }; + + const openProductModal = (product = null) => { + if (product) { + setEditingProduct(product); + setProductForm({ ...product }); + } else { + setEditingProduct(null); + setProductForm({ name: '', productId: '', price: 0, quota: 0, currency: 'USD', + }); + } + setShowProductModal(true); + }; + + const closeProductModal = () => { + setShowProductModal(false); + setEditingProduct(null); + setProductForm({ + name: '', + productId: '', + price: 0, + quota: 0, + currency: 'USD', }); - const formApiRef = useRef(null); + }; - useEffect(() => { - if (props.options && formApiRef.current) { - const currentInputs = { - CreemApiKey: props.options.CreemApiKey || '', - CreemWebhookSecret: props.options.CreemWebhookSecret || '', - CreemProducts: props.options.CreemProducts || '[]', - CreemTestMode: props.options.CreemTestMode === 'true', - }; - setInputs(currentInputs); - setOriginInputs({ ...currentInputs }); - formApiRef.current.setValues(currentInputs); + const saveProduct = () => { + if ( + !productForm.name || + !productForm.productId || + productForm.price <= 0 || + productForm.quota <= 0 || + !productForm.currency + ) { + showError(t('请填写完整的产品信息')); + return; + } - // Parse products - try { - const parsedProducts = JSON.parse(currentInputs.CreemProducts); - setProducts(parsedProducts); - } catch (e) { - setProducts([]); - } - } - }, [props.options]); + let newProducts = [...products]; + if (editingProduct) { + // 编辑现有产品 + const index = newProducts.findIndex( + (p) => p.productId === editingProduct.productId, + ); + if (index !== -1) { + newProducts[index] = { ...productForm }; + } + } else { + // 添加新产品 + if (newProducts.find((p) => p.productId === productForm.productId)) { + showError(t('产品ID已存在')); + return; + } + newProducts.push({ ...productForm }); + } - const handleFormChange = (values) => { - setInputs(values); - }; + setProducts(newProducts); + closeProductModal(); + }; - const submitCreemSetting = async () => { - setLoading(true); - try { - const options = []; + const deleteProduct = (productId) => { + const newProducts = products.filter((p) => p.productId !== productId); + setProducts(newProducts); + }; - if (inputs.CreemApiKey && inputs.CreemApiKey !== '') { - options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey }); - } + const columns = [ + { + title: t('产品名称'), + dataIndex: 'name', + key: 'name', + }, + { + title: t('产品ID'), + dataIndex: 'productId', + key: 'productId', + }, + { + title: t('展示价格'), + dataIndex: 'price', + key: 'price', + render: (price, record) => + `${record.currency === 'EUR' ? '€' : '$'}${price}`, + }, + { + title: t('充值额度'), + dataIndex: 'quota', + key: 'quota', + }, + { + title: t('操作'), + key: 'action', + render: (_, record) => ( +
+ +
+ ), + }, + ]; - if (inputs.CreemWebhookSecret && inputs.CreemWebhookSecret !== '') { - options.push({ key: 'CreemWebhookSecret', value: inputs.CreemWebhookSecret }); - } + return ( + +
(formApiRef.current = api)} + > + + + {t('Creem 介绍')} + + Creem Official Site + +
+
+ - // Save test mode setting - options.push({ key: 'CreemTestMode', value: inputs.CreemTestMode ? 'true' : 'false' }); + + + + + + + + + + + - // Save products as JSON string - options.push({ key: 'CreemProducts', value: JSON.stringify(products) }); +
+
+ {t('产品配置')} + +
- // 发送请求 - const requestQueue = options.map(opt => - API.put('/api/option/', { - key: opt.key, - value: opt.value, - }) - ); - - const results = await Promise.all(requestQueue); - - // 检查所有请求是否成功 - const errorResults = results.filter(res => !res.data.success); - if (errorResults.length > 0) { - errorResults.forEach(res => { - showError(res.data.message); - }); - } else { - showSuccess(t('更新成功')); - // 更新本地存储的原始值 - setOriginInputs({ ...inputs }); - props.refresh?.(); - } - } catch (error) { - showError(t('更新失败')); - } - setLoading(false); - }; - - const openProductModal = (product = null) => { - if (product) { - setEditingProduct(product); - setProductForm({ ...product }); - } else { - setEditingProduct(null); - setProductForm({ - name: '', - productId: '', - price: 0, - quota: 0, - currency: 'USD', - }); - } - setShowProductModal(true); - }; - - const closeProductModal = () => { - setShowProductModal(false); - setEditingProduct(null); - setProductForm({ - name: '', - productId: '', - price: 0, - quota: 0, - currency: 'USD', - }); - }; - - const saveProduct = () => { - if (!productForm.name || !productForm.productId || productForm.price <= 0 || productForm.quota <= 0 || !productForm.currency) { - showError(t('请填写完整的产品信息')); - return; - } - - let newProducts = [...products]; - if (editingProduct) { - // 编辑现有产品 - const index = newProducts.findIndex(p => p.productId === editingProduct.productId); - if (index !== -1) { - newProducts[index] = { ...productForm }; - } - } else { - // 添加新产品 - if (newProducts.find(p => p.productId === productForm.productId)) { - showError(t('产品ID已存在')); - return; - } - newProducts.push({ ...productForm }); - } - - setProducts(newProducts); - closeProductModal(); - }; - - const deleteProduct = (productId) => { - const newProducts = products.filter(p => p.productId !== productId); - setProducts(newProducts); - }; - - const columns = [ - { - title: t('产品名称'), - dataIndex: 'name', - key: 'name', - }, - { - title: t('产品ID'), - dataIndex: 'productId', - key: 'productId', - }, - { - title: t('展示价格'), - dataIndex: 'price', - key: 'price', - render: (price, record) => `${record.currency === 'EUR' ? '€' : '$'}${price}`, - }, - { - title: t('充值额度'), - dataIndex: 'quota', - key: 'quota', - }, - { - title: t('操作'), - key: 'action', - render: (_, record) => ( -
- -