style: 优化向导/灵感模式AI生成页面样式
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Card, Button, Space, Typography, message, Progress } from 'antd';
|
import { Card, Button, Space, Typography, message, Progress, theme } from 'antd';
|
||||||
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons';
|
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||||
import { wizardStreamApi } from '../services/api';
|
import { wizardStreamApi } from '../services/api';
|
||||||
import type { ApiError } from '../types';
|
import type { ApiError } from '../types';
|
||||||
@@ -53,6 +53,9 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
resumeProjectId
|
resumeProjectId
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { token } = theme.useToken();
|
||||||
|
const alphaColor = (color: string, alpha: number) =>
|
||||||
|
`color-mix(in srgb, ${color} ${(alpha * 100).toFixed(0)}%, transparent)`;
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -947,12 +950,45 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 获取步骤状态图标和颜色
|
// 获取步骤状态图标和样式
|
||||||
const getStepStatus = (step: GenerationStep) => {
|
const getStepStatus = (step: GenerationStep) => {
|
||||||
if (step === 'completed') return { icon: <CheckCircleOutlined />, color: 'var(--color-success)' };
|
if (step === 'completed') {
|
||||||
if (step === 'processing') return { icon: <LoadingOutlined />, color: 'var(--color-primary)' };
|
return {
|
||||||
if (step === 'error') return { icon: '✗', color: 'var(--color-error)' };
|
icon: <CheckCircleOutlined />,
|
||||||
return { icon: '○', color: 'var(--color-text-quaternary)' };
|
color: token.colorSuccess,
|
||||||
|
text: '已完成',
|
||||||
|
background: `linear-gradient(135deg, ${alphaColor(token.colorSuccess, 0.12)} 0%, ${token.colorBgContainer} 100%)`,
|
||||||
|
borderColor: alphaColor(token.colorSuccess, 0.28),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 'processing') {
|
||||||
|
return {
|
||||||
|
icon: <LoadingOutlined spin />,
|
||||||
|
color: token.colorPrimary,
|
||||||
|
text: '进行中',
|
||||||
|
background: `linear-gradient(135deg, ${alphaColor(token.colorPrimary, 0.14)} 0%, ${token.colorBgContainer} 100%)`,
|
||||||
|
borderColor: alphaColor(token.colorPrimary, 0.32),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step === 'error') {
|
||||||
|
return {
|
||||||
|
icon: '✕',
|
||||||
|
color: token.colorError,
|
||||||
|
text: '失败',
|
||||||
|
background: `linear-gradient(135deg, ${alphaColor(token.colorError, 0.12)} 0%, ${token.colorBgContainer} 100%)`,
|
||||||
|
borderColor: alphaColor(token.colorError, 0.32),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon: '○',
|
||||||
|
color: token.colorTextQuaternary,
|
||||||
|
text: '等待中',
|
||||||
|
background: token.colorFillQuaternary,
|
||||||
|
borderColor: token.colorBorderSecondary,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasError = generationSteps.worldBuilding === 'error' ||
|
const hasError = generationSteps.worldBuilding === 'error' ||
|
||||||
@@ -960,95 +996,215 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
generationSteps.characters === 'error' ||
|
generationSteps.characters === 'error' ||
|
||||||
generationSteps.outline === 'error';
|
generationSteps.outline === 'error';
|
||||||
|
|
||||||
|
const progressAccentColor = hasError
|
||||||
|
? token.colorError
|
||||||
|
: progress === 100
|
||||||
|
? token.colorSuccess
|
||||||
|
: token.colorPrimary;
|
||||||
|
|
||||||
|
const stepItems = [
|
||||||
|
{ key: 'worldBuilding', label: '生成世界观', step: generationSteps.worldBuilding },
|
||||||
|
{ key: 'careers', label: '生成职业体系', step: generationSteps.careers },
|
||||||
|
{ key: 'characters', label: '生成角色', step: generationSteps.characters },
|
||||||
|
{ key: 'outline', label: '生成大纲', step: generationSteps.outline },
|
||||||
|
];
|
||||||
|
|
||||||
|
const availableViewportHeight = isMobile
|
||||||
|
? 'calc(100dvh - 96px)'
|
||||||
|
: 'calc(100dvh - 128px)';
|
||||||
|
|
||||||
// 渲染生成进度页面
|
// 渲染生成进度页面
|
||||||
const renderGenerating = () => (
|
const renderGenerating = () => (
|
||||||
<div style={{
|
<div
|
||||||
textAlign: 'center',
|
style={{
|
||||||
padding: isMobile ? '32px 16px' : '40px 20px',
|
padding: isMobile ? '4px 0 8px' : '8px 0 12px',
|
||||||
maxWidth: '100%',
|
maxWidth: 920,
|
||||||
overflow: 'hidden'
|
margin: '0 auto',
|
||||||
}}>
|
overflow: 'hidden',
|
||||||
<Title
|
minHeight: availableViewportHeight,
|
||||||
level={isMobile ? 4 : 3}
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: hasError ? 'flex-start' : 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 32,
|
marginBottom: 14,
|
||||||
color: 'var(--color-text-primary)',
|
padding: isMobile ? '18px 16px' : '24px 24px 20px',
|
||||||
wordBreak: 'break-word',
|
borderRadius: 18,
|
||||||
whiteSpace: 'normal',
|
border: `1px solid ${alphaColor(hasError ? token.colorError : token.colorPrimary, 0.18)}`,
|
||||||
overflowWrap: 'break-word'
|
background: `linear-gradient(135deg, ${alphaColor(token.colorPrimary, 0.12)} 0%, ${token.colorBgContainer} 48%, ${alphaColor(hasError ? token.colorError : token.colorSuccess, hasError ? 0.08 : 0.04)} 100%)`,
|
||||||
|
boxShadow: `0 12px 28px ${alphaColor(token.colorText, 0.06)}`,
|
||||||
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
正在为《{config.title}》生成内容
|
<Title
|
||||||
</Title>
|
level={isMobile ? 4 : 3}
|
||||||
|
style={{
|
||||||
<Card style={{ marginBottom: 24, maxWidth: '100%' }}>
|
marginBottom: 8,
|
||||||
<Progress
|
color: token.colorTextHeading,
|
||||||
percent={progress}
|
wordBreak: 'break-word',
|
||||||
status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')}
|
whiteSpace: 'normal',
|
||||||
strokeColor={{
|
overflowWrap: 'break-word',
|
||||||
'0%': 'var(--color-primary)',
|
|
||||||
'100%': 'var(--color-primary-active)',
|
|
||||||
}}
|
}}
|
||||||
style={{ marginBottom: 24 }}
|
>
|
||||||
/>
|
正在为《{config.title}》生成内容
|
||||||
|
</Title>
|
||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
fontSize: isMobile ? 14 : 16,
|
maxWidth: 620,
|
||||||
marginBottom: 32,
|
margin: '0 auto',
|
||||||
color: hasError ? 'var(--color-error)' : 'var(--color-text-secondary)',
|
color: token.colorTextSecondary,
|
||||||
|
fontSize: isMobile ? 13 : 14,
|
||||||
|
lineHeight: 1.7,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
overflowWrap: 'break-word'
|
overflowWrap: 'break-word',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{progressMessage}
|
{hasError
|
||||||
|
? '生成流程中断,已保留当前进度与上下文信息,可从失败步骤继续重试。'
|
||||||
|
: '系统会依次生成世界观、职业体系、角色与大纲,请耐心等待。'}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
|
||||||
{errorDetails && (
|
<Card
|
||||||
<Card
|
style={{
|
||||||
size="small"
|
marginBottom: 12,
|
||||||
|
borderRadius: 18,
|
||||||
|
border: `1px solid ${alphaColor(token.colorText, 0.08)}`,
|
||||||
|
background: `linear-gradient(180deg, ${alphaColor(token.colorBgContainer, 0.97)} 0%, ${alphaColor(token.colorPrimary, 0.03)} 100%)`,
|
||||||
|
boxShadow: `0 10px 24px ${alphaColor(token.colorText, 0.06)}`,
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
padding: isMobile ? 14 : 20,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: isMobile ? '14px 14px 16px' : '16px 18px 18px',
|
||||||
|
marginBottom: 16,
|
||||||
|
borderRadius: 14,
|
||||||
|
background: token.colorFillQuaternary,
|
||||||
|
border: `1px solid ${alphaColor(progressAccentColor, 0.18)}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 24,
|
display: 'flex',
|
||||||
background: 'var(--color-error-bg)',
|
justifyContent: 'space-between',
|
||||||
borderColor: 'var(--color-error-border)',
|
alignItems: isMobile ? 'flex-start' : 'center',
|
||||||
textAlign: 'left',
|
flexDirection: isMobile ? 'column' : 'row',
|
||||||
maxWidth: '100%',
|
gap: 10,
|
||||||
overflow: 'hidden'
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text strong style={{ color: 'var(--color-error)' }}>错误详情:</Text>
|
<div style={{ flex: 1, textAlign: 'left' }}>
|
||||||
<br />
|
<Text
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 6,
|
||||||
|
color: token.colorTextTertiary,
|
||||||
|
fontSize: 12,
|
||||||
|
letterSpacing: 0.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
当前进度
|
||||||
|
</Text>
|
||||||
|
<Paragraph
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
color: hasError ? token.colorError : token.colorText,
|
||||||
|
fontSize: isMobile ? 13 : 15,
|
||||||
|
lineHeight: 1.7,
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
overflowWrap: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{progressMessage || '准备生成...'}
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minWidth: isMobile ? 'auto' : 96,
|
||||||
|
textAlign: isMobile ? 'left' : 'right',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: isMobile ? 24 : 32,
|
||||||
|
lineHeight: 1,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: progressAccentColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{progress}%
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Progress
|
||||||
|
percent={progress}
|
||||||
|
showInfo={false}
|
||||||
|
status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')}
|
||||||
|
strokeColor={progress === 100
|
||||||
|
? {
|
||||||
|
'0%': token.colorSuccess,
|
||||||
|
'100%': token.colorSuccessActive,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
'0%': token.colorPrimary,
|
||||||
|
'100%': token.colorPrimaryActive,
|
||||||
|
}}
|
||||||
|
trailColor={token.colorFillTertiary}
|
||||||
|
strokeLinecap="round"
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{errorDetails && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
padding: isMobile ? '12px 14px' : '14px 16px',
|
||||||
|
borderRadius: 14,
|
||||||
|
background: `linear-gradient(135deg, ${alphaColor(token.colorError, 0.12)} 0%, ${token.colorBgContainer} 100%)`,
|
||||||
|
border: `1px solid ${alphaColor(token.colorError, 0.24)}`,
|
||||||
|
textAlign: 'left',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text strong style={{ color: token.colorError, display: 'block', marginBottom: 8 }}>
|
||||||
|
错误详情
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-text-secondary)',
|
color: token.colorTextSecondary,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
lineHeight: 1.7,
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
overflowWrap: 'break-word',
|
overflowWrap: 'break-word',
|
||||||
display: 'block'
|
display: 'block',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{errorDetails}
|
{errorDetails}
|
||||||
</Text>
|
</Text>
|
||||||
</Card>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Space
|
<div
|
||||||
direction="vertical"
|
|
||||||
size={16}
|
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
display: 'grid',
|
||||||
maxWidth: isMobile ? '100%' : 400,
|
gap: 10,
|
||||||
margin: '0 auto'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[
|
{stepItems.map(({ key, label, step }) => {
|
||||||
{ key: 'worldBuilding', label: '生成世界观', step: generationSteps.worldBuilding },
|
|
||||||
{ key: 'careers', label: '生成职业体系', step: generationSteps.careers },
|
|
||||||
{ key: 'characters', label: '生成角色', step: generationSteps.characters },
|
|
||||||
{ key: 'outline', label: '生成大纲', step: generationSteps.outline },
|
|
||||||
].map(({ key, label, step }) => {
|
|
||||||
const status = getStepStatus(step);
|
const status = getStepStatus(step);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -1057,71 +1213,127 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: isMobile ? '10px 12px' : '12px 20px',
|
padding: isMobile ? '10px 12px' : '12px 14px',
|
||||||
background: step === 'processing' ? 'var(--color-info-bg)' : (step === 'error' ? 'var(--color-error-bg)' : 'var(--color-bg-layout)'),
|
background: status.background,
|
||||||
borderRadius: 8,
|
borderRadius: 14,
|
||||||
border: `1px solid ${step === 'processing' ? 'var(--color-info-border)' : (step === 'error' ? 'var(--color-error-border)' : 'var(--color-border-secondary)')}`,
|
border: `1px solid ${status.borderColor}`,
|
||||||
gap: '8px',
|
gap: 12,
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
overflow: 'hidden'
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: status.color,
|
||||||
|
background: alphaColor(status.color, 0.12),
|
||||||
|
fontSize: 18,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status.icon}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
minWidth: 0,
|
||||||
|
flex: 1,
|
||||||
|
textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
fontSize: isMobile ? 13 : 14,
|
||||||
|
fontWeight: step === 'processing' ? 600 : 500,
|
||||||
|
color: token.colorText,
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
overflowWrap: 'break-word',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: step === 'pending' ? token.colorTextTertiary : status.color,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status.text}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: isMobile ? 14 : 16,
|
fontSize: 12,
|
||||||
fontWeight: step === 'processing' ? 600 : 400,
|
fontWeight: 600,
|
||||||
wordBreak: 'break-word',
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
overflowWrap: 'break-word',
|
|
||||||
flex: 1,
|
|
||||||
textAlign: 'left'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 20,
|
|
||||||
color: status.color,
|
color: status.color,
|
||||||
flexShrink: 0
|
padding: '4px 10px',
|
||||||
|
borderRadius: 999,
|
||||||
|
background: alphaColor(status.color, 0.1),
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{status.icon}
|
{status.text}
|
||||||
</span>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Space>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
type="secondary"
|
type="secondary"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-text-secondary)',
|
marginBottom: hasError ? 14 : 0,
|
||||||
opacity: 0.9,
|
color: token.colorTextSecondary,
|
||||||
|
opacity: 0.92,
|
||||||
|
textAlign: 'center',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
overflowWrap: 'break-word',
|
overflowWrap: 'break-word',
|
||||||
fontSize: isMobile ? 14 : 16
|
fontSize: isMobile ? 13 : 14,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasError ? '生成过程中出现错误,请点击重试按钮重新生成' : '请耐心等待,AI正在为您精心创作...'}
|
{hasError ? '可点击下方智能重试,从失败节点继续生成,避免重复执行已完成步骤。' : '请勿关闭页面,生成完成后将自动进入项目详情页。'}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<Space style={{ marginTop: 16 }}>
|
<Space style={{ width: '100%', justifyContent: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={handleSmartRetry}
|
onClick={handleSmartRetry}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
style={{
|
||||||
|
minWidth: isMobile ? '100%' : 160,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 12,
|
||||||
|
boxShadow: `0 10px 24px ${alphaColor(token.colorPrimary, 0.22)}`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
智能重试
|
智能重试
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user