소스 검색

Remove deprecated components and hooks

CaIon 1 주 전
부모
커밋
9a7a29eed8

+ 0 - 1
.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

+ 0 - 113
web/src/components/common/examples/ChannelKeyViewExample.jsx

@@ -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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-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 (
-    <>
-      {/* 查看密钥按钮 */}
-      <Button type='primary' theme='outline' onClick={handleViewKey}>
-        {t('查看密钥')}
-      </Button>
-
-      {/* 安全验证模态框 */}
-      <SecureVerificationModal
-        visible={isModalVisible}
-        verificationMethods={verificationMethods}
-        verificationState={verificationState}
-        onVerify={executeVerification}
-        onCancel={cancelVerification}
-        onCodeChange={setVerificationCode}
-        onMethodSwitch={switchVerificationMethod}
-        title={verificationState.title}
-        description={verificationState.description}
-      />
-
-      {/* 密钥显示模态框 */}
-      <Modal
-        title={t('渠道密钥信息')}
-        visible={showKeyModal}
-        onCancel={() => setShowKeyModal(false)}
-        footer={
-          <Button type='primary' onClick={() => setShowKeyModal(false)}>
-            {t('完成')}
-          </Button>
-        }
-        width={700}
-        style={{ maxWidth: '90vw' }}
-      >
-        <ChannelKeyDisplay
-          keyData={keyData}
-          showSuccessIcon={true}
-          successText={t('密钥获取成功')}
-          showWarning={true}
-        />
-      </Modal>
-    </>
-  );
-};
-
-export default ChannelKeyViewExample;

+ 0 - 148
web/src/components/common/modals/TwoFactorAuthModal.jsx

@@ -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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-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 (
-    <Modal
-      title={
-        <div className='flex items-center'>
-          <div className='w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center mr-3'>
-            <svg
-              className='w-4 h-4 text-blue-600 dark:text-blue-400'
-              fill='currentColor'
-              viewBox='0 0 20 20'
-            >
-              <path
-                fillRule='evenodd'
-                d='M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z'
-                clipRule='evenodd'
-              />
-            </svg>
-          </div>
-          {title || t('安全验证')}
-        </div>
-      }
-      visible={visible}
-      onCancel={onCancel}
-      footer={
-        <>
-          <Button onClick={onCancel}>{t('取消')}</Button>
-          <Button
-            type='primary'
-            loading={loading}
-            disabled={!code || loading}
-            onClick={onVerify}
-          >
-            {t('验证')}
-          </Button>
-        </>
-      }
-      width={500}
-      style={{ maxWidth: '90vw' }}
-    >
-      <div className='space-y-6'>
-        {/* 安全提示 */}
-        <div className='bg-blue-50 dark:bg-blue-900 rounded-lg p-4'>
-          <div className='flex items-start'>
-            <svg
-              className='w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 mr-3 flex-shrink-0'
-              fill='currentColor'
-              viewBox='0 0 20 20'
-            >
-              <path
-                fillRule='evenodd'
-                d='M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
-                clipRule='evenodd'
-              />
-            </svg>
-            <div>
-              <Typography.Text
-                strong
-                className='text-blue-800 dark:text-blue-200'
-              >
-                {t('安全验证')}
-              </Typography.Text>
-              <Typography.Text className='block text-blue-700 dark:text-blue-300 text-sm mt-1'>
-                {description || t('为了保护账户安全,请验证您的两步验证码。')}
-              </Typography.Text>
-            </div>
-          </div>
-        </div>
-
-        {/* 验证码输入 */}
-        <div>
-          <Typography.Text strong className='block mb-2'>
-            {t('验证身份')}
-          </Typography.Text>
-          <Input
-            placeholder={placeholder || t('请输入认证器验证码或备用码')}
-            value={code}
-            onChange={onCodeChange}
-            size='large'
-            maxLength={8}
-            onKeyDown={handleKeyDown}
-            autoFocus
-          />
-          <Typography.Text type='tertiary' size='small' className='mt-2 block'>
-            {t(
-              '支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。',
-            )}
-          </Typography.Text>
-        </div>
-      </div>
-    </Modal>
-  );
-};
-
-export default TwoFactorAuthModal;

+ 0 - 40
web/src/components/playground/index.js

@@ -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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-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';

+ 0 - 280
web/src/components/settings/personal/cards/ModelsList.jsx

