style:1.重构整个项目的主题颜色,样式风格采用中国风元素 2.优化更新日志逻辑,不再间隔1h自动刷新过于频繁触发403响应
This commit is contained in:
@@ -748,15 +748,15 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
|
|
||||||
// 获取步骤状态图标和颜色
|
// 获取步骤状态图标和颜色
|
||||||
const getStepStatus = (step: GenerationStep) => {
|
const getStepStatus = (step: GenerationStep) => {
|
||||||
if (step === 'completed') return { icon: <CheckCircleOutlined />, color: '#52c41a' };
|
if (step === 'completed') return { icon: <CheckCircleOutlined />, color: 'var(--color-success)' };
|
||||||
if (step === 'processing') return { icon: <LoadingOutlined />, color: '#1890ff' };
|
if (step === 'processing') return { icon: <LoadingOutlined />, color: 'var(--color-primary)' };
|
||||||
if (step === 'error') return { icon: '✗', color: '#ff4d4f' };
|
if (step === 'error') return { icon: '✗', color: 'var(--color-error)' };
|
||||||
return { icon: '○', color: '#d9d9d9' };
|
return { icon: '○', color: 'var(--color-text-quaternary)' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasError = generationSteps.worldBuilding === 'error' ||
|
const hasError = generationSteps.worldBuilding === 'error' ||
|
||||||
generationSteps.characters === 'error' ||
|
generationSteps.characters === 'error' ||
|
||||||
generationSteps.outline === 'error';
|
generationSteps.outline === 'error';
|
||||||
|
|
||||||
// 渲染生成进度页面
|
// 渲染生成进度页面
|
||||||
const renderGenerating = () => (
|
const renderGenerating = () => (
|
||||||
@@ -770,7 +770,7 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
level={isMobile ? 4 : 3}
|
level={isMobile ? 4 : 3}
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 32,
|
marginBottom: 32,
|
||||||
color: '#fff',
|
color: 'var(--color-text-primary)',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
overflowWrap: 'break-word'
|
overflowWrap: 'break-word'
|
||||||
@@ -784,8 +784,8 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
percent={progress}
|
percent={progress}
|
||||||
status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')}
|
status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')}
|
||||||
strokeColor={{
|
strokeColor={{
|
||||||
'0%': '#667eea',
|
'0%': 'var(--color-primary)',
|
||||||
'100%': '#764ba2',
|
'100%': 'var(--color-primary-active)',
|
||||||
}}
|
}}
|
||||||
style={{ marginBottom: 24 }}
|
style={{ marginBottom: 24 }}
|
||||||
/>
|
/>
|
||||||
@@ -794,7 +794,7 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
fontSize: isMobile ? 14 : 16,
|
fontSize: isMobile ? 14 : 16,
|
||||||
marginBottom: 32,
|
marginBottom: 32,
|
||||||
color: hasError ? '#ff4d4f' : '#666',
|
color: hasError ? 'var(--color-error)' : 'var(--color-text-secondary)',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
overflowWrap: 'break-word'
|
overflowWrap: 'break-word'
|
||||||
@@ -808,18 +808,18 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
background: '#fff2f0',
|
background: 'var(--color-error-bg)',
|
||||||
borderColor: '#ffccc7',
|
borderColor: 'var(--color-error-border)',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text strong style={{ color: '#ff4d4f' }}>错误详情:</Text>
|
<Text strong style={{ color: 'var(--color-error)' }}>错误详情:</Text>
|
||||||
<br />
|
<br />
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: '#666',
|
color: 'var(--color-text-secondary)',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
@@ -855,9 +855,9 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: isMobile ? '10px 12px' : '12px 20px',
|
padding: isMobile ? '10px 12px' : '12px 20px',
|
||||||
background: step === 'processing' ? '#f0f5ff' : (step === 'error' ? '#fff2f0' : '#fafafa'),
|
background: step === 'processing' ? 'var(--color-info-bg)' : (step === 'error' ? 'var(--color-error-bg)' : 'var(--color-bg-layout)'),
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
border: `1px solid ${step === 'processing' ? '#d6e4ff' : (step === 'error' ? '#ffccc7' : '#f0f0f0')}`,
|
border: `1px solid ${step === 'processing' ? 'var(--color-info-border)' : (step === 'error' ? 'var(--color-error-border)' : 'var(--color-border-secondary)')}`,
|
||||||
gap: '8px',
|
gap: '8px',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
@@ -894,7 +894,7 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
type="secondary"
|
type="secondary"
|
||||||
style={{
|
style={{
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
color: '#666',
|
color: 'var(--color-text-secondary)',
|
||||||
lineHeight: '1.6',
|
lineHeight: '1.6',
|
||||||
}}>
|
}}>
|
||||||
<p>👋 欢迎加入我们的交流群!</p>
|
<p>👋 欢迎加入我们的交流群!</p>
|
||||||
@@ -72,7 +72,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
<li>🐛 反馈问题和建议</li>
|
<li>🐛 反馈问题和建议</li>
|
||||||
<li>📚 分享创作经验和灵感</li>
|
<li>📚 分享创作经验和灵感</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p style={{ fontWeight: 600, color: '#333', marginBottom: '16px' }}>
|
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '16px' }}>
|
||||||
扫描下方二维码加入交流群:
|
扫描下方二维码加入交流群:
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,7 +83,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
gap: '24px',
|
gap: '24px',
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
background: '#f5f5f5',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
}}>
|
}}>
|
||||||
@@ -94,7 +94,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minWidth: '280px',
|
minWidth: '280px',
|
||||||
}}>
|
}}>
|
||||||
<p style={{ fontWeight: 600, color: '#333', marginBottom: '12px', fontSize: '15px' }}>
|
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '12px', fontSize: '15px' }}>
|
||||||
QQ交流群
|
QQ交流群
|
||||||
</p>
|
</p>
|
||||||
{!qqImageError ? (
|
{!qqImageError ? (
|
||||||
@@ -102,7 +102,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
@@ -128,7 +128,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
color: '#999',
|
color: '#999',
|
||||||
}}>
|
}}>
|
||||||
@@ -144,7 +144,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minWidth: '280px',
|
minWidth: '280px',
|
||||||
}}>
|
}}>
|
||||||
<p style={{ fontWeight: 600, color: '#333', marginBottom: '12px', fontSize: '15px' }}>
|
<p style={{ fontWeight: 600, color: 'var(--color-text-primary)', marginBottom: '12px', fontSize: '15px' }}>
|
||||||
微信交流群
|
微信交流群
|
||||||
</p>
|
</p>
|
||||||
{!wxImageError ? (
|
{!wxImageError ? (
|
||||||
@@ -152,7 +152,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
@@ -178,7 +178,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
color: '#999',
|
color: '#999',
|
||||||
}}>
|
}}>
|
||||||
@@ -191,11 +191,11 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '20px',
|
marginTop: '20px',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
background: '#fff7e6',
|
background: 'var(--color-warning-bg)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid #ffd591',
|
border: '1px solid var(--color-warning-border)',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
color: '#ad6800',
|
color: 'var(--color-warning)',
|
||||||
}}>
|
}}>
|
||||||
💡 提示:选择"今日内不再展示"当天不再显示,选择"永不再展示"将永久隐藏此公告
|
💡 提示:选择"今日内不再展示"当天不再显示,选择"永不再展示"将永久隐藏此公告
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ export default function AppFooter() {
|
|||||||
right: 0,
|
right: 0,
|
||||||
backdropFilter: 'blur(20px) saturate(180%)',
|
backdropFilter: 'blur(20px) saturate(180%)',
|
||||||
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
||||||
borderTop: '1px solid rgba(255, 255, 255, 0.2)',
|
borderTop: '1px solid var(--color-border)',
|
||||||
padding: isMobile ? '8px 12px' : '10px 16px',
|
padding: isMobile ? '8px 12px' : '10px 16px',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
boxShadow: '0 -4px 16px rgba(0, 0, 0, 0.1), 0 -1px 4px rgba(0, 0, 0, 0.06)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.8)', // 半透明背景以支持 backdrop-filter
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -77,29 +78,26 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
color: '#fff',
|
color: 'var(--color-primary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
||||||
cursor: hasUpdate ? 'pointer' : 'default',
|
cursor: hasUpdate ? 'pointer' : 'default',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong style={{ color: '#fff' }}>{VERSION_INFO.projectName}</strong>
|
<strong style={{ color: 'var(--color-text-primary)' }}>{VERSION_INFO.projectName}</strong>
|
||||||
<span>{getVersionString()}</span>
|
<span>{getVersionString()}</span>
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Badge>
|
</Badge>
|
||||||
<Divider type="vertical" style={{ margin: '0 4px', borderColor: 'rgba(255, 255, 255, 0.3)' }} />
|
<Divider type="vertical" style={{ margin: '0 4px', borderColor: 'var(--color-border)' }} />
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<GiftOutlined />}
|
icon={<GiftOutlined />}
|
||||||
onClick={() => window.open('https://mumuverse.space:1588/', '_blank')}
|
onClick={() => window.open('https://mumuverse.space:1588/', '_blank')}
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
color: 'var(--color-text-secondary)',
|
||||||
border: 'none',
|
|
||||||
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.4)',
|
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
height: 24,
|
height: 24,
|
||||||
padding: '0 8px',
|
padding: '0 4px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
@@ -107,7 +105,7 @@ export default function AppFooter() {
|
|||||||
>
|
>
|
||||||
赞助
|
赞助
|
||||||
</Button>
|
</Button>
|
||||||
<Divider type="vertical" style={{ margin: '0 4px', borderColor: 'rgba(255, 255, 255, 0.3)' }} />
|
<Divider type="vertical" style={{ margin: '0 4px', borderColor: 'var(--color-border)' }} />
|
||||||
<Link
|
<Link
|
||||||
href={VERSION_INFO.githubUrl}
|
href={VERSION_INFO.githubUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -117,8 +115,7 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GithubOutlined style={{ fontSize: 12 }} />
|
<GithubOutlined style={{ fontSize: 12 }} />
|
||||||
@@ -126,8 +123,7 @@ export default function AppFooter() {
|
|||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: 'rgba(255, 255, 255, 0.8)',
|
color: 'var(--color-text-tertiary)',
|
||||||
textShadow: '0 1px 2px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ClockCircleOutlined style={{ fontSize: 10, marginRight: 4 }} />
|
<ClockCircleOutlined style={{ fontSize: 10, marginRight: 4 }} />
|
||||||
@@ -139,7 +135,7 @@ export default function AppFooter() {
|
|||||||
<Space
|
<Space
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
size={12}
|
size={12}
|
||||||
split={<Divider type="vertical" style={{ borderColor: 'rgba(255, 255, 255, 0.3)' }} />}
|
split={<Divider type="vertical" style={{ borderColor: 'var(--color-border)' }} />}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -156,8 +152,8 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 6,
|
gap: 6,
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
textShadow: 'none',
|
||||||
cursor: hasUpdate ? 'pointer' : 'default',
|
cursor: hasUpdate ? 'pointer' : 'default',
|
||||||
transition: 'all 0.3s',
|
transition: 'all 0.3s',
|
||||||
}}
|
}}
|
||||||
@@ -172,7 +168,7 @@ export default function AppFooter() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong style={{ color: '#fff' }}>{VERSION_INFO.projectName}</strong>
|
<strong style={{ color: 'var(--color-text-primary)' }}>{VERSION_INFO.projectName}</strong>
|
||||||
<span>{getVersionString()}</span>
|
<span>{getVersionString()}</span>
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -188,8 +184,7 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 6,
|
gap: 6,
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GithubOutlined style={{ fontSize: 13 }} />
|
<GithubOutlined style={{ fontSize: 13 }} />
|
||||||
@@ -203,8 +198,7 @@ export default function AppFooter() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
style={{
|
style={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
LinuxDO 社区
|
LinuxDO 社区
|
||||||
@@ -216,9 +210,9 @@ export default function AppFooter() {
|
|||||||
icon={<GiftOutlined style={{ fontSize: 14 }} />}
|
icon={<GiftOutlined style={{ fontSize: 14 }} />}
|
||||||
onClick={() => window.open('https://mumuverse.space:1588/', '_blank')}
|
onClick={() => window.open('https://mumuverse.space:1588/', '_blank')}
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.5)',
|
boxShadow: '0 4px 12px rgba(77, 128, 136, 0.3)',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
height: 32,
|
height: 32,
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
@@ -250,8 +244,7 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 6,
|
gap: 6,
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyrightOutlined style={{ fontSize: 11 }} />
|
<CopyrightOutlined style={{ fontSize: 11 }} />
|
||||||
@@ -265,8 +258,7 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
color: 'rgba(255, 255, 255, 0.9)',
|
color: 'var(--color-text-tertiary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ClockCircleOutlined style={{ fontSize: 12 }} />
|
<ClockCircleOutlined style={{ fontSize: 12 }} />
|
||||||
@@ -280,12 +272,12 @@ export default function AppFooter() {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
color: '#fff',
|
color: 'var(--color-text-secondary)',
|
||||||
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
|
textShadow: '0 1px 3px rgba(0, 0, 0, 0.05)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>Made with</span>
|
<span>Made with</span>
|
||||||
<HeartFilled style={{ color: '#ff4d4f', fontSize: 11 }} />
|
<HeartFilled style={{ color: 'var(--color-error)', fontSize: 11 }} />
|
||||||
<span>by {VERSION_INFO.author}</span>
|
<span>by {VERSION_INFO.author}</span>
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const cardStyles = {
|
|||||||
// height: 320,
|
// height: 320,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
borderColor: '#1890ff',
|
borderColor: 'var(--color-info)',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
} as CSSProperties,
|
} as CSSProperties,
|
||||||
|
|
||||||
@@ -27,19 +27,20 @@ export const cardStyles = {
|
|||||||
// height: 320,
|
// height: 320,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
borderColor: '#52c41a',
|
borderColor: 'var(--color-success)',
|
||||||
backgroundColor: '#f6ffed',
|
backgroundColor: 'var(--color-bg-base)', // 使用柔和的背景色
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
} as CSSProperties,
|
} as CSSProperties,
|
||||||
|
|
||||||
// 项目卡片样式
|
// 项目卡片样式 - 现代化设计
|
||||||
project: {
|
project: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
borderRadius: 16,
|
borderRadius: 20,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.08)',
|
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04)',
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
border: '1px solid rgba(0, 0, 0, 0.04)',
|
||||||
} as CSSProperties,
|
} as CSSProperties,
|
||||||
|
|
||||||
// 卡片内容区域样式
|
// 卡片内容区域样式
|
||||||
@@ -73,17 +74,17 @@ export const cardStyles = {
|
|||||||
} as CSSProperties),
|
} as CSSProperties),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 卡片悬浮动画
|
// 卡片悬浮动画 - 增强版
|
||||||
export const cardHoverHandlers = {
|
export const cardHoverHandlers = {
|
||||||
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
|
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
target.style.transform = 'translateY(-8px)';
|
target.style.transform = 'translateY(-10px) scale(1.01)';
|
||||||
target.style.boxShadow = '0 12px 32px rgba(0, 0, 0, 0.15)';
|
target.style.boxShadow = '0 20px 40px rgba(77, 128, 136, 0.2), 0 8px 16px rgba(0, 0, 0, 0.08)';
|
||||||
},
|
},
|
||||||
onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
|
onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
target.style.transform = 'translateY(0)';
|
target.style.transform = 'translateY(0) scale(1)';
|
||||||
target.style.boxShadow = '0 4px 16px rgba(0, 0, 0, 0.08)';
|
target.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04)';
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
fetchChangelog,
|
fetchChangelog,
|
||||||
groupChangelogByDate,
|
groupChangelogByDate,
|
||||||
getCachedChangelog,
|
|
||||||
cacheChangelog,
|
cacheChangelog,
|
||||||
markChangelogFetched,
|
|
||||||
shouldFetchChangelog,
|
|
||||||
clearChangelogCache,
|
clearChangelogCache,
|
||||||
type ChangelogEntry,
|
type ChangelogEntry,
|
||||||
} from '../services/changelogService';
|
} from '../services/changelogService';
|
||||||
@@ -50,33 +47,13 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
|
||||||
// 加载更新日志
|
// 加载更新日志
|
||||||
|
// 每次用户打开窗口时才同步获取最新数据,不自动刷新
|
||||||
const loadChangelog = async (pageNum: number = 1, append: boolean = false) => {
|
const loadChangelog = async (pageNum: number = 1, append: boolean = false) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 如果是第一页,先尝试使用缓存
|
// 每次打开都从网络获取最新数据
|
||||||
if (pageNum === 1 && !append) {
|
|
||||||
const cached = getCachedChangelog();
|
|
||||||
if (cached && cached.length > 0) {
|
|
||||||
setChangelog(cached);
|
|
||||||
|
|
||||||
// 后台刷新
|
|
||||||
if (shouldFetchChangelog()) {
|
|
||||||
fetchChangelog(pageNum, 30)
|
|
||||||
.then(entries => {
|
|
||||||
setChangelog(entries);
|
|
||||||
cacheChangelog(entries);
|
|
||||||
markChangelogFetched();
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = await fetchChangelog(pageNum, 30);
|
const entries = await fetchChangelog(pageNum, 30);
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
@@ -86,10 +63,9 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
setChangelog(prev => [...prev, ...entries]);
|
setChangelog(prev => [...prev, ...entries]);
|
||||||
} else {
|
} else {
|
||||||
setChangelog(entries);
|
setChangelog(entries);
|
||||||
// 缓存第一页数据
|
// 缓存第一页数据(用于分页加载时的数据持久化)
|
||||||
if (pageNum === 1) {
|
if (pageNum === 1) {
|
||||||
cacheChangelog(entries);
|
cacheChangelog(entries);
|
||||||
markChangelogFetched();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,10 +156,10 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
<div style={{
|
<div style={{
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
background: '#fff2e8',
|
background: 'var(--color-error-bg)',
|
||||||
border: '1px solid #ffbb96',
|
border: '1px solid var(--color-error-border)',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
color: '#d4380d',
|
color: 'var(--color-error)',
|
||||||
}}>
|
}}>
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
@@ -205,10 +181,10 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
<div style={{
|
<div style={{
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: '#1890ff',
|
color: 'var(--color-primary)',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
paddingBottom: '8px',
|
paddingBottom: '8px',
|
||||||
borderBottom: '2px solid #e8e8e8',
|
borderBottom: '2px solid var(--color-border-secondary)',
|
||||||
}}>
|
}}>
|
||||||
<ClockCircleOutlined style={{ marginRight: '8px' }} />
|
<ClockCircleOutlined style={{ marginRight: '8px' }} />
|
||||||
{formatDate(date)}
|
{formatDate(date)}
|
||||||
@@ -226,8 +202,8 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
width: '24px',
|
width: '24px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
border: `2px solid ${config.color === 'default' ? '#d9d9d9' : config.color}`,
|
border: `2px solid ${config.color === 'default' ? 'var(--color-border)' : config.color}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -245,7 +221,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
{entry.scope && (
|
{entry.scope && (
|
||||||
<Tag color="blue">{entry.scope}</Tag>
|
<Tag color="blue">{entry.scope}</Tag>
|
||||||
)}
|
)}
|
||||||
<span style={{ color: '#999', fontSize: '12px' }}>
|
<span style={{ color: 'var(--color-text-tertiary)', fontSize: '12px' }}>
|
||||||
{formatTime(entry.date)}
|
{formatTime(entry.date)}
|
||||||
</span>
|
</span>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -254,7 +230,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
marginTop: '8px',
|
marginTop: '8px',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
lineHeight: '1.6',
|
lineHeight: '1.6',
|
||||||
color: '#333',
|
color: 'var(--color-text-primary)',
|
||||||
}}>
|
}}>
|
||||||
{entry.message}
|
{entry.message}
|
||||||
</div>
|
</div>
|
||||||
@@ -263,7 +239,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
{entry.author.avatar && (
|
{entry.author.avatar && (
|
||||||
<Avatar size="small" src={entry.author.avatar} />
|
<Avatar size="small" src={entry.author.avatar} />
|
||||||
)}
|
)}
|
||||||
<span style={{ color: '#666', fontSize: '13px' }}>
|
<span style={{ color: 'var(--color-text-secondary)', fontSize: '13px' }}>
|
||||||
{entry.author.username || entry.author.name}
|
{entry.author.username || entry.author.name}
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
@@ -284,42 +260,46 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{hasMore && (
|
{
|
||||||
<div style={{ textAlign: 'center', marginTop: '24px' }}>
|
hasMore && (
|
||||||
<Button
|
<div style={{ textAlign: 'center', marginTop: '24px' }}>
|
||||||
type="default"
|
<Button
|
||||||
onClick={handleLoadMore}
|
type="default"
|
||||||
loading={loading}
|
onClick={handleLoadMore}
|
||||||
>
|
loading={loading}
|
||||||
加载更多
|
>
|
||||||
</Button>
|
加载更多
|
||||||
</div>
|
</Button>
|
||||||
)}
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{!hasMore && changelog.length > 0 && (
|
{
|
||||||
<div style={{
|
!hasMore && changelog.length > 0 && (
|
||||||
textAlign: 'center',
|
<div style={{
|
||||||
color: '#999',
|
textAlign: 'center',
|
||||||
padding: '16px 0',
|
color: 'var(--color-text-tertiary)',
|
||||||
fontSize: '14px',
|
padding: '16px 0',
|
||||||
}}>
|
fontSize: '14px',
|
||||||
已显示所有更新日志
|
}}>
|
||||||
</div>
|
已显示所有更新日志
|
||||||
)}
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '24px',
|
marginTop: '24px',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
background: '#f0f5ff',
|
background: 'var(--color-info-bg)',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #adc6ff',
|
border: '1px solid var(--color-info-border)',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
color: '#1d39c4',
|
color: 'var(--color-primary)',
|
||||||
}}>
|
}}>
|
||||||
💡 提示:更新日志每小时自动刷新一次,数据来源于 GitHub 提交历史
|
💡 提示:每次打开窗口时自动获取最新更新日志,数据来源于 GitHub 提交历史
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -191,13 +191,13 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
|
|
||||||
switch (task.status) {
|
switch (task.status) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return <ClockCircleOutlined style={{ color: '#faad14' }} />;
|
return <ClockCircleOutlined style={{ color: 'var(--color-warning)' }} />;
|
||||||
case 'running':
|
case 'running':
|
||||||
return <Spin />;
|
return <Spin />;
|
||||||
case 'completed':
|
case 'completed':
|
||||||
return <CheckCircleOutlined style={{ color: '#52c41a' }} />;
|
return <CheckCircleOutlined style={{ color: 'var(--color-success)' }} />;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return <CloseCircleOutlined style={{ color: '#ff4d4f' }} />;
|
return <CloseCircleOutlined style={{ color: 'var(--color-error)' }} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
color: task.status === 'failed' ? '#ff4d4f' : '#262626'
|
color: task.status === 'failed' ? 'var(--color-error)' : 'var(--color-text-primary)'
|
||||||
}}>
|
}}>
|
||||||
{task.status === 'pending' && '等待分析...'}
|
{task.status === 'pending' && '等待分析...'}
|
||||||
{task.status === 'running' && 'AI正在分析中...'}
|
{task.status === 'running' && 'AI正在分析中...'}
|
||||||
@@ -241,7 +241,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: 12,
|
height: 12,
|
||||||
background: '#f0f0f0',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginBottom: 12
|
marginBottom: 12
|
||||||
@@ -249,10 +249,10 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
<div style={{
|
<div style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: task.status === 'failed'
|
background: task.status === 'failed'
|
||||||
? 'linear-gradient(90deg, #ff4d4f 0%, #ff7875 100%)'
|
? 'var(--color-error)'
|
||||||
: task.progress === 100
|
: task.progress === 100
|
||||||
? 'linear-gradient(90deg, #52c41a 0%, #73d13d 100%)'
|
? 'var(--color-success)'
|
||||||
: 'linear-gradient(90deg, #1890ff 0%, #40a9ff 100%)',
|
: 'var(--color-primary)',
|
||||||
width: `${task.progress}%`,
|
width: `${task.progress}%`,
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
@@ -267,8 +267,8 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: task.status === 'failed' ? '#ff4d4f' :
|
color: task.status === 'failed' ? 'var(--color-error)' :
|
||||||
task.progress === 100 ? '#52c41a' : '#1890ff',
|
task.progress === 100 ? 'var(--color-success)' : 'var(--color-primary)',
|
||||||
marginBottom: 8
|
marginBottom: 8
|
||||||
}}>
|
}}>
|
||||||
{task.progress}%
|
{task.progress}%
|
||||||
@@ -279,7 +279,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: '#595959',
|
color: 'var(--color-text-secondary)',
|
||||||
minHeight: 24,
|
minHeight: 24,
|
||||||
marginBottom: 16
|
marginBottom: 16
|
||||||
}}>
|
}}>
|
||||||
@@ -307,7 +307,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: '#8c8c8c',
|
color: 'var(--color-text-tertiary)',
|
||||||
marginTop: 16
|
marginTop: 16
|
||||||
}}>
|
}}>
|
||||||
分析过程需要一定时间,请耐心等待
|
分析过程需要一定时间,请耐心等待
|
||||||
@@ -374,7 +374,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
title="整体质量"
|
title="整体质量"
|
||||||
value={analysis_data.overall_quality_score || 0}
|
value={analysis_data.overall_quality_score || 0}
|
||||||
suffix="/ 10"
|
suffix="/ 10"
|
||||||
valueStyle={{ color: '#3f8600' }}
|
valueStyle={{ color: 'var(--color-success)' }}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={isMobile ? 12 : 6}>
|
<Col span={isMobile ? 12 : 6}>
|
||||||
@@ -431,27 +431,27 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
children: (
|
children: (
|
||||||
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
||||||
<Card size={isMobile ? 'small' : 'default'}>
|
<Card size={isMobile ? 'small' : 'default'}>
|
||||||
{analysis_data.hooks && analysis_data.hooks.length > 0 ? (
|
{analysis_data.hooks && analysis_data.hooks.length > 0 ? (
|
||||||
<List
|
<List
|
||||||
dataSource={analysis_data.hooks}
|
dataSource={analysis_data.hooks}
|
||||||
renderItem={(hook) => (
|
renderItem={(hook) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<Tag color="blue">{hook.type}</Tag>
|
<Tag color="blue">{hook.type}</Tag>
|
||||||
<Tag color="orange">{hook.position}</Tag>
|
<Tag color="orange">{hook.position}</Tag>
|
||||||
<Tag color="red">强度: {hook.strength}/10</Tag>
|
<Tag color="red">强度: {hook.strength}/10</Tag>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
description={hook.content}
|
description={hook.content}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="暂无钩子" />
|
<Empty description="暂无钩子" />
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -463,32 +463,32 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
children: (
|
children: (
|
||||||
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
||||||
<Card size={isMobile ? 'small' : 'default'}>
|
<Card size={isMobile ? 'small' : 'default'}>
|
||||||
{analysis_data.foreshadows && analysis_data.foreshadows.length > 0 ? (
|
{analysis_data.foreshadows && analysis_data.foreshadows.length > 0 ? (
|
||||||
<List
|
<List
|
||||||
dataSource={analysis_data.foreshadows}
|
dataSource={analysis_data.foreshadows}
|
||||||
renderItem={(foreshadow) => (
|
renderItem={(foreshadow) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
title={
|
title={
|
||||||
<div>
|
<div>
|
||||||
<Tag color={foreshadow.type === 'planted' ? 'green' : 'purple'}>
|
<Tag color={foreshadow.type === 'planted' ? 'green' : 'purple'}>
|
||||||
{foreshadow.type === 'planted' ? '已埋下' : '已回收'}
|
{foreshadow.type === 'planted' ? '已埋下' : '已回收'}
|
||||||
</Tag>
|
</Tag>
|
||||||
<Tag>强度: {foreshadow.strength}/10</Tag>
|
<Tag>强度: {foreshadow.strength}/10</Tag>
|
||||||
<Tag>隐藏度: {foreshadow.subtlety}/10</Tag>
|
<Tag>隐藏度: {foreshadow.subtlety}/10</Tag>
|
||||||
{foreshadow.reference_chapter && (
|
{foreshadow.reference_chapter && (
|
||||||
<Tag color="cyan">呼应第{foreshadow.reference_chapter}章</Tag>
|
<Tag color="cyan">呼应第{foreshadow.reference_chapter}章</Tag>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
description={foreshadow.content}
|
description={foreshadow.content}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="暂无伏笔" />
|
<Empty description="暂无伏笔" />
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -500,41 +500,41 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
children: (
|
children: (
|
||||||
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
||||||
<Card size={isMobile ? 'small' : 'default'}>
|
<Card size={isMobile ? 'small' : 'default'}>
|
||||||
{analysis_data.emotional_tone ? (
|
{analysis_data.emotional_tone ? (
|
||||||
<div>
|
<div>
|
||||||
<Row gutter={isMobile ? 8 : 16} style={{ marginBottom: isMobile ? 16 : 24 }}>
|
<Row gutter={isMobile ? 8 : 16} style={{ marginBottom: isMobile ? 16 : 24 }}>
|
||||||
<Col span={isMobile ? 24 : 12}>
|
<Col span={isMobile ? 24 : 12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="主导情绪"
|
title="主导情绪"
|
||||||
value={analysis_data.emotional_tone}
|
value={analysis_data.emotional_tone}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={isMobile ? 24 : 12}>
|
<Col span={isMobile ? 24 : 12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="情感强度"
|
title="情感强度"
|
||||||
value={(analysis_data.emotional_intensity * 10).toFixed(1)}
|
value={(analysis_data.emotional_intensity * 10).toFixed(1)}
|
||||||
suffix="/ 10"
|
suffix="/ 10"
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Card type="inner" title="剧情阶段" size="small">
|
<Card type="inner" title="剧情阶段" size="small">
|
||||||
<p><strong>阶段:</strong>{analysis_data.plot_stage}</p>
|
<p><strong>阶段:</strong>{analysis_data.plot_stage}</p>
|
||||||
<p><strong>冲突等级:</strong>{analysis_data.conflict_level} / 10</p>
|
<p><strong>冲突等级:</strong>{analysis_data.conflict_level} / 10</p>
|
||||||
{analysis_data.conflict_types && analysis_data.conflict_types.length > 0 && (
|
{analysis_data.conflict_types && analysis_data.conflict_types.length > 0 && (
|
||||||
<div style={{ marginTop: 8 }}>
|
<div style={{ marginTop: 8 }}>
|
||||||
<strong>冲突类型:</strong>
|
<strong>冲突类型:</strong>
|
||||||
{analysis_data.conflict_types.map((type, idx) => (
|
{analysis_data.conflict_types.map((type, idx) => (
|
||||||
<Tag key={idx} color="red" style={{ margin: 4 }}>
|
<Tag key={idx} color="red" style={{ margin: 4 }}>
|
||||||
{type}
|
{type}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="暂无情感分析" />
|
<Empty description="暂无情感分析" />
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -546,37 +546,37 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
children: (
|
children: (
|
||||||
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
||||||
<Card size={isMobile ? 'small' : 'default'}>
|
<Card size={isMobile ? 'small' : 'default'}>
|
||||||
{analysis_data.character_states && analysis_data.character_states.length > 0 ? (
|
{analysis_data.character_states && analysis_data.character_states.length > 0 ? (
|
||||||
<List
|
<List
|
||||||
dataSource={analysis_data.character_states}
|
dataSource={analysis_data.character_states}
|
||||||
renderItem={(char) => (
|
renderItem={(char) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Card
|
<Card
|
||||||
type="inner"
|
type="inner"
|
||||||
title={char.character_name}
|
title={char.character_name}
|
||||||
size="small"
|
size="small"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<p><strong>状态变化:</strong>{char.state_before} → {char.state_after}</p>
|
<p><strong>状态变化:</strong>{char.state_before} → {char.state_after}</p>
|
||||||
<p><strong>心理变化:</strong>{char.psychological_change}</p>
|
<p><strong>心理变化:</strong>{char.psychological_change}</p>
|
||||||
<p><strong>关键事件:</strong>{char.key_event}</p>
|
<p><strong>关键事件:</strong>{char.key_event}</p>
|
||||||
{char.relationship_changes && Object.keys(char.relationship_changes).length > 0 && (
|
{char.relationship_changes && Object.keys(char.relationship_changes).length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<strong>关系变化:</strong>
|
<strong>关系变化:</strong>
|
||||||
{Object.entries(char.relationship_changes).map(([name, change]) => (
|
{Object.entries(char.relationship_changes).map(([name, change]) => (
|
||||||
<Tag key={name} color="blue" style={{ margin: 4 }}>
|
<Tag key={name} color="blue" style={{ margin: 4 }}>
|
||||||
与{name}: {change}
|
与{name}: {change}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="暂无角色分析" />
|
<Empty description="暂无角色分析" />
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -588,38 +588,38 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
|
|||||||
children: (
|
children: (
|
||||||
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
<div style={{ height: isMobile ? 'calc(80vh - 180px)' : 'calc(90vh - 220px)', overflowY: 'auto', paddingRight: '8px' }}>
|
||||||
<Card size={isMobile ? 'small' : 'default'}>
|
<Card size={isMobile ? 'small' : 'default'}>
|
||||||
{memories && memories.length > 0 ? (
|
{memories && memories.length > 0 ? (
|
||||||
<List
|
<List
|
||||||
dataSource={memories}
|
dataSource={memories}
|
||||||
renderItem={(memory) => (
|
renderItem={(memory) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
title={
|
title={
|
||||||
<div>
|
|
||||||
<Tag color="blue">{memory.type}</Tag>
|
|
||||||
<Tag color="orange">重要性: {memory.importance.toFixed(1)}</Tag>
|
|
||||||
{memory.is_foreshadow === 1 && <Tag color="green">已埋下伏笔</Tag>}
|
|
||||||
{memory.is_foreshadow === 2 && <Tag color="purple">已回收伏笔</Tag>}
|
|
||||||
<span style={{ marginLeft: 8 }}>{memory.title}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<div>
|
|
||||||
<p>{memory.content}</p>
|
|
||||||
<div>
|
<div>
|
||||||
{memory.tags.map((tag, idx) => (
|
<Tag color="blue">{memory.type}</Tag>
|
||||||
<Tag key={idx} style={{ margin: 2 }}>{tag}</Tag>
|
<Tag color="orange">重要性: {memory.importance.toFixed(1)}</Tag>
|
||||||
))}
|
{memory.is_foreshadow === 1 && <Tag color="green">已埋下伏笔</Tag>}
|
||||||
|
{memory.is_foreshadow === 2 && <Tag color="purple">已回收伏笔</Tag>}
|
||||||
|
<span style={{ marginLeft: 8 }}>{memory.title}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
description={
|
||||||
/>
|
<div>
|
||||||
</List.Item>
|
<p>{memory.content}</p>
|
||||||
)}
|
<div>
|
||||||
/>
|
{memory.tags.map((tag, idx) => (
|
||||||
) : (
|
<Tag key={idx} style={{ margin: 2 }}>{tag}</Tag>
|
||||||
<Empty description="暂无记忆片段" />
|
))}
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Empty description="暂无记忆片段" />
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ const ChapterContentComparison: React.FC<ChapterContentComparisonProps> = ({
|
|||||||
title="字数变化"
|
title="字数变化"
|
||||||
value={wordCountDiff}
|
value={wordCountDiff}
|
||||||
suffix="字"
|
suffix="字"
|
||||||
valueStyle={{ color: wordCountDiff > 0 ? '#3f8600' : '#cf1322' }}
|
valueStyle={{ color: wordCountDiff > 0 ? 'var(--color-success)' : 'var(--color-error)' }}
|
||||||
prefix={wordCountDiff > 0 ? '+' : ''}
|
prefix={wordCountDiff > 0 ? '+' : ''}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -165,7 +165,7 @@ const ChapterContentComparison: React.FC<ChapterContentComparisonProps> = ({
|
|||||||
title="变化比例"
|
title="变化比例"
|
||||||
value={wordCountDiffPercent}
|
value={wordCountDiffPercent}
|
||||||
suffix="%"
|
suffix="%"
|
||||||
valueStyle={{ color: Math.abs(parseFloat(wordCountDiffPercent)) < 10 ? '#1890ff' : '#faad14' }}
|
valueStyle={{ color: Math.abs(parseFloat(wordCountDiffPercent)) < 10 ? 'var(--color-primary)' : 'var(--color-warning)' }}
|
||||||
prefix={wordCountDiff > 0 ? '+' : ''}
|
prefix={wordCountDiff > 0 ? '+' : ''}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -176,7 +176,7 @@ const ChapterContentComparison: React.FC<ChapterContentComparisonProps> = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
maxHeight: 'calc(90vh - 300px)',
|
maxHeight: 'calc(90vh - 300px)',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
border: '1px solid #d9d9d9',
|
border: '1px solid var(--color-border)',
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
}}>
|
}}>
|
||||||
<ReactDiffViewer
|
<ReactDiffViewer
|
||||||
@@ -190,19 +190,19 @@ const ChapterContentComparison: React.FC<ChapterContentComparisonProps> = ({
|
|||||||
styles={{
|
styles={{
|
||||||
variables: {
|
variables: {
|
||||||
light: {
|
light: {
|
||||||
diffViewerBackground: '#fff',
|
diffViewerBackground: '#fff', // Keep white for diff viewer readability
|
||||||
addedBackground: '#e6ffed',
|
addedBackground: 'var(--color-success-bg)',
|
||||||
addedColor: '#24292e',
|
addedColor: 'var(--color-text-primary)',
|
||||||
removedBackground: '#ffeef0',
|
removedBackground: 'var(--color-error-bg)',
|
||||||
removedColor: '#24292e',
|
removedColor: 'var(--color-text-primary)',
|
||||||
wordAddedBackground: '#acf2bd',
|
wordAddedBackground: 'var(--color-success-border)',
|
||||||
wordRemovedBackground: '#fdb8c0',
|
wordRemovedBackground: 'var(--color-error-border)',
|
||||||
addedGutterBackground: '#cdffd8',
|
addedGutterBackground: 'var(--color-success-bg)',
|
||||||
removedGutterBackground: '#ffdce0',
|
removedGutterBackground: 'var(--color-error-bg)',
|
||||||
gutterBackground: '#f6f8fa',
|
gutterBackground: 'var(--color-bg-layout)',
|
||||||
gutterBackgroundDark: '#f3f4f6',
|
gutterBackgroundDark: 'var(--color-bg-container)',
|
||||||
highlightBackground: '#fffbdd',
|
highlightBackground: 'var(--color-warning-bg)',
|
||||||
highlightGutterBackground: '#fff5b1',
|
highlightGutterBackground: 'var(--color-warning-border)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ export const SSELoadingOverlay: React.FC<SSELoadingOverlayProps> = ({
|
|||||||
marginBottom: 24
|
marginBottom: 24
|
||||||
}}>
|
}}>
|
||||||
<Spin
|
<Spin
|
||||||
indicator={<LoadingOutlined style={{ fontSize: 48, color: '#1890ff' }} spin />}
|
indicator={<LoadingOutlined style={{ fontSize: 48, color: 'var(--color-primary)' }} spin />}
|
||||||
/>
|
/>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
color: '#262626'
|
color: 'var(--color-text-primary)'
|
||||||
}}>
|
}}>
|
||||||
AI生成中...
|
AI生成中...
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +60,7 @@ export const SSELoadingOverlay: React.FC<SSELoadingOverlayProps> = ({
|
|||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: 12,
|
height: 12,
|
||||||
background: '#f0f0f0',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginBottom: 12
|
marginBottom: 12
|
||||||
@@ -68,12 +68,12 @@ export const SSELoadingOverlay: React.FC<SSELoadingOverlayProps> = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: progress === 100
|
background: progress === 100
|
||||||
? 'linear-gradient(90deg, #52c41a 0%, #73d13d 100%)'
|
? 'linear-gradient(90deg, var(--color-success) 0%, var(--color-success-active) 100%)'
|
||||||
: 'linear-gradient(90deg, #1890ff 0%, #40a9ff 100%)',
|
: 'linear-gradient(90deg, var(--color-primary) 0%, var(--color-primary-active) 100%)',
|
||||||
width: `${progress}%`,
|
width: `${progress}%`,
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
boxShadow: progress > 0 ? '0 0 10px rgba(24, 144, 255, 0.3)' : 'none'
|
boxShadow: progress > 0 ? 'var(--shadow-card)' : 'none'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ export const SSELoadingOverlay: React.FC<SSELoadingOverlayProps> = ({
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: progress === 100 ? '#52c41a' : '#1890ff',
|
color: progress === 100 ? 'var(--color-success)' : 'var(--color-primary)',
|
||||||
marginBottom: 8
|
marginBottom: 8
|
||||||
}}>
|
}}>
|
||||||
{progress}%
|
{progress}%
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ export const SSEProgressModal: React.FC<SSEProgressModalProps> = ({
|
|||||||
marginBottom: 24
|
marginBottom: 24
|
||||||
}}>
|
}}>
|
||||||
<Spin
|
<Spin
|
||||||
indicator={<LoadingOutlined style={{ fontSize: 48, color: '#1890ff' }} spin />}
|
indicator={<LoadingOutlined style={{ fontSize: 48, color: 'var(--color-primary)' }} spin />}
|
||||||
/>
|
/>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
color: '#262626'
|
color: 'var(--color-text-primary)'
|
||||||
}}>
|
}}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +72,7 @@ export const SSEProgressModal: React.FC<SSEProgressModalProps> = ({
|
|||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: 12,
|
height: 12,
|
||||||
background: '#f0f0f0',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginBottom: showPercentage ? 12 : 0
|
marginBottom: showPercentage ? 12 : 0
|
||||||
@@ -80,12 +80,12 @@ export const SSEProgressModal: React.FC<SSEProgressModalProps> = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: progress === 100
|
background: progress === 100
|
||||||
? 'linear-gradient(90deg, #52c41a 0%, #73d13d 100%)'
|
? 'linear-gradient(90deg, var(--color-success) 0%, var(--color-success-active) 100%)'
|
||||||
: 'linear-gradient(90deg, #1890ff 0%, #40a9ff 100%)',
|
: 'linear-gradient(90deg, var(--color-primary) 0%, var(--color-primary-active) 100%)',
|
||||||
width: `${progress}%`,
|
width: `${progress}%`,
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
boxShadow: progress > 0 ? '0 0 10px rgba(24, 144, 255, 0.3)' : 'none'
|
boxShadow: progress > 0 ? 'var(--shadow-card)' : 'none'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ export const SSEProgressModal: React.FC<SSEProgressModalProps> = ({
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: progress === 100 ? '#52c41a' : '#1890ff',
|
color: progress === 100 ? 'var(--color-success)' : 'var(--color-primary)',
|
||||||
marginBottom: 8
|
marginBottom: 8
|
||||||
}}>
|
}}>
|
||||||
{progress}%
|
{progress}%
|
||||||
@@ -107,7 +107,7 @@ export const SSEProgressModal: React.FC<SSEProgressModalProps> = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: '#595959',
|
color: 'var(--color-text-secondary)',
|
||||||
minHeight: 24,
|
minHeight: 24,
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
marginBottom: 16
|
marginBottom: 16
|
||||||
@@ -119,7 +119,7 @@ export const SSEProgressModal: React.FC<SSEProgressModalProps> = ({
|
|||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: '#8c8c8c',
|
color: 'var(--color-text-tertiary)',
|
||||||
marginBottom: onCancel ? 16 : 0
|
marginBottom: onCancel ? 16 : 0
|
||||||
}}>
|
}}>
|
||||||
请勿关闭页面,生成过程需要一定时间
|
请勿关闭页面,生成过程需要一定时间
|
||||||
|
|||||||
@@ -122,23 +122,23 @@ export default function UserMenu() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 12,
|
gap: 12,
|
||||||
padding: '8px 16px',
|
padding: '8px 16px',
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'rgba(255, 255, 255, 0.6)', // 保持半透明以配合 Backdrop
|
||||||
backdropFilter: 'blur(10px)',
|
backdropFilter: 'blur(10px)',
|
||||||
WebkitBackdropFilter: 'blur(10px)',
|
WebkitBackdropFilter: 'blur(10px)',
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
border: '1px solid rgba(102, 126, 234, 0.2)',
|
border: '1px solid var(--color-border)',
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 1)';
|
e.currentTarget.style.background = 'var(--color-bg-container)'; // 悬浮时变实
|
||||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-elevated)';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.95)';
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.6)';
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-card)';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
@@ -147,9 +147,9 @@ export default function UserMenu() {
|
|||||||
icon={<UserOutlined />}
|
icon={<UserOutlined />}
|
||||||
size={40}
|
size={40}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#1890ff',
|
backgroundColor: 'var(--color-primary)',
|
||||||
border: '3px solid #fff',
|
border: '3px solid #fff',
|
||||||
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.3)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{currentUser.is_admin && (
|
{currentUser.is_admin && (
|
||||||
@@ -173,14 +173,14 @@ export default function UserMenu() {
|
|||||||
</div>
|
</div>
|
||||||
<Space direction="vertical" size={0} style={{ display: window.innerWidth <= 768 ? 'none' : 'flex' }}>
|
<Space direction="vertical" size={0} style={{ display: window.innerWidth <= 768 ? 'none' : 'flex' }}>
|
||||||
<Text strong style={{
|
<Text strong style={{
|
||||||
color: '#262626',
|
color: 'var(--color-text-primary)',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
}}>
|
}}>
|
||||||
{currentUser.display_name || currentUser.username}
|
{currentUser.display_name || currentUser.username}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#8c8c8c',
|
color: 'var(--color-text-secondary)',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
lineHeight: '18px',
|
lineHeight: '18px',
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
+276
-7
@@ -1,11 +1,63 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
/* --- 中国风配色方案 (Chinese Style Palette) --- */
|
||||||
line-height: 1.5;
|
|
||||||
|
/* 主色调:天青 (Cerulean / Azure) - 类似汝窑 */
|
||||||
|
--color-primary: #4D8088;
|
||||||
|
--color-primary-hover: #5F9EA8;
|
||||||
|
--color-primary-active: #3A666C;
|
||||||
|
|
||||||
|
/* 辅助色 */
|
||||||
|
--color-success: #52C41A;
|
||||||
|
--color-warning: #FAAD14;
|
||||||
|
--color-error: #FF4D4F;
|
||||||
|
--color-info: #1890FF;
|
||||||
|
|
||||||
|
/* 功能色背景/边框 */
|
||||||
|
--color-success-bg: #F6FFED;
|
||||||
|
--color-success-border: #B7EB8F;
|
||||||
|
--color-warning-bg: #FFFBE6;
|
||||||
|
--color-warning-border: #FFE58F;
|
||||||
|
--color-error-bg: #FFF2F0;
|
||||||
|
--color-error-border: #FFCCC7;
|
||||||
|
--color-info-bg: #E6F7FF;
|
||||||
|
--color-info-border: #91D5FF;
|
||||||
|
|
||||||
|
/* 背景色 */
|
||||||
|
--color-bg-base: #F8F6F1;
|
||||||
|
/* 米汤色 (Rice Soup / Cream) - 用于页面背景 */
|
||||||
|
--color-bg-container: #FFFFFF;
|
||||||
|
/* 纯白 - 用于卡片/容器 */
|
||||||
|
--color-bg-layout: #F0F2F5;
|
||||||
|
--color-bg-spotlight: #3A666C;
|
||||||
|
--color-bg-mask: rgba(0, 0, 0, 0.45);
|
||||||
|
|
||||||
|
/* 文本色 */
|
||||||
|
--color-text-base: #2B2B2B;
|
||||||
|
/* 墨色 (Ink) */
|
||||||
|
--color-text-primary: #2B2B2B;
|
||||||
|
--color-text-secondary: #595959;
|
||||||
|
/* 此时 (Secondary Text) */
|
||||||
|
--color-text-tertiary: #8C8C8C;
|
||||||
|
--color-text-quaternary: #BFBFBF;
|
||||||
|
|
||||||
|
/* 边框色 */
|
||||||
|
--color-border: #D9D9D9;
|
||||||
|
--color-border-secondary: #F0F0F0;
|
||||||
|
|
||||||
|
/* 阴影 */
|
||||||
|
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
--shadow-elevated: 0 8px 24px rgba(77, 128, 136, 0.15);
|
||||||
|
/* 带一点主色调的阴影 */
|
||||||
|
--shadow-primary: 0 4px 16px rgba(77, 128, 136, 0.25);
|
||||||
|
--shadow-header: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", "Heiti SC", Inter, system-ui, sans-serif;
|
||||||
|
line-height: 1.5715;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
color-scheme: light dark;
|
color-scheme: light;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: var(--color-text-base);
|
||||||
background-color: #f0f2f5;
|
background-color: var(--color-bg-base);
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
@@ -72,7 +124,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端优化触摸区域 */
|
/* 移动端优化触摸区域 */
|
||||||
button, a, [role="button"] {
|
button,
|
||||||
|
a,
|
||||||
|
[role="button"] {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
}
|
}
|
||||||
@@ -95,7 +149,9 @@ body {
|
|||||||
|
|
||||||
/* 移动端禁止长按选择 */
|
/* 移动端禁止长按选择 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
img, button {
|
|
||||||
|
img,
|
||||||
|
button {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
@@ -131,3 +187,216 @@ body {
|
|||||||
.ant-tabs-dropdown {
|
.ant-tabs-dropdown {
|
||||||
z-index: 2000 !important;
|
z-index: 2000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 现代化侧边栏样式 (Modern Sidebar Styles) ===== */
|
||||||
|
|
||||||
|
/* 侧边栏容器 - 毛玻璃效果 */
|
||||||
|
.modern-sider {
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(255, 255, 255, 0.95) 0%,
|
||||||
|
rgba(248, 246, 241, 0.98) 100%) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border-right: 1px solid rgba(77, 128, 136, 0.12);
|
||||||
|
box-shadow:
|
||||||
|
4px 0 24px rgba(77, 128, 136, 0.08),
|
||||||
|
1px 0 0 rgba(255, 255, 255, 0.8) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏菜单整体样式 */
|
||||||
|
.modern-sider .ant-menu {
|
||||||
|
background: transparent !important;
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单项基础样式 */
|
||||||
|
.modern-sider .ant-menu-item {
|
||||||
|
margin: 6px 12px !important;
|
||||||
|
padding: 0 16px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
height: 48px !important;
|
||||||
|
line-height: 48px !important;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠状态下的菜单项 */
|
||||||
|
.modern-sider.ant-layout-sider-collapsed .ant-menu-item {
|
||||||
|
margin: 6px 8px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单项悬停效果 - 仅未选中项 */
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover {
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
rgba(77, 128, 136, 0.15) 0%,
|
||||||
|
rgba(95, 158, 168, 0.22) 100%) !important;
|
||||||
|
transform: translateX(6px);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(77, 128, 136, 0.15),
|
||||||
|
inset 0 0 0 1px rgba(77, 128, 136, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠状态悬停效果 - 仅未选中项 */
|
||||||
|
.modern-sider.ant-layout-sider-collapsed .ant-menu-item:not(.ant-menu-item-selected):hover {
|
||||||
|
transform: translateX(0) scale(1.08);
|
||||||
|
box-shadow:
|
||||||
|
0 4px 12px rgba(77, 128, 136, 0.2),
|
||||||
|
inset 0 0 0 1px rgba(77, 128, 136, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单项选中状态 - 增强版 */
|
||||||
|
.modern-sider .ant-menu-item-selected {
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
var(--color-primary) 0%,
|
||||||
|
#5A9BA5 50%,
|
||||||
|
var(--color-primary-hover) 100%) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 6px 20px rgba(77, 128, 136, 0.45),
|
||||||
|
0 3px 8px rgba(77, 128, 136, 0.25),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中项悬停 - 微发光效果 */
|
||||||
|
.modern-sider .ant-menu-item-selected:hover {
|
||||||
|
transform: translateX(0) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 8px 24px rgba(77, 128, 136, 0.5),
|
||||||
|
0 4px 10px rgba(77, 128, 136, 0.3),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||||
|
filter: brightness(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-sider.ant-layout-sider-collapsed .ant-menu-item-selected:hover {
|
||||||
|
transform: scale(1.05) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中状态文字和图标颜色 */
|
||||||
|
.modern-sider .ant-menu-item-selected,
|
||||||
|
.modern-sider .ant-menu-item-selected a,
|
||||||
|
.modern-sider .ant-menu-item-selected .anticon {
|
||||||
|
color: #fff !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 未选中状态颜色 */
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected),
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected) a {
|
||||||
|
color: var(--color-text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected) .anticon {
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单项悬停时文字和图标颜色 */
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover,
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover a {
|
||||||
|
color: var(--color-primary-active) !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover .anticon {
|
||||||
|
color: var(--color-primary-active) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图标样式优化 */
|
||||||
|
.modern-sider .ant-menu-item .anticon {
|
||||||
|
font-size: 18px !important;
|
||||||
|
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-sider.ant-layout-sider-collapsed .ant-menu-item .anticon {
|
||||||
|
font-size: 22px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 悬停时图标微动效 */
|
||||||
|
.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover .anticon {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中项左侧指示条 */
|
||||||
|
.modern-sider .ant-menu-item-selected::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 4px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 折叠状态隐藏指示条 */
|
||||||
|
.modern-sider.ant-layout-sider-collapsed .ant-menu-item-selected::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 链接文字样式 */
|
||||||
|
.modern-sider .ant-menu-item a {
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏顶部装饰线 */
|
||||||
|
.modern-sider::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
var(--color-primary) 0%,
|
||||||
|
var(--color-primary-hover) 50%,
|
||||||
|
var(--color-primary) 100%);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏底部渐变遮罩 */
|
||||||
|
.modern-sider::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(to top,
|
||||||
|
rgba(248, 246, 241, 0.95) 0%,
|
||||||
|
transparent 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单项波纹效果 */
|
||||||
|
.modern-sider .ant-menu-item::after {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单标题样式 (如果有分组) */
|
||||||
|
.modern-sider .ant-menu-item-group-title {
|
||||||
|
color: var(--color-text-tertiary) !important;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
padding: 16px 24px 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip 样式优化 (折叠状态) */
|
||||||
|
.ant-tooltip .ant-tooltip-inner {
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
var(--color-primary) 0%,
|
||||||
|
var(--color-primary-hover) 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 4px 12px rgba(77, 128, 136, 0.3);
|
||||||
|
}
|
||||||
+28
-1
@@ -8,7 +8,34 @@ import App from './App.tsx'
|
|||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ConfigProvider locale={zhCN}>
|
<ConfigProvider
|
||||||
|
locale={zhCN}
|
||||||
|
theme={{
|
||||||
|
token: {
|
||||||
|
colorPrimary: '#4D8088', // 天青
|
||||||
|
colorBgBase: '#F8F6F1', // 米汤色
|
||||||
|
colorTextBase: '#2B2B2B', // 墨色
|
||||||
|
borderRadius: 6,
|
||||||
|
wireframe: false,
|
||||||
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif",
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Layout: {
|
||||||
|
bodyBg: '#F8F6F1',
|
||||||
|
headerBg: '#FFFFFF',
|
||||||
|
siderBg: '#FFFFFF',
|
||||||
|
},
|
||||||
|
Card: {
|
||||||
|
colorBgContainer: '#FFFFFF',
|
||||||
|
boxShadowTertiary: '0 4px 12px rgba(0, 0, 0, 0.05)', // 更柔和的阴影
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
borderRadius: 6,
|
||||||
|
controlHeight: 36,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<App />
|
<App />
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const { TextArea } = Input;
|
|||||||
|
|
||||||
export default function Chapters() {
|
export default function Chapters() {
|
||||||
const { currentProject, chapters, setCurrentChapter, setCurrentProject } = useStore();
|
const { currentProject, chapters, setCurrentChapter, setCurrentProject } = useStore();
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
||||||
const [isContinuing, setIsContinuing] = useState(false);
|
const [isContinuing, setIsContinuing] = useState(false);
|
||||||
@@ -433,7 +434,7 @@ export default function Chapters() {
|
|||||||
|
|
||||||
const selectedStyle = writingStyles.find(s => s.id === selectedStyleId);
|
const selectedStyle = writingStyles.find(s => s.id === selectedStyleId);
|
||||||
|
|
||||||
const modal = Modal.confirm({
|
const instance = modal.confirm({
|
||||||
title: 'AI创作章节内容',
|
title: 'AI创作章节内容',
|
||||||
width: 700,
|
width: 700,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -455,11 +456,11 @@ export default function Chapters() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
background: '#f0f5ff',
|
background: 'var(--color-info-bg)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
border: '1px solid #adc6ff'
|
border: '1px solid var(--color-info-border)'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ marginBottom: 8, fontWeight: 500, color: '#1890ff' }}>
|
<div style={{ marginBottom: 8, fontWeight: 500, color: 'var(--color-primary)' }}>
|
||||||
📚 将引用的前置章节(共{previousChapters.length}章):
|
📚 将引用的前置章节(共{previousChapters.length}章):
|
||||||
</div>
|
</div>
|
||||||
<div style={{ maxHeight: 150, overflowY: 'auto' }}>
|
<div style={{ maxHeight: 150, overflowY: 'auto' }}>
|
||||||
@@ -484,7 +485,7 @@ export default function Chapters() {
|
|||||||
okButtonProps: { danger: true },
|
okButtonProps: { danger: true },
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
modal.update({
|
instance.update({
|
||||||
okButtonProps: { danger: true, loading: true },
|
okButtonProps: { danger: true, loading: true },
|
||||||
cancelButtonProps: { disabled: true },
|
cancelButtonProps: { disabled: true },
|
||||||
closable: false,
|
closable: false,
|
||||||
@@ -495,7 +496,7 @@ export default function Chapters() {
|
|||||||
try {
|
try {
|
||||||
if (!selectedStyleId) {
|
if (!selectedStyleId) {
|
||||||
message.error('请先选择写作风格');
|
message.error('请先选择写作风格');
|
||||||
modal.update({
|
instance.update({
|
||||||
okButtonProps: { danger: true, loading: false },
|
okButtonProps: { danger: true, loading: false },
|
||||||
cancelButtonProps: { disabled: false },
|
cancelButtonProps: { disabled: false },
|
||||||
closable: true,
|
closable: true,
|
||||||
@@ -505,9 +506,9 @@ export default function Chapters() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await handleGenerate();
|
await handleGenerate();
|
||||||
modal.destroy();
|
instance.destroy();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
modal.update({
|
instance.update({
|
||||||
okButtonProps: { danger: true, loading: false },
|
okButtonProps: { danger: true, loading: false },
|
||||||
cancelButtonProps: { disabled: false },
|
cancelButtonProps: { disabled: false },
|
||||||
closable: true,
|
closable: true,
|
||||||
@@ -579,7 +580,7 @@ export default function Chapters() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '导出项目章节',
|
title: '导出项目章节',
|
||||||
content: `确定要将《${currentProject.title}》的所有章节导出为TXT文件吗?`,
|
content: `确定要将《${currentProject.title}》的所有章节导出为TXT文件吗?`,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -840,7 +841,7 @@ export default function Chapters() {
|
|||||||
? Math.max(...chapters.map(c => c.chapter_number)) + 1
|
? Math.max(...chapters.map(c => c.chapter_number)) + 1
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '手动创建章节',
|
title: '手动创建章节',
|
||||||
width: 600,
|
width: 600,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -937,7 +938,7 @@ export default function Chapters() {
|
|||||||
|
|
||||||
if (conflictChapter) {
|
if (conflictChapter) {
|
||||||
// 显示冲突提示Modal
|
// 显示冲突提示Modal
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '章节序号冲突',
|
title: '章节序号冲突',
|
||||||
icon: <InfoCircleOutlined style={{ color: '#ff4d4f' }} />,
|
icon: <InfoCircleOutlined style={{ color: '#ff4d4f' }} />,
|
||||||
width: 500,
|
width: 500,
|
||||||
@@ -1074,10 +1075,10 @@ export default function Chapters() {
|
|||||||
try {
|
try {
|
||||||
const planData: ExpansionPlanData = JSON.parse(chapter.expansion_plan);
|
const planData: ExpansionPlanData = JSON.parse(chapter.expansion_plan);
|
||||||
|
|
||||||
Modal.info({
|
modal.info({
|
||||||
title: (
|
title: (
|
||||||
<Space style={{ flexWrap: 'wrap' }}>
|
<Space style={{ flexWrap: 'wrap' }}>
|
||||||
<InfoCircleOutlined style={{ color: '#1890ff' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-primary)' }} />
|
||||||
<span style={{ wordBreak: 'break-word' }}>第{chapter.chapter_number}章展开规划</span>
|
<span style={{ wordBreak: 'break-word' }}>第{chapter.chapter_number}章展开规划</span>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
@@ -1366,11 +1367,12 @@ export default function Chapters() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
{contextHolder}
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: 'var(--color-bg-container)',
|
||||||
padding: isMobile ? '12px 0' : '16px 0',
|
padding: isMobile ? '12px 0' : '16px 0',
|
||||||
marginBottom: isMobile ? 12 : 16,
|
marginBottom: isMobile ? 12 : 16,
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid #f0f0f0',
|
||||||
@@ -1486,7 +1488,7 @@ export default function Chapters() {
|
|||||||
>
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
avatar={!isMobile && <FileTextOutlined style={{ fontSize: 32, color: '#1890ff' }} />}
|
avatar={!isMobile && <FileTextOutlined style={{ fontSize: 32, color: 'var(--color-primary)' }} />}
|
||||||
title={
|
title={
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -1500,7 +1502,7 @@ export default function Chapters() {
|
|||||||
</span>
|
</span>
|
||||||
<Space wrap size={isMobile ? 4 : 8}>
|
<Space wrap size={isMobile ? 4 : 8}>
|
||||||
<Tag color={getStatusColor(item.status)}>{getStatusText(item.status)}</Tag>
|
<Tag color={getStatusColor(item.status)}>{getStatusText(item.status)}</Tag>
|
||||||
<Badge count={`${item.word_count || 0}字`} style={{ backgroundColor: '#52c41a' }} />
|
<Badge count={`${item.word_count || 0}字`} style={{ backgroundColor: 'var(--color-success)' }} />
|
||||||
{renderAnalysisStatus(item.id)}
|
{renderAnalysisStatus(item.id)}
|
||||||
{!canGenerateChapter(item) && (
|
{!canGenerateChapter(item) && (
|
||||||
<Tooltip title={getGenerateDisabledReason(item)}>
|
<Tooltip title={getGenerateDisabledReason(item)}>
|
||||||
@@ -1591,11 +1593,11 @@ export default function Chapters() {
|
|||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
count={`${group.chapters.length} 章`}
|
count={`${group.chapters.length} 章`}
|
||||||
style={{ backgroundColor: '#52c41a' }}
|
style={{ backgroundColor: 'var(--color-success)' }}
|
||||||
/>
|
/>
|
||||||
<Badge
|
<Badge
|
||||||
count={`${group.chapters.reduce((sum, ch) => sum + (ch.word_count || 0), 0)} 字`}
|
count={`${group.chapters.reduce((sum, ch) => sum + (ch.word_count || 0), 0)} 字`}
|
||||||
style={{ backgroundColor: '#1890ff' }}
|
style={{ backgroundColor: 'var(--color-primary)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -1681,7 +1683,7 @@ export default function Chapters() {
|
|||||||
>
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
avatar={!isMobile && <FileTextOutlined style={{ fontSize: 32, color: '#1890ff' }} />}
|
avatar={!isMobile && <FileTextOutlined style={{ fontSize: 32, color: 'var(--color-primary)' }} />}
|
||||||
title={
|
title={
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -1695,7 +1697,7 @@ export default function Chapters() {
|
|||||||
</span>
|
</span>
|
||||||
<Space wrap size={isMobile ? 4 : 8}>
|
<Space wrap size={isMobile ? 4 : 8}>
|
||||||
<Tag color={getStatusColor(item.status)}>{getStatusText(item.status)}</Tag>
|
<Tag color={getStatusColor(item.status)}>{getStatusText(item.status)}</Tag>
|
||||||
<Badge count={`${item.word_count || 0}字`} style={{ backgroundColor: '#52c41a' }} />
|
<Badge count={`${item.word_count || 0}字`} style={{ backgroundColor: 'var(--color-success)' }} />
|
||||||
{renderAnalysisStatus(item.id)}
|
{renderAnalysisStatus(item.id)}
|
||||||
{!canGenerateChapter(item) && (
|
{!canGenerateChapter(item) && (
|
||||||
<Tooltip title={getGenerateDisabledReason(item)}>
|
<Tooltip title={getGenerateDisabledReason(item)}>
|
||||||
@@ -1708,7 +1710,7 @@ export default function Chapters() {
|
|||||||
{item.expansion_plan && (
|
{item.expansion_plan && (
|
||||||
<Tooltip title="查看展开详情">
|
<Tooltip title="查看展开详情">
|
||||||
<InfoCircleOutlined
|
<InfoCircleOutlined
|
||||||
style={{ color: '#1890ff', cursor: 'pointer', fontSize: 16 }}
|
style={{ color: 'var(--color-primary)', cursor: 'pointer', fontSize: 16 }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
showExpansionPlanModal(item);
|
showExpansionPlanModal(item);
|
||||||
@@ -1718,7 +1720,7 @@ export default function Chapters() {
|
|||||||
)}
|
)}
|
||||||
<Tooltip title={item.expansion_plan ? "编辑规划信息" : "创建规划信息"}>
|
<Tooltip title={item.expansion_plan ? "编辑规划信息" : "创建规划信息"}>
|
||||||
<FormOutlined
|
<FormOutlined
|
||||||
style={{ color: '#52c41a', cursor: 'pointer', fontSize: 16 }}
|
style={{ color: 'var(--color-success)', cursor: 'pointer', fontSize: 16 }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleOpenPlanEditor(item);
|
handleOpenPlanEditor(item);
|
||||||
@@ -1992,7 +1994,7 @@ export default function Chapters() {
|
|||||||
<Select.Option value="omniscient">全知视角</Select.Option>
|
<Select.Option value="omniscient">全知视角</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
{temporaryNarrativePerspective && (
|
{temporaryNarrativePerspective && (
|
||||||
<div style={{ color: '#52c41a', fontSize: 12, marginTop: 4 }}>
|
<div style={{ color: 'var(--color-success)', fontSize: 12, marginTop: 4 }}>
|
||||||
✓ {getNarrativePerspectiveText(temporaryNarrativePerspective)}
|
✓ {getNarrativePerspectiveText(temporaryNarrativePerspective)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -2169,7 +2171,7 @@ export default function Chapters() {
|
|||||||
open={batchGenerateVisible}
|
open={batchGenerateVisible}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
if (batchGenerating) {
|
if (batchGenerating) {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '确认取消',
|
title: '确认取消',
|
||||||
content: '批量生成正在进行中,确定要取消吗?',
|
content: '批量生成正在进行中,确定要取消吗?',
|
||||||
okText: '确定取消',
|
okText: '确定取消',
|
||||||
@@ -2374,7 +2376,7 @@ export default function Chapters() {
|
|||||||
danger
|
danger
|
||||||
icon={<StopOutlined />}
|
icon={<StopOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '确认取消',
|
title: '确认取消',
|
||||||
content: '确定要取消批量生成吗?已生成的章节将保留。',
|
content: '确定要取消批量生成吗?已生成的章节将保留。',
|
||||||
okText: '确定取消',
|
okText: '确定取消',
|
||||||
@@ -2409,7 +2411,7 @@ export default function Chapters() {
|
|||||||
}
|
}
|
||||||
title="批量生成章节"
|
title="批量生成章节"
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '确认取消',
|
title: '确认取消',
|
||||||
content: '确定要取消批量生成吗?已生成的章节将保留。',
|
content: '确定要取消批量生成吗?已生成的章节将保留。',
|
||||||
okText: '确定取消',
|
okText: '确定取消',
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export default function Characters() {
|
|||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentProject?.id]);
|
}, [currentProject?.id]);
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
if (!currentProject) return null;
|
if (!currentProject) return null;
|
||||||
|
|
||||||
@@ -218,7 +219,7 @@ export default function Characters() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showGenerateModal = () => {
|
const showGenerateModal = () => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: 'AI生成角色',
|
title: 'AI生成角色',
|
||||||
width: 600,
|
width: 600,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -256,7 +257,7 @@ export default function Characters() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showGenerateOrgModal = () => {
|
const showGenerateOrgModal = () => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: 'AI生成组织',
|
title: 'AI生成组织',
|
||||||
width: 600,
|
width: 600,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -306,14 +307,15 @@ export default function Characters() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
{contextHolder}
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: 'var(--color-bg-container)',
|
||||||
padding: isMobile ? '12px 0' : '16px 0',
|
padding: isMobile ? '12px 0' : '16px 0',
|
||||||
marginBottom: isMobile ? 12 : 16,
|
marginBottom: isMobile ? 12 : 16,
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid var(--color-border-secondary)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: isMobile ? 'column' : 'row',
|
flexDirection: isMobile ? 'column' : 'row',
|
||||||
gap: isMobile ? 12 : 0,
|
gap: isMobile ? 12 : 0,
|
||||||
@@ -370,45 +372,45 @@ export default function Characters() {
|
|||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: isMobile ? 60 : 72,
|
top: isMobile ? 60 : 72,
|
||||||
zIndex: 9,
|
zIndex: 9,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: 'var(--color-bg-container)',
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid var(--color-border-secondary)',
|
||||||
}}>
|
}}>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTab}
|
activeKey={activeTab}
|
||||||
onChange={(key) => setActiveTab(key as 'all' | 'character' | 'organization')}
|
onChange={(key) => setActiveTab(key as 'all' | 'character' | 'organization')}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'all',
|
key: 'all',
|
||||||
label: `全部 (${characters.length})`,
|
label: `全部 (${characters.length})`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'character',
|
key: 'character',
|
||||||
label: (
|
label: (
|
||||||
<span>
|
<span>
|
||||||
<UserOutlined /> 角色 ({characterList.length})
|
<UserOutlined /> 角色 ({characterList.length})
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'organization',
|
key: 'organization',
|
||||||
label: (
|
label: (
|
||||||
<span>
|
<span>
|
||||||
<TeamOutlined /> 组织 ({organizationList.length})
|
<TeamOutlined /> 组织 ({organizationList.length})
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||||
{characters.length === 0 ? (
|
{characters.length === 0 ? (
|
||||||
<Empty description="还没有角色或组织,开始创建吧!" />
|
<Empty description="还没有角色或组织,开始创建吧!" />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Row gutter={isMobile ? [8, 8] : characterGridConfig.gutter}>
|
<Row gutter={isMobile ? [8, 8] : characterGridConfig.gutter}>
|
||||||
{activeTab === 'all' && (
|
{activeTab === 'all' && (
|
||||||
<>
|
<>
|
||||||
{characterList.length > 0 && (
|
{characterList.length > 0 && (
|
||||||
@@ -516,8 +518,8 @@ export default function Characters() {
|
|||||||
activeTab === 'character'
|
activeTab === 'character'
|
||||||
? '暂无角色'
|
? '暂无角色'
|
||||||
: activeTab === 'organization'
|
: activeTab === 'organization'
|
||||||
? '暂无组织'
|
? '暂无组织'
|
||||||
: '暂无数据'
|
: '暂无数据'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ const CACHE_EXPIRY = 24 * 60 * 60 * 1000;
|
|||||||
const Inspiration: React.FC = () => {
|
const Inspiration: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [currentStep, setCurrentStep] = useState<Step>('idea');
|
const [currentStep, setCurrentStep] = useState<Step>('idea');
|
||||||
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsMobile(window.innerWidth <= 768);
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [messages, setMessages] = useState<Message[]>([
|
const [messages, setMessages] = useState<Message[]>([
|
||||||
{
|
{
|
||||||
type: 'ai',
|
type: 'ai',
|
||||||
@@ -215,8 +225,8 @@ const Inspiration: React.FC = () => {
|
|||||||
setMessages(prev => {
|
setMessages(prev => {
|
||||||
const newMessages = [...prev];
|
const newMessages = [...prev];
|
||||||
if (newMessages[newMessages.length - 1].type === 'ai' &&
|
if (newMessages[newMessages.length - 1].type === 'ai' &&
|
||||||
(newMessages[newMessages.length - 1].content.includes('生成失败') ||
|
(newMessages[newMessages.length - 1].content.includes('生成失败') ||
|
||||||
newMessages[newMessages.length - 1].content.includes('出错了'))) {
|
newMessages[newMessages.length - 1].content.includes('出错了'))) {
|
||||||
newMessages.pop();
|
newMessages.pop();
|
||||||
}
|
}
|
||||||
return newMessages;
|
return newMessages;
|
||||||
@@ -697,7 +707,7 @@ const Inspiration: React.FC = () => {
|
|||||||
<Card
|
<Card
|
||||||
ref={chatContainerRef}
|
ref={chatContainerRef}
|
||||||
style={{
|
style={{
|
||||||
height: window.innerWidth <= 768 ? 'calc(100vh - 280px)' : 600,
|
height: isMobile ? 'calc(100vh - 280px)' : 600,
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
|
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
|
||||||
@@ -721,16 +731,16 @@ const Inspiration: React.FC = () => {
|
|||||||
maxWidth: '80%',
|
maxWidth: '80%',
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
background: msg.type === 'ai' ? '#f0f0f0' : '#1890ff',
|
background: msg.type === 'ai' ? 'var(--color-bg-container)' : 'var(--color-primary)',
|
||||||
color: msg.type === 'ai' ? '#000' : '#fff',
|
color: msg.type === 'ai' ? 'var(--color-text-primary)' : '#fff',
|
||||||
boxShadow: msg.type === 'ai'
|
boxShadow: msg.type === 'ai'
|
||||||
? '0 2px 8px rgba(0,0,0,0.08)'
|
? 'var(--shadow-card)'
|
||||||
: '0 2px 8px rgba(24,144,255,0.3)',
|
: 'var(--shadow-primary)',
|
||||||
}}>
|
}}>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
color: msg.type === 'ai' ? '#000' : '#fff',
|
color: msg.type === 'ai' ? 'var(--color-text-primary)' : '#fff',
|
||||||
whiteSpace: 'pre-wrap'
|
whiteSpace: 'pre-wrap'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -752,13 +762,13 @@ const Inspiration: React.FC = () => {
|
|||||||
style={{
|
style={{
|
||||||
cursor: msg.optionsDisabled ? 'not-allowed' : 'pointer',
|
cursor: msg.optionsDisabled ? 'not-allowed' : 'pointer',
|
||||||
border: msg.isMultiSelect && selectedOptions.includes(option)
|
border: msg.isMultiSelect && selectedOptions.includes(option)
|
||||||
? '2px solid #1890ff'
|
? '2px solid var(--color-primary)'
|
||||||
: '1px solid #d9d9d9',
|
: '1px solid var(--color-border)',
|
||||||
background: msg.optionsDisabled
|
background: msg.optionsDisabled
|
||||||
? '#f5f5f5'
|
? 'var(--color-bg-layout)'
|
||||||
: msg.isMultiSelect && selectedOptions.includes(option)
|
: msg.isMultiSelect && selectedOptions.includes(option)
|
||||||
? '#e6f7ff'
|
? 'var(--color-bg-spotlight)' // Need to ensure this exists or use safe fallback
|
||||||
: '#fff',
|
: 'var(--color-bg-container)',
|
||||||
opacity: msg.optionsDisabled ? 0.6 : 1,
|
opacity: msg.optionsDisabled ? 0.6 : 1,
|
||||||
animation: 'floatIn 0.6s ease-out',
|
animation: 'floatIn 0.6s ease-out',
|
||||||
animationDelay: `${optIndex * 0.1}s`,
|
animationDelay: `${optIndex * 0.1}s`,
|
||||||
@@ -768,7 +778,7 @@ const Inspiration: React.FC = () => {
|
|||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
if (!msg.optionsDisabled) {
|
if (!msg.optionsDisabled) {
|
||||||
e.currentTarget.style.transform = 'translateY(-2px) scale(1.02)';
|
e.currentTarget.style.transform = 'translateY(-2px) scale(1.02)';
|
||||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(24,144,255,0.2)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-elevated)';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
@@ -854,8 +864,7 @@ const Inspiration: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-bg-base)',
|
||||||
padding: window.innerWidth <= 768 ? '12px' : '24px'
|
|
||||||
}}>
|
}}>
|
||||||
<style>
|
<style>
|
||||||
{`
|
{`
|
||||||
@@ -894,53 +903,65 @@ const Inspiration: React.FC = () => {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
<div style={{ maxWidth: 800, margin: '0 auto' }}>
|
|
||||||
|
{/* 顶部标题栏 - 固定不滚动 */}
|
||||||
|
<div style={{
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: 100,
|
||||||
|
background: 'var(--color-primary)',
|
||||||
|
boxShadow: 'var(--shadow-header)',
|
||||||
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
marginBottom: window.innerWidth <= 768 ? 12 : 24,
|
maxWidth: 1200,
|
||||||
position: 'relative'
|
margin: '0 auto',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: isMobile ? '12px 16px' : '16px 24px',
|
||||||
}}>
|
}}>
|
||||||
<Button
|
<Button
|
||||||
icon={<ArrowLeftOutlined />}
|
icon={<ArrowLeftOutlined />}
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
type="text"
|
size={isMobile ? 'middle' : 'large'}
|
||||||
size={window.innerWidth <= 768 ? 'small' : 'middle'}
|
|
||||||
style={{
|
style={{
|
||||||
|
background: 'rgba(255,255,255,0.2)',
|
||||||
|
borderColor: 'rgba(255,255,255,0.3)',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
padding: window.innerWidth <= 768 ? '4px 8px' : '4px 15px',
|
|
||||||
height: window.innerWidth <= 768 ? 32 : 'auto',
|
|
||||||
position: window.innerWidth <= 768 ? 'absolute' : 'static',
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
zIndex: 1
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{window.innerWidth <= 768 ? '返回' : '返回项目列表'}
|
{isMobile ? '返回' : '返回项目列表'}
|
||||||
</Button>
|
</Button>
|
||||||
<div style={{
|
|
||||||
textAlign: 'center',
|
<div style={{ textAlign: 'center' }}>
|
||||||
paddingTop: window.innerWidth <= 768 ? 0 : 0
|
|
||||||
}}>
|
|
||||||
<Title
|
<Title
|
||||||
level={window.innerWidth <= 768 ? 4 : 2}
|
level={isMobile ? 4 : 2}
|
||||||
style={{
|
style={{
|
||||||
color: '#fff',
|
|
||||||
margin: 0,
|
margin: 0,
|
||||||
marginBottom: window.innerWidth <= 768 ? 4 : 8
|
color: '#fff',
|
||||||
|
textShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||||
|
lineHeight: 1.2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
✨ 灵感模式
|
✨ 灵感模式
|
||||||
</Title>
|
</Title>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
color: '#fff',
|
color: 'rgba(255,255,255,0.85)',
|
||||||
display: 'block',
|
fontSize: isMobile ? 12 : 14,
|
||||||
fontSize: window.innerWidth <= 768 ? 12 : 14,
|
|
||||||
opacity: 0.9
|
|
||||||
}}>
|
}}>
|
||||||
通过对话快速创建你的小说项目
|
通过对话快速创建你的小说项目
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div style={{ width: isMobile ? 60 : 120 }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
maxWidth: 800,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: isMobile ? '16px 12px' : '24px 24px',
|
||||||
|
}}>
|
||||||
{(currentStep === 'idea' || currentStep === 'title' || currentStep === 'description' ||
|
{(currentStep === 'idea' || currentStep === 'title' || currentStep === 'description' ||
|
||||||
currentStep === 'theme' || currentStep === 'genre' || currentStep === 'perspective' ||
|
currentStep === 'theme' || currentStep === 'genre' || currentStep === 'perspective' ||
|
||||||
currentStep === 'outline_mode' || currentStep === 'confirm') && renderChat()}
|
currentStep === 'outline_mode' || currentStep === 'confirm') && renderChat()}
|
||||||
@@ -950,7 +971,7 @@ const Inspiration: React.FC = () => {
|
|||||||
storagePrefix="inspiration"
|
storagePrefix="inspiration"
|
||||||
onComplete={handleComplete}
|
onComplete={handleComplete}
|
||||||
onBack={handleBackToChat}
|
onBack={handleBackToChat}
|
||||||
isMobile={window.innerWidth <= 768}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -959,4 +980,3 @@ const Inspiration: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default Inspiration;
|
export default Inspiration;
|
||||||
|
|
||||||
+140
-141
@@ -96,9 +96,9 @@ export default function Login() {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-bg-base)',
|
||||||
}}>
|
}}>
|
||||||
<Spin size="large" style={{ color: '#fff' }} />
|
<Spin size="large" style={{ color: 'var(--color-primary)' }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -141,10 +141,10 @@ export default function Login() {
|
|||||||
height: 48,
|
height: 48,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
boxShadow: '0 4px 16px rgba(102, 126, 234, 0.4)',
|
boxShadow: 'var(--shadow-primary)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
登录
|
登录
|
||||||
@@ -178,19 +178,19 @@ export default function Login() {
|
|||||||
height: 52,
|
height: 52,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
boxShadow: '0 4px 16px rgba(102, 126, 234, 0.4)',
|
boxShadow: 'var(--shadow-primary)',
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||||
e.currentTarget.style.boxShadow = '0 6px 24px rgba(102, 126, 234, 0.5)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-elevated)';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
e.currentTarget.style.boxShadow = '0 4px 16px rgba(102, 126, 234, 0.4)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-primary)';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
使用 LinuxDO 登录
|
使用 LinuxDO 登录
|
||||||
@@ -224,144 +224,143 @@ export default function Login() {
|
|||||||
onNeverShow={handleNeverShow}
|
onNeverShow={handleNeverShow}
|
||||||
/>
|
/>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-bg-base)',
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
{/* 装饰性背景元素 */}
|
{/* 装饰性背景元素 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '-10%',
|
top: '-10%',
|
||||||
right: '-5%',
|
right: '-5%',
|
||||||
width: '400px',
|
width: '400px',
|
||||||
height: '400px',
|
height: '400px',
|
||||||
background: 'rgba(255, 255, 255, 0.1)',
|
background: 'var(--color-primary)',
|
||||||
borderRadius: '50%',
|
opacity: 0.1,
|
||||||
filter: 'blur(60px)',
|
borderRadius: '50%',
|
||||||
}} />
|
filter: 'blur(60px)',
|
||||||
<div style={{
|
}} />
|
||||||
position: 'absolute',
|
<div style={{
|
||||||
bottom: '-10%',
|
position: 'absolute',
|
||||||
left: '-5%',
|
bottom: '-10%',
|
||||||
width: '350px',
|
left: '-5%',
|
||||||
height: '350px',
|
width: '350px',
|
||||||
background: 'rgba(255, 255, 255, 0.08)',
|
height: '350px',
|
||||||
borderRadius: '50%',
|
background: 'var(--color-success)',
|
||||||
filter: 'blur(60px)',
|
opacity: 0.08,
|
||||||
}} />
|
borderRadius: '50%',
|
||||||
|
filter: 'blur(60px)',
|
||||||
|
}} />
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: 420,
|
maxWidth: 420,
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'var(--color-bg-container)',
|
||||||
backdropFilter: 'blur(20px)',
|
backdropFilter: 'blur(20px)',
|
||||||
WebkitBackdropFilter: 'blur(20px)',
|
WebkitBackdropFilter: 'blur(20px)',
|
||||||
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.2)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
border: '1px solid rgba(255, 255, 255, 0.3)',
|
border: '1px solid var(--color-border)',
|
||||||
borderRadius: '16px',
|
borderRadius: '16px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
bodyStyle={{
|
bodyStyle={{
|
||||||
padding: '40px 32px',
|
padding: '40px 32px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Space direction="vertical" size="large" style={{ width: '100%', textAlign: 'center' }}>
|
<Space direction="vertical" size="large" style={{ width: '100%', textAlign: 'center' }}>
|
||||||
{/* Logo区域 */}
|
{/* Logo区域 */}
|
||||||
<div style={{ marginBottom: '8px' }}>
|
<div style={{ marginBottom: '8px' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: '72px',
|
width: '72px',
|
||||||
height: '72px',
|
height: '72px',
|
||||||
margin: '0 auto 20px',
|
margin: '0 auto 20px',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
borderRadius: '20px',
|
borderRadius: '20px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
boxShadow: '0 8px 24px rgba(102, 126, 234, 0.4)',
|
boxShadow: 'var(--shadow-primary)',
|
||||||
}}>
|
}}>
|
||||||
<img
|
<img
|
||||||
src="/logo.svg"
|
src="/logo.svg"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
style={{
|
style={{
|
||||||
width: '48px',
|
width: '48px',
|
||||||
height: '48px',
|
height: '48px',
|
||||||
filter: 'brightness(0) invert(1)',
|
filter: 'brightness(0) invert(1)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<Title level={2} style={{
|
||||||
|
marginBottom: 8,
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
fontWeight: 700,
|
||||||
|
}}>
|
||||||
|
AI小说创作助手
|
||||||
|
</Title>
|
||||||
|
<Paragraph style={{
|
||||||
|
color: 'var(--color-text-secondary)',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginBottom: 0,
|
||||||
|
}}>
|
||||||
|
{localAuthEnabled && linuxdoEnabled ? '选择登录方式' :
|
||||||
|
localAuthEnabled ? '使用账户密码登录' :
|
||||||
|
'使用 LinuxDO 账号登录'}
|
||||||
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
<Title level={2} style={{
|
|
||||||
marginBottom: 8,
|
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
WebkitTextFillColor: 'transparent',
|
|
||||||
backgroundClip: 'text',
|
|
||||||
fontWeight: 700,
|
|
||||||
}}>
|
|
||||||
AI小说创作助手
|
|
||||||
</Title>
|
|
||||||
<Paragraph style={{
|
|
||||||
color: '#666',
|
|
||||||
fontSize: '14px',
|
|
||||||
marginBottom: 0,
|
|
||||||
}}>
|
|
||||||
{localAuthEnabled && linuxdoEnabled ? '选择登录方式' :
|
|
||||||
localAuthEnabled ? '使用账户密码登录' :
|
|
||||||
'使用 LinuxDO 账号登录'}
|
|
||||||
</Paragraph>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 登录方式 */}
|
{/* 登录方式 */}
|
||||||
{localAuthEnabled && linuxdoEnabled ? (
|
{localAuthEnabled && linuxdoEnabled ? (
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey="local"
|
defaultActiveKey="local"
|
||||||
centered
|
centered
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'local',
|
key: 'local',
|
||||||
label: '账户密码',
|
label: '账户密码',
|
||||||
children: renderLocalLogin(),
|
children: renderLocalLogin(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'linuxdo',
|
key: 'linuxdo',
|
||||||
label: 'LinuxDO',
|
label: 'LinuxDO',
|
||||||
children: renderLinuxDOLogin(),
|
children: renderLinuxDOLogin(),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
) : localAuthEnabled ? (
|
) : localAuthEnabled ? (
|
||||||
renderLocalLogin()
|
renderLocalLogin()
|
||||||
) : (
|
) : (
|
||||||
renderLinuxDOLogin()
|
renderLinuxDOLogin()
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 提示信息 */}
|
{/* 提示信息 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
background: 'rgba(102, 126, 234, 0.08)',
|
background: 'rgba(77, 128, 136, 0.08)',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
border: '1px solid rgba(102, 126, 234, 0.1)',
|
border: '1px solid var(--color-border)',
|
||||||
}}>
|
|
||||||
<Paragraph style={{
|
|
||||||
fontSize: 13,
|
|
||||||
color: '#666',
|
|
||||||
marginBottom: 0,
|
|
||||||
lineHeight: 1.6,
|
|
||||||
}}>
|
}}>
|
||||||
🎉 首次登录将自动创建账号
|
<Paragraph style={{
|
||||||
<br />
|
fontSize: 13,
|
||||||
🔒 每个用户拥有独立的数据空间
|
color: 'var(--color-text-secondary)',
|
||||||
</Paragraph>
|
marginBottom: 0,
|
||||||
</div>
|
lineHeight: 1.6,
|
||||||
</Space>
|
}}>
|
||||||
</Card>
|
🎉 首次登录将自动创建账号
|
||||||
</div>
|
<br />
|
||||||
|
🔒 每个用户拥有独立的数据空间
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+269
-243
@@ -17,7 +17,8 @@ import {
|
|||||||
Empty,
|
Empty,
|
||||||
Alert,
|
Alert,
|
||||||
Descriptions,
|
Descriptions,
|
||||||
Layout,
|
Row,
|
||||||
|
Col,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
@@ -35,7 +36,6 @@ import type { MCPPlugin, MCPTool } from '../types';
|
|||||||
|
|
||||||
const { Paragraph, Text, Title } = Typography;
|
const { Paragraph, Text, Title } = Typography;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const { Header, Content } = Layout;
|
|
||||||
|
|
||||||
export default function MCPPluginsPage() {
|
export default function MCPPluginsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -228,9 +228,9 @@ export default function MCPPluginsPage() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
background: '#fff2f0',
|
background: 'var(--color-error-bg)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
color: '#cf1322',
|
color: 'var(--color-error)',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
@@ -260,8 +260,8 @@ export default function MCPPluginsPage() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
background: '#fff7e6',
|
background: 'var(--color-warning-bg)',
|
||||||
border: '1px solid #ffd591',
|
border: '1px solid var(--color-warning-border)',
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
}}>
|
}}>
|
||||||
<Text style={{ fontSize: '13px', color: '#ad6800' }}>
|
<Text style={{ fontSize: '13px', color: '#ad6800' }}>
|
||||||
@@ -340,267 +340,293 @@ export default function MCPPluginsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh', background: '#f0f2f5' }}>
|
<div style={{
|
||||||
{/* 顶部导航栏 */}
|
minHeight: '100vh',
|
||||||
<Header style={{
|
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
padding: isMobile ? '20px 16px' : '40px 24px',
|
||||||
padding: isMobile ? '0 16px' : '0 24px',
|
display: 'flex',
|
||||||
display: 'flex',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
}}>
|
||||||
justifyContent: 'space-between',
|
<div style={{
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
zIndex: 1000,
|
|
||||||
height: isMobile ? 56 : 64
|
|
||||||
}}>
|
|
||||||
<Space size={isMobile ? 12 : 16}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<ArrowLeftOutlined />}
|
|
||||||
onClick={() => navigate('/')}
|
|
||||||
style={{
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: isMobile ? 16 : 18,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!isMobile && '返回'}
|
|
||||||
</Button>
|
|
||||||
<Title level={isMobile ? 4 : 3} style={{
|
|
||||||
margin: 0,
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: isMobile ? 18 : 24
|
|
||||||
}}>
|
|
||||||
MCP插件管理
|
|
||||||
</Title>
|
|
||||||
</Space>
|
|
||||||
</Header>
|
|
||||||
|
|
||||||
{/* 主内容区 */}
|
|
||||||
<Content style={{
|
|
||||||
marginTop: isMobile ? 56 : 64,
|
|
||||||
padding: isMobile ? '16px' : '24px',
|
|
||||||
maxWidth: 1400,
|
maxWidth: 1400,
|
||||||
|
margin: '0 auto',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
margin: `${isMobile ? 56 : 64}px auto 0`,
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
}}>
|
}}>
|
||||||
|
{/* 顶部导航卡片 */}
|
||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: isMobile ? 8 : 12,
|
background: 'linear-gradient(135deg, var(--color-primary) 0%, #5A9BA5 50%, var(--color-primary-hover) 100%)',
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
borderRadius: isMobile ? 16 : 24,
|
||||||
marginBottom: isMobile ? 16 : 24
|
boxShadow: '0 12px 40px rgba(77, 128, 136, 0.25), 0 4px 12px rgba(0, 0, 0, 0.06)',
|
||||||
|
marginBottom: isMobile ? 20 : 24,
|
||||||
|
border: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{
|
{/* 装饰性背景元素 */}
|
||||||
display: 'flex',
|
<div style={{ position: 'absolute', top: -60, right: -60, width: 200, height: 200, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.08)', pointerEvents: 'none' }} />
|
||||||
justifyContent: 'space-between',
|
<div style={{ position: 'absolute', bottom: -40, left: '30%', width: 120, height: 120, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.05)', pointerEvents: 'none' }} />
|
||||||
alignItems: 'center',
|
<div style={{ position: 'absolute', top: '50%', right: '15%', width: 80, height: 80, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.06)', pointerEvents: 'none' }} />
|
||||||
marginBottom: isMobile ? 16 : 20
|
|
||||||
}}>
|
|
||||||
<Title level={isMobile ? 5 : 4} style={{ margin: 0 }}>
|
|
||||||
我的插件
|
|
||||||
</Title>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={handleCreate}
|
|
||||||
size={isMobile ? 'middle' : 'large'}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
添加插件
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<Row align="middle" justify="space-between" gutter={[16, 16]} style={{ position: 'relative', zIndex: 1 }}>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Space direction="vertical" size={4}>
|
||||||
|
<Space align="center">
|
||||||
|
<Title level={isMobile ? 3 : 2} style={{ margin: 0, color: '#fff', textShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
|
||||||
|
<ToolOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 8 }} />
|
||||||
|
MCP插件管理
|
||||||
|
</Title>
|
||||||
|
</Space>
|
||||||
|
<Text style={{ fontSize: isMobile ? 12 : 14, color: 'rgba(255,255,255,0.85)', marginLeft: isMobile ? 40 : 48 }}>
|
||||||
|
扩展AI能力,连接外部工具与服务
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Space size={12} style={{ display: 'flex', justifyContent: isMobile ? 'flex-start' : 'flex-end', width: '100%' }}>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowLeftOutlined />}
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
color: '#fff',
|
||||||
|
backdropFilter: 'blur(10px)',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.25)';
|
||||||
|
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
|
||||||
|
e.currentTarget.style.transform = 'none';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
返回主页
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleCreate}
|
||||||
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'rgba(255, 193, 7, 0.95)',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
boxShadow: '0 4px 16px rgba(255, 193, 7, 0.4)',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加插件
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* 使用提示 */}
|
||||||
<Alert
|
<Alert
|
||||||
message="什么是 MCP 插件?"
|
message={
|
||||||
|
<Space align="center">
|
||||||
|
<InfoCircleOutlined style={{ fontSize: 16, color: 'var(--color-primary)' }} />
|
||||||
|
<Text strong style={{ fontSize: isMobile ? 13 : 14, color: 'var(--color-text-primary)' }}>什么是 MCP 插件?</Text>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
description={
|
description={
|
||||||
<div style={{ fontSize: isMobile ? '12px' : '14px' }}>
|
<div>
|
||||||
<p style={{ margin: '8px 0' }}>
|
<Text style={{ fontSize: isMobile ? 12 : 13, display: 'block', marginBottom: 8 }}>
|
||||||
MCP (Model Context Protocol) 是一个标准化的协议,允许 AI 调用外部工具获取数据。
|
• <strong>MCP (Model Context Protocol)</strong> 是一个标准化的协议,允许 AI 调用外部工具获取数据。
|
||||||
</p>
|
</Text>
|
||||||
<p style={{ margin: '8px 0 0 0' }}>
|
<Text style={{ fontSize: isMobile ? 12 : 13, display: 'block' }}>
|
||||||
通过添加 MCP 插件,AI 可以访问搜索引擎、数据库、API 等外部服务,增强创作能力。
|
• 通过添加 MCP 插件,AI 可以访问搜索引擎、数据库、API 等外部服务,增强创作能力。
|
||||||
</p>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
type="info"
|
type="info"
|
||||||
showIcon
|
showIcon={false}
|
||||||
icon={<InfoCircleOutlined />}
|
style={{
|
||||||
style={{ marginBottom: isMobile ? 16 : 20 }}
|
marginTop: isMobile ? 16 : 24,
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'rgba(230, 247, 255, 0.6)',
|
||||||
|
border: '1px solid rgba(145, 213, 255, 0.6)',
|
||||||
|
backdropFilter: 'blur(5px)'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 插件列表 */}
|
{/* 主内容区 */}
|
||||||
<Spin spinning={loading}>
|
<div style={{ flex: 1 }}>
|
||||||
{plugins.length === 0 ? (
|
|
||||||
<Empty
|
{/* 插件列表 */}
|
||||||
description="还没有添加任何插件"
|
<Spin spinning={loading}>
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
{plugins.length === 0 ? (
|
||||||
style={{ padding: isMobile ? '40px 0' : '60px 0' }}
|
<Empty
|
||||||
>
|
description="还没有添加任何插件"
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
添加第一个插件
|
style={{ padding: isMobile ? '40px 0' : '60px 0' }}
|
||||||
</Button>
|
>
|
||||||
</Empty>
|
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
||||||
) : (
|
添加第一个插件
|
||||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
</Button>
|
||||||
{plugins.map((plugin) => (
|
</Empty>
|
||||||
<Card
|
) : (
|
||||||
key={plugin.id}
|
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||||
size="small"
|
{plugins.map((plugin) => (
|
||||||
style={{
|
<Card
|
||||||
borderRadius: 8,
|
key={plugin.id}
|
||||||
border: '1px solid #f0f0f0',
|
size="small"
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
borderRadius: 8,
|
||||||
justifyContent: 'space-between',
|
border: '1px solid #f0f0f0',
|
||||||
alignItems: 'flex-start',
|
|
||||||
gap: '16px',
|
|
||||||
flexWrap: isMobile ? 'wrap' : 'nowrap',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div
|
||||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
style={{
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
display: 'flex',
|
||||||
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
|
justifyContent: 'space-between',
|
||||||
{plugin.display_name || plugin.plugin_name}
|
alignItems: 'flex-start',
|
||||||
</Text>
|
gap: '16px',
|
||||||
{getStatusTag(plugin)}
|
flexWrap: isMobile ? 'wrap' : 'nowrap',
|
||||||
<Tag color={plugin.plugin_type === 'http' ? 'blue' : 'cyan'}>
|
}}
|
||||||
{plugin.plugin_type?.toUpperCase() || 'UNKNOWN'}
|
>
|
||||||
</Tag>
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
{plugin.category && plugin.category !== 'general' && (
|
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||||
<Tag color="purple">{plugin.category}</Tag>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
||||||
|
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
|
||||||
|
{plugin.display_name || plugin.plugin_name}
|
||||||
|
</Text>
|
||||||
|
{getStatusTag(plugin)}
|
||||||
|
<Tag color={plugin.plugin_type === 'http' ? 'blue' : 'cyan'}>
|
||||||
|
{plugin.plugin_type?.toUpperCase() || 'UNKNOWN'}
|
||||||
|
</Tag>
|
||||||
|
{plugin.category && plugin.category !== 'general' && (
|
||||||
|
<Tag color="purple">{plugin.category}</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{plugin.description && (
|
||||||
|
<Paragraph
|
||||||
|
type="secondary"
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
fontSize: isMobile ? '12px' : '13px',
|
||||||
|
}}
|
||||||
|
ellipsis={{ rows: 2 }}
|
||||||
|
>
|
||||||
|
{plugin.description}
|
||||||
|
</Paragraph>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
{plugin.description && (
|
{/* 只显示有值的URL或命令,脱敏处理敏感信息 */}
|
||||||
<Paragraph
|
{plugin.plugin_type === 'http' && plugin.server_url && (
|
||||||
type="secondary"
|
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||||
|
<Text type="secondary" code>
|
||||||
|
{(() => {
|
||||||
|
// 脱敏处理:隐藏URL中的API Key
|
||||||
|
const url = plugin.server_url;
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
// 替换查询参数中的敏感信息
|
||||||
|
const params = new URLSearchParams(urlObj.search);
|
||||||
|
let maskedUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;
|
||||||
|
|
||||||
|
const sensitiveKeys = ['apiKey', 'api_key', 'key', 'token', 'secret', 'password', 'auth'];
|
||||||
|
let hasParams = false;
|
||||||
|
|
||||||
|
params.forEach((value, key) => {
|
||||||
|
const isSensitive = sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()));
|
||||||
|
const maskedValue = isSensitive ? '***' : value;
|
||||||
|
maskedUrl += (hasParams ? '&' : '?') + `${key}=${maskedValue}`;
|
||||||
|
hasParams = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return maskedUrl;
|
||||||
|
} catch {
|
||||||
|
// 如果URL解析失败,尝试简单替换
|
||||||
|
return url.replace(/([?&])(apiKey|api_key|key|token|secret|password|auth)=([^&]+)/gi, '$1$2=***');
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{plugin.plugin_type === 'stdio' && plugin.command && (
|
||||||
|
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||||
|
<Text type="secondary" code>
|
||||||
|
{plugin.command} {plugin.args?.join(' ')}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 显示最后错误信息 */}
|
||||||
|
{plugin.last_error && (
|
||||||
|
<Text type="danger" style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||||
|
错误: {plugin.last_error}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Space size="small" wrap>
|
||||||
|
<Tooltip title={plugin.enabled ? '禁用插件' : '启用插件'}>
|
||||||
|
<Switch
|
||||||
|
checked={plugin.enabled}
|
||||||
|
onChange={(checked) => handleToggle(plugin, checked)}
|
||||||
|
size={isMobile ? 'small' : 'default'}
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
flexShrink: 0,
|
||||||
fontSize: isMobile ? '12px' : '13px',
|
height: isMobile ? 16 : 22,
|
||||||
|
minHeight: isMobile ? 16 : 22,
|
||||||
|
lineHeight: isMobile ? '16px' : '22px'
|
||||||
}}
|
}}
|
||||||
ellipsis={{ rows: 2 }}
|
/>
|
||||||
>
|
</Tooltip>
|
||||||
{plugin.description}
|
<Tooltip title="测试连接">
|
||||||
</Paragraph>
|
<Button
|
||||||
)}
|
icon={<ThunderboltOutlined />}
|
||||||
|
onClick={() => handleTest(plugin.id)}
|
||||||
{/* 只显示有值的URL或命令,脱敏处理敏感信息 */}
|
loading={testingPluginId === plugin.id}
|
||||||
{plugin.plugin_type === 'http' && plugin.server_url && (
|
size={isMobile ? 'small' : 'middle'}
|
||||||
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
/>
|
||||||
<Text type="secondary" code>
|
</Tooltip>
|
||||||
{(() => {
|
<Tooltip title="查看工具">
|
||||||
// 脱敏处理:隐藏URL中的API Key
|
<Button
|
||||||
const url = plugin.server_url;
|
icon={<ToolOutlined />}
|
||||||
try {
|
onClick={() => handleViewTools(plugin.id)}
|
||||||
const urlObj = new URL(url);
|
disabled={!plugin.enabled || plugin.status !== 'active'}
|
||||||
// 替换查询参数中的敏感信息
|
size={isMobile ? 'small' : 'middle'}
|
||||||
const params = new URLSearchParams(urlObj.search);
|
/>
|
||||||
let maskedUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;
|
</Tooltip>
|
||||||
|
<Tooltip title="编辑">
|
||||||
const sensitiveKeys = ['apiKey', 'api_key', 'key', 'token', 'secret', 'password', 'auth'];
|
<Button
|
||||||
let hasParams = false;
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => handleEdit(plugin)}
|
||||||
params.forEach((value, key) => {
|
size={isMobile ? 'small' : 'middle'}
|
||||||
const isSensitive = sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()));
|
/>
|
||||||
const maskedValue = isSensitive ? '***' : value;
|
</Tooltip>
|
||||||
maskedUrl += (hasParams ? '&' : '?') + `${key}=${maskedValue}`;
|
<Tooltip title="删除">
|
||||||
hasParams = true;
|
<Button
|
||||||
});
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
return maskedUrl;
|
onClick={() => handleDelete(plugin)}
|
||||||
} catch {
|
size={isMobile ? 'small' : 'middle'}
|
||||||
// 如果URL解析失败,尝试简单替换
|
/>
|
||||||
return url.replace(/([?&])(apiKey|api_key|key|token|secret|password|auth)=([^&]+)/gi, '$1$2=***');
|
</Tooltip>
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{plugin.plugin_type === 'stdio' && plugin.command && (
|
|
||||||
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
|
||||||
<Text type="secondary" code>
|
|
||||||
{plugin.command} {plugin.args?.join(' ')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 显示最后错误信息 */}
|
|
||||||
{plugin.last_error && (
|
|
||||||
<Text type="danger" style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
|
||||||
错误: {plugin.last_error}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
</Card>
|
||||||
<Space size="small" wrap>
|
))}
|
||||||
<Tooltip title={plugin.enabled ? '禁用插件' : '启用插件'}>
|
</Space>
|
||||||
<Switch
|
)}
|
||||||
checked={plugin.enabled}
|
</Spin>
|
||||||
onChange={(checked) => handleToggle(plugin, checked)}
|
</div>
|
||||||
size={isMobile ? 'small' : 'default'}
|
</div>
|
||||||
style={{
|
|
||||||
flexShrink: 0,
|
|
||||||
height: isMobile ? 16 : 22,
|
|
||||||
minHeight: isMobile ? 16 : 22,
|
|
||||||
lineHeight: isMobile ? '16px' : '22px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="测试连接">
|
|
||||||
<Button
|
|
||||||
icon={<ThunderboltOutlined />}
|
|
||||||
onClick={() => handleTest(plugin.id)}
|
|
||||||
loading={testingPluginId === plugin.id}
|
|
||||||
size={isMobile ? 'small' : 'middle'}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="查看工具">
|
|
||||||
<Button
|
|
||||||
icon={<ToolOutlined />}
|
|
||||||
onClick={() => handleViewTools(plugin.id)}
|
|
||||||
disabled={!plugin.enabled || plugin.status !== 'active'}
|
|
||||||
size={isMobile ? 'small' : 'middle'}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="编辑">
|
|
||||||
<Button
|
|
||||||
icon={<EditOutlined />}
|
|
||||||
onClick={() => handleEdit(plugin)}
|
|
||||||
size={isMobile ? 'small' : 'middle'}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="删除">
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={() => handleDelete(plugin)}
|
|
||||||
size={isMobile ? 'small' : 'middle'}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</Spin>
|
|
||||||
</Content>
|
|
||||||
|
|
||||||
{/* 创建/编辑插件模态框 */}
|
{/* 创建/编辑插件模态框 */}
|
||||||
<Modal
|
<Modal
|
||||||
@@ -710,6 +736,6 @@ export default function MCPPluginsPage() {
|
|||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</Layout>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,7 @@ export default function Organizations() {
|
|||||||
const [editMemberForm] = Form.useForm();
|
const [editMemberForm] = Form.useForm();
|
||||||
const [editOrgForm] = Form.useForm();
|
const [editOrgForm] = Form.useForm();
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
@@ -129,7 +130,7 @@ export default function Organizations() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveMember = async (memberId: string) => {
|
const handleRemoveMember = async (memberId: string) => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '确认移除',
|
title: '确认移除',
|
||||||
content: '确定要移除该成员吗?',
|
content: '确定要移除该成员吗?',
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -294,6 +295,7 @@ export default function Organizations() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{contextHolder}
|
||||||
<Card
|
<Card
|
||||||
title={
|
title={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
@@ -326,7 +328,7 @@ export default function Organizations() {
|
|||||||
hoverable
|
hoverable
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
border: selectedOrg?.id === org.id ? '2px solid #1890ff' : '1px solid #d9d9d9'
|
border: selectedOrg?.id === org.id ? '2px solid var(--color-primary)' : '1px solid var(--color-border-secondary)'
|
||||||
}}
|
}}
|
||||||
onClick={() => handleSelectOrganization(org)}
|
onClick={() => handleSelectOrganization(org)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag, InputNumber, Tooltip, Tabs } from 'antd';
|
import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag, InputNumber, Tooltip, Tabs } from 'antd';
|
||||||
import { EditOutlined, DeleteOutlined, ThunderboltOutlined, BranchesOutlined, AppstoreAddOutlined, CheckCircleOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { EditOutlined, DeleteOutlined, ThunderboltOutlined, BranchesOutlined, AppstoreAddOutlined, CheckCircleOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { useStore } from '../store';
|
import { useStore } from '../store';
|
||||||
@@ -41,6 +41,7 @@ export default function Outline() {
|
|||||||
const [editForm] = Form.useForm();
|
const [editForm] = Form.useForm();
|
||||||
const [generateForm] = Form.useForm();
|
const [generateForm] = Form.useForm();
|
||||||
const [expansionForm] = Form.useForm();
|
const [expansionForm] = Form.useForm();
|
||||||
|
const [modalApi, contextHolder] = Modal.useModal();
|
||||||
const [batchExpansionForm] = Form.useForm();
|
const [batchExpansionForm] = Form.useForm();
|
||||||
const [manualCreateForm] = Form.useForm();
|
const [manualCreateForm] = Form.useForm();
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||||
@@ -135,7 +136,7 @@ export default function Outline() {
|
|||||||
const outline = outlines.find(o => o.id === id);
|
const outline = outlines.find(o => o.id === id);
|
||||||
if (outline) {
|
if (outline) {
|
||||||
editForm.setFieldsValue(outline);
|
editForm.setFieldsValue(outline);
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: '编辑大纲',
|
title: '编辑大纲',
|
||||||
width: 600,
|
width: 600,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -335,7 +336,7 @@ export default function Outline() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: hasOutlines ? (
|
title: hasOutlines ? (
|
||||||
<Space>
|
<Space>
|
||||||
<span>AI生成/续写大纲</span>
|
<span>AI生成/续写大纲</span>
|
||||||
@@ -504,7 +505,7 @@ export default function Outline() {
|
|||||||
console.log('已同步到Form,当前Form值:', generateForm.getFieldsValue());
|
console.log('已同步到Form,当前Form值:', generateForm.getFieldsValue());
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{ color: '#666', fontSize: 12, marginTop: 4 }}>
|
<div style={{ color: 'var(--color-text-tertiary)', fontSize: 12, marginTop: 4 }}>
|
||||||
{defaultModel ? `当前默认模型: ${loadedModels.find(m => m.value === defaultModel)?.label || defaultModel}` : '未配置默认模型'}
|
{defaultModel ? `当前默认模型: ${loadedModels.find(m => m.value === defaultModel)?.label || defaultModel}` : '未配置默认模型'}
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -526,7 +527,7 @@ export default function Outline() {
|
|||||||
? Math.max(...outlines.map(o => o.order_index)) + 1
|
? Math.max(...outlines.map(o => o.order_index)) + 1
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: '手动创建大纲',
|
title: '手动创建大纲',
|
||||||
width: 600,
|
width: 600,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -574,26 +575,26 @@ export default function Outline() {
|
|||||||
// 校验序号是否重复
|
// 校验序号是否重复
|
||||||
const existingOutline = outlines.find(o => o.order_index === values.order_index);
|
const existingOutline = outlines.find(o => o.order_index === values.order_index);
|
||||||
if (existingOutline) {
|
if (existingOutline) {
|
||||||
Modal.warning({
|
modalApi.warning({
|
||||||
title: '序号冲突',
|
title: '序号冲突',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<p>序号 <strong>{values.order_index}</strong> 已被使用:</p>
|
<p>序号 <strong>{values.order_index}</strong> 已被使用:</p>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: 12,
|
padding: 12,
|
||||||
background: '#fff7e6',
|
background: 'var(--color-warning-bg)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
border: '1px solid #ffd591',
|
border: '1px solid var(--color-warning-border)',
|
||||||
marginTop: 8
|
marginTop: 8
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontWeight: 500, color: '#fa8c16' }}>
|
<div style={{ fontWeight: 500, color: 'var(--color-warning)' }}>
|
||||||
{currentProject?.outline_mode === 'one-to-one'
|
{currentProject?.outline_mode === 'one-to-one'
|
||||||
? `第${existingOutline.order_index}章`
|
? `第${existingOutline.order_index}章`
|
||||||
: `第${existingOutline.order_index}卷`
|
: `第${existingOutline.order_index}卷`
|
||||||
}:{existingOutline.title}
|
}:{existingOutline.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ marginTop: 12, color: '#666' }}>
|
<p style={{ marginTop: 12, color: 'var(--color-text-secondary)' }}>
|
||||||
💡 建议使用序号 <strong>{nextOrderIndex}</strong>,或选择其他未使用的序号
|
💡 建议使用序号 <strong>{nextOrderIndex}</strong>,或选择其他未使用的序号
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -644,7 +645,7 @@ export default function Outline() {
|
|||||||
if (!prevChapters.has_chapters) {
|
if (!prevChapters.has_chapters) {
|
||||||
// 如果前面有未展开的大纲,显示提示并阻止操作
|
// 如果前面有未展开的大纲,显示提示并阻止操作
|
||||||
setIsExpanding(false);
|
setIsExpanding(false);
|
||||||
Modal.warning({
|
modalApi.warning({
|
||||||
title: '请按顺序展开大纲',
|
title: '请按顺序展开大纲',
|
||||||
width: 600,
|
width: 600,
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -655,18 +656,18 @@ export default function Outline() {
|
|||||||
</p>
|
</p>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: 12,
|
padding: 12,
|
||||||
background: '#fff7e6',
|
background: 'var(--color-warning-bg)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
border: '1px solid #ffd591'
|
border: '1px solid var(--color-warning-border)'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontWeight: 500, marginBottom: 8, color: '#fa8c16' }}>
|
<div style={{ fontWeight: 500, marginBottom: 8, color: 'var(--color-warning)' }}>
|
||||||
⚠️ 需要先展开:
|
⚠️ 需要先展开:
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: '#666' }}>
|
<div style={{ color: 'var(--color-text-secondary)' }}>
|
||||||
第{prevOutline.order_index}卷:《{prevOutline.title}》
|
第{prevOutline.order_index}卷:《{prevOutline.title}》
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ marginTop: 12, color: '#666', fontSize: 13 }}>
|
<p style={{ marginTop: 12, color: 'var(--color-text-secondary)', fontSize: 13 }}>
|
||||||
💡 提示:您也可以使用「批量展开」功能,系统会自动按顺序处理所有大纲。
|
💡 提示:您也可以使用「批量展开」功能,系统会自动按顺序处理所有大纲。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -694,7 +695,7 @@ export default function Outline() {
|
|||||||
|
|
||||||
// 如果没有章节,显示展开表单
|
// 如果没有章节,显示展开表单
|
||||||
setIsExpanding(false);
|
setIsExpanding(false);
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: (
|
title: (
|
||||||
<Space>
|
<Space>
|
||||||
<BranchesOutlined />
|
<BranchesOutlined />
|
||||||
@@ -705,9 +706,9 @@ export default function Outline() {
|
|||||||
centered: true,
|
centered: true,
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16, padding: 12, background: '#f5f5f5', borderRadius: 4 }}>
|
<div style={{ marginBottom: 16, padding: 12, background: 'var(--color-bg-layout)', borderRadius: 4 }}>
|
||||||
<div style={{ fontWeight: 500, marginBottom: 4 }}>大纲标题</div>
|
<div style={{ fontWeight: 500, marginBottom: 4 }}>大纲标题</div>
|
||||||
<div style={{ color: '#666' }}>{outlineTitle}</div>
|
<div style={{ color: 'var(--color-text-secondary)' }}>{outlineTitle}</div>
|
||||||
</div>
|
</div>
|
||||||
<Form
|
<Form
|
||||||
form={expansionForm}
|
form={expansionForm}
|
||||||
@@ -855,10 +856,10 @@ export default function Outline() {
|
|||||||
}> | null;
|
}> | null;
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const modal = Modal.info({
|
modalApi.info({
|
||||||
title: (
|
title: (
|
||||||
<Space style={{ flexWrap: 'wrap' }}>
|
<Space style={{ flexWrap: 'wrap' }}>
|
||||||
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
<CheckCircleOutlined style={{ color: 'var(--color-success)' }} />
|
||||||
<span>已存在的展开章节</span>
|
<span>已存在的展开章节</span>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
@@ -876,20 +877,20 @@ export default function Outline() {
|
|||||||
overflowY: 'auto'
|
overflowY: 'auto'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
footer: (_, { OkBtn }) => (
|
footer: (_: any, { OkBtn }: any) => (
|
||||||
<Space wrap style={{ width: '100%', justifyContent: isMobile ? 'center' : 'flex-end' }}>
|
<Space wrap style={{ width: '100%', justifyContent: isMobile ? 'center' : 'flex-end' }}>
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modal.destroy();
|
Modal.destroyAll();
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
icon: <ExclamationCircleOutlined />,
|
icon: <ExclamationCircleOutlined />,
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<p>此操作将删除大纲《{outlineTitle}》展开的所有 <strong>{data.chapter_count}</strong> 个章节。</p>
|
<p>此操作将删除大纲《{outlineTitle}》展开的所有 <strong>{data.chapter_count}</strong> 个章节。</p>
|
||||||
<p style={{ color: '#1890ff', marginTop: 8 }}>
|
<p style={{ color: 'var(--color-primary)', marginTop: 8 }}>
|
||||||
📝 注意:大纲本身会保留,您可以重新展开
|
📝 注意:大纲本身会保留,您可以重新展开
|
||||||
</p>
|
</p>
|
||||||
<p style={{ color: '#ff4d4f', marginTop: 8 }}>
|
<p style={{ color: '#ff4d4f', marginTop: 8 }}>
|
||||||
@@ -1077,13 +1078,14 @@ export default function Outline() {
|
|||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div >
|
||||||
)
|
)
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div >
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1093,10 +1095,10 @@ export default function Outline() {
|
|||||||
// 缓存AI生成的规划数据
|
// 缓存AI生成的规划数据
|
||||||
const cachedPlans = response.chapter_plans;
|
const cachedPlans = response.chapter_plans;
|
||||||
|
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: (
|
title: (
|
||||||
<Space>
|
<Space>
|
||||||
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
<CheckCircleOutlined style={{ color: 'var(--color-success)' }} />
|
||||||
<span>展开规划预览</span>
|
<span>展开规划预览</span>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
@@ -1222,7 +1224,7 @@ export default function Outline() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: (
|
title: (
|
||||||
<Space>
|
<Space>
|
||||||
<AppstoreAddOutlined />
|
<AppstoreAddOutlined />
|
||||||
@@ -1233,7 +1235,7 @@ export default function Outline() {
|
|||||||
centered: true,
|
centered: true,
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16, padding: 12, background: '#fff3cd', borderRadius: 4 }}>
|
<div style={{ marginBottom: 16, padding: 12, background: 'var(--color-warning-bg)', borderRadius: 4 }}>
|
||||||
<div style={{ color: '#856404' }}>
|
<div style={{ color: '#856404' }}>
|
||||||
⚠️ 将对当前项目的所有 {outlines.length} 个大纲进行展开
|
⚠️ 将对当前项目的所有 {outlines.length} 个大纲进行展开
|
||||||
</div>
|
</div>
|
||||||
@@ -1361,11 +1363,11 @@ export default function Outline() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
background: '#fffbe6',
|
background: 'var(--color-warning-bg)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
border: '1px solid #ffe58f'
|
border: '1px solid #ffe58f'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ fontWeight: 500, marginBottom: 8, color: '#faad14' }}>
|
<div style={{ fontWeight: 500, marginBottom: 8, color: 'var(--color-warning)' }}>
|
||||||
⚠️ 以下大纲已展开过,已自动跳过:
|
⚠️ 以下大纲已展开过,已自动跳过:
|
||||||
</div>
|
</div>
|
||||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||||
@@ -1404,7 +1406,7 @@ export default function Outline() {
|
|||||||
background: selectedOutlineIdx === idx ? '#e6f7ff' : 'transparent',
|
background: selectedOutlineIdx === idx ? '#e6f7ff' : 'transparent',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
border: selectedOutlineIdx === idx ? '1px solid #1890ff' : '1px solid transparent'
|
border: selectedOutlineIdx === idx ? '1px solid var(--color-primary)' : '1px solid transparent'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
@@ -1445,7 +1447,7 @@ export default function Outline() {
|
|||||||
background: selectedChapterIdx === idx ? '#e6f7ff' : 'transparent',
|
background: selectedChapterIdx === idx ? '#e6f7ff' : 'transparent',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
border: selectedChapterIdx === idx ? '1px solid #1890ff' : '1px solid transparent'
|
border: selectedChapterIdx === idx ? '1px solid var(--color-primary)' : '1px solid transparent'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
@@ -1719,7 +1721,7 @@ export default function Outline() {
|
|||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
<ExclamationCircleOutlined style={{ color: '#faad14' }} />
|
<ExclamationCircleOutlined style={{ color: 'var(--color-warning)' }} />
|
||||||
<span>确认引入新角色</span>
|
<span>确认引入新角色</span>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -1731,7 +1733,7 @@ export default function Outline() {
|
|||||||
handleConfirmCharacters(selectedCharacters);
|
handleConfirmCharacters(selectedCharacters);
|
||||||
}}
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
Modal.confirm({
|
modalApi.confirm({
|
||||||
title: '确认操作',
|
title: '确认操作',
|
||||||
content: '是否跳过角色创建,直接续写大纲?',
|
content: '是否跳过角色创建,直接续写大纲?',
|
||||||
okText: '跳过角色,继续续写',
|
okText: '跳过角色,继续续写',
|
||||||
@@ -1745,7 +1747,7 @@ export default function Outline() {
|
|||||||
cancelText="跳过角色创建"
|
cancelText="跳过角色创建"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16, padding: 12, background: '#fffbe6', borderRadius: 4, border: '1px solid #ffe58f' }}>
|
<div style={{ marginBottom: 16, padding: 12, background: 'var(--color-warning-bg)', borderRadius: 4, border: '1px solid var(--color-warning-border)' }}>
|
||||||
<div style={{ fontWeight: 500, marginBottom: 8, color: '#d48806' }}>
|
<div style={{ fontWeight: 500, marginBottom: 8, color: '#d48806' }}>
|
||||||
AI 分析结果
|
AI 分析结果
|
||||||
</div>
|
</div>
|
||||||
@@ -1785,7 +1787,7 @@ export default function Outline() {
|
|||||||
padding: 12,
|
padding: 12,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
border: selectedCharacterIndices.includes(index) ? '1px solid #1890ff' : '1px solid #f0f0f0',
|
border: selectedCharacterIndices.includes(index) ? '1px solid var(--color-primary)' : '1px solid var(--color-border-secondary)',
|
||||||
cursor: 'pointer'
|
cursor: 'pointer'
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1859,7 +1861,7 @@ export default function Outline() {
|
|||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
<CheckCircleOutlined style={{ color: '#52c41a' }} />
|
<CheckCircleOutlined style={{ color: 'var(--color-success)' }} />
|
||||||
<span>批量展开规划预览</span>
|
<span>批量展开规划预览</span>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -1875,6 +1877,7 @@ export default function Outline() {
|
|||||||
{renderBatchPreviewContent()}
|
{renderBatchPreviewContent()}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{contextHolder}
|
||||||
{/* SSE进度Modal - 使用统一组件 */}
|
{/* SSE进度Modal - 使用统一组件 */}
|
||||||
<SSEProgressModal
|
<SSEProgressModal
|
||||||
visible={sseModalVisible}
|
visible={sseModalVisible}
|
||||||
@@ -1889,7 +1892,7 @@ export default function Outline() {
|
|||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
backgroundColor: '#fff',
|
backgroundColor: 'var(--color-bg-container)',
|
||||||
padding: isMobile ? '12px 0' : '16px 0',
|
padding: isMobile ? '12px 0' : '16px 0',
|
||||||
marginBottom: isMobile ? 12 : 16,
|
marginBottom: isMobile ? 12 : 16,
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid #f0f0f0',
|
||||||
@@ -1992,7 +1995,7 @@ export default function Outline() {
|
|||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
title={
|
title={
|
||||||
<Space size="small" style={{ fontSize: isMobile ? 14 : 16, flexWrap: 'wrap' }}>
|
<Space size="small" style={{ fontSize: isMobile ? 14 : 16, flexWrap: 'wrap' }}>
|
||||||
<span style={{ color: '#1890ff', fontWeight: 'bold' }}>
|
<span style={{ color: 'var(--color-primary)', fontWeight: 'bold' }}>
|
||||||
{currentProject?.outline_mode === 'one-to-one'
|
{currentProject?.outline_mode === 'one-to-one'
|
||||||
? `第${item.order_index || '?'}章`
|
? `第${item.order_index || '?'}章`
|
||||||
: `第${item.order_index || '?'}卷`
|
: `第${item.order_index || '?'}卷`
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export default function ProjectDetail() {
|
|||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh', height: '100vh', overflow: 'hidden' }}>
|
<Layout style={{ minHeight: '100vh', height: '100vh', overflow: 'hidden' }}>
|
||||||
<Header style={{
|
<Header style={{
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
padding: mobile ? '0 12px' : '0 24px',
|
padding: mobile ? '0 12px' : '0 24px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -203,7 +203,7 @@ export default function ProjectDetail() {
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
boxShadow: 'var(--shadow-header)',
|
||||||
height: mobile ? 56 : 70
|
height: mobile ? 56 : 70
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', zIndex: 1 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', zIndex: 1 }}>
|
||||||
@@ -279,7 +279,7 @@ export default function ProjectDetail() {
|
|||||||
<Card
|
<Card
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255,255,255,0.95)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
minWidth: '80px',
|
minWidth: '80px',
|
||||||
@@ -289,10 +289,10 @@ export default function ProjectDetail() {
|
|||||||
styles={{ body: { padding: '8px' } }}
|
styles={{ body: { padding: '8px' } }}
|
||||||
>
|
>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={<span style={{ fontSize: '11px', color: '#666' }}>大纲</span>}
|
title={<span style={{ fontSize: '11px', color: 'var(--color-text-secondary)' }}>大纲</span>}
|
||||||
value={outlines.length}
|
value={outlines.length}
|
||||||
suffix="条"
|
suffix="条"
|
||||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#667eea' }}
|
valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-primary)' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -300,7 +300,7 @@ export default function ProjectDetail() {
|
|||||||
<Card
|
<Card
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255,255,255,0.95)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
minWidth: '80px',
|
minWidth: '80px',
|
||||||
@@ -310,10 +310,10 @@ export default function ProjectDetail() {
|
|||||||
styles={{ body: { padding: '8px' } }}
|
styles={{ body: { padding: '8px' } }}
|
||||||
>
|
>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={<span style={{ fontSize: '11px', color: '#666' }}>角色</span>}
|
title={<span style={{ fontSize: '11px', color: 'var(--color-text-secondary)' }}>角色</span>}
|
||||||
value={characters.length}
|
value={characters.length}
|
||||||
suffix="个"
|
suffix="个"
|
||||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#52c41a' }}
|
valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-success)' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -321,7 +321,7 @@ export default function ProjectDetail() {
|
|||||||
<Card
|
<Card
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255,255,255,0.95)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
minWidth: '80px',
|
minWidth: '80px',
|
||||||
@@ -331,10 +331,10 @@ export default function ProjectDetail() {
|
|||||||
styles={{ body: { padding: '8px' } }}
|
styles={{ body: { padding: '8px' } }}
|
||||||
>
|
>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={<span style={{ fontSize: '11px', color: '#666' }}>章节</span>}
|
title={<span style={{ fontSize: '11px', color: 'var(--color-text-secondary)' }}>章节</span>}
|
||||||
value={chapters.length}
|
value={chapters.length}
|
||||||
suffix="章"
|
suffix="章"
|
||||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#1890ff' }}
|
valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-info)' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -342,7 +342,7 @@ export default function ProjectDetail() {
|
|||||||
<Card
|
<Card
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255,255,255,0.95)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
minWidth: '80px',
|
minWidth: '80px',
|
||||||
@@ -352,10 +352,10 @@ export default function ProjectDetail() {
|
|||||||
styles={{ body: { padding: '8px' } }}
|
styles={{ body: { padding: '8px' } }}
|
||||||
>
|
>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={<span style={{ fontSize: '11px', color: '#666' }}>已写</span>}
|
title={<span style={{ fontSize: '11px', color: 'var(--color-text-secondary)' }}>已写</span>}
|
||||||
value={currentProject.current_words}
|
value={currentProject.current_words}
|
||||||
suffix="字"
|
suffix="字"
|
||||||
valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#fa8c16' }}
|
valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-warning)' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -384,15 +384,14 @@ export default function ProjectDetail() {
|
|||||||
trigger={null}
|
trigger={null}
|
||||||
width={220}
|
width={220}
|
||||||
collapsedWidth={60}
|
collapsedWidth={60}
|
||||||
|
className="modern-sider"
|
||||||
style={{
|
style={{
|
||||||
background: '#fff',
|
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 70,
|
top: 70,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
boxShadow: '2px 0 12px rgba(0,0,0,0.08)',
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
transition: 'all 0.2s',
|
|
||||||
height: 'calc(100vh - 70px)'
|
height: 'calc(100vh - 70px)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -412,7 +411,7 @@ export default function ProjectDetail() {
|
|||||||
}}>
|
}}>
|
||||||
<Content
|
<Content
|
||||||
style={{
|
style={{
|
||||||
background: '#f5f7fa',
|
background: 'var(--color-bg-base)',
|
||||||
padding: mobile ? 12 : 24,
|
padding: mobile ? 12 : 24,
|
||||||
height: mobile ? 'calc(100vh - 56px)' : 'calc(100vh - 70px)',
|
height: mobile ? 'calc(100vh - 56px)' : 'calc(100vh - 70px)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -421,10 +420,10 @@ export default function ProjectDetail() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: '#fff',
|
background: 'var(--color-bg-container)',
|
||||||
padding: mobile ? 12 : 24,
|
padding: mobile ? 12 : 24,
|
||||||
borderRadius: mobile ? '8px' : '12px',
|
borderRadius: mobile ? '8px' : '12px',
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
+1029
-847
File diff suppressed because it is too large
Load Diff
@@ -195,7 +195,7 @@ export default function ProjectWizardNew() {
|
|||||||
<Card
|
<Card
|
||||||
hoverable
|
hoverable
|
||||||
style={{
|
style={{
|
||||||
borderColor: form.getFieldValue('outline_mode') === 'one-to-one' ? '#1890ff' : '#d9d9d9',
|
borderColor: form.getFieldValue('outline_mode') === 'one-to-one' ? 'var(--color-primary)' : 'var(--color-border)',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
@@ -204,7 +204,7 @@ export default function ProjectWizardNew() {
|
|||||||
<Radio value="one-to-one" style={{ width: '100%' }}>
|
<Radio value="one-to-one" style={{ width: '100%' }}>
|
||||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
<div style={{ fontSize: 16, fontWeight: 'bold' }}>
|
<div style={{ fontSize: 16, fontWeight: 'bold' }}>
|
||||||
<CheckCircleOutlined style={{ marginRight: 8, color: '#52c41a' }} />
|
<CheckCircleOutlined style={{ marginRight: 8, color: 'var(--color-success)' }} />
|
||||||
传统模式 (1→1)
|
传统模式 (1→1)
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 12, color: '#666' }}>
|
<div style={{ fontSize: 12, color: '#666' }}>
|
||||||
@@ -222,7 +222,7 @@ export default function ProjectWizardNew() {
|
|||||||
<Card
|
<Card
|
||||||
hoverable
|
hoverable
|
||||||
style={{
|
style={{
|
||||||
borderColor: form.getFieldValue('outline_mode') === 'one-to-many' ? '#1890ff' : '#d9d9d9',
|
borderColor: form.getFieldValue('outline_mode') === 'one-to-many' ? 'var(--color-primary)' : 'var(--color-border)',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
@@ -231,7 +231,7 @@ export default function ProjectWizardNew() {
|
|||||||
<Radio value="one-to-many" style={{ width: '100%' }}>
|
<Radio value="one-to-many" style={{ width: '100%' }}>
|
||||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
<div style={{ fontSize: 16, fontWeight: 'bold' }}>
|
<div style={{ fontSize: 16, fontWeight: 'bold' }}>
|
||||||
<CheckCircleOutlined style={{ marginRight: 8, color: '#52c41a' }} />
|
<CheckCircleOutlined style={{ marginRight: 8, color: 'var(--color-success)' }} />
|
||||||
细化模式 (1→N) 推荐
|
细化模式 (1→N) 推荐
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 12, color: '#666' }}>
|
<div style={{ fontSize: 12, color: '#666' }}>
|
||||||
@@ -321,17 +321,15 @@ export default function ProjectWizardNew() {
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: currentStep === 'generating'
|
background: 'var(--color-bg-base)',
|
||||||
? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
|
||||||
: '#f5f7fa',
|
|
||||||
}}>
|
}}>
|
||||||
{/* 顶部标题栏 - 固定不滚动 */}
|
{/* 顶部标题栏 - 固定不滚动 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
boxShadow: 'var(--shadow-header)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
maxWidth: 1200,
|
maxWidth: 1200,
|
||||||
|
|||||||
@@ -193,50 +193,87 @@ export default function PromptTemplates() {
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
|
||||||
padding: isMobile ? '20px 16px' : '40px 24px'
|
padding: isMobile ? '20px 16px' : '40px 24px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
}}>
|
}}>
|
||||||
{/* 头部卡片 */}
|
|
||||||
<div style={{
|
<div style={{
|
||||||
maxWidth: 1400,
|
maxWidth: 1400,
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
marginBottom: isMobile ? 20 : 40
|
width: '100%',
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
}}>
|
}}>
|
||||||
|
{/* 顶部导航卡片 */}
|
||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'linear-gradient(135deg, var(--color-primary) 0%, #5A9BA5 50%, var(--color-primary-hover) 100%)',
|
||||||
borderRadius: isMobile ? 12 : 16,
|
borderRadius: isMobile ? 16 : 24,
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 12px 40px rgba(77, 128, 136, 0.25), 0 4px 12px rgba(0, 0, 0, 0.06)',
|
||||||
|
marginBottom: isMobile ? 20 : 24,
|
||||||
|
border: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Row align="middle" justify="space-between" gutter={[16, 16]}>
|
{/* 装饰性背景元素 */}
|
||||||
|
<div style={{ position: 'absolute', top: -60, right: -60, width: 200, height: 200, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.08)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'absolute', bottom: -40, left: '30%', width: 120, height: 120, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.05)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'absolute', top: '50%', right: '15%', width: 80, height: 80, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.06)', pointerEvents: 'none' }} />
|
||||||
|
|
||||||
|
<Row align="middle" justify="space-between" gutter={[16, 16]} style={{ position: 'relative', zIndex: 1 }}>
|
||||||
<Col xs={24} sm={12} md={14}>
|
<Col xs={24} sm={12} md={14}>
|
||||||
<Space direction="vertical" size={4}>
|
<Space direction="vertical" size={4}>
|
||||||
<Space align="center">
|
<Title level={isMobile ? 3 : 2} style={{ margin: 0, color: '#fff', textShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
|
||||||
<Button
|
<FileSearchOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 8 }} />
|
||||||
type="text"
|
提示词模板管理
|
||||||
icon={<ArrowLeftOutlined />}
|
</Title>
|
||||||
onClick={() => navigate('/projects')}
|
<Text style={{ fontSize: isMobile ? 12 : 14, color: 'rgba(255,255,255,0.85)', marginLeft: isMobile ? 40 : 48 }}>
|
||||||
size={isMobile ? 'small' : 'middle'}
|
|
||||||
/>
|
|
||||||
<Title level={isMobile ? 3 : 2} style={{ margin: 0 }}>
|
|
||||||
<FileSearchOutlined style={{ color: '#667eea', marginRight: 8 }} />
|
|
||||||
提示词模板管理
|
|
||||||
</Title>
|
|
||||||
</Space>
|
|
||||||
<Text type="secondary" style={{ fontSize: isMobile ? 12 : 14, marginLeft: isMobile ? 40 : 48 }}>
|
|
||||||
自定义AI生成提示词,打造个性化创作体验
|
自定义AI生成提示词,打造个性化创作体验
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={10}>
|
<Col xs={24} sm={12} md={10}>
|
||||||
<Space wrap style={{ justifyContent: isMobile ? 'flex-start' : 'flex-end', width: '100%' }}>
|
<Space wrap style={{ justifyContent: isMobile ? 'flex-start' : 'flex-end', width: '100%' }}>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowLeftOutlined />}
|
||||||
|
onClick={() => navigate('/projects')}
|
||||||
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
color: '#fff',
|
||||||
|
backdropFilter: 'blur(10px)',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.25)';
|
||||||
|
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
|
||||||
|
e.currentTarget.style.transform = 'none';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
返回项目
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
icon={<DownloadOutlined />}
|
icon={<DownloadOutlined />}
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
size={isMobile ? 'small' : 'middle'}
|
size={isMobile ? 'small' : 'middle'}
|
||||||
style={{ borderRadius: 8 }}
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
color: '#fff',
|
||||||
|
backdropFilter: 'blur(10px)',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
导出配置
|
导出配置
|
||||||
</Button>
|
</Button>
|
||||||
@@ -248,7 +285,14 @@ export default function PromptTemplates() {
|
|||||||
<Button
|
<Button
|
||||||
icon={<UploadOutlined />}
|
icon={<UploadOutlined />}
|
||||||
size={isMobile ? 'small' : 'middle'}
|
size={isMobile ? 'small' : 'middle'}
|
||||||
style={{ borderRadius: 8 }}
|
style={{
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
color: '#fff',
|
||||||
|
backdropFilter: 'blur(10px)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
导入配置
|
导入配置
|
||||||
</Button>
|
</Button>
|
||||||
@@ -261,7 +305,7 @@ export default function PromptTemplates() {
|
|||||||
<Alert
|
<Alert
|
||||||
message={
|
message={
|
||||||
<Space align="center">
|
<Space align="center">
|
||||||
<InfoCircleOutlined style={{ fontSize: 16, color: '#1890ff' }} />
|
<InfoCircleOutlined style={{ fontSize: 16, color: 'var(--color-primary)' }} />
|
||||||
<Text strong style={{ fontSize: isMobile ? 13 : 14 }}>使用说明</Text>
|
<Text strong style={{ fontSize: isMobile ? 13 : 14 }}>使用说明</Text>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -280,151 +324,151 @@ export default function PromptTemplates() {
|
|||||||
style={{
|
style={{
|
||||||
marginTop: isMobile ? 16 : 24,
|
marginTop: isMobile ? 16 : 24,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
background: 'linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%)',
|
background: 'var(--color-info-bg)',
|
||||||
border: '1px solid #91d5ff'
|
border: '1px solid var(--color-info-border)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 主内容区 */}
|
{/* 主内容区 */}
|
||||||
<div style={{ maxWidth: 1400, margin: '0 auto' }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
{/* 分类标签 */}
|
{/* 分类标签 */}
|
||||||
{categories.length > 0 && (
|
{categories.length > 0 && (
|
||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'rgba(255, 255, 255, 0.95)',
|
||||||
borderRadius: isMobile ? 12 : 16,
|
borderRadius: isMobile ? 12 : 16,
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
||||||
marginBottom: isMobile ? 16 : 24
|
marginBottom: isMobile ? 16 : 24
|
||||||
}}
|
}}
|
||||||
styles={{ body: { padding: isMobile ? '12px' : '16px' } }}
|
styles={{ body: { padding: isMobile ? '12px' : '16px' } }}
|
||||||
>
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={selectedCategory}
|
activeKey={selectedCategory}
|
||||||
onChange={setSelectedCategory}
|
onChange={setSelectedCategory}
|
||||||
items={[
|
items={[
|
||||||
{ key: '0', label: `全部 (${categories.reduce((sum, cat) => sum + cat.count, 0)})` },
|
{ key: '0', label: `全部 (${categories.reduce((sum, cat) => sum + cat.count, 0)})` },
|
||||||
...categories.map((cat, index) => ({
|
...categories.map((cat, index) => ({
|
||||||
key: (index + 1).toString(),
|
key: (index + 1).toString(),
|
||||||
label: `${cat.category} (${cat.count})`
|
label: `${cat.category} (${cat.count})`
|
||||||
}))
|
}))
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 模板列表 */}
|
{/* 模板列表 */}
|
||||||
{currentTemplates.length === 0 ? (
|
{currentTemplates.length === 0 ? (
|
||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'rgba(255, 255, 255, 0.95)',
|
||||||
borderRadius: 16,
|
borderRadius: isMobile ? 12 : 16,
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Empty
|
<Empty
|
||||||
description="暂无模板数据"
|
description="暂无模板数据"
|
||||||
style={{ padding: '80px 0' }}
|
style={{ padding: '80px 0' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{currentTemplates.map(template => (
|
{currentTemplates.map(template => (
|
||||||
<Col {...gridConfig} key={template.id}>
|
<Col {...gridConfig} key={template.id}>
|
||||||
<Card
|
<Card
|
||||||
hoverable
|
hoverable
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={cardStyles.project}
|
style={cardStyles.project}
|
||||||
styles={{ body: { padding: 0, overflow: 'hidden' } }}
|
styles={{ body: { padding: 0, overflow: 'hidden' } }}
|
||||||
{...cardHoverHandlers}
|
{...cardHoverHandlers}
|
||||||
>
|
>
|
||||||
{/* 头部 */}
|
{/* 头部 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
background: template.is_system_default
|
background: template.is_system_default
|
||||||
? 'linear-gradient(135deg, #a8a8a8 0%, #636363 100%)'
|
? 'var(--color-bg-layout)'
|
||||||
: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
: 'var(--color-primary)',
|
||||||
padding: isMobile ? '16px' : '20px',
|
padding: isMobile ? '16px' : '20px',
|
||||||
position: 'relative'
|
position: 'relative'
|
||||||
}}>
|
}}>
|
||||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<Title level={isMobile ? 5 : 4} style={{ margin: 0, color: '#fff', flex: 1 }} ellipsis>
|
<Title level={isMobile ? 5 : 4} style={{ margin: 0, color: template.is_system_default ? 'var(--color-text-primary)' : '#fff', flex: 1 }} ellipsis>
|
||||||
{template.template_name}
|
{template.template_name}
|
||||||
</Title>
|
</Title>
|
||||||
{!template.is_system_default && (
|
{!template.is_system_default && (
|
||||||
<Switch
|
<Switch
|
||||||
checked={template.is_active}
|
checked={template.is_active}
|
||||||
onChange={(checked) => handleToggleActive(template, checked)}
|
onChange={(checked) => handleToggleActive(template, checked)}
|
||||||
size={isMobile ? 'small' : 'default'}
|
size={isMobile ? 'small' : 'default'}
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Tag color="rgba(255,255,255,0.3)" style={{ color: '#fff', border: 'none' }}>
|
<Tag color={template.is_system_default ? 'default' : 'rgba(255,255,255,0.3)'} style={{ color: template.is_system_default ? 'var(--color-text-secondary)' : '#fff', border: 'none' }}>
|
||||||
{template.category}
|
{template.category}
|
||||||
</Tag>
|
</Tag>
|
||||||
<Tag color="rgba(255,255,255,0.3)" style={{ color: '#fff', border: 'none' }}>
|
<Tag color={template.is_system_default ? 'default' : 'rgba(255,255,255,0.3)'} style={{ color: template.is_system_default ? 'var(--color-text-secondary)' : '#fff', border: 'none' }}>
|
||||||
{template.is_system_default ? '系统默认' : '已自定义'}
|
{template.is_system_default ? '系统默认' : '已自定义'}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 内容 */}
|
||||||
|
<div style={{ padding: isMobile ? '16px' : '20px' }}>
|
||||||
|
<Paragraph
|
||||||
|
type="secondary"
|
||||||
|
ellipsis={{ rows: 3 }}
|
||||||
|
style={{ minHeight: 66, marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
{template.description || '暂无描述'}
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Space wrap style={{ marginBottom: 16 }}>
|
||||||
|
<Tag
|
||||||
|
icon={<CheckCircleOutlined />}
|
||||||
|
color={template.is_system_default || template.is_active ? 'success' : 'default'}
|
||||||
|
>
|
||||||
|
{template.is_system_default ? '始终启用' : (template.is_active ? '已启用' : '已禁用')}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 内容 */}
|
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 16 }}>
|
||||||
<div style={{ padding: isMobile ? '16px' : '20px' }}>
|
模板键: {template.template_key}
|
||||||
<Paragraph
|
</Text>
|
||||||
type="secondary"
|
|
||||||
ellipsis={{ rows: 3 }}
|
|
||||||
style={{ minHeight: 66, marginBottom: 16 }}
|
|
||||||
>
|
|
||||||
{template.description || '暂无描述'}
|
|
||||||
</Paragraph>
|
|
||||||
|
|
||||||
<Space wrap style={{ marginBottom: 16 }}>
|
{/* 操作按钮 */}
|
||||||
<Tag
|
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||||
icon={<CheckCircleOutlined />}
|
<Button
|
||||||
color={template.is_system_default || template.is_active ? 'success' : 'default'}
|
type="primary"
|
||||||
>
|
icon={<EditOutlined />}
|
||||||
{template.is_system_default ? '始终启用' : (template.is_active ? '已启用' : '已禁用')}
|
onClick={() => handleEdit(template)}
|
||||||
</Tag>
|
size={isMobile ? 'small' : 'middle'}
|
||||||
</Space>
|
style={{ borderRadius: 6 }}
|
||||||
|
>
|
||||||
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 16 }}>
|
编辑
|
||||||
模板键: {template.template_key}
|
</Button>
|
||||||
</Text>
|
<Button
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
{/* 操作按钮 */}
|
onClick={() => handleReset(template.template_key)}
|
||||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
size={isMobile ? 'small' : 'middle'}
|
||||||
<Button
|
style={{ borderRadius: 6 }}
|
||||||
type="primary"
|
>
|
||||||
icon={<EditOutlined />}
|
重置
|
||||||
onClick={() => handleEdit(template)}
|
</Button>
|
||||||
size={isMobile ? 'small' : 'middle'}
|
</Space>
|
||||||
style={{ borderRadius: 6 }}
|
</div>
|
||||||
>
|
</Card>
|
||||||
编辑
|
</Col>
|
||||||
</Button>
|
))}
|
||||||
<Button
|
</Row>
|
||||||
icon={<ReloadOutlined />}
|
)}
|
||||||
onClick={() => handleReset(template.template_key)}
|
</Spin>
|
||||||
size={isMobile ? 'small' : 'middle'}
|
</div>
|
||||||
style={{ borderRadius: 6 }}
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</Spin>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 编辑对话框 */}
|
{/* 编辑对话框 */}
|
||||||
@@ -486,6 +530,6 @@ export default function PromptTemplates() {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ export default function SettingsPage() {
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-bg-base)',
|
||||||
padding: isMobile ? '16px 12px' : '40px 24px'
|
padding: isMobile ? '16px 12px' : '40px 24px'
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -248,9 +248,9 @@ export default function SettingsPage() {
|
|||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'var(--color-bg-container)',
|
||||||
borderRadius: isMobile ? 12 : 16,
|
borderRadius: isMobile ? 12 : 16,
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
}}
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
body: {
|
body: {
|
||||||
@@ -281,7 +281,7 @@ export default function SettingsPage() {
|
|||||||
fontSize: isMobile ? '18px' : undefined
|
fontSize: isMobile ? '18px' : undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SettingOutlined style={{ marginRight: 8, color: '#667eea' }} />
|
<SettingOutlined style={{ marginRight: 8, color: 'var(--color-primary)' }} />
|
||||||
{isMobile ? 'API 设置' : 'AI API 设置'}
|
{isMobile ? 'API 设置' : 'AI API 设置'}
|
||||||
</Title>
|
</Title>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -341,7 +341,7 @@ export default function SettingsPage() {
|
|||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<span>API 提供商</span>
|
<span>API 提供商</span>
|
||||||
<Tooltip title="选择你的AI服务提供商">
|
<Tooltip title="选择你的AI服务提供商">
|
||||||
<InfoCircleOutlined style={{ color: '#8c8c8c', fontSize: isMobile ? '12px' : '14px' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-text-secondary)', fontSize: isMobile ? '12px' : '14px' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -362,7 +362,7 @@ export default function SettingsPage() {
|
|||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<span>API 密钥</span>
|
<span>API 密钥</span>
|
||||||
<Tooltip title="你的API密钥,将加密存储">
|
<Tooltip title="你的API密钥,将加密存储">
|
||||||
<InfoCircleOutlined style={{ color: '#8c8c8c', fontSize: isMobile ? '12px' : '14px' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-text-secondary)', fontSize: isMobile ? '12px' : '14px' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -381,7 +381,7 @@ export default function SettingsPage() {
|
|||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<span>API 地址</span>
|
<span>API 地址</span>
|
||||||
<Tooltip title="API的基础URL地址">
|
<Tooltip title="API的基础URL地址">
|
||||||
<InfoCircleOutlined style={{ color: '#8c8c8c', fontSize: isMobile ? '12px' : '14px' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-text-secondary)', fontSize: isMobile ? '12px' : '14px' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -402,7 +402,7 @@ export default function SettingsPage() {
|
|||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<span>模型名称</span>
|
<span>模型名称</span>
|
||||||
<Tooltip title="AI模型的名称,如 gpt-4, gpt-3.5-turbo">
|
<Tooltip title="AI模型的名称,如 gpt-4, gpt-3.5-turbo">
|
||||||
<InfoCircleOutlined style={{ color: '#8c8c8c', fontSize: isMobile ? '12px' : '14px' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-text-secondary)', fontSize: isMobile ? '12px' : '14px' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,7 @@ export default function SettingsPage() {
|
|||||||
<>
|
<>
|
||||||
{menu}
|
{menu}
|
||||||
{fetchingModels && (
|
{fetchingModels && (
|
||||||
<div style={{ padding: '8px 12px', color: '#8c8c8c', textAlign: 'center', fontSize: isMobile ? '12px' : '14px' }}>
|
<div style={{ padding: '8px 12px', color: 'var(--color-text-secondary)', textAlign: 'center', fontSize: isMobile ? '12px' : '14px' }}>
|
||||||
<Spin size="small" /> 正在获取模型列表...
|
<Spin size="small" /> 正在获取模型列表...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -434,7 +434,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!fetchingModels && modelOptions.length === 0 && !modelsFetched && (
|
{!fetchingModels && modelOptions.length === 0 && !modelsFetched && (
|
||||||
<div style={{ padding: '8px 12px', color: '#8c8c8c', textAlign: 'center', fontSize: isMobile ? '12px' : '14px' }}>
|
<div style={{ padding: '8px 12px', color: 'var(--color-text-secondary)', textAlign: 'center', fontSize: isMobile ? '12px' : '14px' }}>
|
||||||
点击输入框自动获取模型列表
|
点击输入框自动获取模型列表
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -446,7 +446,7 @@ export default function SettingsPage() {
|
|||||||
<Spin size="small" /> 加载中...
|
<Spin size="small" /> 加载中...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ padding: '8px 12px', color: '#8c8c8c', textAlign: 'center', fontSize: isMobile ? '12px' : '14px' }}>
|
<div style={{ padding: '8px 12px', color: 'var(--color-text-secondary)', textAlign: 'center', fontSize: isMobile ? '12px' : '14px' }}>
|
||||||
未找到匹配的模型
|
未找到匹配的模型
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -506,7 +506,7 @@ export default function SettingsPage() {
|
|||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<span>温度参数</span>
|
<span>温度参数</span>
|
||||||
<Tooltip title="控制输出的随机性,值越高越随机(0.0-2.0)">
|
<Tooltip title="控制输出的随机性,值越高越随机(0.0-2.0)">
|
||||||
<InfoCircleOutlined style={{ color: '#8c8c8c', fontSize: isMobile ? '12px' : '14px' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-text-secondary)', fontSize: isMobile ? '12px' : '14px' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -530,7 +530,7 @@ export default function SettingsPage() {
|
|||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<span>最大 Token 数</span>
|
<span>最大 Token 数</span>
|
||||||
<Tooltip title="单次请求的最大token数量">
|
<Tooltip title="单次请求的最大token数量">
|
||||||
<InfoCircleOutlined style={{ color: '#8c8c8c', fontSize: isMobile ? '12px' : '14px' }} />
|
<InfoCircleOutlined style={{ color: 'var(--color-text-secondary)', fontSize: isMobile ? '12px' : '14px' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -554,9 +554,9 @@ export default function SettingsPage() {
|
|||||||
message={
|
message={
|
||||||
<Space>
|
<Space>
|
||||||
{testResult.success ? (
|
{testResult.success ? (
|
||||||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: isMobile ? '16px' : '18px' }} />
|
<CheckCircleOutlined style={{ color: 'var(--color-success)', fontSize: isMobile ? '16px' : '18px' }} />
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: isMobile ? '16px' : '18px' }} />
|
<CloseCircleOutlined style={{ color: 'var(--color-error)', fontSize: isMobile ? '16px' : '18px' }} />
|
||||||
)}
|
)}
|
||||||
<span style={{ fontSize: isMobile ? '14px' : '16px', fontWeight: 500 }}>
|
<span style={{ fontSize: isMobile ? '14px' : '16px', fontWeight: 500 }}>
|
||||||
{testResult.message}
|
{testResult.message}
|
||||||
@@ -585,7 +585,7 @@ export default function SettingsPage() {
|
|||||||
<div style={{ color: '#595959' }}>{testResult.response_preview}</div>
|
<div style={{ color: '#595959' }}>{testResult.response_preview}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ color: '#52c41a', fontSize: isMobile ? '12px' : '13px', marginTop: '4px' }}>
|
<div style={{ color: 'var(--color-success)', fontSize: isMobile ? '12px' : '13px', marginTop: '4px' }}>
|
||||||
✓ API 配置正确,可以正常使用
|
✓ API 配置正确,可以正常使用
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -604,7 +604,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{testResult.error_type && (
|
{testResult.error_type && (
|
||||||
<div style={{ fontSize: isMobile ? '11px' : '12px', color: '#8c8c8c' }}>
|
<div style={{ fontSize: isMobile ? '11px' : '12px', color: 'var(--color-text-secondary)' }}>
|
||||||
错误类型: {testResult.error_type}
|
错误类型: {testResult.error_type}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -639,61 +639,61 @@ export default function SettingsPage() {
|
|||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
<Form.Item style={{ marginBottom: 0, marginTop: isMobile ? 24 : 32 }}>
|
<Form.Item style={{ marginBottom: 0, marginTop: isMobile ? 24 : 32 }}>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
// 移动端:垂直堆叠布局
|
// 移动端:垂直堆叠布局
|
||||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
icon={<SaveOutlined />}
|
icon={<SaveOutlined />}
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
block
|
block
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
height: '44px'
|
height: '44px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
保存设置
|
保存设置
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
icon={<ThunderboltOutlined />}
|
||||||
|
onClick={handleTestConnection}
|
||||||
|
loading={testingApi}
|
||||||
|
block
|
||||||
|
style={{
|
||||||
|
borderColor: 'var(--color-success)',
|
||||||
|
color: 'var(--color-success)',
|
||||||
|
fontWeight: 500,
|
||||||
|
height: '44px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{testingApi ? '测试中...' : '测试连接'}
|
||||||
|
</Button>
|
||||||
|
<Space size="middle" style={{ width: '100%' }}>
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
icon={<ThunderboltOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
onClick={handleTestConnection}
|
onClick={handleReset}
|
||||||
loading={testingApi}
|
style={{ flex: 1, height: '44px' }}
|
||||||
block
|
|
||||||
style={{
|
|
||||||
borderColor: '#52c41a',
|
|
||||||
color: '#52c41a',
|
|
||||||
fontWeight: 500,
|
|
||||||
height: '44px'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{testingApi ? '测试中...' : '测试连接'}
|
重置
|
||||||
</Button>
|
</Button>
|
||||||
<Space size="middle" style={{ width: '100%' }}>
|
{hasSettings && (
|
||||||
<Button
|
<Button
|
||||||
|
danger
|
||||||
size="large"
|
size="large"
|
||||||
icon={<ReloadOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={handleReset}
|
onClick={handleDelete}
|
||||||
|
loading={loading}
|
||||||
style={{ flex: 1, height: '44px' }}
|
style={{ flex: 1, height: '44px' }}
|
||||||
>
|
>
|
||||||
重置
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
{hasSettings && (
|
)}
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
size="large"
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={handleDelete}
|
|
||||||
loading={loading}
|
|
||||||
style={{ flex: 1, height: '44px' }}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Space>
|
</Space>
|
||||||
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
// 桌面端:删除在左边,测试、重置和保存在右边
|
// 桌面端:删除在左边,测试、重置和保存在右边
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -729,8 +729,8 @@ export default function SettingsPage() {
|
|||||||
onClick={handleTestConnection}
|
onClick={handleTestConnection}
|
||||||
loading={testingApi}
|
loading={testingApi}
|
||||||
style={{
|
style={{
|
||||||
borderColor: '#52c41a',
|
borderColor: 'var(--color-success)',
|
||||||
color: '#52c41a',
|
color: 'var(--color-success)',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
minWidth: '100px'
|
minWidth: '100px'
|
||||||
}}
|
}}
|
||||||
@@ -754,7 +754,7 @@ export default function SettingsPage() {
|
|||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
minWidth: '120px',
|
minWidth: '120px',
|
||||||
fontWeight: 500
|
fontWeight: 500
|
||||||
|
|||||||
@@ -28,17 +28,17 @@ const sponsorOptions: SponsorOption[] = [
|
|||||||
|
|
||||||
const benefits = [
|
const benefits = [
|
||||||
{
|
{
|
||||||
icon: <FileTextOutlined style={{ fontSize: '32px', color: '#1890ff' }} />,
|
icon: <FileTextOutlined style={{ fontSize: '32px', color: 'var(--color-primary)' }} />,
|
||||||
title: '优先需求响应',
|
title: '优先需求响应',
|
||||||
description: '您的功能需求和问题反馈将获得优先处理'
|
description: '您的功能需求和问题反馈将获得优先处理'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <RocketOutlined style={{ fontSize: '32px', color: '#52c41a' }} />,
|
icon: <RocketOutlined style={{ fontSize: '32px', color: 'var(--color-success)' }} />,
|
||||||
title: 'Windows一键启动',
|
title: 'Windows一键启动',
|
||||||
description: '获取免安装EXE程序,双击即可使用'
|
description: '获取免安装EXE程序,双击即可使用'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MessageOutlined style={{ fontSize: '32px', color: '#fa8c16' }} />,
|
icon: <MessageOutlined style={{ fontSize: '32px', color: 'var(--color-warning)' }} />,
|
||||||
title: '专属技术支持',
|
title: '专属技术支持',
|
||||||
description: '加入赞助者群,获得远程协助和配置指导'
|
description: '加入赞助者群,获得远程协助和配置指导'
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ export default function Sponsor() {
|
|||||||
<div style={{
|
<div style={{
|
||||||
marginTop: '16px',
|
marginTop: '16px',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'var(--color-primary)',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
}}>
|
}}>
|
||||||
@@ -91,7 +91,7 @@ export default function Sponsor() {
|
|||||||
{/* 赞助专属权益 */}
|
{/* 赞助专属权益 */}
|
||||||
<div style={{ marginBottom: '32px' }}>
|
<div style={{ marginBottom: '32px' }}>
|
||||||
<Title level={3} style={{ textAlign: 'center', marginBottom: '20px' }}>
|
<Title level={3} style={{ textAlign: 'center', marginBottom: '20px' }}>
|
||||||
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: '8px' }} />
|
<CheckCircleOutlined style={{ color: 'var(--color-success)', marginRight: '8px' }} />
|
||||||
赞助专属权益
|
赞助专属权益
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
@@ -139,27 +139,27 @@ export default function Sponsor() {
|
|||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
boxShadow: 'var(--shadow-card)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.3s',
|
transition: 'all 0.3s',
|
||||||
border: '2px solid #f0f0f0'
|
border: '2px solid var(--color-border)'
|
||||||
}}
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
body: { padding: '20px 12px' }
|
body: { padding: '20px 12px' }
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.transform = 'translateY(-8px)';
|
e.currentTarget.style.transform = 'translateY(-8px)';
|
||||||
e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.15)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-elevated)';
|
||||||
e.currentTarget.style.borderColor = '#1890ff';
|
e.currentTarget.style.borderColor = 'var(--color-primary)';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.08)';
|
e.currentTarget.style.boxShadow = 'var(--shadow-card)';
|
||||||
e.currentTarget.style.borderColor = '#f0f0f0';
|
e.currentTarget.style.borderColor = 'var(--color-border)';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Title level={3} style={{
|
<Title level={3} style={{
|
||||||
color: '#1890ff',
|
color: 'var(--color-primary)',
|
||||||
marginBottom: '4px',
|
marginBottom: '4px',
|
||||||
fontSize: '28px',
|
fontSize: '28px',
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default function UserManagement() {
|
|||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [editForm] = Form.useForm();
|
const [editForm] = Form.useForm();
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
// 过滤用户列表
|
// 过滤用户列表
|
||||||
const filteredUsers = users.filter(user => {
|
const filteredUsers = users.filter(user => {
|
||||||
@@ -97,7 +98,7 @@ export default function UserManagement() {
|
|||||||
|
|
||||||
// 如果有默认密码,显示给管理员
|
// 如果有默认密码,显示给管理员
|
||||||
if (res.default_password) {
|
if (res.default_password) {
|
||||||
Modal.info({
|
modal.info({
|
||||||
title: '用户创建成功',
|
title: '用户创建成功',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
@@ -180,7 +181,7 @@ export default function UserManagement() {
|
|||||||
newPassword || undefined
|
newPassword || undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
Modal.info({
|
modal.info({
|
||||||
title: '密码重置成功',
|
title: '密码重置成功',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
@@ -226,7 +227,7 @@ export default function UserManagement() {
|
|||||||
width: 150,
|
width: 150,
|
||||||
render: (text: string) => (
|
render: (text: string) => (
|
||||||
<Space>
|
<Space>
|
||||||
<UserOutlined style={{ color: '#1890ff' }} />
|
<UserOutlined style={{ color: 'var(--color-primary)' }} />
|
||||||
<Text strong>{text}</Text>
|
<Text strong>{text}</Text>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
@@ -314,7 +315,7 @@ export default function UserManagement() {
|
|||||||
icon: isActive ? <StopOutlined /> : <CheckCircleOutlined />,
|
icon: isActive ? <StopOutlined /> : <CheckCircleOutlined />,
|
||||||
danger: isActive,
|
danger: isActive,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: `确定${isActive ? '禁用' : '启用'}该用户吗?`,
|
title: `确定${isActive ? '禁用' : '启用'}该用户吗?`,
|
||||||
onOk: () => handleToggleStatus(record),
|
onOk: () => handleToggleStatus(record),
|
||||||
okText: '确定',
|
okText: '确定',
|
||||||
@@ -328,7 +329,7 @@ export default function UserManagement() {
|
|||||||
icon: <DeleteOutlined />,
|
icon: <DeleteOutlined />,
|
||||||
danger: true,
|
danger: true,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '确定删除该用户吗?此操作不可恢复!',
|
title: '确定删除该用户吗?此操作不可恢复!',
|
||||||
onOk: () => handleDelete(record),
|
onOk: () => handleDelete(record),
|
||||||
okText: '确定',
|
okText: '确定',
|
||||||
@@ -418,12 +419,13 @@ export default function UserManagement() {
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
|
||||||
padding: isMobile ? '20px 16px' : '40px 24px',
|
padding: isMobile ? '20px 16px' : '40px 24px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
|
{contextHolder}
|
||||||
<div style={{
|
<div style={{
|
||||||
maxWidth: 1400,
|
maxWidth: 1400,
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
@@ -437,20 +439,28 @@ export default function UserManagement() {
|
|||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'linear-gradient(135deg, var(--color-primary) 0%, #5A9BA5 50%, var(--color-primary-hover) 100%)',
|
||||||
borderRadius: isMobile ? 12 : 16,
|
borderRadius: isMobile ? 16 : 24,
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 12px 40px rgba(77, 128, 136, 0.25), 0 4px 12px rgba(0, 0, 0, 0.06)',
|
||||||
marginBottom: isMobile ? 20 : 24,
|
marginBottom: isMobile ? 20 : 24,
|
||||||
|
border: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Row align="middle" justify="space-between" gutter={[16, 16]}>
|
{/* 装饰性背景元素 */}
|
||||||
|
<div style={{ position: 'absolute', top: -60, right: -60, width: 200, height: 200, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.08)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'absolute', bottom: -40, left: '30%', width: 120, height: 120, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.05)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'absolute', top: '50%', right: '15%', width: 80, height: 80, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.06)', pointerEvents: 'none' }} />
|
||||||
|
|
||||||
|
<Row align="middle" justify="space-between" gutter={[16, 16]} style={{ position: 'relative', zIndex: 1 }}>
|
||||||
<Col xs={24} sm={12}>
|
<Col xs={24} sm={12}>
|
||||||
<Space direction="vertical" size={4}>
|
<Space direction="vertical" size={4}>
|
||||||
<Title level={isMobile ? 3 : 2} style={{ margin: 0 }}>
|
<Title level={isMobile ? 3 : 2} style={{ margin: 0, color: '#fff', textShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
|
||||||
<TeamOutlined style={{ color: '#fa8c16', marginRight: 8 }} />
|
<TeamOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 12 }} />
|
||||||
用户管理
|
用户管理
|
||||||
</Title>
|
</Title>
|
||||||
<Text type="secondary" style={{ fontSize: isMobile ? 12 : 14 }}>
|
<Text style={{ fontSize: isMobile ? 12 : 14, color: 'rgba(255,255,255,0.85)' }}>
|
||||||
管理系统用户和权限
|
管理系统用户和权限
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -461,20 +471,21 @@ export default function UserManagement() {
|
|||||||
icon={<ArrowLeftOutlined />}
|
icon={<ArrowLeftOutlined />}
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 8,
|
borderRadius: 12,
|
||||||
borderColor: '#d9d9d9',
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
color: '#fff',
|
||||||
|
backdropFilter: 'blur(10px)',
|
||||||
transition: 'all 0.3s ease'
|
transition: 'all 0.3s ease'
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.borderColor = '#667eea';
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.25)';
|
||||||
e.currentTarget.style.color = '#667eea';
|
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||||
e.currentTarget.style.boxShadow = '0 2px 12px rgba(102, 126, 234, 0.3)';
|
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.borderColor = '#d9d9d9';
|
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
|
||||||
e.currentTarget.style.color = 'rgba(0, 0, 0, 0.88)';
|
e.currentTarget.style.transform = 'none';
|
||||||
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.08)';
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
返回主页
|
返回主页
|
||||||
@@ -484,10 +495,12 @@ export default function UserManagement() {
|
|||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => setModalVisible(true)}
|
onClick={() => setModalVisible(true)}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 8,
|
borderRadius: 12,
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'rgba(255, 193, 7, 0.95)',
|
||||||
border: 'none',
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.4)'
|
boxShadow: '0 4px 16px rgba(255, 193, 7, 0.4)',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 600
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
添加用户
|
添加用户
|
||||||
@@ -502,9 +515,11 @@ export default function UserManagement() {
|
|||||||
<Card
|
<Card
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'rgba(255, 255, 255, 0.7)',
|
||||||
borderRadius: isMobile ? 12 : 16,
|
borderRadius: isMobile ? 16 : 24,
|
||||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
|
border: '1px solid rgba(255, 255, 255, 0.4)',
|
||||||
|
backdropFilter: 'blur(20px)',
|
||||||
|
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.04)',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -521,7 +536,7 @@ export default function UserManagement() {
|
|||||||
{/* 搜索栏 */}
|
{/* 搜索栏 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '16px 24px 0 24px',
|
padding: '16px 24px 0 24px',
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid rgba(0, 0, 0, 0.03)',
|
||||||
}}>
|
}}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索用户名、显示名称或用户ID"
|
placeholder="搜索用户名、显示名称或用户ID"
|
||||||
@@ -560,8 +575,8 @@ export default function UserManagement() {
|
|||||||
{/* 固定分页控件 */}
|
{/* 固定分页控件 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '16px 24px 24px 24px',
|
padding: '16px 24px 24px 24px',
|
||||||
borderTop: '1px solid #f0f0f0',
|
borderTop: '1px solid rgba(0, 0, 0, 0.03)',
|
||||||
background: 'rgba(255, 255, 255, 0.95)',
|
background: 'transparent',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
+114
-112
@@ -25,12 +25,13 @@ export default function WorldSetting() {
|
|||||||
rules: string;
|
rules: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [isSavingPreview, setIsSavingPreview] = useState(false);
|
const [isSavingPreview, setIsSavingPreview] = useState(false);
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
// AI重新生成世界观
|
// AI重新生成世界观
|
||||||
const handleRegenerate = async () => {
|
const handleRegenerate = async () => {
|
||||||
if (!currentProject) return;
|
if (!currentProject) return;
|
||||||
|
|
||||||
Modal.confirm({
|
modal.confirm({
|
||||||
title: '确认重新生成',
|
title: '确认重新生成',
|
||||||
content: '确定要使用AI重新生成世界观设定吗?这将替换当前的世界观内容。',
|
content: '确定要使用AI重新生成世界观设定吗?这将替换当前的世界观内容。',
|
||||||
centered: true,
|
centered: true,
|
||||||
@@ -139,11 +140,11 @@ export default function WorldSetting() {
|
|||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
padding: '16px 0',
|
padding: '16px 0',
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid var(--color-border-secondary)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}>
|
}}>
|
||||||
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: '#1890ff' }} />
|
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: 'var(--color-primary)' }} />
|
||||||
<h2 style={{ margin: 0 }}>世界设定</h2>
|
<h2 style={{ margin: 0 }}>世界设定</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -164,6 +165,7 @@ export default function WorldSetting() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
{contextHolder}
|
||||||
{/* 固定头部 */}
|
{/* 固定头部 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
@@ -178,7 +180,7 @@ export default function WorldSetting() {
|
|||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between'
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: '#1890ff' }} />
|
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: 'var(--color-primary)' }} />
|
||||||
<h2 style={{ margin: 0 }}>世界设定</h2>
|
<h2 style={{ margin: 0 }}>世界设定</h2>
|
||||||
</div>
|
</div>
|
||||||
<Space>
|
<Space>
|
||||||
@@ -210,116 +212,116 @@ export default function WorldSetting() {
|
|||||||
{/* 可滚动内容区域 */}
|
{/* 可滚动内容区域 */}
|
||||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
...cardStyles.base,
|
...cardStyles.base,
|
||||||
marginBottom: 16
|
marginBottom: 16
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<span style={{ fontSize: 18, fontWeight: 500 }}>
|
<span style={{ fontSize: 18, fontWeight: 500 }}>
|
||||||
基础信息
|
基础信息
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Descriptions bordered column={1} styles={{ label: { width: 120, fontWeight: 500 } }}>
|
<Descriptions bordered column={1} styles={{ label: { width: 120, fontWeight: 500 } }}>
|
||||||
<Descriptions.Item label="小说名称">{currentProject.title}</Descriptions.Item>
|
<Descriptions.Item label="小说名称">{currentProject.title}</Descriptions.Item>
|
||||||
{currentProject.description && (
|
{currentProject.description && (
|
||||||
<Descriptions.Item label="小说简介">{currentProject.description}</Descriptions.Item>
|
<Descriptions.Item label="小说简介">{currentProject.description}</Descriptions.Item>
|
||||||
)}
|
)}
|
||||||
<Descriptions.Item label="小说主题">{currentProject.theme || '未设定'}</Descriptions.Item>
|
<Descriptions.Item label="小说主题">{currentProject.theme || '未设定'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="小说类型">{currentProject.genre || '未设定'}</Descriptions.Item>
|
<Descriptions.Item label="小说类型">{currentProject.genre || '未设定'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="叙事视角">{currentProject.narrative_perspective || '未设定'}</Descriptions.Item>
|
<Descriptions.Item label="叙事视角">{currentProject.narrative_perspective || '未设定'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="目标字数">
|
<Descriptions.Item label="目标字数">
|
||||||
{currentProject.target_words ? `${currentProject.target_words.toLocaleString()} 字` : '未设定'}
|
{currentProject.target_words ? `${currentProject.target_words.toLocaleString()} 字` : '未设定'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
...cardStyles.base,
|
...cardStyles.base,
|
||||||
marginBottom: 16
|
marginBottom: 16
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<span style={{ fontSize: 18, fontWeight: 500 }}>
|
<span style={{ fontSize: 18, fontWeight: 500 }}>
|
||||||
<GlobalOutlined style={{ marginRight: 8 }} />
|
<GlobalOutlined style={{ marginRight: 8 }} />
|
||||||
小说世界观
|
小说世界观
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div style={{ padding: '16px 0' }}>
|
<div style={{ padding: '16px 0' }}>
|
||||||
{currentProject.world_time_period && (
|
{currentProject.world_time_period && (
|
||||||
<div style={{ marginBottom: 24 }}>
|
<div style={{ marginBottom: 24 }}>
|
||||||
<Title level={5} style={{ color: '#1890ff', marginBottom: 12 }}>
|
<Title level={5} style={{ color: 'var(--color-primary)', marginBottom: 12 }}>
|
||||||
时间设定
|
时间设定
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph style={{
|
<Paragraph style={{
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
lineHeight: 1.8,
|
lineHeight: 1.8,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
background: '#f5f5f5',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
borderLeft: '4px solid #1890ff'
|
borderLeft: '4px solid var(--color-primary)'
|
||||||
}}>
|
}}>
|
||||||
{currentProject.world_time_period}
|
{currentProject.world_time_period}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentProject.world_location && (
|
{currentProject.world_location && (
|
||||||
<div style={{ marginBottom: 24 }}>
|
<div style={{ marginBottom: 24 }}>
|
||||||
<Title level={5} style={{ color: '#52c41a', marginBottom: 12 }}>
|
<Title level={5} style={{ color: 'var(--color-success)', marginBottom: 12 }}>
|
||||||
地点设定
|
地点设定
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph style={{
|
<Paragraph style={{
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
lineHeight: 1.8,
|
lineHeight: 1.8,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
background: '#f5f5f5',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
borderLeft: '4px solid #52c41a'
|
borderLeft: '4px solid var(--color-success)'
|
||||||
}}>
|
}}>
|
||||||
{currentProject.world_location}
|
{currentProject.world_location}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentProject.world_atmosphere && (
|
{currentProject.world_atmosphere && (
|
||||||
<div style={{ marginBottom: 24 }}>
|
<div style={{ marginBottom: 24 }}>
|
||||||
<Title level={5} style={{ color: '#faad14', marginBottom: 12 }}>
|
<Title level={5} style={{ color: 'var(--color-warning)', marginBottom: 12 }}>
|
||||||
氛围设定
|
氛围设定
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph style={{
|
<Paragraph style={{
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
lineHeight: 1.8,
|
lineHeight: 1.8,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
background: '#f5f5f5',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
borderLeft: '4px solid #faad14'
|
borderLeft: '4px solid var(--color-warning)'
|
||||||
}}>
|
}}>
|
||||||
{currentProject.world_atmosphere}
|
{currentProject.world_atmosphere}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentProject.world_rules && (
|
{currentProject.world_rules && (
|
||||||
<div style={{ marginBottom: 0 }}>
|
<div style={{ marginBottom: 0 }}>
|
||||||
<Title level={5} style={{ color: '#f5222d', marginBottom: 12 }}>
|
<Title level={5} style={{ color: 'var(--color-error)', marginBottom: 12 }}>
|
||||||
规则设定
|
规则设定
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph style={{
|
<Paragraph style={{
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
lineHeight: 1.8,
|
lineHeight: 1.8,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
background: '#f5f5f5',
|
background: 'var(--color-bg-layout)',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
borderLeft: '4px solid #f5222d'
|
borderLeft: '4px solid var(--color-error)'
|
||||||
}}>
|
}}>
|
||||||
{currentProject.world_rules}
|
{currentProject.world_rules}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 编辑世界观模态框 */}
|
{/* 编辑世界观模态框 */}
|
||||||
@@ -440,14 +442,14 @@ export default function WorldSetting() {
|
|||||||
>
|
>
|
||||||
{newWorldData && (
|
{newWorldData && (
|
||||||
<div style={{ maxHeight: '60vh', overflowY: 'auto' }}>
|
<div style={{ maxHeight: '60vh', overflowY: 'auto' }}>
|
||||||
<div style={{ marginBottom: 24, padding: 16, background: '#fff7e6', border: '1px solid #ffd591', borderRadius: 8 }}>
|
<div style={{ marginBottom: 24, padding: 16, background: 'var(--color-warning-bg)', border: '1px solid var(--color-warning-border)', borderRadius: 8 }}>
|
||||||
<Typography.Text type="warning" strong>
|
<Typography.Text type="warning" strong>
|
||||||
⚠️ 注意:点击"确认替换"将会用新内容替换当前的世界观设定
|
⚠️ 注意:点击"确认替换"将会用新内容替换当前的世界观设定
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: 24 }}>
|
<div style={{ marginBottom: 24 }}>
|
||||||
<Title level={5} style={{ color: '#1890ff', marginBottom: 12 }}>
|
<Title level={5} style={{ color: 'var(--color-primary)', marginBottom: 12 }}>
|
||||||
时间设定
|
时间设定
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph style={{
|
<Paragraph style={{
|
||||||
|
|||||||
Reference in New Issue
Block a user