2025-11-11 19:50:12 +08:00
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
|
import { useNavigate } from 'react-router-dom';
|
2025-10-30 11:14:43 +08:00
|
|
|
|
import {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
Form, Input, InputNumber, Select, Button, message, Card,
|
|
|
|
|
|
Row, Col, Typography, Space, Progress
|
2025-10-30 11:14:43 +08:00
|
|
|
|
} from 'antd';
|
|
|
|
|
|
import {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
RocketOutlined, ArrowLeftOutlined, CheckCircleOutlined,
|
|
|
|
|
|
LoadingOutlined
|
2025-10-30 11:14:43 +08:00
|
|
|
|
} from '@ant-design/icons';
|
2025-11-11 19:50:12 +08:00
|
|
|
|
import { wizardStreamApi } from '../services/api';
|
|
|
|
|
|
import type { WizardBasicInfo, ApiError } from '../types';
|
2025-10-30 11:14:43 +08:00
|
|
|
|
import { SSELoadingOverlay } from '../components/SSELoadingOverlay';
|
|
|
|
|
|
|
|
|
|
|
|
const { TextArea } = Input;
|
|
|
|
|
|
const { Title, Paragraph, Text } = Typography;
|
|
|
|
|
|
|
|
|
|
|
|
export default function ProjectWizardNew() {
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
|
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
2025-11-11 19:50:12 +08:00
|
|
|
|
|
|
|
|
|
|
// 状态管理
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [currentStep, setCurrentStep] = useState<'form' | 'generating' | 'complete'>('form');
|
|
|
|
|
|
const [projectId, setProjectId] = useState<string>('');
|
|
|
|
|
|
const [projectTitle, setProjectTitle] = useState<string>('');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
|
|
|
|
|
// SSE流式进度状态
|
|
|
|
|
|
const [progress, setProgress] = useState(0);
|
|
|
|
|
|
const [progressMessage, setProgressMessage] = useState('');
|
2025-11-11 19:50:12 +08:00
|
|
|
|
const [generationSteps, setGenerationSteps] = useState<{
|
|
|
|
|
|
worldBuilding: 'pending' | 'processing' | 'completed' | 'error';
|
|
|
|
|
|
characters: 'pending' | 'processing' | 'completed' | 'error';
|
|
|
|
|
|
outline: 'pending' | 'processing' | 'completed' | 'error';
|
|
|
|
|
|
}>({
|
|
|
|
|
|
worldBuilding: 'pending',
|
|
|
|
|
|
characters: 'pending',
|
|
|
|
|
|
outline: 'pending'
|
2025-10-30 11:14:43 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
setIsMobile(window.innerWidth <= 768);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
};
|
2025-11-11 19:50:12 +08:00
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
|
|
|
|
}, []);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
// 自动化生成流程
|
|
|
|
|
|
const handleAutoGenerate = async (values: WizardBasicInfo) => {
|
2025-10-30 11:14:43 +08:00
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true);
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setCurrentStep('generating');
|
|
|
|
|
|
setProjectTitle(values.title);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
setProgress(0);
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setProgressMessage('开始创建项目...');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
// 步骤1: 生成世界观并创建项目
|
|
|
|
|
|
setGenerationSteps(prev => ({ ...prev, worldBuilding: 'processing' }));
|
|
|
|
|
|
setProgressMessage('正在生成世界观...');
|
|
|
|
|
|
|
|
|
|
|
|
const worldResult = await wizardStreamApi.generateWorldBuildingStream(
|
2025-10-30 11:14:43 +08:00
|
|
|
|
{
|
|
|
|
|
|
title: values.title,
|
|
|
|
|
|
description: values.description,
|
|
|
|
|
|
theme: values.theme,
|
|
|
|
|
|
genre: Array.isArray(values.genre) ? values.genre.join('、') : values.genre,
|
|
|
|
|
|
narrative_perspective: values.narrative_perspective,
|
|
|
|
|
|
target_words: values.target_words,
|
2025-11-11 19:50:12 +08:00
|
|
|
|
chapter_count: values.chapter_count || 30,
|
|
|
|
|
|
character_count: values.character_count || 5,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
onProgress: (msg, prog) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setProgress(Math.floor(prog / 3)); // 0-33%
|
2025-10-30 11:14:43 +08:00
|
|
|
|
setProgressMessage(msg);
|
|
|
|
|
|
},
|
|
|
|
|
|
onResult: (data) => {
|
|
|
|
|
|
setProjectId(data.project_id);
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setGenerationSteps(prev => ({ ...prev, worldBuilding: 'completed' }));
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
onError: (error) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setGenerationSteps(prev => ({ ...prev, worldBuilding: 'error' }));
|
|
|
|
|
|
throw new Error(error);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
onComplete: () => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
console.log('世界观生成完成');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
if (!worldResult?.project_id) {
|
|
|
|
|
|
throw new Error('项目创建失败');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
const createdProjectId = worldResult.project_id;
|
|
|
|
|
|
setProjectId(createdProjectId);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤2: 生成角色
|
|
|
|
|
|
setGenerationSteps(prev => ({ ...prev, characters: 'processing' }));
|
|
|
|
|
|
setProgressMessage('正在生成角色...');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
await wizardStreamApi.generateCharactersStream(
|
2025-10-30 11:14:43 +08:00
|
|
|
|
{
|
2025-11-11 19:50:12 +08:00
|
|
|
|
project_id: createdProjectId,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
count: values.character_count || 5,
|
|
|
|
|
|
world_context: {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
time_period: worldResult.time_period || '',
|
|
|
|
|
|
location: worldResult.location || '',
|
|
|
|
|
|
atmosphere: worldResult.atmosphere || '',
|
|
|
|
|
|
rules: worldResult.rules || '',
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
2025-11-11 19:50:12 +08:00
|
|
|
|
theme: values.theme,
|
|
|
|
|
|
genre: Array.isArray(values.genre) ? values.genre.join('、') : values.genre,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
onProgress: (msg, prog) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setProgress(33 + Math.floor(prog / 3)); // 33-66%
|
2025-10-30 11:14:43 +08:00
|
|
|
|
setProgressMessage(msg);
|
|
|
|
|
|
},
|
|
|
|
|
|
onResult: (data) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
console.log(`成功生成${data.characters?.length || 0}个角色`);
|
|
|
|
|
|
setGenerationSteps(prev => ({ ...prev, characters: 'completed' }));
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
onError: (error) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setGenerationSteps(prev => ({ ...prev, characters: 'error' }));
|
|
|
|
|
|
throw new Error(error);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
onComplete: () => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
console.log('角色生成完成');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
// 步骤3: 生成大纲
|
|
|
|
|
|
setGenerationSteps(prev => ({ ...prev, outline: 'processing' }));
|
|
|
|
|
|
setProgressMessage('正在生成大纲...');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
|
|
|
|
|
await wizardStreamApi.generateCompleteOutlineStream(
|
|
|
|
|
|
{
|
2025-11-11 19:50:12 +08:00
|
|
|
|
project_id: createdProjectId,
|
2025-11-21 15:49:39 +08:00
|
|
|
|
chapter_count: 3, // 生成3个大纲节点(不展开)
|
2025-11-11 19:50:12 +08:00
|
|
|
|
narrative_perspective: values.narrative_perspective,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
target_words: values.target_words,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
onProgress: (msg, prog) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setProgress(66 + Math.floor(prog / 3)); // 66-99%
|
2025-10-30 11:14:43 +08:00
|
|
|
|
setProgressMessage(msg);
|
|
|
|
|
|
},
|
2025-11-11 19:50:12 +08:00
|
|
|
|
onResult: () => {
|
|
|
|
|
|
console.log('大纲生成完成');
|
|
|
|
|
|
setGenerationSteps(prev => ({ ...prev, outline: 'completed' }));
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
onError: (error) => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
setGenerationSteps(prev => ({ ...prev, outline: 'error' }));
|
|
|
|
|
|
throw new Error(error);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
onComplete: () => {
|
2025-11-11 19:50:12 +08:00
|
|
|
|
console.log('大纲生成完成');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
// 全部完成
|
|
|
|
|
|
setProgress(100);
|
|
|
|
|
|
setProgressMessage('项目创建完成!');
|
|
|
|
|
|
setCurrentStep('complete');
|
|
|
|
|
|
message.success('项目创建成功!');
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
const apiError = error as ApiError;
|
2025-11-11 19:50:12 +08:00
|
|
|
|
message.error('创建项目失败:' + (apiError.response?.data?.detail || apiError.message || '未知错误'));
|
|
|
|
|
|
setCurrentStep('form');
|
|
|
|
|
|
setGenerationSteps({
|
|
|
|
|
|
worldBuilding: 'pending',
|
|
|
|
|
|
characters: 'pending',
|
|
|
|
|
|
outline: 'pending'
|
2025-10-30 11:14:43 +08:00
|
|
|
|
});
|
2025-11-11 19:50:12 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
// 渲染表单页面
|
|
|
|
|
|
const renderForm = () => (
|
2025-10-30 11:14:43 +08:00
|
|
|
|
<Card>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Title level={isMobile ? 4 : 3} style={{ marginBottom: 24 }}>
|
|
|
|
|
|
创建新项目
|
|
|
|
|
|
</Title>
|
|
|
|
|
|
<Paragraph type="secondary" style={{ marginBottom: 32 }}>
|
2025-11-21 15:49:39 +08:00
|
|
|
|
填写基本信息后,AI将自动为您生成世界观、角色和大纲节点(大纲可在项目内手动展开为章节)
|
2025-11-11 19:50:12 +08:00
|
|
|
|
</Paragraph>
|
|
|
|
|
|
|
|
|
|
|
|
<Form
|
|
|
|
|
|
form={form}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
onFinish={handleAutoGenerate}
|
|
|
|
|
|
initialValues={{
|
|
|
|
|
|
genre: ['玄幻'],
|
|
|
|
|
|
chapter_count: 30,
|
|
|
|
|
|
narrative_perspective: '第三人称',
|
|
|
|
|
|
character_count: 5,
|
|
|
|
|
|
target_words: 100000,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="书名"
|
|
|
|
|
|
name="title"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入书名' }]}
|
|
|
|
|
|
>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
<Input placeholder="输入你的小说标题" size="large" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="小说简介"
|
|
|
|
|
|
name="description"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入小说简介' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<TextArea
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
placeholder="用一段话介绍你的小说..."
|
|
|
|
|
|
showCount
|
|
|
|
|
|
maxLength={300}
|
|
|
|
|
|
/>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="主题"
|
|
|
|
|
|
name="theme"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入主题' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<TextArea
|
|
|
|
|
|
rows={4}
|
|
|
|
|
|
placeholder="描述你的小说主题..."
|
|
|
|
|
|
showCount
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
/>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="类型"
|
|
|
|
|
|
name="genre"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择小说类型' }]}
|
|
|
|
|
|
>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
<Select
|
|
|
|
|
|
mode="tags"
|
2025-11-11 19:50:12 +08:00
|
|
|
|
placeholder="选择或输入类型标签(如:玄幻、都市、修仙)"
|
2025-10-30 11:14:43 +08:00
|
|
|
|
size="large"
|
|
|
|
|
|
tokenSeparators={[',']}
|
|
|
|
|
|
maxTagCount={5}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select.Option value="玄幻">玄幻</Select.Option>
|
|
|
|
|
|
<Select.Option value="都市">都市</Select.Option>
|
|
|
|
|
|
<Select.Option value="历史">历史</Select.Option>
|
|
|
|
|
|
<Select.Option value="科幻">科幻</Select.Option>
|
|
|
|
|
|
<Select.Option value="武侠">武侠</Select.Option>
|
|
|
|
|
|
<Select.Option value="仙侠">仙侠</Select.Option>
|
|
|
|
|
|
<Select.Option value="奇幻">奇幻</Select.Option>
|
|
|
|
|
|
<Select.Option value="悬疑">悬疑</Select.Option>
|
|
|
|
|
|
<Select.Option value="言情">言情</Select.Option>
|
|
|
|
|
|
<Select.Option value="修仙">修仙</Select.Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col xs={24} sm={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="叙事视角"
|
|
|
|
|
|
name="narrative_perspective"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择叙事视角' }]}
|
2025-11-03 15:28:51 +08:00
|
|
|
|
>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Select size="large" placeholder="选择小说的叙事视角">
|
|
|
|
|
|
<Select.Option value="第一人称">第一人称</Select.Option>
|
|
|
|
|
|
<Select.Option value="第三人称">第三人称</Select.Option>
|
|
|
|
|
|
<Select.Option value="全知视角">全知视角</Select.Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col xs={24} sm={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="角色数量"
|
|
|
|
|
|
name="character_count"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入角色数量' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<InputNumber
|
|
|
|
|
|
min={3}
|
|
|
|
|
|
max={20}
|
|
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
addonAfter="个"
|
|
|
|
|
|
placeholder="AI生成的角色数量"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Row>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
label="目标字数"
|
|
|
|
|
|
name="target_words"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入目标字数' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<InputNumber
|
|
|
|
|
|
min={10000}
|
|
|
|
|
|
style={{ width: '100%' }}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
size="large"
|
2025-11-11 19:50:12 +08:00
|
|
|
|
addonAfter="字"
|
|
|
|
|
|
placeholder="整部小说的目标字数"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<Form.Item>
|
|
|
|
|
|
<Space direction="vertical" style={{ width: '100%' }} size={12}>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
<Button
|
2025-11-11 19:50:12 +08:00
|
|
|
|
type="primary"
|
|
|
|
|
|
htmlType="submit"
|
2025-10-30 11:14:43 +08:00
|
|
|
|
size="large"
|
|
|
|
|
|
block
|
2025-11-11 19:50:12 +08:00
|
|
|
|
loading={loading}
|
|
|
|
|
|
icon={<RocketOutlined />}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
开始创建项目
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
block
|
2025-11-11 19:50:12 +08:00
|
|
|
|
onClick={() => navigate('/')}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
返回首页
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Button>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
</Space>
|
|
|
|
|
|
</Form.Item>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Form>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
// 渲染生成进度页面
|
|
|
|
|
|
const renderGenerating = () => {
|
|
|
|
|
|
const getStepStatus = (step: 'pending' | 'processing' | 'completed' | 'error') => {
|
|
|
|
|
|
if (step === 'completed') return { icon: <CheckCircleOutlined />, color: '#52c41a' };
|
|
|
|
|
|
if (step === 'processing') return { icon: <LoadingOutlined />, color: '#1890ff' };
|
|
|
|
|
|
if (step === 'error') return { icon: '✗', color: '#ff4d4f' };
|
|
|
|
|
|
return { icon: '○', color: '#d9d9d9' };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<div style={{ textAlign: 'center', padding: isMobile ? '32px 16px' : '40px 0' }}>
|
|
|
|
|
|
<Title level={isMobile ? 4 : 3} style={{ marginBottom: 32 }}>
|
|
|
|
|
|
正在为《{projectTitle}》生成内容
|
|
|
|
|
|
</Title>
|
|
|
|
|
|
|
|
|
|
|
|
<Progress
|
|
|
|
|
|
percent={progress}
|
|
|
|
|
|
status={progress === 100 ? 'success' : 'active'}
|
|
|
|
|
|
strokeColor={{
|
|
|
|
|
|
'0%': '#667eea',
|
|
|
|
|
|
'100%': '#764ba2',
|
|
|
|
|
|
}}
|
|
|
|
|
|
style={{ marginBottom: 32 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Paragraph style={{ fontSize: 16, marginBottom: 48, color: '#666' }}>
|
|
|
|
|
|
{progressMessage}
|
|
|
|
|
|
</Paragraph>
|
|
|
|
|
|
|
|
|
|
|
|
<Space direction="vertical" size={24} style={{ width: '100%', maxWidth: 400, margin: '0 auto' }}>
|
|
|
|
|
|
{[
|
|
|
|
|
|
{ key: 'worldBuilding', label: '生成世界观', step: generationSteps.worldBuilding },
|
|
|
|
|
|
{ key: 'characters', label: '生成角色', step: generationSteps.characters },
|
|
|
|
|
|
{ key: 'outline', label: '生成大纲', step: generationSteps.outline },
|
|
|
|
|
|
].map(({ key, label, step }) => {
|
|
|
|
|
|
const status = getStepStatus(step);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={key}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
padding: '12px 20px',
|
|
|
|
|
|
background: step === 'processing' ? '#f0f5ff' : '#fafafa',
|
|
|
|
|
|
borderRadius: 8,
|
|
|
|
|
|
border: `1px solid ${step === 'processing' ? '#d6e4ff' : '#f0f0f0'}`,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Text style={{ fontSize: 16, fontWeight: step === 'processing' ? 600 : 400 }}>
|
|
|
|
|
|
{label}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
<span style={{ fontSize: 20, color: status.color }}>
|
|
|
|
|
|
{status.icon}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
|
|
|
|
|
|
<Paragraph type="secondary" style={{ marginTop: 48 }}>
|
|
|
|
|
|
请耐心等待,AI正在为您精心创作...
|
|
|
|
|
|
</Paragraph>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染完成页面
|
2025-10-30 11:14:43 +08:00
|
|
|
|
const renderComplete = () => (
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
padding: isMobile ? '32px 16px' : '40px 0'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
fontSize: isMobile ? 56 : 72,
|
|
|
|
|
|
color: '#52c41a',
|
|
|
|
|
|
marginBottom: isMobile ? 16 : 24
|
|
|
|
|
|
}}>
|
|
|
|
|
|
✓
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Title
|
|
|
|
|
|
level={isMobile ? 3 : 2}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
color: '#52c41a',
|
|
|
|
|
|
marginBottom: isMobile ? 8 : 16
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
项目创建完成!
|
|
|
|
|
|
</Title>
|
|
|
|
|
|
<Paragraph style={{
|
|
|
|
|
|
fontSize: isMobile ? 14 : 16,
|
2025-11-11 19:50:12 +08:00
|
|
|
|
marginTop: isMobile ? 16 : 24,
|
|
|
|
|
|
marginBottom: isMobile ? 32 : 48,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}}>
|
2025-11-21 15:49:39 +08:00
|
|
|
|
《{projectTitle}》已成功创建,包含完整的世界观、角色和大纲节点
|
|
|
|
|
|
</Paragraph>
|
|
|
|
|
|
<Paragraph type="secondary" style={{
|
|
|
|
|
|
fontSize: isMobile ? 12 : 14,
|
|
|
|
|
|
marginTop: 8,
|
|
|
|
|
|
}}>
|
|
|
|
|
|
💡 提示:进入项目后,可在"大纲"页面将大纲节点展开为详细章节
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Paragraph>
|
|
|
|
|
|
|
|
|
|
|
|
<Space
|
|
|
|
|
|
size={isMobile ? 12 : 16}
|
|
|
|
|
|
direction={isMobile ? 'vertical' : 'horizontal'}
|
|
|
|
|
|
style={{ width: isMobile ? '100%' : 'auto' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
onClick={() => navigate('/')}
|
|
|
|
|
|
block={isMobile}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
|
height: isMobile ? 44 : 40
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
返回首页
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
icon={<RocketOutlined />}
|
|
|
|
|
|
onClick={() => navigate(`/project/${projectId}`)}
|
|
|
|
|
|
block={isMobile}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
|
height: isMobile ? 44 : 40
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
进入项目
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
minHeight: '100vh',
|
2025-11-11 19:50:12 +08:00
|
|
|
|
background: '#f5f7fa',
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}}>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
{/* 顶部标题栏 - 固定不滚动 */}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
<div style={{
|
2025-11-11 19:50:12 +08:00
|
|
|
|
position: 'sticky',
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
zIndex: 100,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
|
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
maxWidth: 1200,
|
|
|
|
|
|
margin: '0 auto',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'space-between',
|
2025-11-11 19:50:12 +08:00
|
|
|
|
padding: isMobile ? '12px 16px' : '16px 24px',
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
icon={<ArrowLeftOutlined />}
|
|
|
|
|
|
onClick={() => navigate('/')}
|
|
|
|
|
|
size={isMobile ? 'middle' : 'large'}
|
2025-11-11 19:50:12 +08:00
|
|
|
|
disabled={currentStep === 'generating'}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
style={{
|
|
|
|
|
|
background: 'rgba(255,255,255,0.2)',
|
|
|
|
|
|
borderColor: 'rgba(255,255,255,0.3)',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
{isMobile ? '返回' : '返回首页'}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
<Title level={isMobile ? 4 : 2} style={{
|
|
|
|
|
|
margin: 0,
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
textShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
|
|
|
|
|
}}>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
项目创建向导
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</Title>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
<div style={{ width: isMobile ? 60 : 120 }}></div>
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-11-11 19:50:12 +08:00
|
|
|
|
{/* 内容区域 */}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
<div style={{
|
2025-11-11 19:50:12 +08:00
|
|
|
|
maxWidth: 800,
|
2025-10-30 11:14:43 +08:00
|
|
|
|
margin: '0 auto',
|
2025-11-11 19:50:12 +08:00
|
|
|
|
padding: isMobile ? '16px 12px' : '24px 24px',
|
2025-10-30 11:14:43 +08:00
|
|
|
|
}}>
|
2025-11-11 19:50:12 +08:00
|
|
|
|
{currentStep === 'form' && renderForm()}
|
|
|
|
|
|
{currentStep === 'generating' && renderGenerating()}
|
|
|
|
|
|
{currentStep === 'complete' && renderComplete()}
|
2025-10-30 11:14:43 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* SSE加载覆盖层 */}
|
|
|
|
|
|
<SSELoadingOverlay
|
|
|
|
|
|
loading={loading}
|
|
|
|
|
|
progress={progress}
|
|
|
|
|
|
message={progressMessage}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
2025-11-11 19:50:12 +08:00
|
|
|
|
}
|