import { useState, useEffect } from 'react'; import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag, InputNumber, Tabs } from 'antd'; import { EditOutlined, DeleteOutlined, ThunderboltOutlined, BranchesOutlined, AppstoreAddOutlined, CheckCircleOutlined, ExclamationCircleOutlined, PlusOutlined, FileTextOutlined } from '@ant-design/icons'; import { useStore } from '../store'; import { useOutlineSync } from '../store/hooks'; import { cardStyles } from '../components/CardStyles'; import { SSEPostClient } from '../utils/sseClient'; import { SSEProgressModal } from '../components/SSEProgressModal'; import { outlineApi, chapterApi, projectApi } from '../services/api'; import type { OutlineExpansionResponse, BatchOutlineExpansionResponse, ChapterPlanItem, ApiError } from '../types'; // 角色预测数据类型 interface PredictedCharacter { name?: string; role_description: string; suggested_role_type: string; importance: string; appearance_chapter: number; key_abilities: string[]; plot_function: string; relationship_suggestions: Array<{ target_character_name: string; relationship_type: string; description?: string; }>; } interface CharacterConfirmationData { code: string; message: string; predicted_characters: PredictedCharacter[]; reason: string; chapter_range: string; } // 组织预测数据类型 interface PredictedOrganization { name?: string; organization_description: string; organization_type: string; importance: string; appearance_chapter: number; power_level: number; plot_function: string; location?: string; motto?: string; initial_members: Array<{ character_name: string; position: string; reason?: string; }>; relationship_suggestions: Array<{ target_organization: string; relationship_type: string; reason?: string; }>; } interface OrganizationConfirmationData { code: string; message: string; predicted_organizations: PredictedOrganization[]; reason: string; chapter_range: string; } // 大纲生成请求数据类型 interface OutlineGenerateRequestData { project_id: string; genre: string; theme: string; chapter_count: number; narrative_perspective: string; target_words: number; requirements?: string; mode: 'auto' | 'new' | 'continue'; story_direction?: string; plot_stage: 'development' | 'climax' | 'ending'; enable_auto_characters: boolean; require_character_confirmation: boolean; enable_auto_organizations: boolean; require_organization_confirmation: boolean; model?: string; provider?: string; confirmed_characters?: PredictedCharacter[]; confirmed_organizations?: PredictedOrganization[]; } // 跳过的大纲信息类型 interface SkippedOutlineInfo { outline_id: string; outline_title: string; reason: string; } // 场景类型 interface SceneInfo { location: string; characters: string[]; purpose: string; } const { TextArea } = Input; export default function Outline() { const { currentProject, outlines, setCurrentProject } = useStore(); const [isGenerating, setIsGenerating] = useState(false); const [editForm] = Form.useForm(); const [generateForm] = Form.useForm(); const [expansionForm] = Form.useForm(); const [modalApi, contextHolder] = Modal.useModal(); const [batchExpansionForm] = Form.useForm(); const [manualCreateForm] = Form.useForm(); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); const [isExpanding, setIsExpanding] = useState(false); // ✅ 新增:记录每个大纲的展开状态 const [outlineExpandStatus, setOutlineExpandStatus] = useState>({}); // 角色确认相关状态 const [characterConfirmData, setCharacterConfirmData] = useState(null); const [characterConfirmVisible, setCharacterConfirmVisible] = useState(false); const [pendingGenerateData, setPendingGenerateData] = useState(null); const [selectedCharacterIndices, setSelectedCharacterIndices] = useState([]); // 组织确认相关状态 const [organizationConfirmData, setOrganizationConfirmData] = useState(null); const [organizationConfirmVisible, setOrganizationConfirmVisible] = useState(false); const [selectedOrganizationIndices, setSelectedOrganizationIndices] = useState([]); // 缓存批量展开的规划数据,避免重复AI调用 const [cachedBatchExpansionResponse, setCachedBatchExpansionResponse] = useState(null); // 批量展开预览的状态 const [batchPreviewVisible, setBatchPreviewVisible] = useState(false); const [batchPreviewData, setBatchPreviewData] = useState(null); const [selectedOutlineIdx, setSelectedOutlineIdx] = useState(0); const [selectedChapterIdx, setSelectedChapterIdx] = useState(0); // SSE进度状态 const [sseProgress, setSSEProgress] = useState(0); const [sseMessage, setSSEMessage] = useState(''); const [sseModalVisible, setSSEModalVisible] = useState(false); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth <= 768); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // 使用同步 hooks const { refreshOutlines, updateOutline, deleteOutline } = useOutlineSync(); // 初始加载大纲列表 useEffect(() => { if (currentProject?.id) { refreshOutlines(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentProject?.id]); // 只依赖 ID,不依赖函数 // ✅ 新增:加载所有大纲的展开状态 useEffect(() => { const loadExpandStatus = async () => { if (outlines.length === 0) return; const statusMap: Record = {}; for (const outline of outlines) { try { const chapters = await outlineApi.getOutlineChapters(outline.id); statusMap[outline.id] = chapters.has_chapters; } catch (error) { console.error(`加载大纲 ${outline.id} 状态失败:`, error); statusMap[outline.id] = false; } } setOutlineExpandStatus(statusMap); }; loadExpandStatus(); }, [outlines]); // 当角色确认数据变化时,初始化选中状态(默认全选) useEffect(() => { if (characterConfirmData) { setSelectedCharacterIndices( characterConfirmData.predicted_characters.map((_, idx) => idx) ); } }, [characterConfirmData]); // 当组织确认数据变化时,初始化选中状态(默认全选) useEffect(() => { if (organizationConfirmData) { setSelectedOrganizationIndices( organizationConfirmData.predicted_organizations.map((_, idx) => idx) ); } }, [organizationConfirmData]); // 移除事件监听,避免无限循环 // Hook 内部已经更新了 store,不需要再次刷新 if (!currentProject) return null; // 确保大纲按 order_index 排序 const sortedOutlines = [...outlines].sort((a, b) => a.order_index - b.order_index); const handleOpenEditModal = (id: string) => { const outline = outlines.find(o => o.id === id); if (outline) { editForm.setFieldsValue(outline); modalApi.confirm({ title: '编辑大纲', width: 600, centered: true, content: (