import React, { useState, useEffect } from 'react'; import { Modal, Form, Input, Button, Checkbox, InputNumber, Space, Alert, Divider, Tag, message, Collapse, Card, Radio } from 'antd'; import { ReloadOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; import { ssePost } from '../utils/sseClient'; import { SSEProgressModal } from './SSEProgressModal'; const { TextArea } = Input; const { Panel } = Collapse; interface Suggestion { category: string; content: string; priority: string; } interface ChapterRegenerationModalProps { visible: boolean; onCancel: () => void; onSuccess: (newContent: string, wordCount: number) => void; chapterId: string; chapterTitle: string; chapterNumber: number; suggestions?: Suggestion[]; hasAnalysis: boolean; } const ChapterRegenerationModal: React.FC = ({ visible, onCancel, onSuccess, chapterId, chapterTitle, chapterNumber, suggestions = [], hasAnalysis }) => { const [form] = Form.useForm(); const [modal, contextHolder] = Modal.useModal(); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const [status, setStatus] = useState<'idle' | 'generating' | 'success' | 'error'>('idle'); const [errorMessage, setErrorMessage] = useState(''); const [wordCount, setWordCount] = useState(0); const [selectedSuggestions, setSelectedSuggestions] = useState([]); const [modificationSource, setModificationSource] = useState<'custom' | 'analysis_suggestions' | 'mixed'>('custom'); useEffect(() => { if (visible) { // 重置状态 setStatus('idle'); setProgress(0); setErrorMessage(''); setWordCount(0); setSelectedSuggestions([]); // 如果有分析建议,默认选择混合模式 if (hasAnalysis && suggestions.length > 0) { setModificationSource('mixed'); } else { setModificationSource('custom'); } // 设置默认值 form.setFieldsValue({ modification_source: hasAnalysis && suggestions.length > 0 ? 'mixed' : 'custom', target_word_count: 3000, preserve_structure: false, preserve_character_traits: true, focus_areas: [] }); } }, [visible, hasAnalysis, suggestions.length, form]); const handleSubmit = async () => { try { const values = await form.validateFields(); // 验证至少提供一种修改指令 if (values.modification_source === 'custom' && !values.custom_instructions?.trim()) { message.error('请输入自定义修改要求'); return; } if (values.modification_source === 'analysis_suggestions' && selectedSuggestions.length === 0) { message.error('请选择至少一条分析建议'); return; } if (values.modification_source === 'mixed' && selectedSuggestions.length === 0 && !values.custom_instructions?.trim()) { message.error('请至少选择一条建议或输入自定义要求'); return; } setLoading(true); setStatus('generating'); setProgress(0); setWordCount(0); // 构建请求数据 interface RegenerationRequest { modification_source: string; custom_instructions?: string; selected_suggestion_indices: number[]; preserve_elements: { preserve_structure: boolean; preserve_dialogues: string[]; preserve_plot_points: string[]; preserve_character_traits: boolean; }; style_id?: string; target_word_count: number; focus_areas: string[]; } const requestData: RegenerationRequest = { modification_source: values.modification_source, custom_instructions: values.custom_instructions, selected_suggestion_indices: selectedSuggestions, preserve_elements: { preserve_structure: values.preserve_structure, preserve_dialogues: values.preserve_dialogues || [], preserve_plot_points: values.preserve_plot_points || [], preserve_character_traits: values.preserve_character_traits }, style_id: values.style_id, target_word_count: values.target_word_count, focus_areas: values.focus_areas || [] }; let accumulatedContent = ''; let currentWordCount = 0; // 使用SSE流式生成 await ssePost( `/api/chapters/${chapterId}/regenerate-stream`, requestData, { onProgress: (_msg: string, prog: number, _status: string, wordCount?: number) => { // 后端发送的进度消息 setProgress(prog); // 如果后端提供了word_count,使用它;否则使用累积的字数 if (wordCount !== undefined) { setWordCount(wordCount); currentWordCount = wordCount; } }, onChunk: (content: string) => { // 累积内容块 accumulatedContent += content; // 仅作为备用字数统计 currentWordCount = accumulatedContent.length; // 不再自己计算进度,完全依赖后端发送的progress消息 }, onResult: (data: { word_count?: number }) => { // 生成完成,确保使用最新的累积内容 setProgress(100); setStatus('success'); const finalWordCount = data.word_count || currentWordCount; setWordCount(finalWordCount); message.success('重新生成完成!'); // 直接调用onSuccess打开对比界面,传递最终的累积内容 setTimeout(() => { onSuccess(accumulatedContent, finalWordCount); }, 500); }, onComplete: () => { // SSE完成 }, onError: (error: string, code?: number) => { console.error('SSE Error:', error, code); setStatus('error'); setErrorMessage(error || '生成失败'); message.error('重新生成失败: ' + (error || '未知错误')); } } ); } catch (error: unknown) { console.error('提交失败:', error); setStatus('error'); const err = error as Error; setErrorMessage(err.message || '提交失败'); message.error('操作失败: ' + (err.message || '未知错误')); } finally { setLoading(false); } }; const handleSuggestionSelect = (index: number, checked: boolean) => { if (checked) { setSelectedSuggestions([...selectedSuggestions, index]); } else { setSelectedSuggestions(selectedSuggestions.filter(i => i !== index)); } }; const handleCancel = () => { if (loading) { modal.confirm({ title: '确认取消', content: '生成正在进行中,确定要取消吗?', centered: true, onOk: () => { setLoading(false); setStatus('idle'); onCancel(); } }); } else { onCancel(); } }; return ( <> {contextHolder} 取消 , ] ) } > {status === 'success' && ( } style={{ marginBottom: 16 }} /> )} {status === 'error' && ( } style={{ marginBottom: 16 }} /> )}
{/* 修改来源 */} setModificationSource(e.target.value)}> 仅自定义修改 {hasAnalysis && suggestions.length > 0 && ( <> 仅分析建议 混合模式 )} {/* 分析建议选择 */} {hasAnalysis && suggestions.length > 0 && (modificationSource === 'analysis_suggestions' || modificationSource === 'mixed') && ( {suggestions.map((suggestion, index) => ( handleSuggestionSelect(index, e.target.checked)} > {suggestion.category} {suggestion.content} ))} )} {/* 自定义修改要求 */} {(modificationSource === 'custom' || modificationSource === 'mixed') && (