@@ -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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-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 (
-    <div className='py-4'>
-      {/* 卡片头部 */}
-      <div className='flex items-center mb-4'>
-        <Avatar size='small' color='green' className='mr-3 shadow-md'>
-          <Settings size={16} />
-        </Avatar>
-        <div>
-          <Typography.Text className='text-lg font-medium'>
-            {t('可用模型')}
-          </Typography.Text>
-          <div className='text-xs text-gray-600'>
-            {t('查看当前可用的所有模型')}
-          </div>
-        </div>
-      </div>
-
-      {/* 可用模型部分 */}
-      <div className='bg-gray-50 dark:bg-gray-800 rounded-xl'>
-        {modelsLoading ? (
-          // 骨架屏加载状态 - 模拟实际加载后的布局
-          <div className='space-y-4'>
-            {/* 模拟分类标签 */}
-            <div
-              className='mb-4'
-              style={{ borderBottom: '1px solid var(--semi-color-border)' }}
-            >
-              <div className='flex overflow-x-auto py-2 gap-2'>
-                {Array.from({ length: 8 }).map((_, index) => (
-                  <Skeleton.Button
-                    key={`cat-${index}`}
-                    style={{
-                      width: index === 0 ? 130 : 100 + Math.random() * 50,
-                      height: 36,
-                      borderRadius: 8,
-                    }}
-                  />
-                ))}
-              </div>
-            </div>
-
-            {/* 模拟模型标签列表 */}
-            <div className='flex flex-wrap gap-2'>
-              {Array.from({ length: 20 }).map((_, index) => (
-                <Skeleton.Button
-                  key={`model-${index}`}
-                  style={{
-                    width: 100 + Math.random() * 100,
-                    height: 32,
-                    borderRadius: 16,
-                    margin: '4px',
-                  }}
-                />
-              ))}
-            </div>
-          </div>
-        ) : models.length === 0 ? (
-          <div className='py-8'>
-            <Empty
-              image={
-                <IllustrationNoContent style={{ width: 150, height: 150 }} />
-              }
-              darkModeImage={
-                <IllustrationNoContentDark
-                  style={{ width: 150, height: 150 }}
-                />
-              }
-              description={t('没有可用模型')}
-              style={{ padding: '24px 0' }}
-            />
-          </div>
-        ) : (
-          <>
-            {/* 模型分类标签页 */}
-            <div className='mb-4'>
-              <Tabs
-                type='card'
-                activeKey={activeModelCategory}
-                onChange={(key) => 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 (
-                      <TabPane
-                        tab={
-                          <span className='flex items-center gap-2'>
-                            {category.icon && (
-                              <span className='w-4 h-4'>{category.icon}</span>
-                            )}
-                            {category.label}
-                            <Tag
-                              color={
-                                activeModelCategory === key ? 'red' : 'grey'
-                              }
-                              size='small'
-                              shape='circle'
-                            >
-                              {modelCount}
-                            </Tag>
-                          </span>
-                        }
-                        itemKey={key}
-                        key={key}
-                      />
-                    );
-                  },
-                )}
-              </Tabs>
-            </div>
-
-            <div className='bg-white dark:bg-gray-700 rounded-lg p-3'>
-              {(() => {
-                // 根据当前选中的分类过滤模型
-                const categories = getModelCategories(t);
-                const filteredModels =
-                  activeModelCategory === 'all'
-                    ? models
-                    : models.filter((model) =>
-                        categories[activeModelCategory].filter({
-                          model_name: model,
-                        }),
-                      );
-
-                // 如果过滤后没有模型,显示空状态
-                if (filteredModels.length === 0) {
-                  return (
-                    <Empty
-                      image={
-                        <IllustrationNoContent
-                          style={{ width: 120, height: 120 }}
-                        />
-                      }
-                      darkModeImage={
-                        <IllustrationNoContentDark
-                          style={{ width: 120, height: 120 }}
-                        />
-                      }
-                      description={t('该分类下没有可用模型')}
-                      style={{ padding: '16px 0' }}
-                    />
-                  );
-                }
-
-                if (filteredModels.length <= MODELS_DISPLAY_COUNT) {
-                  return (
-                    <Space wrap>
-                      {filteredModels.map((model) =>
-                        renderModelTag(model, {
-                          size: 'small',
-                          shape: 'circle',
-                          onClick: () => copyText(model),
-                        }),
-                      )}
-                    </Space>
-                  );
-                } else {
-                  return (
-                    <>
-                      <Collapsible isOpen={isModelsExpanded}>
-                        <Space wrap>
-                          {filteredModels.map((model) =>
-                            renderModelTag(model, {
-                              size: 'small',
-                              shape: 'circle',
-                              onClick: () => copyText(model),
-                            }),
-                          )}
-                          <Tag
-                            color='grey'
-                            type='light'
-                            className='cursor-pointer !rounded-lg'
-                            onClick={() => setIsModelsExpanded(false)}
-                            icon={<IconChevronUp />}
-                          >
-                            {t('收起')}
-                          </Tag>
-                        </Space>
-                      </Collapsible>
-                      {!isModelsExpanded && (
-                        <Space wrap>
-                          {filteredModels
-                            .slice(0, MODELS_DISPLAY_COUNT)
-                            .map((model) =>
-                              renderModelTag(model, {
-                                size: 'small',
-                                shape: 'circle',
-                                onClick: () => copyText(model),
-                              }),
-                            )}
-                          <Tag
-                            color='grey'
-                            type='light'
-                            className='cursor-pointer !rounded-lg'
-                            onClick={() => setIsModelsExpanded(true)}
-                            icon={<IconChevronDown />}
-                          >
-                            {t('更多')}{' '}
-                            {filteredModels.length - MODELS_DISPLAY_COUNT}{' '}
-                            {t('个模型')}
-                          </Tag>
-                        </Space>
-                      )}
-                    </>
-                  );
-                }
-              })()}
-            </div>
-          </>
-        )}
-      </div>
-    </div>
-  );
-};
-
-export default ModelsList;

+ 0 - 44
web/src/components/table/models/ModelsDescription.jsx

@@ -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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-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 (
-    <div className='flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full'>
-      <div className='flex items-center text-green-500'>
-        <Layers size={16} className='mr-2' />
-        <Text>{t('模型管理')}</Text>
-      </div>
-
-      <CompactModeToggle
-        compactMode={compactMode}
-        setCompactMode={setCompactMode}
-        t={t}
-      />
-    </div>
-  );
-};
-
-export default ModelsDescription;

+ 0 - 123
web/src/components/table/users/modals/BindSubscriptionModal.jsx

