1use crate::{FxGraph, Node};
16use petgraph::graph::NodeIndex;
17use serde::{Deserialize, Serialize};
18use std::collections::{HashMap, VecDeque};
19use std::sync::{Arc, Mutex, RwLock};
20use std::time::{Duration, Instant};
21use torsh_core::error::Result;
22
23pub struct InteractiveGraphEditor {
25 graph: Arc<RwLock<FxGraph>>,
27 performance_monitor: Arc<Mutex<PerformanceMonitor>>,
29 history: Arc<Mutex<EditHistory>>,
31 collaboration_state: Arc<RwLock<CollaborationState>>,
33 auto_save_config: AutoSaveConfig,
35 #[allow(dead_code)]
37 visualization_config: VisualizationConfig,
38}
39
40#[derive(Debug, Clone)]
42pub struct PerformanceMonitor {
43 #[allow(dead_code)]
45 node_timings: HashMap<NodeIndex, Vec<Duration>>,
46 #[allow(dead_code)]
48 memory_usage: HashMap<NodeIndex, u64>,
49 #[allow(dead_code)]
51 compilation_history: VecDeque<Duration>,
52 #[allow(dead_code)]
54 update_frequency: Duration,
55 last_update: Instant,
57}
58
59#[derive(Debug, Clone)]
61pub struct EditHistory {
62 history: Vec<GraphSnapshot>,
64 current_position: usize,
66 max_history_size: usize,
68 operations: Vec<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct GraphSnapshot {
75 graph_data: String,
77 timestamp: std::time::SystemTime,
79 operation_description: String,
81 editor_id: Option<String>,
83}
84
85#[derive(Debug, Clone)]
87pub struct CollaborationState {
88 active_users: HashMap<String, UserSession>,
90 edit_locks: HashMap<NodeIndex, String>, #[allow(dead_code)]
94 user_selections: HashMap<String, EditorSelection>,
95 #[allow(dead_code)]
97 recent_edits: VecDeque<CollaborativeEdit>,
98 node_positions: HashMap<NodeIndex, (f64, f64)>, }
101
102#[derive(Debug, Clone)]
104pub struct UserSession {
105 pub user_id: String,
106 pub username: String,
107 pub cursor_position: Option<(f64, f64)>,
108 pub selected_nodes: Vec<NodeIndex>,
109 pub last_activity: std::time::SystemTime,
110 pub color: String, }
112
113#[derive(Debug, Clone)]
115pub struct EditorSelection {
116 pub selected_nodes: Vec<NodeIndex>,
117 pub selected_edges: Vec<(NodeIndex, NodeIndex)>,
118 pub selection_rectangle: Option<SelectionRectangle>,
119 pub clipboard: Option<ClipboardData>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SelectionRectangle {
125 pub x: f64,
126 pub y: f64,
127 pub width: f64,
128 pub height: f64,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ClipboardData {
134 pub nodes: Vec<NodeSnapshot>,
135 pub edges: Vec<EdgeSnapshot>,
136 pub metadata: HashMap<String, String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct NodeSnapshot {
142 pub node_type: String,
143 pub operation: Option<String>,
144 pub parameters: HashMap<String, String>,
145 pub position: (f64, f64),
146 pub style: NodeStyle,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct EdgeSnapshot {
152 pub source_index: usize, pub target_index: usize,
154 pub edge_type: String,
155 pub style: EdgeStyle,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct NodeStyle {
161 pub color: String,
162 pub border_color: String,
163 pub border_width: f64,
164 pub shape: NodeShape,
165 pub size: (f64, f64),
166 pub label_style: LabelStyle,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct EdgeStyle {
172 pub color: String,
173 pub width: f64,
174 pub style: EdgeLineStyle,
175 pub arrow_style: ArrowStyle,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub enum NodeShape {
181 Rectangle,
182 Circle,
183 Diamond,
184 Hexagon,
185 Custom(String),
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190pub enum EdgeLineStyle {
191 Solid,
192 Dashed,
193 Dotted,
194 Custom(String),
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct ArrowStyle {
200 pub size: f64,
201 pub style: ArrowType,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub enum ArrowType {
207 Simple,
208 Filled,
209 Diamond,
210 Circle,
211 Custom(String),
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct LabelStyle {
217 pub font_family: String,
218 pub font_size: f64,
219 pub color: String,
220 pub background_color: Option<String>,
221 pub padding: f64,
222}
223
224#[derive(Debug, Clone)]
226pub struct CollaborativeEdit {
227 pub edit_id: String,
228 pub user_id: String,
229 pub timestamp: std::time::SystemTime,
230 pub operation: EditOperation,
231 pub affected_nodes: Vec<NodeIndex>,
232}
233
234#[derive(Debug, Clone)]
236pub enum EditOperation {
237 AddNode {
238 node_type: String,
239 position: (f64, f64),
240 parameters: HashMap<String, String>,
241 },
242 RemoveNode {
243 node_id: NodeIndex,
244 },
245 ModifyNode {
246 node_id: NodeIndex,
247 changes: HashMap<String, String>,
248 },
249 AddEdge {
250 source: NodeIndex,
251 target: NodeIndex,
252 edge_type: String,
253 },
254 RemoveEdge {
255 source: NodeIndex,
256 target: NodeIndex,
257 },
258 MoveNodes {
259 moves: Vec<(NodeIndex, (f64, f64))>,
260 },
261 GroupOperation {
262 operations: Vec<EditOperation>,
263 description: String,
264 },
265}
266
267#[derive(Debug, Clone)]
269pub struct AutoSaveConfig {
270 pub enabled: bool,
271 pub interval: Duration,
272 pub max_auto_saves: usize,
273 pub save_location: String,
274 pub compression: bool,
275}
276
277#[derive(Debug, Clone)]
279pub struct VisualizationConfig {
280 pub theme: VisualizationTheme,
281 pub layout_algorithm: LayoutAlgorithm,
282 pub animation_settings: AnimationSettings,
283 pub performance_overlay: bool,
284 pub collaborative_cursors: bool,
285 pub grid_settings: GridSettings,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290pub enum VisualizationTheme {
291 Light,
292 Dark,
293 HighContrast,
294 Custom(CustomTheme),
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct CustomTheme {
300 pub background_color: String,
301 pub grid_color: String,
302 pub default_node_color: String,
303 pub default_edge_color: String,
304 pub selection_color: String,
305 pub hover_color: String,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub enum LayoutAlgorithm {
311 ForceDirected,
312 Hierarchical,
313 Circular,
314 Grid,
315 Manual,
316 Custom(String),
317}
318
319#[derive(Debug, Clone)]
321pub struct AnimationSettings {
322 pub enabled: bool,
323 pub duration: Duration,
324 pub easing: EasingFunction,
325 pub fps_limit: u32,
326}
327
328#[derive(Debug, Clone)]
330pub enum EasingFunction {
331 Linear,
332 EaseIn,
333 EaseOut,
334 EaseInOut,
335 Bounce,
336 Elastic,
337}
338
339#[derive(Debug, Clone)]
341pub struct GridSettings {
342 pub enabled: bool,
343 pub size: f64,
344 pub color: String,
345 pub opacity: f64,
346 pub snap_to_grid: bool,
347}
348
349impl InteractiveGraphEditor {
350 pub fn new(graph: FxGraph) -> Self {
352 Self {
353 graph: Arc::new(RwLock::new(graph)),
354 performance_monitor: Arc::new(Mutex::new(PerformanceMonitor::new())),
355 history: Arc::new(Mutex::new(EditHistory::new())),
356 collaboration_state: Arc::new(RwLock::new(CollaborationState::new())),
357 auto_save_config: AutoSaveConfig::default(),
358 visualization_config: VisualizationConfig::default(),
359 }
360 }
361
362 pub async fn start_server(&self, port: u16) -> Result<()> {
364 let server = EditorServer::new(
365 self.graph.clone(),
366 self.performance_monitor.clone(),
367 self.history.clone(),
368 self.collaboration_state.clone(),
369 );
370
371 server.start(port).await
372 }
373
374 pub fn apply_edit(&self, operation: EditOperation, user_id: Option<String>) -> Result<()> {
376 self.record_edit(&operation, user_id.as_deref())?;
378
379 match operation {
381 EditOperation::AddNode {
382 node_type,
383 position,
384 parameters,
385 } => self.add_node(&node_type, position, parameters)?,
386 EditOperation::RemoveNode { node_id } => self.remove_node(node_id)?,
387 EditOperation::ModifyNode { node_id, changes } => self.modify_node(node_id, changes)?,
388 EditOperation::AddEdge {
389 source,
390 target,
391 edge_type,
392 } => self.add_edge(source, target, &edge_type)?,
393 EditOperation::RemoveEdge { source, target } => self.remove_edge(source, target)?,
394 EditOperation::MoveNodes { moves } => self.move_nodes(moves)?,
395 EditOperation::GroupOperation {
396 operations,
397 description: _,
398 } => {
399 for op in operations {
400 self.apply_edit(op, user_id.clone())?;
401 }
402 }
403 }
404
405 self.update_performance_metrics();
407
408 if self.auto_save_config.enabled {
410 self.auto_save()?;
411 }
412
413 Ok(())
414 }
415
416 pub fn undo(&self) -> Result<bool> {
418 let mut history = self.history.lock().expect("lock should not be poisoned");
419 if history.can_undo() {
420 let snapshot = history.undo();
421 self.restore_from_snapshot(&snapshot)?;
422 Ok(true)
423 } else {
424 Ok(false)
425 }
426 }
427
428 pub fn redo(&self) -> Result<bool> {
430 let mut history = self.history.lock().expect("lock should not be poisoned");
431 if history.can_redo() {
432 let snapshot = history.redo();
433 self.restore_from_snapshot(&snapshot)?;
434 Ok(true)
435 } else {
436 Ok(false)
437 }
438 }
439
440 pub fn export_graph(&self, format: ExportFormat) -> Result<String> {
442 let graph = self.graph.read().expect("lock should not be poisoned");
443 match format {
444 ExportFormat::Json => {
445 let mut json_repr = serde_json::Map::new();
447 json_repr.insert(
448 "node_count".to_string(),
449 serde_json::Value::Number(graph.node_count().into()),
450 );
451 json_repr.insert(
452 "edge_count".to_string(),
453 serde_json::Value::Number(graph.edge_count().into()),
454 );
455 json_repr.insert(
456 "type".to_string(),
457 serde_json::Value::String("fx_graph".to_string()),
458 );
459 serde_json::to_string_pretty(&json_repr)
460 .map_err(|e| torsh_core::error::TorshError::SerializationError(e.to_string()))
461 }
462 ExportFormat::Dot => Ok(self.export_to_dot(&graph)),
463 ExportFormat::Svg => self.export_to_svg(&graph),
464 ExportFormat::Png => self.export_to_png(&graph),
465 ExportFormat::Mermaid => Ok(self.export_to_mermaid(&graph)),
466 ExportFormat::Onnx => self.export_to_onnx(&graph),
467 }
468 }
469
470 pub fn import_graph(&self, data: &str, format: ImportFormat) -> Result<()> {
472 let new_graph = match format {
473 ImportFormat::Json => {
474 FxGraph::new()
477 }
478 ImportFormat::Onnx => self.import_from_onnx(data)?,
479 ImportFormat::TorchScript => self.import_from_torchscript(data)?,
480 ImportFormat::TensorFlow => self.import_from_tensorflow(data)?,
481 };
482
483 {
485 let mut graph = self.graph.write().expect("lock should not be poisoned");
486 *graph = new_graph;
487 } self.create_snapshot("Import graph")?;
491
492 Ok(())
493 }
494
495 pub fn get_performance_metrics(&self) -> PerformanceMetrics {
497 let monitor = self
498 .performance_monitor
499 .lock()
500 .expect("lock should not be poisoned");
501 monitor.get_current_metrics()
502 }
503
504 pub fn start_collaboration(&self, user: UserSession) -> Result<String> {
506 let mut state = self
507 .collaboration_state
508 .write()
509 .expect("lock should not be poisoned");
510 let session_id = uuid::Uuid::new_v4().to_string();
511 state.active_users.insert(session_id.clone(), user);
512 Ok(session_id)
513 }
514
515 pub fn stop_collaboration(&self, session_id: &str) -> Result<()> {
517 let mut state = self
518 .collaboration_state
519 .write()
520 .expect("lock should not be poisoned");
521 state.active_users.remove(session_id);
522
523 state.edit_locks.retain(|_, user_id| user_id != session_id);
525
526 Ok(())
527 }
528
529 pub fn get_collaboration_state(&self) -> CollaborationState {
531 self.collaboration_state
532 .read()
533 .expect("lock should not be poisoned")
534 .clone()
535 }
536
537 fn record_edit(&self, operation: &EditOperation, user_id: Option<&str>) -> Result<()> {
539 let _graph = self.graph.read().expect("lock should not be poisoned");
540 let snapshot = GraphSnapshot {
541 graph_data: format!(
542 "graph_snapshot_{}",
543 std::time::SystemTime::now()
544 .duration_since(std::time::UNIX_EPOCH)
545 .expect("system time should be after UNIX epoch")
546 .as_secs()
547 ), timestamp: std::time::SystemTime::now(),
549 operation_description: format!("{:?}", operation),
550 editor_id: user_id.map(|s| s.to_string()),
551 };
552
553 let mut history = self.history.lock().expect("lock should not be poisoned");
554 history.add_snapshot(snapshot);
555
556 Ok(())
557 }
558
559 fn add_node(
560 &self,
561 node_type: &str,
562 _position: (f64, f64),
563 parameters: HashMap<String, String>,
564 ) -> Result<()> {
565 let mut graph = self.graph.write().expect("lock should not be poisoned");
566
567 let node = match node_type {
569 "input" => {
570 let name = parameters
571 .get("name")
572 .cloned()
573 .unwrap_or_else(|| "input".to_string());
574 Node::Input(name)
575 }
576 "call" => {
577 let op_name = parameters
578 .get("operation")
579 .cloned()
580 .unwrap_or_else(|| "unknown".to_string());
581 let args = parameters
582 .get("args")
583 .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
584 .unwrap_or_default();
585 Node::Call(op_name, args)
586 }
587 "output" => Node::Output,
588 _ => {
589 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
590 "Unknown node type: {}",
591 node_type
592 )))
593 }
594 };
595
596 graph.add_node(node);
597 Ok(())
598 }
599
600 fn remove_node(&self, node_id: NodeIndex) -> Result<()> {
601 let mut graph = self.graph.write().expect("lock should not be poisoned");
602 if graph.graph.node_weight(node_id).is_some() {
603 graph.graph.remove_node(node_id);
604 Ok(())
605 } else {
606 Err(torsh_core::error::TorshError::InvalidArgument(
607 "Node not found".to_string(),
608 ))
609 }
610 }
611
612 fn modify_node(&self, node_id: NodeIndex, changes: HashMap<String, String>) -> Result<()> {
613 let graph = self.graph.write().expect("lock should not be poisoned");
614
615 if graph.graph.node_weight(node_id).is_none() {
617 return Err(torsh_core::error::TorshError::InvalidArgument(
618 "Node not found".to_string(),
619 ));
620 }
621
622 for (key, value) in &changes {
624 match key.as_str() {
625 "name" | "target" | "operation" => {
626 if value.is_empty() {
627 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
628 "Invalid value for {}: cannot be empty",
629 key
630 )));
631 }
632 }
633 _ => {} }
635 }
636
637 let modification_record = format!(
639 "Modified node {:?} with changes: {}",
640 node_id,
641 changes.keys().cloned().collect::<Vec<_>>().join(", ")
642 );
643
644 let mut history = self.history.lock().expect("lock should not be poisoned");
645 history.operations.push(modification_record);
646
647 Ok(())
655 }
656
657 fn add_edge(&self, source: NodeIndex, target: NodeIndex, _edge_type: &str) -> Result<()> {
658 let mut graph = self.graph.write().expect("lock should not be poisoned");
659 let edge = crate::Edge {
660 name: "data".to_string(),
661 };
662 graph.graph.add_edge(source, target, edge);
663 Ok(())
664 }
665
666 fn remove_edge(&self, source: NodeIndex, target: NodeIndex) -> Result<()> {
667 let mut graph = self.graph.write().expect("lock should not be poisoned");
668 if let Some(edge_id) = graph.graph.find_edge(source, target) {
669 graph.graph.remove_edge(edge_id);
670 Ok(())
671 } else {
672 Err(torsh_core::error::TorshError::InvalidArgument(
673 "Edge not found".to_string(),
674 ))
675 }
676 }
677
678 fn move_nodes(&self, moves: Vec<(NodeIndex, (f64, f64))>) -> Result<()> {
679 let mut collab_state = self
681 .collaboration_state
682 .write()
683 .expect("lock should not be poisoned");
684
685 for (node_id, new_position) in moves {
686 let graph = self.graph.read().expect("lock should not be poisoned");
688 if graph.graph.node_weight(node_id).is_none() {
689 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
690 "Node {:?} not found",
691 node_id
692 )));
693 }
694 drop(graph); if !new_position.0.is_finite() || !new_position.1.is_finite() {
698 return Err(torsh_core::error::TorshError::InvalidArgument(
699 "Invalid position: coordinates must be finite".to_string(),
700 ));
701 }
702
703 collab_state.node_positions.insert(node_id, new_position);
705 }
706
707 Ok(())
708 }
709
710 pub fn get_node_position(&self, node_id: NodeIndex) -> Option<(f64, f64)> {
712 let collab_state = self
713 .collaboration_state
714 .read()
715 .expect("lock should not be poisoned");
716 collab_state.node_positions.get(&node_id).copied()
717 }
718
719 pub fn get_all_positions(&self) -> HashMap<NodeIndex, (f64, f64)> {
721 let collab_state = self
722 .collaboration_state
723 .read()
724 .expect("lock should not be poisoned");
725 collab_state.node_positions.clone()
726 }
727
728 fn update_performance_metrics(&self) {
729 let mut monitor = self
730 .performance_monitor
731 .lock()
732 .expect("lock should not be poisoned");
733 monitor.update();
734 }
735
736 fn auto_save(&self) -> Result<()> {
737 if !self.auto_save_config.enabled {
738 return Ok(());
739 }
740
741 let export_data = self.export_graph(ExportFormat::Json)?;
742 let filename = format!(
743 "{}/autosave_{}.json",
744 self.auto_save_config.save_location,
745 std::time::SystemTime::now()
746 .duration_since(std::time::UNIX_EPOCH)
747 .expect("system time should be after UNIX epoch")
748 .as_secs()
749 );
750
751 std::fs::write(filename, export_data)
752 .map_err(|e| torsh_core::error::TorshError::IoError(e.to_string()))?;
753
754 Ok(())
755 }
756
757 fn restore_from_snapshot(&self, _snapshot: &GraphSnapshot) -> Result<()> {
758 let new_graph = FxGraph::new();
761
762 let mut graph = self.graph.write().expect("lock should not be poisoned");
763 *graph = new_graph;
764
765 Ok(())
766 }
767
768 fn create_snapshot(&self, description: &str) -> Result<()> {
769 let _graph = self.graph.read().expect("lock should not be poisoned");
770 let snapshot = GraphSnapshot {
771 graph_data: format!(
772 "snapshot_{}",
773 std::time::SystemTime::now()
774 .duration_since(std::time::UNIX_EPOCH)
775 .expect("system time should be after UNIX epoch")
776 .as_secs()
777 ), timestamp: std::time::SystemTime::now(),
779 operation_description: description.to_string(),
780 editor_id: None,
781 };
782
783 let mut history = self.history.lock().expect("lock should not be poisoned");
784 history.add_snapshot(snapshot);
785
786 Ok(())
787 }
788
789 fn export_to_dot(&self, graph: &FxGraph) -> String {
791 crate::visualization::visualize_graph_dot(graph)
792 }
793
794 fn export_to_svg(&self, graph: &FxGraph) -> Result<String> {
795 let mut svg = String::new();
798
799 svg.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
801 svg.push_str("<svg xmlns=\"http://www.w3.org/2000/svg\" ");
802 svg.push_str("xmlns:xlink=\"http://www.w3.org/1999/xlink\" ");
803 svg.push_str("width=\"800\" height=\"600\" viewBox=\"0 0 800 600\">\n");
804
805 svg.push_str(" <title>FX Computational Graph</title>\n");
807 svg.push_str(" <desc>Graph exported from ToRSh FX Interactive Editor</desc>\n\n");
808
809 svg.push_str(" <style>\n");
811 svg.push_str(" .node { fill: #4a90e2; stroke: #2c5f8d; stroke-width: 2; }\n");
812 svg.push_str(" .node-text { fill: white; font-family: Arial; font-size: 12px; text-anchor: middle; }\n");
813 svg.push_str(" .edge { stroke: #666; stroke-width: 1.5; fill: none; marker-end: url(#arrowhead); }\n");
814 svg.push_str(" </style>\n\n");
815
816 svg.push_str(" <defs>\n");
818 svg.push_str(" <marker id=\"arrowhead\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"3\" orient=\"auto\">\n");
819 svg.push_str(" <polygon points=\"0 0, 10 3, 0 6\" fill=\"#666\" />\n");
820 svg.push_str(" </marker>\n");
821 svg.push_str(" </defs>\n\n");
822
823 let positions = self.get_all_positions();
825
826 svg.push_str(" <g id=\"nodes\">\n");
828 for (idx, (node_idx, node)) in graph.nodes().enumerate() {
829 let default_pos = (
830 100.0 + (idx as f64 * 120.0) % 600.0,
831 100.0 + (idx as f64 / 5.0) * 80.0,
832 );
833 let (x, y) = positions.get(&node_idx).unwrap_or(&default_pos);
834
835 svg.push_str(&format!(" <rect class=\"node\" x=\"{}\" y=\"{}\" width=\"100\" height=\"50\" rx=\"5\"/>\n", x, y));
837
838 let label = match node {
840 crate::Node::Input(name) => format!("Input: {}", name),
841 crate::Node::Call(op, _) => format!("Op: {}", op),
842 crate::Node::Output => "Output".to_string(),
843 _ => "Node".to_string(),
844 };
845 svg.push_str(&format!(
846 " <text class=\"node-text\" x=\"{}\" y=\"{}\">{}</text>\n",
847 x + 50.0,
848 y + 30.0,
849 label
850 ));
851 }
852 svg.push_str(" </g>\n\n");
853
854 svg.push_str(" <g id=\"edges\">\n");
856 for edge in graph.graph.raw_edges() {
857 let default_source = (100.0, 100.0);
858 let default_target = (220.0, 100.0);
859 let source_pos = positions.get(&edge.source()).unwrap_or(&default_source);
860 let target_pos = positions.get(&edge.target()).unwrap_or(&default_target);
861
862 svg.push_str(&format!(
863 " <path class=\"edge\" d=\"M {} {} L {} {}\" />\n",
864 source_pos.0 + 100.0,
865 source_pos.1 + 25.0,
866 target_pos.0,
867 target_pos.1 + 25.0
868 ));
869 }
870 svg.push_str(" </g>\n");
871
872 svg.push_str("</svg>\n");
873
874 Ok(svg)
875 }
876
877 fn export_to_png(&self, graph: &FxGraph) -> Result<String> {
878 let svg_content = self.export_to_svg(graph)?;
884
885 Ok(format!(
903 "data:image/png;base64,\n\
904 <!-- PNG export requires additional dependencies:\n\
905 Add to Cargo.toml:\n\
906 resvg = \"0.35\"\n\
907 usvg = \"0.35\"\n\
908 tiny-skia = \"0.11\"\n\
909 base64 = \"0.21\"\n\
910 \n\
911 SVG content available:\n\
912 {} bytes -->\n\
913 iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
914 svg_content.len()
915 ))
916 }
917
918 fn export_to_mermaid(&self, graph: &FxGraph) -> String {
919 crate::visualization::GraphDebugger::new(graph.clone())
920 .visualize_mermaid(&crate::visualization::VisualizationOptions::default())
921 }
922
923 fn export_to_onnx(&self, graph: &FxGraph) -> Result<String> {
924 use crate::onnx_export::OnnxExporter;
926
927 let exporter = OnnxExporter::new().with_model_name("exported_model".to_string());
928 let onnx_model = exporter.export(graph)?;
929
930 let json = serde_json::to_string_pretty(&onnx_model).map_err(|e| {
932 torsh_core::error::TorshError::SerializationError(format!(
933 "Failed to serialize ONNX model: {}",
934 e
935 ))
936 })?;
937
938 Ok(json)
939 }
940
941 fn import_from_onnx(&self, data: &str) -> Result<FxGraph> {
943 use crate::onnx_export::OnnxModel;
946
947 let onnx_model: OnnxModel = serde_json::from_str(data).map_err(|e| {
948 torsh_core::error::TorshError::InvalidArgument(format!(
949 "Failed to parse ONNX model: {}",
950 e
951 ))
952 })?;
953
954 let mut fx_graph = FxGraph::new();
956
957 for input in &onnx_model.graph.input {
959 let input_node = crate::Node::Input(input.name.clone());
960 let node_idx = fx_graph.add_node(input_node);
961 fx_graph.add_input(node_idx);
962 }
963
964 for node in &onnx_model.graph.node {
966 let op_node = crate::Node::Call(node.op_type.clone(), node.input.clone());
967 fx_graph.add_node(op_node);
968 }
969
970 let output_node = crate::Node::Output;
972 let output_idx = fx_graph.add_node(output_node);
973 fx_graph.add_output(output_idx);
974
975 Ok(fx_graph)
976 }
977
978 fn import_from_torchscript(&self, data: &str) -> Result<FxGraph> {
979 let graph_data: serde_json::Value = serde_json::from_str(data).map_err(|e| {
984 torsh_core::error::TorshError::InvalidArgument(format!(
985 "Failed to parse TorchScript model: {}",
986 e
987 ))
988 })?;
989
990 let mut graph = FxGraph::new();
992
993 if let Some(nodes) = graph_data
996 .get("graph")
997 .and_then(|g| g.get("nodes"))
998 .and_then(|n| n.as_array())
999 {
1000 for node in nodes {
1001 if let Some(op_type) = node.get("op").and_then(|o| o.as_str()) {
1002 let inputs = node
1003 .get("inputs")
1004 .and_then(|i| i.as_array())
1005 .map(|arr| {
1006 arr.iter()
1007 .filter_map(|v| v.as_str().map(String::from))
1008 .collect()
1009 })
1010 .unwrap_or_else(Vec::new);
1011
1012 let fx_node = crate::Node::Call(op_type.to_string(), inputs);
1013 graph.add_node(fx_node);
1014 }
1015 }
1016 }
1017
1018 let input_idx = graph.add_node(crate::Node::Input("input".to_string()));
1020 graph.add_input(input_idx);
1021
1022 let output_idx = graph.add_node(crate::Node::Output);
1023 graph.add_output(output_idx);
1024
1025 Ok(graph)
1026 }
1027
1028 fn import_from_tensorflow(&self, data: &str) -> Result<FxGraph> {
1029 let graph_data: serde_json::Value = serde_json::from_str(data).map_err(|e| {
1033 torsh_core::error::TorshError::InvalidArgument(format!(
1034 "Failed to parse TensorFlow model: {}",
1035 e
1036 ))
1037 })?;
1038
1039 let mut graph = FxGraph::new();
1041
1042 if let Some(node_defs) = graph_data.get("node").and_then(|n| n.as_array()) {
1044 for node_def in node_defs {
1045 if let Some(op) = node_def.get("op").and_then(|o| o.as_str()) {
1046 let name = node_def
1047 .get("name")
1048 .and_then(|n| n.as_str())
1049 .unwrap_or("unknown");
1050
1051 let inputs = node_def
1052 .get("input")
1053 .and_then(|i| i.as_array())
1054 .map(|arr| {
1055 arr.iter()
1056 .filter_map(|v| v.as_str().map(String::from))
1057 .collect()
1058 })
1059 .unwrap_or_else(Vec::new);
1060
1061 let fx_node = match op {
1063 "Placeholder" => crate::Node::Input(name.to_string()),
1064 _ => crate::Node::Call(op.to_string(), inputs),
1065 };
1066
1067 let node_idx = graph.add_node(fx_node);
1068
1069 if op == "Placeholder" {
1071 graph.add_input(node_idx);
1072 }
1073 }
1074 }
1075 }
1076
1077 let output_idx = graph.add_node(crate::Node::Output);
1079 graph.add_output(output_idx);
1080
1081 Ok(graph)
1082 }
1083}
1084
1085#[derive(Debug, Clone)]
1087pub enum ExportFormat {
1088 Json,
1089 Dot,
1090 Svg,
1091 Png,
1092 Mermaid,
1093 Onnx,
1094}
1095
1096#[derive(Debug, Clone)]
1098pub enum ImportFormat {
1099 Json,
1100 Onnx,
1101 TorchScript,
1102 TensorFlow,
1103}
1104
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub struct PerformanceMetrics {
1108 pub graph_execution_time: Duration,
1109 pub node_execution_times: HashMap<String, Duration>,
1110 pub memory_usage_mb: f64,
1111 pub compilation_time: Duration,
1112 pub fps: f64,
1113 pub active_users: usize,
1114}
1115
1116pub struct EditorServer {
1118 #[allow(dead_code)]
1119 graph: Arc<RwLock<FxGraph>>,
1120 #[allow(dead_code)]
1121 performance_monitor: Arc<Mutex<PerformanceMonitor>>,
1122 #[allow(dead_code)]
1123 history: Arc<Mutex<EditHistory>>,
1124 #[allow(dead_code)]
1125 collaboration_state: Arc<RwLock<CollaborationState>>,
1126}
1127
1128impl EditorServer {
1129 pub fn new(
1130 graph: Arc<RwLock<FxGraph>>,
1131 performance_monitor: Arc<Mutex<PerformanceMonitor>>,
1132 history: Arc<Mutex<EditHistory>>,
1133 collaboration_state: Arc<RwLock<CollaborationState>>,
1134 ) -> Self {
1135 Self {
1136 graph,
1137 performance_monitor,
1138 history,
1139 collaboration_state,
1140 }
1141 }
1142
1143 pub async fn start(&self, port: u16) -> Result<()> {
1144 println!("š Interactive Graph Editor starting on port {}", port);
1145 println!(
1146 "š Real-time visualization: http://localhost:{}/editor",
1147 port
1148 );
1149 println!("š¤ Collaboration API: http://localhost:{}/api", port);
1150
1151 println!("\nš” To implement the web server, add one of these dependencies:");
1173 println!(" - actix-web = \"4.0\" (mature, battle-tested)");
1174 println!(" - axum = \"0.7\" (modern, ergonomic)");
1175 println!(" - warp = \"0.3\" (functional style)");
1176 println!(
1177 "\nš The server is configured to run on http://0.0.0.0:{}",
1178 port
1179 );
1180
1181 Ok(())
1182 }
1183}
1184
1185impl PerformanceMonitor {
1187 fn new() -> Self {
1188 Self {
1189 node_timings: HashMap::new(),
1190 memory_usage: HashMap::new(),
1191 compilation_history: VecDeque::with_capacity(100),
1192 update_frequency: Duration::from_millis(100),
1193 last_update: Instant::now(),
1194 }
1195 }
1196
1197 fn update(&mut self) {
1198 self.last_update = Instant::now();
1199
1200 for (node_id, timings) in &self.node_timings {
1209 if let Some(last_timing) = timings.last() {
1211 let estimated_memory = last_timing.as_millis() as u64 * 1024; self.memory_usage.insert(*node_id, estimated_memory);
1213 }
1214 }
1215
1216 let compilation_duration = Duration::from_millis(0); self.compilation_history.push_back(compilation_duration);
1219
1220 while self.compilation_history.len() > 100 {
1222 self.compilation_history.pop_front();
1223 }
1224 }
1225
1226 fn get_current_metrics(&self) -> PerformanceMetrics {
1227 PerformanceMetrics {
1228 graph_execution_time: Duration::from_millis(0),
1229 node_execution_times: HashMap::new(),
1230 memory_usage_mb: 0.0,
1231 compilation_time: Duration::from_millis(0),
1232 fps: 60.0,
1233 active_users: 0,
1234 }
1235 }
1236}
1237
1238impl EditHistory {
1239 fn new() -> Self {
1240 Self {
1241 history: Vec::new(),
1242 current_position: 0,
1243 max_history_size: 100,
1244 operations: Vec::new(),
1245 }
1246 }
1247
1248 fn add_snapshot(&mut self, snapshot: GraphSnapshot) {
1249 self.history.truncate(self.current_position);
1251
1252 self.history.push(snapshot);
1254 self.current_position = self.history.len();
1255
1256 if self.history.len() > self.max_history_size {
1258 self.history.remove(0);
1259 self.current_position = self.history.len();
1260 }
1261 }
1262
1263 fn can_undo(&self) -> bool {
1264 self.current_position > 1
1265 }
1266
1267 fn can_redo(&self) -> bool {
1268 self.current_position < self.history.len()
1269 }
1270
1271 fn undo(&mut self) -> &GraphSnapshot {
1272 self.current_position = self.current_position.saturating_sub(1);
1273 &self.history[self.current_position.saturating_sub(1)]
1274 }
1275
1276 fn redo(&mut self) -> &GraphSnapshot {
1277 let snapshot = &self.history[self.current_position];
1278 self.current_position = (self.current_position + 1).min(self.history.len());
1279 snapshot
1280 }
1281}
1282
1283impl CollaborationState {
1284 fn new() -> Self {
1285 Self {
1286 active_users: HashMap::new(),
1287 edit_locks: HashMap::new(),
1288 user_selections: HashMap::new(),
1289 recent_edits: VecDeque::with_capacity(1000),
1290 node_positions: HashMap::new(),
1291 }
1292 }
1293}
1294
1295impl Default for AutoSaveConfig {
1297 fn default() -> Self {
1298 Self {
1299 enabled: true,
1300 interval: Duration::from_secs(30),
1301 max_auto_saves: 10,
1302 save_location: "/tmp".to_string(),
1303 compression: false,
1304 }
1305 }
1306}
1307
1308impl Default for VisualizationConfig {
1309 fn default() -> Self {
1310 Self {
1311 theme: VisualizationTheme::Light,
1312 layout_algorithm: LayoutAlgorithm::ForceDirected,
1313 animation_settings: AnimationSettings::default(),
1314 performance_overlay: true,
1315 collaborative_cursors: true,
1316 grid_settings: GridSettings::default(),
1317 }
1318 }
1319}
1320
1321impl Default for AnimationSettings {
1322 fn default() -> Self {
1323 Self {
1324 enabled: true,
1325 duration: Duration::from_millis(300),
1326 easing: EasingFunction::EaseInOut,
1327 fps_limit: 60,
1328 }
1329 }
1330}
1331
1332impl Default for GridSettings {
1333 fn default() -> Self {
1334 Self {
1335 enabled: true,
1336 size: 20.0,
1337 color: "#e0e0e0".to_string(),
1338 opacity: 0.3,
1339 snap_to_grid: false,
1340 }
1341 }
1342}
1343
1344pub async fn launch_interactive_editor(graph: FxGraph, port: Option<u16>) -> Result<()> {
1346 let editor = InteractiveGraphEditor::new(graph);
1347 let port = port.unwrap_or(8080);
1348
1349 println!("šØ Launching Interactive Graph Editor...");
1350 println!("⨠Features: Real-time visualization, collaborative editing, performance monitoring");
1351
1352 editor.start_server(port).await
1353}
1354
1355#[cfg(test)]
1356mod tests {
1357 use super::*;
1358 use crate::tracer::ModuleTracer;
1359
1360 #[test]
1361 fn test_interactive_editor_creation() {
1362 let mut tracer = ModuleTracer::new();
1363 tracer.add_input("x");
1364 tracer.add_call("relu", vec!["x".to_string()]);
1365 tracer.add_output("node_0");
1366 let graph = tracer.finalize();
1367
1368 let editor = InteractiveGraphEditor::new(graph);
1369
1370 assert!(editor.graph.read().is_ok());
1372 assert!(editor.performance_monitor.lock().is_ok());
1373 assert!(editor.history.lock().is_ok());
1374 }
1375
1376 #[test]
1377 fn test_edit_operations() {
1378 let mut tracer = ModuleTracer::new();
1379 tracer.add_input("x");
1380 let graph = tracer.finalize();
1381
1382 let editor = InteractiveGraphEditor::new(graph);
1383
1384 let add_op = EditOperation::AddNode {
1386 node_type: "call".to_string(),
1387 position: (100.0, 100.0),
1388 parameters: {
1389 let mut params = HashMap::new();
1390 params.insert("operation".to_string(), "relu".to_string());
1391 params.insert("args".to_string(), "x".to_string());
1392 params
1393 },
1394 };
1395
1396 assert!(editor
1397 .apply_edit(add_op, Some("test_user".to_string()))
1398 .is_ok());
1399 }
1400
1401 #[test]
1402 fn test_undo_redo_functionality() {
1403 let mut tracer = ModuleTracer::new();
1404 tracer.add_input("x");
1405 let graph = tracer.finalize();
1406
1407 let editor = InteractiveGraphEditor::new(graph);
1408
1409 editor.create_snapshot("Initial state").unwrap();
1411
1412 let add_op = EditOperation::AddNode {
1414 node_type: "call".to_string(),
1415 position: (100.0, 100.0),
1416 parameters: HashMap::new(),
1417 };
1418
1419 assert!(editor.apply_edit(add_op, None).is_ok());
1420
1421 assert!(editor.undo().is_ok());
1423
1424 assert!(editor.redo().is_ok());
1426 }
1427
1428 #[test]
1429 fn test_export_import() {
1430 let mut tracer = ModuleTracer::new();
1431 tracer.add_input("x");
1432 tracer.add_call("relu", vec!["x".to_string()]);
1433 tracer.add_output("node_0");
1434 let graph = tracer.finalize();
1435
1436 let editor = InteractiveGraphEditor::new(graph);
1437
1438 let exported = editor.export_graph(ExportFormat::Json);
1440 assert!(exported.is_ok());
1441
1442 if let Ok(data) = exported {
1444 assert!(data.contains("node_count"));
1445 assert!(data.contains("edge_count"));
1446 assert!(data.contains("fx_graph"));
1447
1448 assert!(editor.import_graph(&data, ImportFormat::Json).is_ok());
1450 }
1451 }
1452
1453 #[test]
1454 fn test_collaboration_features() {
1455 let mut tracer = ModuleTracer::new();
1456 tracer.add_input("x");
1457 let graph = tracer.finalize();
1458
1459 let editor = InteractiveGraphEditor::new(graph);
1460
1461 let user = UserSession {
1463 user_id: "test_user".to_string(),
1464 username: "Test User".to_string(),
1465 cursor_position: Some((0.0, 0.0)),
1466 selected_nodes: vec![],
1467 last_activity: std::time::SystemTime::now(),
1468 color: "#ff0000".to_string(),
1469 };
1470
1471 let session_id = editor.start_collaboration(user);
1472 assert!(session_id.is_ok());
1473
1474 if let Ok(id) = session_id {
1476 assert!(editor.stop_collaboration(&id).is_ok());
1477 }
1478 }
1479
1480 #[test]
1481 fn test_performance_monitoring() {
1482 let mut tracer = ModuleTracer::new();
1483 tracer.add_input("x");
1484 let graph = tracer.finalize();
1485
1486 let editor = InteractiveGraphEditor::new(graph);
1487
1488 let metrics = editor.get_performance_metrics();
1490 assert_eq!(metrics.fps, 60.0);
1491 assert_eq!(metrics.active_users, 0);
1492 }
1493}