import React, { useState, useCallback, useRef, useEffect } from 'react'; import { Send, AlertCircle, Sparkles, Upload, FileImage, X, Video, MessageSquare, RefreshCw, Copy, CheckCircle, Clapperboard, FolderOpen, Save } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; import { veo3SceneWriterService, Veo3SceneWriterResponse, CreateSceneFileResponse } from '../../services/veo3SceneWriterService'; /** * VEO3 场景写作消息接口 */ interface SceneWriterMessage { id: string; type: 'user' | 'assistant'; content: string; timestamp: Date; status?: 'sending' | 'sent' | 'error'; attachments?: string[]; } /** * VEO3 场景写作工具页面 * 集成 ag-ui 实现对话和本地附件选择功能 */ export const Veo3SceneWriterTool: React.FC = () => { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [selectedFiles, setSelectedFiles] = useState([]); const [serviceAvailable, setServiceAvailable] = useState(null); const [copiedMessageId, setCopiedMessageId] = useState(null); const [isCreatingScene, setIsCreatingScene] = useState(false); const [createSceneSuccess, setCreateSceneSuccess] = useState(null); const inputRef = useRef(null); const messagesEndRef = useRef(null); // 检查服务可用性 useEffect(() => { const checkService = async () => { const available = await veo3SceneWriterService.checkServiceAvailability(); setServiceAvailable(available); }; checkService(); }, []); // 自动滚动到底部 const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, []); useEffect(() => { scrollToBottom(); }, [messages, scrollToBottom]); // 生成消息ID const generateMessageId = useCallback(() => { return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; }, []); // 选择文件 const handleSelectFiles = useCallback(async () => { try { const selected = await open({ multiple: true, filters: [ { name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'] }, { name: 'Videos', extensions: ['mp4', 'avi', 'mov', 'mkv', 'webm'] }, { name: 'All Files', extensions: ['*'] } ] }); if (selected && Array.isArray(selected)) { setSelectedFiles(prev => [...prev, ...selected]); } else if (selected && typeof selected === 'string') { setSelectedFiles(prev => [...prev, selected]); } } catch (error) { console.error('文件选择失败:', error); setError('文件选择失败'); } }, []); // 移除选中的文件 const removeSelectedFile = useCallback((index: number) => { setSelectedFiles(prev => prev.filter((_, i) => i !== index)); }, []); // 清空选中的文件 const clearSelectedFiles = useCallback(() => { setSelectedFiles([]); }, []); // 发送消息 const handleSendMessage = useCallback(async () => { if ((!input.trim() && selectedFiles.length === 0) || isLoading) return; const userMessage: SceneWriterMessage = { id: generateMessageId(), type: 'user', content: input.trim() || '(附件)', timestamp: new Date(), status: 'sent', attachments: selectedFiles.length > 0 ? [...selectedFiles] : undefined }; const assistantMessage: SceneWriterMessage = { id: generateMessageId(), type: 'assistant', content: '', timestamp: new Date(), status: 'sending' }; setMessages(prev => [...prev, userMessage, assistantMessage]); setInput(''); setSelectedFiles([]); setIsLoading(true); setError(null); try { let response: Veo3SceneWriterResponse; if (selectedFiles.length > 0) { response = await veo3SceneWriterService.sendMessageWithAttachments( userMessage.content, selectedFiles ); } else { response = await veo3SceneWriterService.sendMessage(userMessage.content); } if (response.success) { setMessages(prev => prev.map(msg => msg.id === assistantMessage.id ? { ...msg, content: response.content, status: 'sent' } : msg )); } else { throw new Error(response.error || '发送失败'); } } catch (err) { const errorMessage = err instanceof Error ? err.message : '未知错误'; setError(errorMessage); setMessages(prev => prev.map(msg => msg.id === assistantMessage.id ? { ...msg, content: `抱歉,处理时出现错误:${errorMessage}`, status: 'error' } : msg )); } finally { setIsLoading(false); } }, [input, selectedFiles, isLoading, generateMessageId]); // 处理键盘事件 const handleKeyPress = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }, [handleSendMessage]); // 清除会话 const handleClearConversation = useCallback(async () => { const success = await veo3SceneWriterService.clearConversation(); if (success) { setMessages([]); setError(null); } }, []); // 重置会话 const handleResetSession = useCallback(() => { veo3SceneWriterService.resetSession(); setMessages([]); setError(null); }, []); // 复制消息内容 const handleCopyMessage = useCallback(async (content: string, messageId: string) => { try { await navigator.clipboard.writeText(content); setCopiedMessageId(messageId); setTimeout(() => setCopiedMessageId(null), 2000); } catch (error) { console.error('复制失败:', error); } }, []); // 选择保存目录并创建场景文件 const handleCreateSceneFile = useCallback(async () => { if (isCreatingScene) return; try { // 选择保存目录 const selectedDir = await open({ directory: true, title: '选择场景文件保存目录' }); if (!selectedDir || typeof selectedDir !== 'string') { return; } setIsCreatingScene(true); setError(null); // 调用创建场景文件服务 const result = await veo3SceneWriterService.createSceneFile(selectedDir); if (result.success && result.file_path) { setCreateSceneSuccess(result.file_path); setTimeout(() => setCreateSceneSuccess(null), 5000); } else { setError(result.error || '创建场景文件失败'); } } catch (error) { console.error('创建场景文件失败:', error); setError(error instanceof Error ? error.message : '创建场景文件失败'); } finally { setIsCreatingScene(false); } }, [isCreatingScene]); // 如果服务不可用,显示错误状态 if (serviceAvailable === false) { return (

VEO3 场景写作服务不可用

请确保后端服务正在运行,并且已正确配置 Gemini API。

); } return (
{/* 头部 */}

VEO3 场景写作工具

专业的影视场景提示词生成助手

{/* 聊天消息区域 */}
{messages.length === 0 ? (

我是 VEO3 场景写作专家,帮助您创建专业的影视场景提示词。您可以上传参考图片或描述场景需求。

{[ '我想创建一个场景提示词', '上传场景参考图片', '生成电影级别的场景', '创建特定风格的场景' ].map((suggestion, index) => ( ))}
) : ( <> {messages.map((message) => (
{/* 消息内容 */}
{message.content}
{/* 附件显示 */} {message.attachments && message.attachments.length > 0 && (
{message.attachments.map((file, index) => (
{file.split('/').pop() || file}
))}
)} {/* 消息操作 */} {message.type === 'assistant' && message.status === 'sent' && (
)} {/* 状态指示 */} {message.status === 'sending' && (
正在处理...
)} {message.status === 'error' && (
发送失败
)}
))}
)}
{/* 错误提示 */} {error && (
{error}
)} {/* 成功提示 */} {createSceneSuccess && (
场景文件创建成功!
{createSceneSuccess}
)} {/* 输入区域 */}
{/* 选中的文件显示 */} {selectedFiles.length > 0 && (
已选择 {selectedFiles.length} 个文件
{selectedFiles.map((file, index) => (
{file.split('/').pop() || file}
))}
)} {/* 输入框 */}