@@ -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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-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 (
-    <Modal
-      title={t('绑定订阅套餐')}
-      visible={visible}
-      onCancel={onCancel}
-      onOk={bind}
-      confirmLoading={loading}
-      maskClosable={false}
-      centered
-    >
-      <Space vertical style={{ width: '100%' }} spacing='medium'>
-        <div className='text-sm'>
-          <Text strong>{t('用户')}:</Text>
-          <Text>{user?.username}</Text>
-          <Text type='tertiary'> (ID: {user?.id})</Text>
-        </div>
-        <Select
-          placeholder={t('选择订阅套餐')}
-          optionList={planOptions}
-          value={selectedPlanId}
-          onChange={setSelectedPlanId}
-          loading={loading}
-          filter
-          style={{ width: '100%' }}
-        />
-        <div className='text-xs text-gray-500'>
-          {t('绑定后会立即生成用户订阅(无需支付),有效期按套餐配置计算。')}
-        </div>
-      </Space>
-    </Modal>
-  );
-};
-
-export default BindSubscriptionModal;

+ 0 - 312
web/src/hooks/model-deployments/useDeploymentResources.js

@@ -1,312 +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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-import { useState, useCallback } from 'react';
-import { API } from '../../helpers';
-import { showError } from '../../helpers';
-
-export const useDeploymentResources = () => {
-  const [hardwareTypes, setHardwareTypes] = useState([]);
-  const [hardwareTotalAvailable, setHardwareTotalAvailable] = useState(0);
-  const [locations, setLocations] = useState([]);
-  const [locationsTotalAvailable, setLocationsTotalAvailable] = useState(0);
-  const [availableReplicas, setAvailableReplicas] = useState([]);
-  const [priceEstimation, setPriceEstimation] = useState(null);
-
-  const [loadingHardware, setLoadingHardware] = useState(false);
-  const [loadingLocations, setLoadingLocations] = useState(false);
-  const [loadingReplicas, setLoadingReplicas] = useState(false);
-  const [loadingPrice, setLoadingPrice] = useState(false);
-
-  const fetchHardwareTypes = useCallback(async () => {
-    try {
-      setLoadingHardware(true);
-      const response = await API.get('/api/deployments/hardware-types');
-      if (response.data.success) {
-        const { hardware_types: hardwareList = [], total_available } =
-          response.data.data || {};
-        const normalizedHardware = hardwareList.map((hardware) => {
-          const availableCountValue = Number(hardware.available_count);
-          const availableCount = Number.isNaN(availableCountValue)
-            ? 0
-            : availableCountValue;
-          const availableBool =
-            typeof hardware.available === 'boolean'
-              ? hardware.available
-              : availableCount > 0;
-
-          return {
-            ...hardware,
-            available: availableBool,
-            available_count: availableCount,
-          };
-        });
-
-        const providedTotal = Number(total_available);
-        const fallbackTotal = normalizedHardware.reduce(
-          (acc, item) =>
-            acc +
-            (Number.isNaN(item.available_count) ? 0 : item.available_count),
-          0,
-        );
-        const hasProvidedTotal =
-          total_available !== undefined &&
-          total_available !== null &&
-          total_available !== '' &&
-          !Number.isNaN(providedTotal);
-
-        setHardwareTypes(normalizedHardware);
-        setHardwareTotalAvailable(
-          hasProvidedTotal ? providedTotal : fallbackTotal,
-        );
-        return normalizedHardware;
-      } else {
-        showError('获取硬件类型失败: ' + response.data.message);
-        setHardwareTotalAvailable(0);
-        return [];
-      }
-    } catch (error) {
-      showError('获取硬件类型失败: ' + error.message);
-      setHardwareTotalAvailable(0);
-      return [];
-    } finally {
-      setLoadingHardware(false);
-    }
-  }, []);
-
-  const fetchLocations = useCallback(async (hardwareId, gpuCount = 1) => {
-    if (!hardwareId) {
-      setLocations([]);
-      setLocationsTotalAvailable(0);
-      return [];
-    }
-
-    try {
-      setLoadingLocations(true);
-      const response = await API.get(
-        `/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}`,
-      );
-      if (response.data.success) {
-        const replicas = response.data.data?.replicas || [];
-        const nextLocationsMap = new Map();
-        replicas.forEach((replica) => {
-          const rawId = replica?.location_id ?? replica?.location?.id;
-          if (rawId === null || rawId === undefined) return;
-
-          const mapKey = String(rawId);
-          if (nextLocationsMap.has(mapKey)) return;
-
-          const rawIso2 =
-            replica?.iso2 ?? replica?.location_iso2 ?? replica?.location?.iso2;
-          const iso2 = rawIso2 ? String(rawIso2).toUpperCase() : '';
-          const name =
-            replica?.location_name ??
-            replica?.location?.name ??
-            replica?.name ??
-            String(rawId);
-
-          nextLocationsMap.set(mapKey, {
-            id: rawId,
-            name: String(name),
-            iso2,
-            region:
-              replica?.region ??
-              replica?.location_region ??
-              replica?.location?.region,
-            country:
-              replica?.country ??
-              replica?.location_country ??
-              replica?.location?.country,
-            code:
-              replica?.code ??
-              replica?.location_code ??
-              replica?.location?.code,
-            available: Number(replica?.available_count) || 0,
-          });
-        });
-
-        const normalizedLocations = Array.from(nextLocationsMap.values());
-        setLocations(normalizedLocations);
-        setLocationsTotalAvailable(
-          normalizedLocations.reduce(
-            (acc, item) => acc + (item.available || 0),
-            0,
-          ),
-        );
-        return normalizedLocations;
-      } else {
-        showError('获取部署位置失败: ' + response.data.message);
-        setLocationsTotalAvailable(0);
-        return [];
-      }
-    } catch (error) {
-      showError('获取部署位置失败: ' + error.message);
-      setLocationsTotalAvailable(0);
-      return [];
-    } finally {
-      setLoadingLocations(false);
-    }
-  }, []);
-
-  const fetchAvailableReplicas = useCallback(
-    async (hardwareId, gpuCount = 1) => {
-      if (!hardwareId) {
-        setAvailableReplicas([]);
-        return [];
-      }
-
-      try {
-        setLoadingReplicas(true);
-        const response = await API.get(
-          `/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}`,
-        );
-        if (response.data.success) {
-          const replicas = response.data.data.replicas || [];
-          setAvailableReplicas(replicas);
-          return replicas;
-        } else {
-          showError('获取可用资源失败: ' + response.data.message);
-          setAvailableReplicas([]);
-          return [];
-        }
-      } catch (error) {
-        console.error('Load available replicas error:', error);
-        setAvailableReplicas([]);
-        return [];
-      } finally {
-        setLoadingReplicas(false);
-      }
-    },
-    [],
-  );
-
-  const calculatePrice = useCallback(async (params) => {
-    const {
-      locationIds,
-      hardwareId,
-      gpusPerContainer,
-      durationHours,
-      replicaCount,
-    } = params;
-
-    if (
-      !locationIds?.length ||
-      !hardwareId ||
-      !gpusPerContainer ||
-      !durationHours ||
-      !replicaCount
-    ) {
-      setPriceEstimation(null);
-      return null;
-    }
-
-    try {
-      setLoadingPrice(true);
-      const requestData = {
-        location_ids: locationIds,
-        hardware_id: hardwareId,
-        gpus_per_container: gpusPerContainer,
-        duration_hours: durationHours,
-        replica_count: replicaCount,
-      };
-
-      const response = await API.post(
-        '/api/deployments/price-estimation',
-        requestData,
-      );
-      if (response.data.success) {
-        const estimation = response.data.data;
-        setPriceEstimation(estimation);
-        return estimation;
-      } else {
-        showError('价格计算失败: ' + response.data.message);
-        setPriceEstimation(null);
-        return null;
-      }
-    } catch (error) {
-      console.error('Price calculation error:', error);
-      setPriceEstimation(null);
-      return null;
-    } finally {
-      setLoadingPrice(false);
-    }
-  }, []);
-
-  const checkClusterNameAvailability = useCallback(async (name) => {
-    if (!name?.trim()) return false;
-
-    try {
-      const response = await API.get(
-        `/api/deployments/check-name?name=${encodeURIComponent(name.trim())}`,
-      );
-      if (response.data.success) {
-        return response.data.data.available;
-      } else {
-        showError('检查名称可用性失败: ' + response.data.message);
-        return false;
-      }
-    } catch (error) {
-      console.error('Check cluster name availability error:', error);
-      return false;
-    }
-  }, []);
-
-  const createDeployment = useCallback(async (deploymentData) => {
-    try {
-      const response = await API.post('/api/deployments', deploymentData);
-      if (response.data.success) {
-        return response.data.data;
-      } else {
-        throw new Error(response.data.message || '创建部署失败');
-      }
-    } catch (error) {
-      throw error;
-    }
-  }, []);
-
-  return {
-    // Data
-    hardwareTypes,
-    hardwareTotalAvailable,
-    locations,
-    locationsTotalAvailable,
-    availableReplicas,
-    priceEstimation,
-
-    // Loading states
-    loadingHardware,
-    loadingLocations,
-    loadingReplicas,
-    loadingPrice,
-
-    // Functions
-    fetchHardwareTypes,
-    fetchLocations,
-    fetchAvailableReplicas,
-    calculatePrice,
-    checkClusterNameAvailability,
-    createDeployment,
-
-    // Clear functions
-    clearPriceEstimation: () => setPriceEstimation(null),
-    clearAvailableReplicas: () => setAvailableReplicas([]),
-  };
-};
-
-export default useDeploymentResources;

