diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 8c6c571..ab757f6 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -800,7 +800,8 @@ pub fn run() { commands::veo3_actor_define_commands::veo3_scene_writer_get_conversation_history, commands::veo3_actor_define_commands::veo3_scene_writer_clear_conversation, commands::veo3_actor_define_commands::veo3_scene_writer_remove_session, - commands::veo3_actor_define_commands::veo3_scene_writer_get_active_sessions + commands::veo3_actor_define_commands::veo3_scene_writer_get_active_sessions, + commands::veo3_actor_define_commands::veo3_scene_writer_create_scene_file ]) .setup(|app| { // 初始化日志系统 diff --git a/apps/desktop/src-tauri/src/presentation/commands/veo3_actor_define_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/veo3_actor_define_commands.rs index 38a6dc0..a1a22c6 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/veo3_actor_define_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/veo3_actor_define_commands.rs @@ -470,6 +470,131 @@ pub async fn veo3_scene_writer_get_active_sessions( Ok(sessions.keys().cloned().collect()) } +/// 创建场景文件请求结构 +#[derive(Debug, Deserialize)] +pub struct CreateSceneFileRequest { + pub session_id: String, + pub file_path: String, + pub file_name: Option, +} + +/// 创建场景文件响应结构 +#[derive(Debug, Serialize)] +pub struct CreateSceneFileResponse { + pub success: bool, + pub file_path: Option, + pub extracted_json: Option, + pub error: Option, +} + +/// 创建场景文件 - 从最近一次对话中提取JSON并保存 +#[tauri::command] +pub async fn veo3_scene_writer_create_scene_file( + request: CreateSceneFileRequest, + session_manager: State<'_, Veo3SceneWriterSessionManager>, +) -> Result { + // 获取最近一次助手回复 + let last_assistant_message = { + let sessions = session_manager.lock().await; + if let Some(scene_writer) = sessions.get(&request.session_id) { + let history = scene_writer.get_conversation_history(); + + // 查找最后一条助手消息 + history.iter() + .rev() + .find(|msg| msg.starts_with("助手: ")) + .map(|msg| msg.strip_prefix("助手: ").unwrap_or(msg).to_string()) + } else { + None + } + }; + + let assistant_message = match last_assistant_message { + Some(msg) => msg, + None => { + return Ok(CreateSceneFileResponse { + success: false, + file_path: None, + extracted_json: None, + error: Some("未找到助手回复消息".to_string()), + }); + } + }; + + // 使用容错JSON解析器提取JSON + let mut parser = match TolerantJsonParser::new(None) { + Ok(p) => p, + Err(e) => { + return Ok(CreateSceneFileResponse { + success: false, + file_path: None, + extracted_json: None, + error: Some(format!("创建JSON解析器失败: {}", e)), + }); + } + }; + + let json_data = match parser.parse(&assistant_message) { + Ok((data, _statistics)) => data, + Err(e) => { + return Ok(CreateSceneFileResponse { + success: false, + file_path: None, + extracted_json: None, + error: Some(format!("JSON解析失败: {}", e)), + }); + } + }; + + // 生成文件名 + let file_name = request.file_name.unwrap_or_else(|| { + let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S"); + format!("veo3_scene_{}.json", timestamp) + }); + + // 确保文件路径存在 + let full_path = Path::new(&request.file_path).join(&file_name); + + if let Some(parent_dir) = full_path.parent() { + if let Err(e) = std::fs::create_dir_all(parent_dir) { + return Ok(CreateSceneFileResponse { + success: false, + file_path: None, + extracted_json: Some(json_data), + error: Some(format!("创建目录失败: {}", e)), + }); + } + } + + // 保存JSON文件 + let json_string = match serde_json::to_string_pretty(&json_data) { + Ok(s) => s, + Err(e) => { + return Ok(CreateSceneFileResponse { + success: false, + file_path: None, + extracted_json: Some(json_data), + error: Some(format!("JSON序列化失败: {}", e)), + }); + } + }; + + match std::fs::write(&full_path, json_string) { + Ok(_) => Ok(CreateSceneFileResponse { + success: true, + file_path: Some(full_path.to_string_lossy().to_string()), + extracted_json: Some(json_data), + error: None, + }), + Err(e) => Ok(CreateSceneFileResponse { + success: false, + file_path: None, + extracted_json: Some(json_data), + error: Some(format!("文件保存失败: {}", e)), + }), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/apps/desktop/src/pages/tools/Veo3SceneWriterTool.tsx b/apps/desktop/src/pages/tools/Veo3SceneWriterTool.tsx index ac005ef..5511c68 100644 --- a/apps/desktop/src/pages/tools/Veo3SceneWriterTool.tsx +++ b/apps/desktop/src/pages/tools/Veo3SceneWriterTool.tsx @@ -12,12 +12,14 @@ import { Copy, CheckCircle, Clapperboard, - FolderOpen + FolderOpen, + Save } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; -import { - veo3SceneWriterService, - Veo3SceneWriterResponse +import { + veo3SceneWriterService, + Veo3SceneWriterResponse, + CreateSceneFileResponse } from '../../services/veo3SceneWriterService'; /** @@ -44,6 +46,8 @@ export const Veo3SceneWriterTool: React.FC = () => { 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); @@ -218,6 +222,41 @@ export const Veo3SceneWriterTool: React.FC = () => { } }, []); + // 选择保存目录并创建场景文件 + 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 ( @@ -385,6 +424,23 @@ export const Veo3SceneWriterTool: React.FC = () => { )} + {/* 成功提示 */} + {createSceneSuccess && ( +
+ +
+ 场景文件创建成功! +
{createSceneSuccess}
+
+ +
+ )} + {/* 输入区域 */}
{/* 选中的文件显示 */} @@ -455,6 +511,20 @@ export const Veo3SceneWriterTool: React.FC = () => { />
+ +