diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 05c3129..aadd13e 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -789,7 +789,8 @@ pub fn run() { commands::veo3_actor_define_commands::veo3_actor_define_get_conversation_history, commands::veo3_actor_define_commands::veo3_actor_define_clear_conversation, commands::veo3_actor_define_commands::veo3_actor_define_remove_session, - commands::veo3_actor_define_commands::veo3_actor_define_get_active_sessions + commands::veo3_actor_define_commands::veo3_actor_define_get_active_sessions, + commands::veo3_actor_define_commands::veo3_actor_define_create_actor_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 a084072..6a9b10c 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 @@ -1,8 +1,12 @@ use std::collections::HashMap; +use std::path::Path; use tokio::sync::Mutex; use tauri::State; use serde::{Deserialize, Serialize}; +use serde_json::Value; +use chrono; use veo3_scene_writer::Veo3ActorDefine; +use crate::presentation::commands::tolerant_json_commands::{ParseJsonRequest, JsonParserState}; /// VEO3 角色定义响应结构 #[derive(Debug, Serialize, Deserialize)] @@ -176,6 +180,139 @@ pub async fn veo3_actor_define_get_active_sessions( Ok(sessions.keys().cloned().collect()) } +/// 创建角色文件请求结构 +#[derive(Debug, Deserialize)] +pub struct CreateActorFileRequest { + pub session_id: String, + pub file_path: String, + pub file_name: Option, +} + +/// 创建角色文件响应结构 +#[derive(Debug, Serialize)] +pub struct CreateActorFileResponse { + pub success: bool, + pub file_path: Option, + pub extracted_json: Option, + pub error: Option, +} + +/// 创建角色文件 - 从最近一次对话中提取JSON并保存 +#[tauri::command] +pub async fn veo3_actor_define_create_actor_file( + request: CreateActorFileRequest, + session_manager: State<'_, Veo3ActorDefineSessionManager>, + json_parser_state: State<'_, JsonParserState>, +) -> Result { + // 获取最近一次助手回复 + let last_assistant_message = { + let sessions = session_manager.lock().await; + if let Some(actor_define) = sessions.get(&request.session_id) { + let history = actor_define.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(CreateActorFileResponse { + success: false, + file_path: None, + extracted_json: None, + error: Some("未找到助手回复消息".to_string()), + }); + } + }; + + // 使用容错JSON解析器提取JSON + let parse_request = ParseJsonRequest { + text: assistant_message, + config: None, + }; + + let parse_response = match crate::presentation::commands::tolerant_json_commands::parse_json_tolerant( + parse_request, + json_parser_state, + ).await { + Ok(response) => response, + Err(e) => { + return Ok(CreateActorFileResponse { + success: false, + file_path: None, + extracted_json: None, + error: Some(format!("JSON解析失败: {}", e)), + }); + } + }; + + if !parse_response.success || parse_response.data.is_none() { + return Ok(CreateActorFileResponse { + success: false, + file_path: None, + extracted_json: None, + error: Some(parse_response.error.unwrap_or_else(|| "JSON解析失败".to_string())), + }); + } + + let json_data = parse_response.data.unwrap(); + + // 生成文件名 + let file_name = request.file_name.unwrap_or_else(|| { + let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S"); + format!("veo3_actor_{}.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(CreateActorFileResponse { + 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(CreateActorFileResponse { + 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(CreateActorFileResponse { + success: true, + file_path: Some(full_path.to_string_lossy().to_string()), + extracted_json: Some(json_data), + error: None, + }), + Err(e) => Ok(CreateActorFileResponse { + 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/Veo3ActorDefineTool.tsx b/apps/desktop/src/pages/tools/Veo3ActorDefineTool.tsx index c71e0ac..511d641 100644 --- a/apps/desktop/src/pages/tools/Veo3ActorDefineTool.tsx +++ b/apps/desktop/src/pages/tools/Veo3ActorDefineTool.tsx @@ -11,12 +11,15 @@ import { RefreshCw, Download, Copy, - CheckCircle + CheckCircle, + UserPlus, + FolderOpen } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; -import { - veo3ActorDefineService, - Veo3ActorDefineResponse +import { + veo3ActorDefineService, + Veo3ActorDefineResponse, + CreateActorFileResponse } from '../../services/veo3ActorDefineService'; /** @@ -43,6 +46,8 @@ export const Veo3ActorDefineTool: React.FC = () => { const [selectedFiles, setSelectedFiles] = useState([]); const [serviceAvailable, setServiceAvailable] = useState(null); const [copiedMessageId, setCopiedMessageId] = useState(null); + const [isCreatingActor, setIsCreatingActor] = useState(false); + const [createActorSuccess, setCreateActorSuccess] = useState(null); const inputRef = useRef(null); const messagesEndRef = useRef(null); @@ -213,6 +218,41 @@ export const Veo3ActorDefineTool: React.FC = () => { } }, []); + // 选择保存目录并创建角色文件 + const handleCreateActorFile = useCallback(async () => { + if (isCreatingActor) return; + + try { + // 选择保存目录 + const selectedDir = await open({ + directory: true, + title: '选择角色文件保存目录' + }); + + if (!selectedDir || typeof selectedDir !== 'string') { + return; + } + + setIsCreatingActor(true); + setError(null); + + // 调用创建角色文件服务 + const result = await veo3ActorDefineService.createActorFile(selectedDir); + + if (result.success && result.file_path) { + setCreateActorSuccess(result.file_path); + setTimeout(() => setCreateActorSuccess(null), 5000); + } else { + setError(result.error || '创建角色文件失败'); + } + } catch (error) { + console.error('创建角色文件失败:', error); + setError(error instanceof Error ? error.message : '创建角色文件失败'); + } finally { + setIsCreatingActor(false); + } + }, [isCreatingActor]); + // 如果服务不可用,显示错误状态 if (serviceAvailable === false) { return ( @@ -380,6 +420,23 @@ export const Veo3ActorDefineTool: React.FC = () => { )} + {/* 成功提示 */} + {createActorSuccess && ( +
+ +
+ 角色文件创建成功! +
{createActorSuccess}
+
+ +
+ )} + {/* 输入区域 */}
{/* 选中的文件显示 */} @@ -450,6 +507,20 @@ export const Veo3ActorDefineTool: React.FC = () => { />
+ +