+ 0 - 286
web/src/hooks/model-deployments/useEnhancedDeploymentActions.jsx

@@ -1,286 +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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-import { useState } from 'react';
-import { API, showError, showSuccess } from '../../helpers';
-
-export const useEnhancedDeploymentActions = (t) => {
-  const [loading, setLoading] = useState({});
-
-  // Set loading state for specific operation
-  const setOperationLoading = (operation, deploymentId, isLoading) => {
-    setLoading((prev) => ({
-      ...prev,
-      [`${operation}_${deploymentId}`]: isLoading,
-    }));
-  };
-
-  // Get loading state for specific operation
-  const isOperationLoading = (operation, deploymentId) => {
-    return loading[`${operation}_${deploymentId}`] || false;
-  };
-
-  // Extend deployment duration
-  const extendDeployment = async (deploymentId, durationHours) => {
-    try {
-      setOperationLoading('extend', deploymentId, true);
-
-      const response = await API.post(
-        `/api/deployments/${deploymentId}/extend`,
-        {
-          duration_hours: durationHours,
-        },
-      );
-
-      if (response.data.success) {
-        showSuccess(t('容器时长延长成功'));
-        return response.data.data;
-      }
-    } catch (error) {
-      showError(
-        t('延长时长失败') +
-          ': ' +
-          (error.response?.data?.message || error.message),
-      );
-      throw error;
-    } finally {
-      setOperationLoading('extend', deploymentId, false);
-    }
-  };
-
-  // Get deployment details
-  const getDeploymentDetails = async (deploymentId) => {
-    try {
-      setOperationLoading('details', deploymentId, true);
-
-      const response = await API.get(`/api/deployments/${deploymentId}`);
-
-      if (response.data.success) {
-        return response.data.data;
-      }
-    } catch (error) {
-      showError(
-        t('获取详情失败') +
-          ': ' +
-          (error.response?.data?.message || error.message),
-      );
-      throw error;
-    } finally {
-      setOperationLoading('details', deploymentId, false);
-    }
-  };
-
-  // Get deployment logs
-  const getDeploymentLogs = async (deploymentId, options = {}) => {
-    try {
-      setOperationLoading('logs', deploymentId, true);
-
-      const params = new URLSearchParams();
-
-      if (options.containerId)
-        params.append('container_id', options.containerId);
-      if (options.level) params.append('level', options.level);
-      if (options.limit) params.append('limit', options.limit.toString());
-      if (options.cursor) params.append('cursor', options.cursor);
-      if (options.follow) params.append('follow', 'true');
-      if (options.startTime) params.append('start_time', options.startTime);
-      if (options.endTime) params.append('end_time', options.endTime);
-
-      const response = await API.get(
-        `/api/deployments/${deploymentId}/logs?${params}`,
-      );
-
-      if (response.data.success) {
-        return response.data.data;
-      }
-    } catch (error) {
-      showError(
-        t('获取日志失败') +
-          ': ' +
-          (error.response?.data?.message || error.message),
-      );
-      throw error;
-    } finally {
-      setOperationLoading('logs', deploymentId, false);
-    }
-  };
-
-  // Update deployment configuration
-  const updateDeploymentConfig = async (deploymentId, config) => {
-    try {
-      setOperationLoading('config', deploymentId, true);
-
-      const response = await API.put(
-        `/api/deployments/${deploymentId}`,
-        config,
-      );
-
-      if (response.data.success) {
-        showSuccess(t('容器配置更新成功'));
-        return response.data.data;
-      }
-    } catch (error) {
-      showError(
-        t('更新配置失败') +
-          ': ' +
-          (error.response?.data?.message || error.message),
-      );
-      throw error;
-    } finally {
-      setOperationLoading('config', deploymentId, false);
-    }
-  };
-
-  // Delete (destroy) deployment
-  const deleteDeployment = async (deploymentId) => {
-    try {
-      setOperationLoading('delete', deploymentId, true);
-
-      const response = await API.delete(`/api/deployments/${deploymentId}`);
-
-      if (response.data.success) {
-        showSuccess(t('容器销毁请求已提交'));
-        return response.data.data;
-      }
-    } catch (error) {
-      showError(
-        t('销毁容器失败') +
-          ': ' +
-          (error.response?.data?.message || error.message),
-      );
-      throw error;
-    } finally {
-      setOperationLoading('delete', deploymentId, false);
-    }
-  };
-
-  // Update deployment name
-  const updateDeploymentName = async (deploymentId, newName) => {
-    try {
-      setOperationLoading('rename', deploymentId, true);
-
-      const response = await API.put(`/api/deployments/${deploymentId}/name`, {
-        name: newName,
-      });
-
-      if (response.data.success) {
-        showSuccess(t('容器名称更新成功'));
-        return response.data.data;
-      }
-    } catch (error) {
-      showError(
-        t('更新名称失败') +
-          ': ' +
-          (error.response?.data?.message || error.message),
-      );
-      throw error;
-    } finally {
-      setOperationLoading('rename', deploymentId, false);
-    }
-  };
-
-  // Batch operations
-  const batchDelete = async (deploymentIds) => {
-    try {
-      setOperationLoading('batch_delete', 'all', true);
-
-      const results = await Promise.allSettled(
-        deploymentIds.map((id) => deleteDeployment(id)),
-      );
-
-      const successful = results.filter((r) => r.status === 'fulfilled').length;
-      const failed = results.filter((r) => r.status === 'rejected').length;
-
-      if (successful > 0) {
-        showSuccess(
-          t('批量操作完成: {{success}}个成功, {{failed}}个失败', {
-            success: successful,
-            failed: failed,
-          }),
-        );
-      }
-
-      return { successful, failed };
-    } catch (error) {
-      showError(t('批量操作失败') + ': ' + error.message);
-      throw error;
-    } finally {
-      setOperationLoading('batch_delete', 'all', false);
-    }
-  };
-
-  // Export logs
-  const exportLogs = async (deploymentId, options = {}) => {
-    try {
-      setOperationLoading('export_logs', deploymentId, true);
-
-      const logs = await getDeploymentLogs(deploymentId, {
-        ...options,
-        limit: 10000, // Get more logs for export
-      });
-
-      if (logs && logs.logs) {
-        const logText = logs.logs
-          .map(
-            (log) =>
-              `[${new Date(log.timestamp).toISOString()}] [${log.level}] ${log.source ? `[${log.source}] ` : ''}${log.message}`,
-          )
-          .join('\n');
-
-        const blob = new Blob([logText], { type: 'text/plain' });
-        const url = URL.createObjectURL(blob);
-        const a = document.createElement('a');
-        a.href = url;
-        a.download = `deployment-${deploymentId}-logs-${new Date().toISOString().split('T')[0]}.txt`;
-        document.body.appendChild(a);
-        a.click();
-        document.body.removeChild(a);
-        URL.revokeObjectURL(url);
-
-        showSuccess(t('日志导出成功'));
-      }
-    } catch (error) {
-      showError(t('导出日志失败') + ': ' + error.message);
-      throw error;
-    } finally {
-      setOperationLoading('export_logs', deploymentId, false);
-    }
-  };
-
-  return {
-    // Actions
-    extendDeployment,
-    getDeploymentDetails,
-    getDeploymentLogs,
-    updateDeploymentConfig,
-    deleteDeployment,
-    updateDeploymentName,
-    batchDelete,
-    exportLogs,
-
-    // Loading states
-    isOperationLoading,
-    loading,
-
-    // Utility
-    setOperationLoading,
-  };
-};
-
-export default useEnhancedDeploymentActions;

