use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TaskManifest { pub id: String, pub app_name: String, pub description: String, pub created_at: DateTime, pub updated_at: DateTime, pub status: ManifestStatus, pub current_status: CurrentStatus, pub sections: Vec, pub total_steps: u32, pub completed_steps: u32, pub runtime_seconds: u64, pub estimated_seconds: u64, pub terminal_output: Vec, pub processing_stats: ProcessingStats, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct CurrentStatus { pub title: String, pub current_action: Option, pub decision_point: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DecisionPoint { pub step_current: u32, pub step_total: u32, pub message: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Default)] pub enum ManifestStatus { #[default] Planning, Ready, Running, Paused, Completed, Failed, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ManifestSection { pub id: String, pub name: String, pub section_type: SectionType, pub status: SectionStatus, pub current_step: u32, pub total_steps: u32, pub global_step_start: u32, pub duration_seconds: Option, pub started_at: Option>, pub completed_at: Option>, pub items: Vec, pub item_groups: Vec, pub children: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum SectionType { DatabaseModels, SchemaDesign, Tables, Files, Pages, Tools, Schedulers, Monitors, Validation, Deployment, } impl std::fmt::Display for SectionType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::DatabaseModels => write!(f, "Database & Models"), Self::SchemaDesign => write!(f, "Database Schema Design"), Self::Tables => write!(f, "Tables"), Self::Files => write!(f, "Files"), Self::Pages => write!(f, "Pages"), Self::Tools => write!(f, "Tools"), Self::Schedulers => write!(f, "Schedulers"), Self::Monitors => write!(f, "Monitors"), Self::Validation => write!(f, "Validation"), Self::Deployment => write!(f, "Deployment"), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Default)] pub enum SectionStatus { #[default] Pending, Running, Completed, Failed, Skipped, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ManifestItem { pub id: String, pub name: String, pub item_type: ItemType, pub status: ItemStatus, pub details: Option, pub duration_seconds: Option, pub started_at: Option>, pub completed_at: Option>, pub metadata: HashMap, } /// Grouped items displayed as a single row (e.g., "email, password_hash, email_verified") #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ItemGroup { pub id: String, pub items: Vec, pub status: ItemStatus, pub duration_seconds: Option, pub started_at: Option>, pub completed_at: Option>, } impl ItemGroup { pub fn new(items: Vec) -> Self { Self { id: Uuid::new_v4().to_string(), items, status: ItemStatus::Pending, duration_seconds: None, started_at: None, completed_at: None, } } pub fn display_name(&self) -> String { self.items.join(", ") } pub fn start(&mut self) { self.status = ItemStatus::Running; self.started_at = Some(Utc::now()); } pub fn complete(&mut self) { self.status = ItemStatus::Completed; self.completed_at = Some(Utc::now()); if let Some(started) = self.started_at { self.duration_seconds = Some((Utc::now() - started).num_seconds() as u64); } } } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum ItemType { Table, Field, Index, File, Page, Tool, Scheduler, Monitor, Config, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[derive(Default)] pub enum ItemStatus { #[default] Pending, Running, Completed, Failed, Skipped, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TerminalLine { pub timestamp: DateTime, pub content: String, pub line_type: TerminalLineType, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum TerminalLineType { Info, Progress, Success, Error, Warning, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ProcessingStats { pub data_points_processed: u64, pub processing_speed: f64, pub sources_per_min: f64, pub estimated_remaining_seconds: u64, } impl TaskManifest { pub fn new(app_name: &str, description: &str) -> Self { Self { id: Uuid::new_v4().to_string(), app_name: app_name.to_string(), description: description.to_string(), created_at: Utc::now(), updated_at: Utc::now(), status: ManifestStatus::Planning, current_status: CurrentStatus { title: description.to_string(), current_action: None, decision_point: None, }, sections: Vec::new(), total_steps: 0, completed_steps: 0, runtime_seconds: 0, estimated_seconds: 0, terminal_output: Vec::new(), processing_stats: ProcessingStats::default(), } } pub fn set_current_action(&mut self, action: &str) { self.current_status.current_action = Some(action.to_string()); self.updated_at = Utc::now(); } pub fn set_decision_point(&mut self, current: u32, total: u32, message: &str) { self.current_status.decision_point = Some(DecisionPoint { step_current: current, step_total: total, message: message.to_string(), }); self.updated_at = Utc::now(); } pub fn add_section(&mut self, mut section: ManifestSection) { // Set global step start for this section section.global_step_start = self.total_steps; // Update global step starts for children let mut child_offset = self.total_steps; for child in &mut section.children { child.global_step_start = child_offset; child_offset += child.total_steps; } self.total_steps += section.total_steps; for child in §ion.children { self.total_steps += child.total_steps; } self.sections.push(section); self.updated_at = Utc::now(); } pub fn start(&mut self) { self.status = ManifestStatus::Running; self.updated_at = Utc::now(); } pub fn complete(&mut self) { self.status = ManifestStatus::Completed; self.completed_steps = self.total_steps; self.updated_at = Utc::now(); } /// Recalculate global_step_start for all sections after modifications pub fn recalculate_global_steps(&mut self) { let mut offset = 0u32; for section in &mut self.sections { section.global_step_start = offset; // Update children's global step starts let mut child_offset = offset; for child in &mut section.children { child.global_step_start = child_offset; child_offset += child.total_steps; } // Add this section's steps (including children) offset += section.total_steps; for child in §ion.children { offset += child.total_steps; } } // Recalculate total self.total_steps = offset; self.updated_at = Utc::now(); } pub fn fail(&mut self) { self.status = ManifestStatus::Failed; self.updated_at = Utc::now(); } pub fn add_terminal_line(&mut self, content: &str, line_type: TerminalLineType) { self.terminal_output.push(TerminalLine { timestamp: Utc::now(), content: content.to_string(), line_type, }); self.updated_at = Utc::now(); } pub fn update_section_status(&mut self, section_id: &str, status: SectionStatus) { for section in &mut self.sections { if section.id == section_id { section.status = status.clone(); if status == SectionStatus::Completed { section.completed_at = Some(Utc::now()); self.completed_steps += section.total_steps; } break; } for child in &mut section.children { if child.id == section_id { child.status = status.clone(); if status == SectionStatus::Completed { child.completed_at = Some(Utc::now()); self.completed_steps += child.total_steps; } break; } } } self.updated_at = Utc::now(); } pub fn update_item_status(&mut self, section_id: &str, item_id: &str, status: ItemStatus) { for section in &mut self.sections { if section.id == section_id { for item in &mut section.items { if item.id == item_id { item.status = status; if status == ItemStatus::Completed { item.completed_at = Some(Utc::now()); } return; } } } for child in &mut section.children { if child.id == section_id { for item in &mut child.items { if item.id == item_id { item.status = status; if status == ItemStatus::Completed { item.completed_at = Some(Utc::now()); } return; } } } } } self.updated_at = Utc::now(); } pub fn update_processing_stats(&mut self, stats: ProcessingStats) { self.processing_stats = stats; self.updated_at = Utc::now(); } pub fn progress_percentage(&self) -> f64 { if self.total_steps == 0 { return 0.0; } (self.completed_steps as f64 / self.total_steps as f64) * 100.0 } pub fn to_markdown(&self) -> String { let mut md = String::new(); md.push_str(&format!("# TASK.md - {}\n\n", self.app_name)); md.push_str(&format!("**Description:** {}\n\n", self.description)); md.push_str(&format!("**Status:** {:?}\n", self.status)); md.push_str(&format!( "**Progress:** {}/{} steps ({}%)\n\n", self.completed_steps, self.total_steps, self.progress_percentage() as u32 )); md.push_str("## Artifacts\n\n"); for section in &self.sections { md.push_str(&format!( "### {} - {:?}\n", section.name, section.status )); md.push_str(&format!( "- Steps: {}/{}\n", section.current_step, section.total_steps )); if !section.items.is_empty() { md.push_str("- Items:\n"); for item in §ion.items { md.push_str(&format!( " - {} ({:?}): {:?}\n", item.name, item.item_type, item.status )); } } for child in §ion.children { md.push_str(&format!( " #### {} - {:?}\n", child.name, child.status )); md.push_str(&format!( " - Steps: {}/{}\n", child.current_step, child.total_steps )); if !child.items.is_empty() { md.push_str(" - Items:\n"); for item in &child.items { md.push_str(&format!( " - {} ({:?}): {:?}\n", item.name, item.item_type, item.status )); } } } md.push('\n'); } md } pub fn to_web_json(&self) -> serde_json::Value { serde_json::json!({ "id": self.id, "app_name": self.app_name, "description": self.description, "status": { "title": self.current_status.title, "runtime_display": format_duration(self.runtime_seconds), "estimated_display": format_duration(self.estimated_seconds), "current_action": self.current_status.current_action, "decision_point": self.current_status.decision_point.as_ref().map(|dp| serde_json::json!({ "step_current": dp.step_current, "step_total": dp.step_total, "message": dp.message })) }, "progress": { "current": self.completed_steps, "total": self.total_steps, "percentage": self.progress_percentage() }, "sections": self.sections.iter().map(section_to_web_json).collect::>(), "terminal": { "lines": self.terminal_output.iter().map(|l| serde_json::json!({ "content": l.content, "type": format!("{:?}", l.line_type).to_lowercase(), "timestamp": l.timestamp.to_rfc3339() })).collect::>(), "stats": { "processed": self.processing_stats.data_points_processed, "speed": format!("{:.1} sources/min", self.processing_stats.sources_per_min), "estimated_completion": format_duration(self.processing_stats.estimated_remaining_seconds) } } }) } pub fn to_task_md(&self) -> String { let mut md = String::new(); md.push_str(&format!("# TASK.md - {}\n\n", self.app_name)); md.push_str("## STATUS\n"); md.push_str(&format!("- {}\n", self.current_status.title)); if let Some(ref action) = self.current_status.current_action { md.push_str(&format!(" - [>] {}\n", action)); } if let Some(ref dp) = self.current_status.decision_point { md.push_str(&format!(" - [ ] Decision Point (Step {}/{}) - {}\n", dp.step_current, dp.step_total, dp.message)); } md.push_str("\n## PROGRESS LOG\n"); for section in &self.sections { let checkbox = match section.status { SectionStatus::Completed => "[x]", SectionStatus::Running => "[>]", _ => "[ ]", }; let global_step = section.global_step_start + section.current_step; md.push_str(&format!("- {} {} (Step {}/{})\n", checkbox, section.name, global_step, self.total_steps)); for child in §ion.children { let child_checkbox = match child.status { SectionStatus::Completed => "[x]", SectionStatus::Running => "[>]", _ => "[ ]", }; md.push_str(&format!(" - {} {} (Step {}/{})\n", child_checkbox, child.name, child.current_step, child.total_steps)); // Render item groups first for group in &child.item_groups { let group_checkbox = match group.status { ItemStatus::Completed => "[x]", ItemStatus::Running => "[>]", _ => "[ ]", }; let duration = group.duration_seconds.map(|s| format!(" - Duration: {} min", s / 60)).unwrap_or_default(); md.push_str(&format!(" - {} {}{}\n", group_checkbox, group.display_name(), duration)); } // Then individual items for item in &child.items { let item_checkbox = match item.status { ItemStatus::Completed => "[x]", ItemStatus::Running => "[>]", _ => "[ ]", }; let duration = item.duration_seconds.map(|s| format!(" - Duration: {}s", s)).unwrap_or_default(); md.push_str(&format!(" - {} {}{}\n", item_checkbox, item.name, duration)); } } // Render section-level item groups for group in §ion.item_groups { let group_checkbox = match group.status { ItemStatus::Completed => "[x]", ItemStatus::Running => "[>]", _ => "[ ]", }; let duration = group.duration_seconds.map(|s| format!(" - Duration: {} min", s / 60)).unwrap_or_default(); md.push_str(&format!(" - {} {}{}\n", group_checkbox, group.display_name(), duration)); } for item in §ion.items { let item_checkbox = match item.status { ItemStatus::Completed => "[x]", ItemStatus::Running => "[>]", _ => "[ ]", }; md.push_str(&format!(" - {} {}\n", item_checkbox, item.name)); } } md } } impl ManifestSection { pub fn new(name: &str, section_type: SectionType) -> Self { Self { id: Uuid::new_v4().to_string(), name: name.to_string(), section_type, status: SectionStatus::Pending, current_step: 0, total_steps: 0, global_step_start: 0, duration_seconds: None, started_at: None, completed_at: None, items: Vec::new(), item_groups: Vec::new(), children: Vec::new(), } } pub fn with_steps(mut self, total: u32) -> Self { self.total_steps = total; self } pub fn add_item(&mut self, item: ManifestItem) { self.total_steps += 1; self.items.push(item); } pub fn add_item_group(&mut self, group: ItemGroup) { self.total_steps += 1; self.item_groups.push(group); } pub fn add_child(&mut self, child: ManifestSection) { self.total_steps += child.total_steps; self.children.push(child); } pub fn start(&mut self) { self.status = SectionStatus::Running; self.started_at = Some(Utc::now()); } pub fn complete(&mut self) { self.status = SectionStatus::Completed; self.completed_at = Some(Utc::now()); self.current_step = self.total_steps; if let Some(started) = self.started_at { self.duration_seconds = Some((Utc::now() - started).num_seconds() as u64); } } pub fn increment_step(&mut self) { self.current_step += 1; } } impl ManifestItem { pub fn new(name: &str, item_type: ItemType) -> Self { Self { id: Uuid::new_v4().to_string(), name: name.to_string(), item_type, status: ItemStatus::Pending, details: None, duration_seconds: None, started_at: None, completed_at: None, metadata: HashMap::new(), } } pub fn with_details(mut self, details: &str) -> Self { self.details = Some(details.to_string()); self } pub fn with_metadata(mut self, key: &str, value: serde_json::Value) -> Self { self.metadata.insert(key.to_string(), value); self } pub fn start(&mut self) { self.status = ItemStatus::Running; self.started_at = Some(Utc::now()); } pub fn complete(&mut self) { self.status = ItemStatus::Completed; self.completed_at = Some(Utc::now()); if let Some(started) = self.started_at { self.duration_seconds = Some((Utc::now() - started).num_seconds() as u64); } } } fn section_to_web_json(section: &ManifestSection) -> serde_json::Value { let checkbox = match section.status { SectionStatus::Completed => "[x]", SectionStatus::Running => "[>]", _ => "[ ]", }; // Calculate global step display (e.g., "Step 24/60") let global_current = section.global_step_start + section.current_step; serde_json::json!({ "id": section.id, "name": section.name, "checkbox": checkbox, "type": format!("{:?}", section.section_type), "status": format!("{:?}", section.status), "progress": { "current": section.current_step, "total": section.total_steps, "display": format!("Step {}/{}", section.current_step, section.total_steps), "global_current": global_current, "global_start": section.global_step_start }, "duration": section.duration_seconds.map(format_duration), "duration_seconds": section.duration_seconds, "items": section.items.iter().map(|i| { let item_checkbox = match i.status { ItemStatus::Completed => "[x]", ItemStatus::Running => "[>]", _ => "[ ]", }; serde_json::json!({ "id": i.id, "name": i.name, "checkbox": item_checkbox, "type": format!("{:?}", i.item_type), "status": format!("{:?}", i.status), "details": i.details, "duration": i.duration_seconds.map(format_duration), "duration_seconds": i.duration_seconds }) }).collect::>(), "item_groups": section.item_groups.iter().map(|g| { let group_checkbox = match g.status { ItemStatus::Completed => "[x]", ItemStatus::Running => "[>]", _ => "[ ]", }; serde_json::json!({ "id": g.id, "name": g.display_name(), "items": g.items, "checkbox": group_checkbox, "status": format!("{:?}", g.status), "duration": g.duration_seconds.map(format_duration), "duration_seconds": g.duration_seconds }) }).collect::>(), "children": section.children.iter().map(section_to_web_json).collect::>() }) } fn format_duration(seconds: u64) -> String { if seconds < 60 { format!("{} sec", seconds) } else if seconds < 3600 { format!("{} min", seconds / 60) } else { let hours = seconds / 3600; let mins = (seconds % 3600) / 60; format!("{} hr {} min", hours, mins) } } pub struct ManifestBuilder { manifest: TaskManifest, } impl ManifestBuilder { pub fn new(app_name: &str, description: &str) -> Self { Self { manifest: TaskManifest::new(app_name, description), } } pub fn with_tables(mut self, tables: Vec) -> Self { if tables.is_empty() { return self; } let mut db_section = ManifestSection::new("Database & Models", SectionType::DatabaseModels); let mut schema_section = ManifestSection::new("Database Schema Design", SectionType::SchemaDesign); // Each table becomes an item in the schema section for table in &tables { let field_count = table.fields.len(); let field_names: Vec = table.fields.iter().take(4).map(|f| f.name.clone()).collect(); let fields_preview = if field_count > 4 { format!("{}, +{} more", field_names.join(", "), field_count - 4) } else { field_names.join(", ") }; let mut item = ManifestItem::new(&table.name, ItemType::Table); item.details = Some(format!("{} fields: {}", field_count, fields_preview)); schema_section.add_item(item); } db_section.add_child(schema_section); self.manifest.add_section(db_section); self } pub fn with_files(mut self, files: Vec) -> Self { if files.is_empty() { return self; } let mut files_section = ManifestSection::new("Files", SectionType::Files); // Group files by type for better organization let html_files: Vec<_> = files.iter().filter(|f| f.filename.ends_with(".html")).collect(); let css_files: Vec<_> = files.iter().filter(|f| f.filename.ends_with(".css")).collect(); let js_files: Vec<_> = files.iter().filter(|f| f.filename.ends_with(".js")).collect(); // Create child section for HTML pages if !html_files.is_empty() { let mut pages_child = ManifestSection::new("HTML Pages", SectionType::Pages); for file in &html_files { let item = ManifestItem::new(&file.filename, ItemType::Page); pages_child.add_item(item); } files_section.add_child(pages_child); } // Create child section for styles if !css_files.is_empty() { let mut styles_child = ManifestSection::new("Stylesheets", SectionType::Files); for file in &css_files { let item = ManifestItem::new(&file.filename, ItemType::File); styles_child.add_item(item); } files_section.add_child(styles_child); } // Create child section for scripts if !js_files.is_empty() { let mut scripts_child = ManifestSection::new("Scripts", SectionType::Files); for file in &js_files { let item = ManifestItem::new(&file.filename, ItemType::File); scripts_child.add_item(item); } files_section.add_child(scripts_child); } self.manifest.add_section(files_section); self } pub fn with_pages(self, _pages: Vec) -> Self { // Pages are now included in Files section as HTML Pages child self } pub fn with_tools(mut self, tools: Vec) -> Self { if tools.is_empty() { return self; } let mut tools_section = ManifestSection::new("Tools & Automation", SectionType::Tools); let mut automation_child = ManifestSection::new("BASIC Scripts", SectionType::Tools); for tool in tools { let item = ManifestItem::new(&tool.name, ItemType::Tool) .with_details(&tool.filename); automation_child.add_item(item); } tools_section.add_child(automation_child); self.manifest.add_section(tools_section); self } pub fn with_schedulers(mut self, schedulers: Vec) -> Self { if schedulers.is_empty() { return self; } let mut sched_section = ManifestSection::new("Scheduled Tasks", SectionType::Schedulers); let mut cron_child = ManifestSection::new("Cron Jobs", SectionType::Schedulers); for scheduler in schedulers { let item = ManifestItem::new(&scheduler.name, ItemType::Scheduler) .with_details(&scheduler.schedule); cron_child.add_item(item); } sched_section.add_child(cron_child); self.manifest.add_section(sched_section); self } pub fn with_monitors(mut self, monitors: Vec) -> Self { if monitors.is_empty() { return self; } let mut mon_section = ManifestSection::new("Monitoring", SectionType::Monitors); let mut alerts_child = ManifestSection::new("Alert Rules", SectionType::Monitors); for monitor in monitors { let item = ManifestItem::new(&monitor.name, ItemType::Monitor).with_details(&monitor.target); alerts_child.add_item(item); } mon_section.add_child(alerts_child); self.manifest.add_section(mon_section); self } pub fn with_estimated_time(mut self, seconds: u64) -> Self { self.manifest.estimated_seconds = seconds; self } pub fn build(mut self) -> TaskManifest { self.manifest.status = ManifestStatus::Ready; self.manifest } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableDefinition { pub name: String, pub fields: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldDefinition { pub name: String, pub field_type: String, pub nullable: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FileDefinition { pub filename: String, pub size_estimate: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PageDefinition { pub filename: String, pub page_type: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolDefinition { pub name: String, pub filename: String, pub triggers: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SchedulerDefinition { pub name: String, pub filename: String, pub schedule: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MonitorDefinition { pub name: String, pub target: String, } pub struct ManifestData { pub tables: Vec, pub files: Vec, pub pages: Vec, pub tools: Vec, pub schedulers: Vec, pub monitors: Vec, } pub fn create_manifest_from_llm_response( app_name: &str, description: &str, data: ManifestData, ) -> TaskManifest { let estimated_time = estimate_generation_time(&data.tables, &data.files, &data.tools, &data.schedulers); ManifestBuilder::new(app_name, description) .with_tables(data.tables) .with_files(data.files) .with_pages(data.pages) .with_tools(data.tools) .with_schedulers(data.schedulers) .with_estimated_time(estimated_time) .build() } fn estimate_generation_time( tables: &[TableDefinition], files: &[FileDefinition], tools: &[ToolDefinition], schedulers: &[SchedulerDefinition], ) -> u64 { let table_time: u64 = tables.iter().map(|t| 5 + t.fields.len() as u64).sum(); let file_time: u64 = files.len() as u64 * 3; let tool_time: u64 = tools.len() as u64 * 10; let sched_time: u64 = schedulers.len() as u64 * 5; table_time + file_time + tool_time + sched_time + 30 }