diff --git a/frontend/src/pages/Inspiration.tsx b/frontend/src/pages/Inspiration.tsx index 88b91a9..43eac05 100644 --- a/frontend/src/pages/Inspiration.tsx +++ b/frontend/src/pages/Inspiration.tsx @@ -61,6 +61,7 @@ const Inspiration: React.FC = () => { // 新增:保存生成数据,用于重试 const [generationData, setGenerationData] = useState(null); + // 保存世界观生成结果,用于后续步骤 const [worldBuildingResult, setWorldBuildingResult] = useState(null); // 滚动容器引用 @@ -364,13 +365,14 @@ const Inspiration: React.FC = () => { }, onResult: (result) => { setProjectId(result.project_id); - setWorldBuildingResult(result); // 保存结果用于重试 + setWorldBuildingResult(result); // 保存世界观结果 setGenerationSteps(prev => ({ ...prev, worldBuilding: 'completed' })); }, onError: (error) => { console.error('世界观生成失败:', error); setErrorDetails(`世界观生成失败: ${error}`); setGenerationSteps(prev => ({ ...prev, worldBuilding: 'error' })); + setLoading(false); // 确保错误时解除加载状态 throw new Error(error); }, onComplete: () => { @@ -385,7 +387,7 @@ const Inspiration: React.FC = () => { const createdProjectId = worldResult.project_id; setProjectId(createdProjectId); - setWorldBuildingResult(worldResult); + setWorldBuildingResult(worldResult); // 保存世界观结果 // 步骤2: 生成角色 setGenerationSteps(prev => ({ ...prev, characters: 'processing' })); @@ -417,6 +419,7 @@ const Inspiration: React.FC = () => { console.error('角色生成失败:', error); setErrorDetails(`角色生成失败: ${error}`); setGenerationSteps(prev => ({ ...prev, characters: 'error' })); + setLoading(false); // 确保错误时解除加载状态 throw new Error(error); }, onComplete: () => { @@ -449,6 +452,7 @@ const Inspiration: React.FC = () => { console.error('大纲生成失败:', error); setErrorDetails(`大纲生成失败: ${error}`); setGenerationSteps(prev => ({ ...prev, outline: 'error' })); + setLoading(false); // 确保错误时解除加载状态 throw new Error(error); }, onComplete: () => { @@ -470,20 +474,49 @@ const Inspiration: React.FC = () => { setErrorDetails(errorMsg); message.error('创建项目失败:' + errorMsg); // 不重置步骤,保持在generating状态以显示重试按钮 - } finally { + setLoading(false); // 确保在错误时也设置loading为false + } + }; + + // 智能重试:从失败的步骤继续生成 + const handleSmartRetry = async () => { + if (!generationData) { + message.warning('缺少生成数据'); + return; + } + + setLoading(true); + setErrorDetails(''); + + try { + // 判断从哪个步骤开始重试 + if (generationSteps.worldBuilding === 'error') { + // 世界观失败,从世界观开始重新生成 + message.info('从世界观步骤开始重新生成...'); + await retryFromWorldBuilding(); + } else if (generationSteps.characters === 'error') { + // 角色失败,从角色开始生成 + message.info('从角色步骤继续生成...'); + await retryFromCharacters(); + } else if (generationSteps.outline === 'error') { + // 大纲失败,从大纲开始生成 + message.info('从大纲步骤继续生成...'); + await retryFromOutline(); + } + } catch (error: any) { + console.error('智能重试失败:', error); + message.error('重试失败:' + (error.message || '未知错误')); setLoading(false); } }; - // 重试世界观生成 - const retryWorldBuilding = async () => { + // 从世界观步骤重新开始 + const retryFromWorldBuilding = async () => { if (!generationData) return; - - setLoading(true); - setErrorDetails(''); + setGenerationSteps(prev => ({ ...prev, worldBuilding: 'processing' })); setProgressMessage('重新生成世界观...'); - + try { const worldResult = await wizardStreamApi.generateWorldBuildingStream( { @@ -505,43 +538,42 @@ const Inspiration: React.FC = () => { setProjectId(result.project_id); setWorldBuildingResult(result); setGenerationSteps(prev => ({ ...prev, worldBuilding: 'completed' })); - message.success('世界观生成成功!'); }, onError: (error) => { console.error('世界观生成失败:', error); setErrorDetails(`世界观生成失败: ${error}`); setGenerationSteps(prev => ({ ...prev, worldBuilding: 'error' })); + setLoading(false); + throw new Error(error); }, onComplete: () => { console.log('世界观重新生成完成'); } } ); - - if (worldResult?.project_id) { - setProjectId(worldResult.project_id); - setWorldBuildingResult(worldResult); + + if (!worldResult?.project_id) { + throw new Error('项目创建失败:未获取到项目ID'); } + + // 继续生成角色和大纲 + await continueFromCharacters(worldResult); } catch (error: any) { - console.error('重试世界观生成失败:', error); - setErrorDetails(error.message || '重试失败'); - } finally { - setLoading(false); + throw error; } }; - // 重试角色生成 - const retryCharacters = async () => { + // 从角色步骤继续 + const retryFromCharacters = async () => { if (!generationData || !projectId || !worldBuildingResult) { - message.warning('请先完成世界观生成'); + message.warning('缺少必要数据,无法从角色步骤继续'); + setLoading(false); return; } - - setLoading(true); - setErrorDetails(''); + setGenerationSteps(prev => ({ ...prev, characters: 'processing' })); setProgressMessage('重新生成角色...'); - + try { await wizardStreamApi.generateCharactersStream( { @@ -564,38 +596,38 @@ const Inspiration: React.FC = () => { onResult: (result) => { console.log(`成功生成${result.characters?.length || 0}个角色`); setGenerationSteps(prev => ({ ...prev, characters: 'completed' })); - message.success('角色生成成功!'); }, onError: (error) => { console.error('角色生成失败:', error); setErrorDetails(`角色生成失败: ${error}`); setGenerationSteps(prev => ({ ...prev, characters: 'error' })); + setLoading(false); + throw new Error(error); }, onComplete: () => { console.log('角色重新生成完成'); } } ); + + // 继续生成大纲 + await continueFromOutline(); } catch (error: any) { - console.error('重试角色生成失败:', error); - setErrorDetails(error.message || '重试失败'); - } finally { - setLoading(false); + throw error; } }; - // 重试大纲生成 - const retryOutline = async () => { + // 从大纲步骤继续 + const retryFromOutline = async () => { if (!generationData || !projectId) { - message.warning('请先完成世界观和角色生成'); + message.warning('缺少必要数据,无法从大纲步骤继续'); + setLoading(false); return; } - - setLoading(true); - setErrorDetails(''); + setGenerationSteps(prev => ({ ...prev, outline: 'processing' })); setProgressMessage('重新生成大纲...'); - + try { await wizardStreamApi.generateCompleteOutlineStream( { @@ -612,29 +644,127 @@ const Inspiration: React.FC = () => { onResult: () => { console.log('大纲生成完成'); setGenerationSteps(prev => ({ ...prev, outline: 'completed' })); - setProgress(100); - setProgressMessage('项目创建完成!'); - setCurrentStep('complete'); - message.success('大纲生成成功!项目创建完成!'); }, onError: (error) => { console.error('大纲生成失败:', error); setErrorDetails(`大纲生成失败: ${error}`); setGenerationSteps(prev => ({ ...prev, outline: 'error' })); + setLoading(false); + throw new Error(error); }, onComplete: () => { console.log('大纲重新生成完成'); } } ); - } catch (error: any) { - console.error('重试大纲生成失败:', error); - setErrorDetails(error.message || '重试失败'); - } finally { + + // 全部完成 + setProgress(100); + setProgressMessage('项目创建完成!'); + setCurrentStep('complete'); + message.success('项目创建成功!'); setLoading(false); + } catch (error: any) { + throw error; } }; + // 从角色步骤开始的完整流程(世界观成功后调用) + const continueFromCharacters = async (worldResult: any) => { + if (!generationData || !worldResult?.project_id) return; + + try { + // 生成角色 + setGenerationSteps(prev => ({ ...prev, characters: 'processing' })); + setProgressMessage('正在生成角色...'); + + await wizardStreamApi.generateCharactersStream( + { + project_id: worldResult.project_id, + count: 5, + world_context: { + time_period: worldResult.time_period || '', + location: worldResult.location || '', + atmosphere: worldResult.atmosphere || '', + rules: worldResult.rules || '', + }, + theme: generationData.theme, + genre: generationData.genre.join('、'), + }, + { + onProgress: (msg, prog) => { + setProgress(33 + Math.floor(prog / 3)); + setProgressMessage(msg); + }, + onResult: (result) => { + console.log(`成功生成${result.characters?.length || 0}个角色`); + setGenerationSteps(prev => ({ ...prev, characters: 'completed' })); + }, + onError: (error) => { + console.error('角色生成失败:', error); + setErrorDetails(`角色生成失败: ${error}`); + setGenerationSteps(prev => ({ ...prev, characters: 'error' })); + setLoading(false); + throw new Error(error); + }, + onComplete: () => { + console.log('角色生成完成'); + } + } + ); + + // 生成大纲 + await continueFromOutline(); + } catch (error: any) { + console.error('继续生成失败:', error); + throw error; + } + }; + + // 从大纲步骤开始的完整流程(角色成功后调用) + const continueFromOutline = async () => { + if (!generationData || !projectId) return; + + setGenerationSteps(prev => ({ ...prev, outline: 'processing' })); + setProgressMessage('正在生成大纲...'); + + await wizardStreamApi.generateCompleteOutlineStream( + { + project_id: projectId, + chapter_count: 5, + narrative_perspective: generationData.narrative_perspective, + target_words: 100000, + }, + { + onProgress: (msg, prog) => { + setProgress(66 + Math.floor(prog / 3)); + setProgressMessage(msg); + }, + onResult: () => { + console.log('大纲生成完成'); + setGenerationSteps(prev => ({ ...prev, outline: 'completed' })); + }, + onError: (error) => { + console.error('大纲生成失败:', error); + setErrorDetails(`大纲生成失败: ${error}`); + setGenerationSteps(prev => ({ ...prev, outline: 'error' })); + setLoading(false); + throw new Error(error); + }, + onComplete: () => { + console.log('大纲生成完成'); + } + } + ); + + // 全部完成 + setProgress(100); + setProgressMessage('项目创建完成!'); + setCurrentStep('complete'); + message.success('项目创建成功!'); + setLoading(false); + }; + const handleConfirmGenres = async () => { if (selectedOptions.length === 0) { message.warning('请至少选择一个类型'); @@ -847,10 +977,10 @@ const Inspiration: React.FC = () => { {[ - { key: 'worldBuilding', label: '生成世界观', step: generationSteps.worldBuilding, retry: retryWorldBuilding }, - { key: 'characters', label: '生成角色', step: generationSteps.characters, retry: retryCharacters }, - { key: 'outline', label: '生成大纲', step: generationSteps.outline, retry: retryOutline }, - ].map(({ key, label, step, retry }) => { + { 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 (
{ {label} - - - {status.icon} - - {step === 'error' && ( - - )} - + + {status.icon} +
); })} @@ -898,18 +1014,13 @@ const Inspiration: React.FC = () => { {hasError && ( )}