| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import React from 'react'; |
| import { Card, Button, Typography } from '@douyinfe/semi-ui'; |
| import { useTranslation } from 'react-i18next'; |
| import { useNavigate } from 'react-router-dom'; |
| import { Settings, Server, AlertCircle, WifiOff } from 'lucide-react'; |
|
|
| const { Title, Text } = Typography; |
|
|
| const DeploymentAccessGuard = ({ |
| children, |
| loading, |
| isEnabled, |
| connectionLoading, |
| connectionOk, |
| connectionError, |
| onRetry, |
| }) => { |
| const { t } = useTranslation(); |
| const navigate = useNavigate(); |
|
|
| const handleGoToSettings = () => { |
| navigate('/console/setting?tab=model-deployment'); |
| }; |
|
|
| if (loading) { |
| return ( |
| <div className='mt-[60px] px-2'> |
| <Card loading={true} style={{ minHeight: '400px' }}> |
| <div style={{ textAlign: 'center', padding: '50px 0' }}> |
| <Text type='secondary'>{t('加载设置中...')}</Text> |
| </div> |
| </Card> |
| </div> |
| ); |
| } |
|
|
| if (!isEnabled) { |
| return ( |
| <div |
| className='mt-[60px] px-4' |
| style={{ |
| minHeight: 'calc(100vh - 60px)', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| }} |
| > |
| <div |
| style={{ |
| maxWidth: '600px', |
| width: '100%', |
| textAlign: 'center', |
| padding: '0 20px', |
| }} |
| > |
| <Card |
| style={{ |
| padding: '60px 40px', |
| borderRadius: '16px', |
| border: '1px solid var(--semi-color-border)', |
| boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08)', |
| background: |
| 'linear-gradient(135deg, var(--semi-color-bg-0) 0%, var(--semi-color-fill-0) 100%)', |
| }} |
| > |
| {/* 图标区域 */} |
| <div style={{ marginBottom: '32px' }}> |
| <div |
| style={{ |
| display: 'inline-flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| width: '120px', |
| height: '120px', |
| borderRadius: '50%', |
| background: |
| 'linear-gradient(135deg, rgba(var(--semi-orange-4), 0.15) 0%, rgba(var(--semi-orange-5), 0.1) 100%)', |
| border: '3px solid rgba(var(--semi-orange-4), 0.3)', |
| marginBottom: '24px', |
| }} |
| > |
| <AlertCircle size={56} color='var(--semi-color-warning)' /> |
| </div> |
| </div> |
| |
| {/* 标题区域 */} |
| <div style={{ marginBottom: '24px' }}> |
| <Title |
| heading={2} |
| style={{ |
| color: 'var(--semi-color-text-0)', |
| margin: '0 0 12px 0', |
| fontSize: '28px', |
| fontWeight: '700', |
| }} |
| > |
| {t('模型部署服务未启用')} |
| </Title> |
| <Text |
| style={{ |
| fontSize: '18px', |
| lineHeight: '1.6', |
| color: 'var(--semi-color-text-1)', |
| display: 'block', |
| }} |
| > |
| {t('访问模型部署功能需要先启用 io.net 部署服务')} |
| </Text> |
| </div> |
| |
| {/* 配置要求区域 */} |
| <div |
| style={{ |
| backgroundColor: 'var(--semi-color-bg-1)', |
| padding: '24px', |
| borderRadius: '12px', |
| border: '1px solid var(--semi-color-border)', |
| margin: '32px 0', |
| boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)', |
| }} |
| > |
| <div |
| style={{ |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| gap: '12px', |
| marginBottom: '16px', |
| }} |
| > |
| <div |
| style={{ |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| width: '32px', |
| height: '32px', |
| borderRadius: '8px', |
| backgroundColor: 'rgba(var(--semi-blue-4), 0.15)', |
| }} |
| > |
| <Server size={20} color='var(--semi-color-primary)' /> |
| </div> |
| <Text |
| strong |
| style={{ |
| fontSize: '16px', |
| color: 'var(--semi-color-text-0)', |
| }} |
| > |
| {t('需要配置的项目')} |
| </Text> |
| </div> |
| |
| <div |
| style={{ |
| display: 'flex', |
| flexDirection: 'column', |
| gap: '12px', |
| alignItems: 'flex-start', |
| textAlign: 'left', |
| maxWidth: '320px', |
| margin: '0 auto', |
| }} |
| > |
| <div |
| style={{ display: 'flex', alignItems: 'center', gap: '12px' }} |
| > |
| <div |
| style={{ |
| width: '6px', |
| height: '6px', |
| borderRadius: '50%', |
| backgroundColor: 'var(--semi-color-primary)', |
| flexShrink: 0, |
| }} |
| ></div> |
| <Text |
| style={{ |
| fontSize: '15px', |
| color: 'var(--semi-color-text-1)', |
| }} |
| > |
| {t('启用 io.net 部署开关')} |
| </Text> |
| </div> |
| <div |
| style={{ display: 'flex', alignItems: 'center', gap: '12px' }} |
| > |
| <div |
| style={{ |
| width: '6px', |
| height: '6px', |
| borderRadius: '50%', |
| backgroundColor: 'var(--semi-color-primary)', |
| flexShrink: 0, |
| }} |
| ></div> |
| <Text |
| style={{ |
| fontSize: '15px', |
| color: 'var(--semi-color-text-1)', |
| }} |
| > |
| {t('配置有效的 io.net API Key')} |
| </Text> |
| </div> |
| </div> |
| </div> |
| |
| {/* 操作链接区域 */} |
| <div style={{ marginBottom: '20px' }}> |
| <div |
| onClick={handleGoToSettings} |
| style={{ |
| display: 'inline-flex', |
| alignItems: 'center', |
| gap: '8px', |
| cursor: 'pointer', |
| padding: '12px 24px', |
| borderRadius: '8px', |
| fontSize: '16px', |
| fontWeight: '500', |
| color: 'var(--semi-color-primary)', |
| background: 'var(--semi-color-fill-0)', |
| border: '1px solid var(--semi-color-border)', |
| transition: 'all 0.2s ease', |
| textDecoration: 'none', |
| }} |
| onMouseEnter={(e) => { |
| e.currentTarget.style.background = 'var(--semi-color-fill-1)'; |
| e.currentTarget.style.transform = 'translateY(-1px)'; |
| e.currentTarget.style.boxShadow = |
| '0 2px 8px rgba(0, 0, 0, 0.1)'; |
| }} |
| onMouseLeave={(e) => { |
| e.currentTarget.style.background = 'var(--semi-color-fill-0)'; |
| e.currentTarget.style.transform = 'translateY(0)'; |
| e.currentTarget.style.boxShadow = 'none'; |
| }} |
| > |
| <Settings size={18} /> |
| {t('前往设置页面')} |
| </div> |
| </div> |
| |
| {/* 底部提示 */} |
| <Text |
| type='tertiary' |
| style={{ |
| fontSize: '14px', |
| color: 'var(--semi-color-text-2)', |
| lineHeight: '1.5', |
| }} |
| > |
| {t('配置完成后刷新页面即可使用模型部署功能')} |
| </Text> |
| </Card> |
| </div> |
| </div> |
| ); |
| } |
|
|
| if (connectionLoading || (connectionOk === null && !connectionError)) { |
| return ( |
| <div className='mt-[60px] px-2'> |
| <Card loading={true} style={{ minHeight: '400px' }}> |
| <div style={{ textAlign: 'center', padding: '50px 0' }}> |
| <Text type='secondary'>{t('正在检查 io.net 连接...')}</Text> |
| </div> |
| </Card> |
| </div> |
| ); |
| } |
|
|
| if (connectionOk === false) { |
| const isExpired = connectionError?.type === 'expired'; |
| const title = isExpired ? t('接口密钥已过期') : t('无法连接 io.net'); |
| const description = isExpired |
| ? t('当前 API 密钥已过期,请在设置中更新。') |
| : t('当前配置无法连接到 io.net。'); |
| const detail = connectionError?.message || ''; |
|
|
| return ( |
| <div |
| className='mt-[60px] px-4' |
| style={{ |
| minHeight: 'calc(100vh - 60px)', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| }} |
| > |
| <div |
| style={{ |
| maxWidth: '600px', |
| width: '100%', |
| textAlign: 'center', |
| padding: '0 20px', |
| }} |
| > |
| <Card |
| style={{ |
| padding: '60px 40px', |
| borderRadius: '16px', |
| border: '1px solid var(--semi-color-border)', |
| boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08)', |
| background: |
| 'linear-gradient(135deg, var(--semi-color-bg-0) 0%, var(--semi-color-fill-0) 100%)', |
| }} |
| > |
| <div style={{ marginBottom: '32px' }}> |
| <div |
| style={{ |
| display: 'inline-flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| width: '120px', |
| height: '120px', |
| borderRadius: '50%', |
| background: |
| 'linear-gradient(135deg, rgba(var(--semi-red-4), 0.15) 0%, rgba(var(--semi-red-5), 0.1) 100%)', |
| border: '3px solid rgba(var(--semi-red-4), 0.3)', |
| marginBottom: '24px', |
| }} |
| > |
| <WifiOff size={56} color='var(--semi-color-danger)' /> |
| </div> |
| </div> |
| |
| <div style={{ marginBottom: '24px' }}> |
| <Title |
| heading={2} |
| style={{ |
| color: 'var(--semi-color-text-0)', |
| margin: '0 0 12px 0', |
| fontSize: '28px', |
| fontWeight: '700', |
| }} |
| > |
| {title} |
| </Title> |
| <Text |
| style={{ |
| fontSize: '18px', |
| lineHeight: '1.6', |
| color: 'var(--semi-color-text-1)', |
| display: 'block', |
| }} |
| > |
| {description} |
| </Text> |
| {detail ? ( |
| <Text |
| type='tertiary' |
| style={{ |
| fontSize: '14px', |
| lineHeight: '1.5', |
| display: 'block', |
| marginTop: '8px', |
| }} |
| > |
| {detail} |
| </Text> |
| ) : null} |
| </div> |
| |
| <div |
| style={{ display: 'flex', gap: '12px', justifyContent: 'center' }} |
| > |
| <Button |
| type='primary' |
| icon={<Settings size={18} />} |
| onClick={handleGoToSettings} |
| > |
| {t('前往设置')} |
| </Button> |
| {onRetry ? ( |
| <Button type='tertiary' onClick={onRetry}> |
| {t('重试连接')} |
| </Button> |
| ) : null} |
| </div> |
| </Card> |
| </div> |
| </div> |
| ); |
| } |
|
|
| return children; |
| }; |
|
|
| export default DeploymentAccessGuard; |
|
|