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, '
【$1】
' ); }; const selectedScript = getSelectedScript(); return (
{/* 左侧面板 */}
{/* 角色设定卡片 */}

角色设定

{registrationData.nickname || '-'}
{registrationData.zodiac || '-'}
{registrationData.mbti || '-'}
{registrationData.hobbies?.join(', ') || '-'}
{/* 创作需求表单 */}

创作需求

{isLoading ? ( <> 编撰中... ) : ( '生成爽文人生' )}
{/* 历史卷轴列表 */}
历史卷轴
{scripts.length > 0 ? ( scripts.map((script) => (
{/* 操作按钮 */}
setSelectedScriptId(script.id)}>
{script.theme}
{script.style} | {script.length} {script.date}
)) ) : (

暂无卷轴

)}
{/* 右侧剧本展示区 */}
{isLoading ? (

{theme || editForm.theme}

{scriptWriter.isWaiting ? '正在理解你的创作目标' : scriptWriter.isDraining ? '正在收束最后一句' : '正在逐字生成剧本'}

{scriptWriter.visibleText || '故事正在生成,请稍候...'} {(scriptWriter.isStreaming || scriptWriter.isDraining) && |}
) : selectedScript ? ( {/* 编辑按钮 */}

{selectedScript.theme}

{selectedScript.style}篇 · {selectedScript.length}卷

) : (

请在左侧设定需求,开启你的爽文人生

)}
{/* 编辑剧本模态框 */}
setEditForm(prev => ({ ...prev, theme: v }))} /> setEditForm(prev => ({ ...prev, style: v }))} /> setEditForm(prev => ({ ...prev, length: v }))} /> {isLoading ? '正在重新编撰...' : '重新生成剧本'}
{/* 删除确认模态框 */} setDeleteConfirmId(null)} title="确认删除" maxWidth="sm">

确定要删除这个剧本吗?此操作不可恢复,关联的实现路径也将被删除。

setDeleteConfirmId(null)} className="flex-1" > 取消 handleDeleteConfirm(deleteConfirmId)} className="flex-1 bg-red-500/10 text-red-400 border-red-400/20 hover:bg-red-500/20" > 确认删除
); }; export default ScriptView;