+ 0 - 488
web/src/pages/Setting/Personal/SettingsSidebarModulesUser.jsx

@@ -1,488 +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 <https://www.gnu.org/licenses/>.
-
-For commercial licensing, please contact [email protected]
-*/
-
-import { useState, useEffect, useContext } from 'react';
-import { useTranslation } from 'react-i18next';
-import {
-  Card,
-  Button,
-  Switch,
-  Typography,
-  Row,
-  Col,
-  Avatar,
-} from '@douyinfe/semi-ui';
-import { API, showSuccess, showError } from '../../../helpers';
-import { StatusContext } from '../../../context/Status';
-import { UserContext } from '../../../context/User';
-import { useUserPermissions } from '../../../hooks/common/useUserPermissions';
-import { mergeAdminConfig, useSidebar } from '../../../hooks/common/useSidebar';
-import { Settings } from 'lucide-react';
-
-const { Text } = Typography;
-
-export default function SettingsSidebarModulesUser() {
-  const { t } = useTranslation();
-  const [loading, setLoading] = useState(false);
-  const [statusState] = useContext(StatusContext);
-
-  // 使用后端权限验证替代前端角色判断
-  const {
-    permissions,
-    loading: permissionsLoading,
-    hasSidebarSettingsPermission,
-    isSidebarSectionAllowed,
-    isSidebarModuleAllowed,
-  } = useUserPermissions();
-
-  // 使用useSidebar钩子获取刷新方法
-  const { refreshUserConfig } = useSidebar();
-
-  // 如果没有边栏设置权限,不显示此组件
-  if (!permissionsLoading && !hasSidebarSettingsPermission()) {
-    return null;
-  }
-
-  // 权限加载中,显示加载状态
-  if (permissionsLoading) {
-    return null;
-  }
-
-  // 根据用户权限生成默认配置
-  const generateDefaultConfig = () => {
-    const defaultConfig = {};
-
-    // 聊天区域 - 所有用户都可以访问
-    if (isSidebarSectionAllowed('chat')) {
-      defaultConfig.chat = {
-        enabled: true,
-        playground: isSidebarModuleAllowed('chat', 'playground'),
-        chat: isSidebarModuleAllowed('chat', 'chat'),
-      };
-    }
-
-    // 控制台区域 - 所有用户都可以访问
-    if (isSidebarSectionAllowed('console')) {
-      defaultConfig.console = {
-        enabled: true,
-        detail: isSidebarModuleAllowed('console', 'detail'),
-        token: isSidebarModuleAllowed('console', 'token'),
-        log: isSidebarModuleAllowed('console', 'log'),
-        midjourney: isSidebarModuleAllowed('console', 'midjourney'),
-        task: isSidebarModuleAllowed('console', 'task'),
-      };
-    }
-
-    // 个人中心区域 - 所有用户都可以访问
-    if (isSidebarSectionAllowed('personal')) {
-      defaultConfig.personal = {
-        enabled: true,
-        topup: isSidebarModuleAllowed('personal', 'topup'),
-        personal: isSidebarModuleAllowed('personal', 'personal'),
-      };
-    }
-
-    // 管理员区域 - 只有管理员可以访问
-    if (isSidebarSectionAllowed('admin')) {
-      defaultConfig.admin = {
-        enabled: true,
-        channel: isSidebarModuleAllowed('admin', 'channel'),
-        models: isSidebarModuleAllowed('admin', 'models'),
-        deployment: isSidebarModuleAllowed('admin', 'deployment'),
-        redemption: isSidebarModuleAllowed('admin', 'redemption'),
-        user: isSidebarModuleAllowed('admin', 'user'),
-        subscription: isSidebarModuleAllowed('admin', 'subscription'),
-        setting: isSidebarModuleAllowed('admin', 'setting'),
-      };
-    }
-
-    return defaultConfig;
-  };
-
-  // 用户个人左侧边栏模块设置
-  const [sidebarModulesUser, setSidebarModulesUser] = useState({});
-
-  // 管理员全局配置
-  const [adminConfig, setAdminConfig] = useState(null);
-
-  // 处理区域级别开关变更
-  function handleSectionChange(sectionKey) {
-    return (checked) => {
-      const newModules = {
-        ...sidebarModulesUser,
-        [sectionKey]: {
-          ...sidebarModulesUser[sectionKey],
-          enabled: checked,
-        },
-      };
-      setSidebarModulesUser(newModules);
-      console.log('用户边栏区域配置变更:', sectionKey, checked, newModules);
-    };
-  }
-
-  // 处理功能级别开关变更
-  function handleModuleChange(sectionKey, moduleKey) {
-    return (checked) => {
-      const newModules = {
-        ...sidebarModulesUser,
-        [sectionKey]: {
-          ...sidebarModulesUser[sectionKey],
-          [moduleKey]: checked,
-        },
-      };
-      setSidebarModulesUser(newModules);
-      console.log(
-        '用户边栏功能配置变更:',
-        sectionKey,
-        moduleKey,
-        checked,
-        newModules,
-      );
-    };
-  }
-
-  // 重置为默认配置(基于权限过滤)
-  function resetSidebarModules() {
-    const defaultConfig = generateDefaultConfig();
-    setSidebarModulesUser(defaultConfig);
-    showSuccess(t('已重置为默认配置'));
-    console.log('用户边栏配置重置为默认:', defaultConfig);
-  }
-
-  // 保存配置
-  async function onSubmit() {
-    setLoading(true);
-    try {
-      console.log('保存用户边栏配置:', sidebarModulesUser);
-      const res = await API.put('/api/user/self', {
-        sidebar_modules: JSON.stringify(sidebarModulesUser),
-      });
-      const { success, message } = res.data;
-      if (success) {
-        showSuccess(t('保存成功'));
-        console.log('用户边栏配置保存成功');
-
-        // 刷新useSidebar钩子中的用户配置,实现实时更新
-        await refreshUserConfig();
-        console.log('用户边栏配置已刷新,边栏将立即更新');
-      } else {
-        showError(message);
-        console.error('用户边栏配置保存失败:', message);
-      }
-    } catch (error) {
-      showError(t('保存失败,请重试'));
-      console.error('用户边栏配置保存异常:', error);
-    } finally {
-      setLoading(false);
-    }
-  }
-
-  // 统一的配置加载逻辑
-  useEffect(() => {
-    const loadConfigs = async () => {
-      try {
-        // 获取管理员全局配置
-        if (statusState?.status?.SidebarModulesAdmin) {
-          try {
-            const adminConf = JSON.parse(
-              statusState.status.SidebarModulesAdmin,
-            );
-            const mergedAdminConf = mergeAdminConfig(adminConf);
-            setAdminConfig(mergedAdminConf);
-            console.log('加载管理员边栏配置:', mergedAdminConf);
-          } catch (error) {
-            const mergedAdminConf = mergeAdminConfig(null);
-            setAdminConfig(mergedAdminConf);
-            console.log(
-              '加载管理员边栏配置失败,使用默认配置:',
-              mergedAdminConf,
-            );
-          }
-        } else {
-          const mergedAdminConf = mergeAdminConfig(null);
-          setAdminConfig(mergedAdminConf);
-          console.log('管理员边栏配置缺失,使用默认配置:', mergedAdminConf);
-        }
-
-        // 获取用户个人配置
-        const userRes = await API.get('/api/user/self');
-        if (userRes.data.success && userRes.data.data.sidebar_modules) {
-          let userConf;
-          // 检查sidebar_modules是字符串还是对象
-          if (typeof userRes.data.data.sidebar_modules === 'string') {
-            userConf = JSON.parse(userRes.data.data.sidebar_modules);
-          } else {
-            userConf = userRes.data.data.sidebar_modules;
-          }
-          console.log('从API加载的用户配置:', userConf);
-
-          // 确保用户配置也经过权限过滤
-          const filteredUserConf = {};
-          Object.keys(userConf).forEach((sectionKey) => {
-            if (isSidebarSectionAllowed(sectionKey)) {
-              filteredUserConf[sectionKey] = { ...userConf[sectionKey] };
-              // 过滤不允许的模块
-              Object.keys(userConf[sectionKey]).forEach((moduleKey) => {
-                if (
-                  moduleKey !== 'enabled' &&
-                  !isSidebarModuleAllowed(sectionKey, moduleKey)
-                ) {
-                  delete filteredUserConf[sectionKey][moduleKey];
-                }
-              });
-            }
-          });
-          setSidebarModulesUser(filteredUserConf);
-          console.log('权限过滤后的用户配置:', filteredUserConf);
-        } else {
-          // 如果用户没有配置,使用权限过滤后的默认配置
-          const defaultConfig = generateDefaultConfig();
-          setSidebarModulesUser(defaultConfig);
-          console.log('用户无配置,使用默认配置:', defaultConfig);
-        }
-      } catch (error) {
-        console.error('加载边栏配置失败:', error);
-        // 出错时也使用默认配置
-        const defaultConfig = generateDefaultConfig();
-        setSidebarModulesUser(defaultConfig);
-      }
-    };
-
-    // 只有权限加载完成且有边栏设置权限时才加载配置
-    if (!permissionsLoading && hasSidebarSettingsPermission()) {
-      loadConfigs();
-    }
-  }, [
-    statusState,
-    permissionsLoading,
-    hasSidebarSettingsPermission,
-    isSidebarSectionAllowed,
-    isSidebarModuleAllowed,
-  ]);
-
-  // 检查功能是否被管理员允许
-  const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
-    if (!adminConfig) return true;
-
-    if (moduleKey) {
-      return (
-        adminConfig[sectionKey]?.enabled && adminConfig[sectionKey]?.[moduleKey]
-      );
-    } else {
-      return adminConfig[sectionKey]?.enabled;
-    }
-  };
-
-  // 区域配置数据(根据后端权限过滤)
-  const sectionConfigs = [
-    {
-      key: 'chat',
-      title: t('聊天区域'),
-      description: t('操练场和聊天功能'),
-      modules: [
-        {
-          key: 'playground',
-          title: t('操练场'),
-          description: t('AI模型测试环境'),
-        },
-        { key: 'chat', title: t('聊天'), description: t('聊天会话管理') },
-      ],
-    },
-    {
-      key: 'console',
-      title: t('控制台区域'),
-      description: t('数据管理和日志查看'),
-      modules: [
-        { key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
-        { key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
-        { key: 'log', title: t('使用日志'), description: t('API使用记录') },
-        {
-          key: 'midjourney',
-          title: t('绘图日志'),
-          description: t('绘图任务记录'),
-        },
-        { key: 'task', title: t('任务日志'), description: t('系统任务记录') },
-      ],
-    },
-    {
-      key: 'personal',
-      title: t('个人中心区域'),
-      description: t('用户个人功能'),
-      modules: [
-        { key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
-        {
-          key: 'personal',
-          title: t('个人设置'),
-          description: t('个人信息设置'),
-        },
-      ],
-    },
-    {
-      key: 'admin',
-      title: t('管理员区域'),
-      description: t('系统管理功能'),
-      modules: [
-        { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
-        { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
-        {
-          key: 'deployment',
-          title: t('模型部署'),
-          description: t('模型部署管理'),
-        },
-        {
-          key: 'subscription',
-          title: t('订阅管理'),
-          description: t('订阅套餐管理'),
-        },
-        {
-          key: 'redemption',
-          title: t('兑换码管理'),
-          description: t('兑换码生成管理'),
-        },
-        { key: 'user', title: t('用户管理'), description: t('用户账户管理') },
-        {
-          key: 'setting',
-          title: t('系统设置'),
-          description: t('系统参数配置'),
-        },
-      ],
-    },
-  ]
-    .filter((section) => {
-      // 使用后端权限验证替代前端角色判断
-      return isSidebarSectionAllowed(section.key);
-    })
-    .map((section) => ({
-      ...section,
-      modules: section.modules.filter((module) =>
-        isSidebarModuleAllowed(section.key, module.key),
-      ),
-    }))
-    .filter(
-      (section) =>
-        // 过滤掉没有可用模块的区域
-        section.modules.length > 0 && isAllowedByAdmin(section.key),
-    );
-
-  return (
-    <Card className='!rounded-2xl shadow-sm border-0'>
-      {/* 卡片头部 */}
-      <div className='flex items-center mb-4'>
-        <Avatar size='small' color='purple' className='mr-3 shadow-md'>
-          <Settings size={16} />
-        </Avatar>
-        <div>
-          <Typography.Text className='text-lg font-medium'>
-            {t('左侧边栏个人设置')}
-          </Typography.Text>
-          <div className='text-xs text-gray-600'>
-            {t('个性化设置左侧边栏的显示内容')}
-          </div>
-        </div>
-      </div>
-
-      <div className='mb-4'>
-        <Text type='secondary' className='text-sm text-gray-600'>
-          {t('您可以个性化设置侧边栏的要显示功能')}
-        </Text>
-      </div>
-
-      {sectionConfigs.map((section) => (
-        <div key={section.key} className='mb-6'>
-          {/* 区域标题和总开关 */}
-          <div className='flex justify-between items-center mb-4 p-4 bg-gray-50 rounded-xl border border-gray-200'>
-            <div>
-              <div className='font-semibold text-base text-gray-900 mb-1'>
-                {section.title}
-              </div>
-              <Text className='text-xs text-gray-600'>
-                {section.description}
-              </Text>
-            </div>
-            <Switch
-              checked={sidebarModulesUser[section.key]?.enabled !== false}
-              onChange={handleSectionChange(section.key)}
-              size='default'
-            />
-          </div>
-
-          {/* 功能模块网格 */}
-          <Row gutter={[12, 12]}>
-            {section.modules.map((module) => (
-              <Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
-                <Card
-                  className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${
-                    sidebarModulesUser[section.key]?.enabled !== false
-                      ? ''
-                      : 'opacity-50'
-                  }`}
-                  bodyStyle={{ padding: '16px' }}
-                  hoverable
-                >
-                  <div className='flex justify-between items-center h-full'>
-                    <div className='flex-1 text-left'>
-                      <div className='font-semibold text-sm text-gray-900 mb-1'>
-                        {module.title}
-                      </div>
-                      <Text className='text-xs text-gray-600 leading-relaxed block'>
-                        {module.description}
-                      </Text>
-                    </div>
-                    <div className='ml-4'>
-                      <Switch
-                        checked={
-                          sidebarModulesUser[section.key]?.[module.key] !==
-                          false
-                        }
-                        onChange={handleModuleChange(section.key, module.key)}
-                        size='default'
-                        disabled={
-                          sidebarModulesUser[section.key]?.enabled === false
-                        }
-                      />
-                    </div>
-                  </div>
-                </Card>
-              </Col>
-            ))}
-          </Row>
-        </div>
-      ))}
-
-      {/* 底部按钮 */}
-      <div className='flex justify-end gap-3 mt-6 pt-4 border-t border-gray-200'>
-        <Button
-          type='tertiary'
-          onClick={resetSidebarModules}
-          className='!rounded-lg'
-        >
-          {t('重置为默认')}
-        </Button>
-        <Button
-          type='primary'
-          onClick={onSubmit}
-          loading={loading}
-          className='!rounded-lg'
-        >
-          {t('保存设置')}
-        </Button>
-      </div>
-    </Card>
-  );
-}