import { useState, useEffect } from 'react'; import { UserCog, PenTool, Sparkles, BookOpen, Loader2, Pencil, Trash2 } from 'lucide-react'; import { GlassCard, GlassButton, GlassInput, GlassSelect } from '../components/ui'; import Modal from '../components/Modal'; import useStore from '../store/useStore'; import { scriptStyles, scriptLengths } from '../utils/constants'; import { generateEpicScript } from '../services/ai'; import useTypewriterStream from '../hooks/useTypewriterStream'; /** * ScriptView 组件 * 爽文剧本视图,包含角色设定、创作需求和剧本展示 * @param {Object} props * @param {Function} props.onOpenProfile - 打开用户资料模态框回调 */ const ScriptView = ({ onOpenProfile }) => { const { registrationData, lifeEvents, scripts, selectedScriptId, addScript, updateScript, deleteScript, setSelectedScriptId, getSelectedScript, loadScripts } = useStore(); // 加载剧本列表 useEffect(() => { loadScripts().catch(() => { // 后端不可用时忽略错误 }); }, [loadScripts]); // 表单状态 const [theme, setTheme] = useState(''); const [style, setStyle] = useState(scriptStyles[0].value); const [length, setLength] = useState(scriptLengths[0].value); const [isLoading, setIsLoading] = useState(false); const [streamContent, setStreamContent] = useState(''); const scriptWriter = useTypewriterStream({ interval: 30, step: 1 }); // 编辑模态框状态 const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [editingScript, setEditingScript] = useState(null); const [editForm, setEditForm] = useState({ theme: '', style: '', length: '' }); // 删除确认状态 const [deleteConfirmId, setDeleteConfirmId] = useState(null); /** * 处理剧本生成 */ const handleGenerate = async () => { if (!theme) { alert('请输入主题'); return; } setIsLoading(true); scriptWriter.reset(); try { setStreamContent(''); const content = await generateEpicScript( { theme, style, length, character: registrationData }, lifeEvents, { onDelta: (_delta, output) => { setStreamContent(output); scriptWriter.push(output); } } ); scriptWriter.finish(content); await scriptWriter.waitForDone(); await addScript({ theme, style, length, content, character: registrationData, events: lifeEvents }); setTheme(''); setStreamContent(''); } catch (error) { scriptWriter.fail('生成失败,请稍后重试'); console.error('Failed to generate script:', error); } finally { setIsLoading(false); } }; /** * 打开编辑模态框 * @param {Object} script - 要编辑的剧本 */ const openEditModal = (script) => { setEditingScript(script); setEditForm({ theme: script.theme || '', style: script.style || scriptStyles[0].value, length: script.length || scriptLengths[0].value }); setIsEditModalOpen(true); }; /** * 关闭编辑模态框 */ const closeEditModal = () => { setIsEditModalOpen(false); setEditingScript(null); setEditForm({ theme: '', style: '', length: '' }); }; /** * 处理编辑提交 */ const handleEditSubmit = async () => { if (!editForm.theme) { alert('请输入主题'); return; } setIsLoading(true); scriptWriter.reset(); try { setStreamContent(''); const content = await generateEpicScript( { theme: editForm.theme, style: editForm.style, length: editForm.length, character: registrationData }, lifeEvents, { onDelta: (_delta, output) => { setStreamContent(output); scriptWriter.push(output); } } ); scriptWriter.finish(content); await scriptWriter.waitForDone(); await updateScript({ id: editingScript.id, theme: editForm.theme, style: editForm.style, length: editForm.length, content, character: registrationData, events: lifeEvents, regenerateContent: false }); closeEditModal(); setStreamContent(''); } catch (error) { scriptWriter.fail('生成失败,请稍后重试'); console.error('Failed to update script:', error); } finally { setIsLoading(false); } }; /** * 处理删除确认 * @param {string} id - 剧本ID */ const handleDeleteConfirm = async (id) => { try { await deleteScript(id); setDeleteConfirmId(null); } catch (error) { console.error('Failed to delete script:', error); alert('删除失败,请稍后重试'); setDeleteConfirmId(null); } }; /** * 格式化剧本内容,高亮【标题】 */ const formatScriptContent = (content) => { if (!content) return ''; return content.replace( /【([^】]+)】/g, '
暂无卷轴
)}{scriptWriter.isWaiting ? '正在理解你的创作目标' : scriptWriter.isDraining ? '正在收束最后一句' : '正在逐字生成剧本'}
{selectedScript.style}篇 · {selectedScript.length}卷
请在左侧设定需求,开启你的爽文人生
确定要删除这个剧本吗?此操作不可恢复,关联的实现路径也将被删除。