diff --git a/.gitattributes b/.gitattributes index 8bf40a14f..63328aaf1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -35,5 +35,4 @@ # GitHub Linguist - Language Detection # ============================================ # Mark web frontend as vendored so GitHub recognizes this as a Go project -web/** linguist-vendored electron/** linguist-vendored diff --git a/web/src/components/common/examples/ChannelKeyViewExample.jsx b/web/src/components/common/examples/ChannelKeyViewExample.jsx deleted file mode 100644 index 1bb2998b2..000000000 --- a/web/src/components/common/examples/ChannelKeyViewExample.jsx +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button, Modal } from '@douyinfe/semi-ui'; -import { useSecureVerification } from '../../../hooks/common/useSecureVerification'; -import { createApiCalls } from '../../../services/secureVerification'; -import SecureVerificationModal from '../modals/SecureVerificationModal'; -import ChannelKeyDisplay from '../ui/ChannelKeyDisplay'; - -/** - * 渠道密钥查看组件使用示例 - * 展示如何使用通用安全验证系统 - */ -const ChannelKeyViewExample = ({ channelId }) => { - const { t } = useTranslation(); - const [keyData, setKeyData] = useState(''); - const [showKeyModal, setShowKeyModal] = useState(false); - - // 使用通用安全验证 Hook - const { - isModalVisible, - verificationMethods, - verificationState, - startVerification, - executeVerification, - cancelVerification, - setVerificationCode, - switchVerificationMethod, - } = useSecureVerification({ - onSuccess: (result) => { - // 验证成功后处理结果 - if (result.success && result.data?.key) { - setKeyData(result.data.key); - setShowKeyModal(true); - } - }, - successMessage: t('密钥获取成功'), - }); - - // 开始查看密钥流程 - const handleViewKey = async () => { - const apiCall = createApiCalls.viewChannelKey(channelId); - - await startVerification(apiCall, { - title: t('查看渠道密钥'), - description: t('为了保护账户安全,请验证您的身份。'), - preferredMethod: 'passkey', // 可以指定首选验证方式 - }); - }; - - return ( - <> - {/* 查看密钥按钮 */} - - - {/* 安全验证模态框 */} - - - {/* 密钥显示模态框 */} - setShowKeyModal(false)} - footer={ - - } - width={700} - style={{ maxWidth: '90vw' }} - > - - - - ); -}; - -export default ChannelKeyViewExample; diff --git a/web/src/components/common/modals/TwoFactorAuthModal.jsx b/web/src/components/common/modals/TwoFactorAuthModal.jsx deleted file mode 100644 index 082e63d79..000000000 --- a/web/src/components/common/modals/TwoFactorAuthModal.jsx +++ /dev/null @@ -1,148 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Modal, Button, Input, Typography } from '@douyinfe/semi-ui'; - -/** - * 可复用的两步验证模态框组件 - * @param {Object} props - * @param {boolean} props.visible - 是否显示模态框 - * @param {string} props.code - 验证码值 - * @param {boolean} props.loading - 是否正在验证 - * @param {Function} props.onCodeChange - 验证码变化回调 - * @param {Function} props.onVerify - 验证回调 - * @param {Function} props.onCancel - 取消回调 - * @param {string} props.title - 模态框标题 - * @param {string} props.description - 验证描述文本 - * @param {string} props.placeholder - 输入框占位文本 - */ -const TwoFactorAuthModal = ({ - visible, - code, - loading, - onCodeChange, - onVerify, - onCancel, - title, - description, - placeholder, -}) => { - const { t } = useTranslation(); - - const handleKeyDown = (e) => { - if (e.key === 'Enter' && code && !loading) { - onVerify(); - } - }; - - return ( - -
- - - -
- {title || t('安全验证')} - - } - visible={visible} - onCancel={onCancel} - footer={ - <> - - - - } - width={500} - style={{ maxWidth: '90vw' }} - > -
- {/* 安全提示 */} -
-
- - - -
- - {t('安全验证')} - - - {description || t('为了保护账户安全,请验证您的两步验证码。')} - -
-
-
- - {/* 验证码输入 */} -
- - {t('验证身份')} - - - - {t( - '支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。', - )} - -
-
-
- ); -}; - -export default TwoFactorAuthModal; diff --git a/web/src/components/playground/index.js b/web/src/components/playground/index.js deleted file mode 100644 index 8c470351b..000000000 --- a/web/src/components/playground/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -export { default as SettingsPanel } from './SettingsPanel'; -export { default as ChatArea } from './ChatArea'; -export { default as DebugPanel } from './DebugPanel'; -export { default as MessageContent } from './MessageContent'; -export { default as MessageActions } from './MessageActions'; -export { default as CustomInputRender } from './CustomInputRender'; -export { default as SSEViewer } from './SSEViewer'; -export { default as ParameterControl } from './ParameterControl'; -export { default as ImageUrlInput } from './ImageUrlInput'; -export { default as FloatingButtons } from './FloatingButtons'; -export { default as ConfigManager } from './ConfigManager'; - -export { - saveConfig, - loadConfig, - clearConfig, - hasStoredConfig, - getConfigTimestamp, - exportConfig, - importConfig, -} from './configStorage'; diff --git a/web/src/components/settings/personal/cards/ModelsList.jsx b/web/src/components/settings/personal/cards/ModelsList.jsx deleted file mode 100644 index 3f374fd44..000000000 --- a/web/src/components/settings/personal/cards/ModelsList.jsx +++ /dev/null @@ -1,280 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React, { useState, useEffect } from 'react'; -import { - Empty, - Skeleton, - Space, - Tag, - Collapsible, - Tabs, - TabPane, - Typography, - Avatar, -} from '@douyinfe/semi-ui'; -import { - IllustrationNoContent, - IllustrationNoContentDark, -} from '@douyinfe/semi-illustrations'; -import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons'; -import { Settings } from 'lucide-react'; -import { renderModelTag, getModelCategories } from '../../../../helpers'; - -const ModelsList = ({ t, models, modelsLoading, copyText }) => { - const [isModelsExpanded, setIsModelsExpanded] = useState(() => { - // Initialize from localStorage if available - const savedState = localStorage.getItem('modelsExpanded'); - return savedState ? JSON.parse(savedState) : false; - }); - const [activeModelCategory, setActiveModelCategory] = useState('all'); - const MODELS_DISPLAY_COUNT = 25; // 默认显示的模型数量 - - // Save models expanded state to localStorage whenever it changes - useEffect(() => { - localStorage.setItem('modelsExpanded', JSON.stringify(isModelsExpanded)); - }, [isModelsExpanded]); - - return ( -
- {/* 卡片头部 */} -
- - - -
- - {t('可用模型')} - -
- {t('查看当前可用的所有模型')} -
-
-
- - {/* 可用模型部分 */} -
- {modelsLoading ? ( - // 骨架屏加载状态 - 模拟实际加载后的布局 -
- {/* 模拟分类标签 */} -
-
- {Array.from({ length: 8 }).map((_, index) => ( - - ))} -
-
- - {/* 模拟模型标签列表 */} -
- {Array.from({ length: 20 }).map((_, index) => ( - - ))} -
-
- ) : models.length === 0 ? ( -
- - } - darkModeImage={ - - } - description={t('没有可用模型')} - style={{ padding: '24px 0' }} - /> -
- ) : ( - <> - {/* 模型分类标签页 */} -
- setActiveModelCategory(key)} - className='mt-2' - collapsible - > - {Object.entries(getModelCategories(t)).map( - ([key, category]) => { - // 计算该分类下的模型数量 - const modelCount = - key === 'all' - ? models.length - : models.filter((model) => - category.filter({ model_name: model }), - ).length; - - if (modelCount === 0 && key !== 'all') return null; - - return ( - - {category.icon && ( - {category.icon} - )} - {category.label} - - {modelCount} - - - } - itemKey={key} - key={key} - /> - ); - }, - )} - -
- -
- {(() => { - // 根据当前选中的分类过滤模型 - const categories = getModelCategories(t); - const filteredModels = - activeModelCategory === 'all' - ? models - : models.filter((model) => - categories[activeModelCategory].filter({ - model_name: model, - }), - ); - - // 如果过滤后没有模型,显示空状态 - if (filteredModels.length === 0) { - return ( - - } - darkModeImage={ - - } - description={t('该分类下没有可用模型')} - style={{ padding: '16px 0' }} - /> - ); - } - - if (filteredModels.length <= MODELS_DISPLAY_COUNT) { - return ( - - {filteredModels.map((model) => - renderModelTag(model, { - size: 'small', - shape: 'circle', - onClick: () => copyText(model), - }), - )} - - ); - } else { - return ( - <> - - - {filteredModels.map((model) => - renderModelTag(model, { - size: 'small', - shape: 'circle', - onClick: () => copyText(model), - }), - )} - setIsModelsExpanded(false)} - icon={} - > - {t('收起')} - - - - {!isModelsExpanded && ( - - {filteredModels - .slice(0, MODELS_DISPLAY_COUNT) - .map((model) => - renderModelTag(model, { - size: 'small', - shape: 'circle', - onClick: () => copyText(model), - }), - )} - setIsModelsExpanded(true)} - icon={} - > - {t('更多')}{' '} - {filteredModels.length - MODELS_DISPLAY_COUNT}{' '} - {t('个模型')} - - - )} - - ); - } - })()} -
- - )} -
-
- ); -}; - -export default ModelsList; diff --git a/web/src/components/table/models/ModelsDescription.jsx b/web/src/components/table/models/ModelsDescription.jsx deleted file mode 100644 index a99abd350..000000000 --- a/web/src/components/table/models/ModelsDescription.jsx +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React from 'react'; -import { Typography } from '@douyinfe/semi-ui'; -import { Layers } from 'lucide-react'; -import CompactModeToggle from '../../common/ui/CompactModeToggle'; - -const { Text } = Typography; - -const ModelsDescription = ({ compactMode, setCompactMode, t }) => { - return ( -
-
- - {t('模型管理')} -
- - -
- ); -}; - -export default ModelsDescription; diff --git a/web/src/components/table/users/modals/BindSubscriptionModal.jsx b/web/src/components/table/users/modals/BindSubscriptionModal.jsx deleted file mode 100644 index baa71d1fb..000000000 --- a/web/src/components/table/users/modals/BindSubscriptionModal.jsx +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React, { useEffect, useMemo, useState } from 'react'; -import { Modal, Select, Space, Typography } from '@douyinfe/semi-ui'; -import { API, showError, showSuccess } from '../../../../helpers'; - -const { Text } = Typography; - -const BindSubscriptionModal = ({ visible, onCancel, user, t, onSuccess }) => { - const [loading, setLoading] = useState(false); - const [plans, setPlans] = useState([]); - const [selectedPlanId, setSelectedPlanId] = useState(null); - - const loadPlans = async () => { - setLoading(true); - try { - const res = await API.get('/api/subscription/admin/plans'); - if (res.data?.success) { - setPlans(res.data.data || []); - } else { - showError(res.data?.message || t('加载失败')); - } - } catch (e) { - showError(t('请求失败')); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - if (visible) { - setSelectedPlanId(null); - loadPlans(); - } - }, [visible]); - - const planOptions = useMemo(() => { - return (plans || []).map((p) => ({ - label: `${p?.plan?.title || ''} (${p?.plan?.currency || 'USD'} ${Number(p?.plan?.price_amount || 0)})`, - value: p?.plan?.id, - })); - }, [plans]); - - const bind = async () => { - if (!user?.id) { - showError(t('用户信息缺失')); - return; - } - if (!selectedPlanId) { - showError(t('请选择订阅套餐')); - return; - } - setLoading(true); - try { - const res = await API.post('/api/subscription/admin/bind', { - user_id: user.id, - plan_id: selectedPlanId, - }); - if (res.data?.success) { - showSuccess(t('绑定成功')); - onSuccess?.(); - onCancel?.(); - } else { - showError(res.data?.message || t('绑定失败')); - } - } catch (e) { - showError(t('请求失败')); - } finally { - setLoading(false); - } - }; - - return ( - - -
- {t('用户')}: - {user?.username} - (ID: {user?.id}) -
-