//! File export action use std::path::PathBuf; use serde_json::Value; use tokio::fs; use crate::types::ExportFormat; use super::ActionError; /// Export files in specified formats pub async fn export_files( formats: &[ExportFormat], data: &Value, output_dir: Option<&str>, ) -> Result { let dir = output_dir .map(PathBuf::from) .unwrap_or_else(|| std::env::temp_dir()); // Ensure directory exists fs::create_dir_all(&dir).await .map_err(|e| ActionError::Export(format!("Failed to create directory: {}", e)))?; let mut paths = Vec::new(); let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S"); for format in formats { let filename = format!("output_{}.{}", timestamp, format.extension()); let path = dir.join(&filename); match format { ExportFormat::Json => { let content = serde_json::to_string_pretty(data) .map_err(|e| ActionError::Export(format!("JSON serialization error: {}", e)))?; fs::write(&path, content).await .map_err(|e| ActionError::Export(format!("Write error: {}", e)))?; } ExportFormat::Markdown => { let content = render_markdown(data); fs::write(&path, content).await .map_err(|e| ActionError::Export(format!("Write error: {}", e)))?; } ExportFormat::Html => { let content = render_html(data); fs::write(&path, content).await .map_err(|e| ActionError::Export(format!("Write error: {}", e)))?; } ExportFormat::Pptx => { // Will integrate with zclaw-kernel export return Err(ActionError::Export("PPTX export requires kernel integration".to_string())); } ExportFormat::Pdf => { return Err(ActionError::Export("PDF export not yet implemented".to_string())); } } paths.push(serde_json::json!({ "format": format.extension(), "path": path.to_string_lossy(), "filename": filename, })); } Ok(Value::Array(paths)) } /// Render data to markdown fn render_markdown(data: &Value) -> String { let mut md = String::new(); if let Some(title) = data.get("title").and_then(|v| v.as_str()) { md.push_str(&format!("# {}\n\n", title)); } if let Some(description) = data.get("description").and_then(|v| v.as_str()) { md.push_str(&format!("{}\n\n", description)); } if let Some(outline) = data.get("outline") { md.push_str("## 大纲\n\n"); if let Some(items) = outline.get("items").and_then(|v| v.as_array()) { for (i, item) in items.iter().enumerate() { if let Some(text) = item.get("title").and_then(|v| v.as_str()) { md.push_str(&format!("{}. {}\n", i + 1, text)); } } md.push_str("\n"); } } if let Some(scenes) = data.get("scenes").and_then(|v| v.as_array()) { md.push_str("## 场景\n\n"); for scene in scenes { if let Some(title) = scene.get("title").and_then(|v| v.as_str()) { md.push_str(&format!("### {}\n\n", title)); } if let Some(content) = scene.get("content").and_then(|v| v.as_str()) { md.push_str(&format!("{}\n\n", content)); } } } md } /// Render data to HTML fn render_html(data: &Value) -> String { let mut html = String::from(r#" Export "#); if let Some(title) = data.get("title").and_then(|v| v.as_str()) { html.push_str(&format!("

{}

", title)); } if let Some(description) = data.get("description").and_then(|v| v.as_str()) { html.push_str(&format!("

{}

", description)); } if let Some(outline) = data.get("outline") { html.push_str("

大纲

    "); if let Some(items) = outline.get("items").and_then(|v| v.as_array()) { for item in items { if let Some(text) = item.get("title").and_then(|v| v.as_str()) { html.push_str(&format!("
  1. {}
  2. ", text)); } } } html.push_str("
"); } if let Some(scenes) = data.get("scenes").and_then(|v| v.as_array()) { html.push_str("

场景

"); for scene in scenes { html.push_str("
"); if let Some(title) = scene.get("title").and_then(|v| v.as_str()) { html.push_str(&format!("

{}

", title)); } if let Some(content) = scene.get("content").and_then(|v| v.as_str()) { html.push_str(&format!("

{}

", content)); } html.push_str("
"); } } html.push_str(""); html }