import { useState, useEffect } from 'react'; import { Modal, Progress, Spin, Alert, Tabs, Card, Tag, List, Empty, Statistic, Row, Col, Button } from 'antd'; import { ThunderboltOutlined, BulbOutlined, FireOutlined, HeartOutlined, TeamOutlined, TrophyOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, EditOutlined } from '@ant-design/icons'; import type { AnalysisTask, ChapterAnalysisResponse } from '../types'; import ChapterRegenerationModal from './ChapterRegenerationModal'; import ChapterContentComparison from './ChapterContentComparison'; // 判断是否为移动设备 const isMobileDevice = () => window.innerWidth < 768; interface ChapterAnalysisProps { chapterId: string; visible: boolean; onClose: () => void; } export default function ChapterAnalysis({ chapterId, visible, onClose }: ChapterAnalysisProps) { const [task, setTask] = useState(null); const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [isMobile, setIsMobile] = useState(isMobileDevice()); const [regenerationModalVisible, setRegenerationModalVisible] = useState(false); const [comparisonModalVisible, setComparisonModalVisible] = useState(false); const [chapterInfo, setChapterInfo] = useState<{ title: string; chapter_number: number; content: string } | null>(null); const [newGeneratedContent, setNewGeneratedContent] = useState(''); const [newContentWordCount, setNewContentWordCount] = useState(0); useEffect(() => { if (visible && chapterId) { fetchAnalysisStatus(); } // 监听窗口大小变化 const handleResize = () => { setIsMobile(isMobileDevice()); }; window.addEventListener('resize', handleResize); // 清理函数:组件卸载或关闭时清除轮询 return () => { window.removeEventListener('resize', handleResize); // 清除可能存在的轮询 }; }, [visible, chapterId]); const fetchAnalysisStatus = async () => { try { setLoading(true); setError(null); // 同时获取章节信息 const chapterResponse = await fetch(`/api/chapters/${chapterId}`); if (chapterResponse.ok) { const chapterData = await chapterResponse.json(); setChapterInfo({ title: chapterData.title, chapter_number: chapterData.chapter_number, content: chapterData.content || '' }); } const response = await fetch(`/api/chapters/${chapterId}/analysis/status`); if (response.status === 404) { setTask(null); setError('该章节还未进行分析'); return; } if (!response.ok) { throw new Error('获取分析状态失败'); } const taskData: AnalysisTask = await response.json(); // 如果状态为 none(无任务),设置 task 为 null,让前端显示"开始分析"按钮 if (taskData.status === 'none' || !taskData.has_task) { setTask(null); setError(null); // 清除错误,这不是错误状态 return; } setTask(taskData); if (taskData.status === 'completed') { await fetchAnalysisResult(); } else if (taskData.status === 'running' || taskData.status === 'pending') { // 开始轮询 startPolling(); } } catch (err) { setError((err as Error).message); } finally { setLoading(false); } }; const fetchAnalysisResult = async () => { try { const response = await fetch(`/api/chapters/${chapterId}/analysis`); if (!response.ok) { throw new Error('获取分析结果失败'); } const data: ChapterAnalysisResponse = await response.json(); setAnalysis(data); } catch (err) { setError((err as Error).message); } }; const startPolling = () => { const pollInterval = setInterval(async () => { try { const response = await fetch(`/api/chapters/${chapterId}/analysis/status`); if (!response.ok) return; const taskData: AnalysisTask = await response.json(); setTask(taskData); if (taskData.status === 'completed') { clearInterval(pollInterval); await fetchAnalysisResult(); } else if (taskData.status === 'failed') { clearInterval(pollInterval); setError(taskData.error_message || '分析失败'); } } catch (err) { console.error('轮询错误:', err); } }, 2000); // 5分钟超时 setTimeout(() => clearInterval(pollInterval), 300000); }; const triggerAnalysis = async () => { try { setLoading(true); setError(null); const response = await fetch(`/api/chapters/${chapterId}/analyze`, { method: 'POST' }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || '触发分析失败'); } // 触发成功后立即关闭Modal,让父组件的状态管理接管 onClose(); } catch (err) { setError((err as Error).message); } finally { setLoading(false); } }; const renderStatusIcon = () => { if (!task) return null; switch (task.status) { case 'pending': return ; case 'running': return ; case 'completed': return ; case 'failed': return ; default: return null; } }; const renderProgress = () => { if (!task || task.status === 'completed') return null; return (
{renderStatusIcon()} {task.status === 'pending' && '等待分析...'} {task.status === 'running' && 'AI正在分析中...'} {task.status === 'failed' && '分析失败'}
{task.status === 'failed' && task.error_message && ( )}
); }; // 将分析建议转换为重新生成组件需要的格式 const convertSuggestionsForRegeneration = () => { if (!analysis?.analysis?.suggestions) return []; return analysis.analysis.suggestions.map((suggestion, index) => ({ category: '改进建议', content: suggestion, priority: index < 3 ? 'high' : 'medium' })); }; const renderAnalysisResult = () => { if (!analysis) return null; const { analysis: analysis_data, memories } = analysis; return ( , children: (
{/* 根据建议重新生成按钮 */} {analysis_data.suggestions && analysis_data.suggestions.length > 0 && (

AI已分析出 {analysis_data.suggestions.length} 条改进建议,您可以根据这些建议重新生成章节内容。

} type="info" showIcon style={{ marginBottom: 16 }} /> )} {analysis_data.analysis_report && (
                      {analysis_data.analysis_report}
                    
)} {analysis_data.suggestions && analysis_data.suggestions.length > 0 && ( 改进建议} size={isMobile ? 'small' : 'default'}> ( {index + 1}. {item} )} /> )} ) }, { key: 'hooks', label: `钩子 (${analysis_data.hooks?.length || 0})`, icon: , children: (
{analysis_data.hooks && analysis_data.hooks.length > 0 ? ( ( {hook.type} {hook.position} 强度: {hook.strength}/10
} description={hook.content} /> )} /> ) : ( )} ) }, { key: 'foreshadows', label: `伏笔 (${analysis_data.foreshadows?.length || 0})`, icon: , children: (
{analysis_data.foreshadows && analysis_data.foreshadows.length > 0 ? ( ( {foreshadow.type === 'planted' ? '已埋下' : '已回收'} 强度: {foreshadow.strength}/10 隐藏度: {foreshadow.subtlety}/10 {foreshadow.reference_chapter && ( 呼应第{foreshadow.reference_chapter}章 )}
} description={foreshadow.content} /> )} /> ) : ( )} ) }, { key: 'emotion', label: '情感曲线', icon: , children: (
{analysis_data.emotional_tone ? (

阶段:{analysis_data.plot_stage}

冲突等级:{analysis_data.conflict_level} / 10

{analysis_data.conflict_types && analysis_data.conflict_types.length > 0 && (
冲突类型: {analysis_data.conflict_types.map((type, idx) => ( {type} ))}
)}
) : ( )}
) }, { key: 'characters', label: `角色 (${analysis_data.character_states?.length || 0})`, icon: , children: (
{analysis_data.character_states && analysis_data.character_states.length > 0 ? ( (

状态变化:{char.state_before} → {char.state_after}

心理变化:{char.psychological_change}

关键事件:{char.key_event}

{char.relationship_changes && Object.keys(char.relationship_changes).length > 0 && (
关系变化: {Object.entries(char.relationship_changes).map(([name, change]) => ( 与{name}: {change} ))}
)}
)} /> ) : ( )}
) }, { key: 'memories', label: `记忆 (${memories?.length || 0})`, icon: , children: (
{memories && memories.length > 0 ? ( ( {memory.type} 重要性: {memory.importance.toFixed(1)} {memory.is_foreshadow === 1 && 已埋下伏笔} {memory.is_foreshadow === 2 && 已回收伏笔} {memory.title}
} description={

{memory.content}

{memory.tags.map((tag, idx) => ( {tag} ))}
} /> )} /> ) : ( )} ) } ]} /> ); }; return ( 关闭 , !task && !loading && ( ), task && (task.status === 'failed') && ( ), task && task.status === 'completed' && ( ) ].filter(Boolean)} > {loading && !task && (

加载中...

)} {error && ( )} {task && task.status !== 'completed' && renderProgress()} {task && task.status === 'completed' && analysis && renderAnalysisResult()} {/* 重新生成Modal */} {chapterInfo && ( setRegenerationModalVisible(false)} onSuccess={(newContent: string, wordCount: number) => { // 保存新生成的内容 setNewGeneratedContent(newContent); setNewContentWordCount(wordCount); // 关闭重新生成对话框 setRegenerationModalVisible(false); // 打开对比界面 setComparisonModalVisible(true); }} chapterId={chapterId} chapterTitle={chapterInfo.title} chapterNumber={chapterInfo.chapter_number} suggestions={convertSuggestionsForRegeneration()} hasAnalysis={true} /> )} {/* 内容对比组件 */} {chapterInfo && comparisonModalVisible && ( setComparisonModalVisible(false)} chapterId={chapterId} chapterTitle={chapterInfo.title} originalContent={chapterInfo.content} newContent={newGeneratedContent} wordCount={newContentWordCount} onApply={() => { // 应用新内容后刷新章节信息 fetchAnalysisStatus(); }} onDiscard={() => { // 放弃新内容,清空状态 setNewGeneratedContent(''); setNewContentWordCount(0); }} /> )}
); }