import React, { useState, useCallback, useEffect } from 'react'; import { EditorContent, useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { motion } from "framer-motion"; import { CharacterTokenExtension } from './CharacterToken'; import { ShotTitle } from './ShotTitle'; import { ReadonlyText } from './ReadonlyText'; import { Sparkles } from 'lucide-react'; import { toast } from 'sonner'; const initialContent = { type: 'doc', content: [ { type: 'shotTitle', attrs: { title: `分镜1` }, }, { type: 'paragraph', content: [ { type: 'text', text: '镜头聚焦在' }, { type: 'characterToken', attrs: { name: '"沙利"·沙利文中士', gender: '男', age: '28', avatar: 'https://i.pravatar.cc/40?u=z3' }}, { type: 'text', text: ' 愤怒的脸上,他被压制住了。他发出一声绝望的吼叫,盖过了枪声。' }, ] }, { type: 'paragraph', content: [ { type: 'shotTitle', attrs: { title: `对话` } }, { type: 'characterToken', attrs: { name: '"沙利"·沙利文中士', gender: '男', age: '28', avatar: 'https://i.pravatar.cc/40?u=z3' }}, { type: 'text', text: ':' }, { type: 'text', text: '掩护!趴下!' } ] }, { type: 'shotTitle', attrs: { title: `分镜2` }, }, { type: 'paragraph', content: [ { type: 'characterToken', attrs: { name: '李四', gender: '女', age: '26', avatar: 'https://i.pravatar.cc/40?u=l4' }}, { type: 'text', text: ' 微微低头,没有说话。' } ] } ] }; interface ShotEditorProps { roles?: any[]; onAddSegment?: () => void; onCharacterClick?: (attrs: any) => void; } declare module '@tiptap/core' { interface Commands { characterToken: { setCharacterToken: (attrs: any) => ReturnType; } } } const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick: (attrs: any) => void }, ShotEditorProps>( function ShotEditor({ onAddSegment, onCharacterClick, roles }, ref) { const [segments, setSegments] = useState(initialContent.content); const [isOptimizing, setIsOptimizing] = useState(false); const handleSmartPolish = () => { setIsOptimizing(true); setTimeout(() => { setIsOptimizing(false); }, 3000); }; const editor = useEditor({ extensions: [ StarterKit, CharacterTokenExtension.configure({ roles }), ShotTitle, ReadonlyText, ], content: { type: 'doc', content: segments }, editorProps: { attributes: { class: 'prose prose-invert max-w-none min-h-[150px] focus:outline-none' } }, immediatelyRender: false, onCreate: ({ editor }) => { editor.setOptions({ editable: true }) }, }) const addSegment = () => { if (!editor) return; // 自动编号(获取已有 shotTitle 节点数量) const doc = editor.state.doc; let shotCount = 0; doc.descendants((node) => { if (node.type.name === 'shotTitle' && node.attrs.title.includes('分镜')) { shotCount++; } }); // 不能超过4个分镜 if (shotCount >= 4) { toast.error('不能超过4个分镜', { duration: 3000, position: 'top-center', richColors: true, }); return; } editor.chain().focus('end').insertContent([ { type: 'shotTitle', attrs: { title: `分镜${shotCount + 1}` }, }, { type: 'paragraph', content: [ { type: 'text', text: '镜头描述' } ] }, { type: 'shotTitle', attrs: { title: `对话` }, }, { type: 'paragraph', content: [ { type: 'characterToken', attrs: { name: '讲话人', gender: '女', age: '26', avatar: 'https://i.pravatar.cc/40?u=l4' }}, { type: 'text', text: ':' }, { type: 'text', text: '讲话内容' } ] } ]) .focus('end') // 聚焦到文档末尾 .run(); // 调用外部传入的回调函数 onAddSegment?.(); }; // 暴露方法给父组件 React.useImperativeHandle(ref, () => ({ addSegment, onCharacterClick: (attrs: any) => { onCharacterClick?.(attrs); } })); if (!editor) { return null } return (
{/* 智能润色按钮 */} {isOptimizing ? "优化中..." : "智能优化"}
) } ); export default ShotEditor;