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.remove_node(node_id);
606 Ok(())
607 } else {
608 Err(torsh_core::error::TorshError::InvalidArgument(
609 "Node not found".to_string(),
610 ))
611 }
612 }
613
614 fn modify_node(&self, node_id: NodeIndex, changes: HashMap<String, String>) -> Result<()> {
615 let graph = self.graph.write().expect("lock should not be poisoned");
616
617 if graph.graph.node_weight(node_id).is_none() {
619 return Err(torsh_core::error::TorshError::InvalidArgument(
620 "Node not found".to_string(),
621 ));
622 }
623
624 for (key, value) in &changes {
626 match key.as_str() {
627 "name" | "target" | "operation" => {
628 if value.is_empty() {
629 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
630 "Invalid value for {}: cannot be empty",
631 key
632 )));
633 }
634 }
635 _ => {} }
637 }
638
639 let modification_record = format!(
641 "Modified node {:?} with changes: {}",
642 node_id,
643 changes.keys().cloned().collect::<Vec<_>>().join(", ")
644 );
645
646 let mut history = self.history.lock().expect("lock should not be poisoned");
647 history.operations.push(modification_record);
648
649 Ok(())
657 }
658
659 fn add_edge(&self, source: NodeIndex, target: NodeIndex, _edge_type: &str) -> Result<()> {
660 let mut graph = self.graph.write().expect("lock should not be poisoned");
661 let edge = crate::Edge {
662 name: "data".to_string(),
663 };
664 graph.graph.add_edge(source, target, edge);
665 Ok(())
666 }
667
668 fn remove_edge(&self, source: NodeIndex, target: NodeIndex) -> Result<()> {
669 let mut graph = self.graph.write().expect("lock should not be poisoned");
670 if let Some(edge_id) = graph.graph.find_edge(source, target) {
671 graph.graph.remove_edge(edge_id);
672 Ok(())
673 } else {
674 Err(torsh_core::error::TorshError::InvalidArgument(
675 "Edge not found".to_string(),
676 ))
677 }
678 }
679
680 fn move_nodes(&self, moves: Vec<(NodeIndex, (f64, f64))>) -> Result<()> {
681 let mut collab_state = self
683 .collaboration_state
684 .write()
685 .expect("lock should not be poisoned");
686
687 for (node_id, new_position) in moves {
688 let graph = self.graph.read().expect("lock should not be poisoned");
690 if graph.graph.node_weight(node_id).is_none() {
691 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
692 "Node {:?} not found",
693 node_id
694 )));
695 }
696 drop(graph); if !new_position.0.is_finite() || !new_position.1.is_finite() {
700 return Err(torsh_core::error::TorshError::InvalidArgument(
701 "Invalid position: coordinates must be finite".to_string(),
702 ));
703 }
704
705 collab_state.node_positions.insert(node_id, new_position);
707 }
708
709 Ok(())
710 }
711
712 pub fn get_node_position(&self, node_id: NodeIndex) -> Option<(f64, f64)> {
714 let collab_state = self
715 .collaboration_state
716 .read()
717 .expect("lock should not be poisoned");
718 collab_state.node_positions.get(&node_id).copied()
719 }
720
721 pub fn get_all_positions(&self) -> HashMap<NodeIndex, (f64, f64)> {
723 let collab_state = self
724 .collaboration_state
725 .read()
726 .expect("lock should not be poisoned");
727 collab_state.node_positions.clone()
728 }
729
730 fn update_performance_metrics(&self) {
731 let mut monitor = self
732 .performance_monitor
733 .lock()
734 .expect("lock should not be poisoned");
735 monitor.update();
736 }
737
738 fn auto_save(&self) -> Result<()> {
739 if !self.auto_save_config.enabled {
740 return Ok(());
741 }
742
743 let export_data = self.export_graph(ExportFormat::Json)?;
744 let filename = format!(
745 "{}/autosave_{}.json",
746 self.auto_save_config.save_location,
747 std::time::SystemTime::now()
748 .duration_since(std::time::UNIX_EPOCH)
749 .expect("system time should be after UNIX epoch")
750 .as_secs()
751 );
752
753 std::fs::write(filename, export_data)
754 .map_err(|e| torsh_core::error::TorshError::IoError(e.to_string()))?;
755
756 Ok(())
757 }
758
759 fn restore_from_snapshot(&self, _snapshot: &GraphSnapshot) -> Result<()> {
760 let new_graph = FxGraph::new();
763
764 let mut graph = self.graph.write().expect("lock should not be poisoned");
765 *graph = new_graph;
766
767 Ok(())
768 }
769
770 fn create_snapshot(&self, description: &str) -> Result<()> {
771 let _graph = self.graph.read().expect("lock should not be poisoned");
772 let snapshot = GraphSnapshot {
773 graph_data: format!(
774 "snapshot_{}",
775 std::time::SystemTime::now()
776 .duration_since(std::time::UNIX_EPOCH)
777 .expect("system time should be after UNIX epoch")
778 .as_secs()
779 ), timestamp: std::time::SystemTime::now(),
781 operation_description: description.to_string(),
782 editor_id: None,
783 };
784
785 let mut history = self.history.lock().expect("lock should not be poisoned");
786 history.add_snapshot(snapshot);
787
788 Ok(())
789 }
790
791 fn export_to_dot(&self, graph: &FxGraph) -> String {
793 crate::visualization::visualize_graph_dot(graph)
794 }
795
796 fn export_to_svg(&self, graph: &FxGraph) -> Result<String> {
797 let mut svg = String::new();
800
801 svg.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
803 svg.push_str("<svg xmlns=\"http://www.w3.org/2000/svg\" ");
804 svg.push_str("xmlns:xlink=\"http://www.w3.org/1999/xlink\" ");
805 svg.push_str("width=\"800\" height=\"600\" viewBox=\"0 0 800 600\">\n");
806
807 svg.push_str(" <title>FX Computational Graph</title>\n");
809 svg.push_str(" <desc>Graph exported from ToRSh FX Interactive Editor</desc>\n\n");
810
811 svg.push_str(" <style>\n");
813 svg.push_str(" .node { fill: #4a90e2; stroke: #2c5f8d; stroke-width: 2; }\n");
814 svg.push_str(" .node-text { fill: white; font-family: Arial; font-size: 12px; text-anchor: middle; }\n");
815 svg.push_str(" .edge { stroke: #666; stroke-width: 1.5; fill: none; marker-end: url(#arrowhead); }\n");
816 svg.push_str(" </style>\n\n");
817
818 svg.push_str(" <defs>\n");
820 svg.push_str(" <marker id=\"arrowhead\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"3\" orient=\"auto\">\n");
821 svg.push_str(" <polygon points=\"0 0, 10 3, 0 6\" fill=\"#666\" />\n");
822 svg.push_str(" </marker>\n");
823 svg.push_str(" </defs>\n\n");
824
825 let positions = self.get_all_positions();
827
828 svg.push_str(" <g id=\"nodes\">\n");
830 for (idx, (node_idx, node)) in graph.nodes().enumerate() {
831 let default_pos = (
832 100.0 + (idx as f64 * 120.0) % 600.0,
833 100.0 + (idx as f64 / 5.0) * 80.0,
834 );
835 let (x, y) = positions.get(&node_idx).unwrap_or(&default_pos);
836
837 svg.push_str(&format!(" <rect class=\"node\" x=\"{}\" y=\"{}\" width=\"100\" height=\"50\" rx=\"5\"/>\n", x, y));
839
840 let label = match node {
842 crate::Node::Input(name) => format!("Input: {}", name),
843 crate::Node::Call(op, _) => format!("Op: {}", op),
844 crate::Node::Output => "Output".to_string(),
845 _ => "Node".to_string(),
846 };
847 svg.push_str(&format!(
848 " <text class=\"node-text\" x=\"{}\" y=\"{}\">{}</text>\n",
849 x + 50.0,
850 y + 30.0,
851 label
852 ));
853 }
854 svg.push_str(" </g>\n\n");
855
856 svg.push_str(" <g id=\"edges\">\n");
858 for edge in graph.graph.raw_edges() {
859 let default_source = (100.0, 100.0);
860 let default_target = (220.0, 100.0);
861 let source_pos = positions.get(&edge.source()).unwrap_or(&default_source);
862 let target_pos = positions.get(&edge.target()).unwrap_or(&default_target);
863
864 svg.push_str(&format!(
865 " <path class=\"edge\" d=\"M {} {} L {} {}\" />\n",
866 source_pos.0 + 100.0,
867 source_pos.1 + 25.0,
868 target_pos.0,
869 target_pos.1 + 25.0
870 ));
871 }
872 svg.push_str(" </g>\n");
873
874 svg.push_str("</svg>\n");
875
876 Ok(svg)
877 }
878
879 fn export_to_png(&self, graph: &FxGraph) -> Result<String> {
880 let svg_content = self.export_to_svg(graph)?;
886
887 Ok(format!(
905 "data:image/png;base64,\n\
906 <!-- PNG export requires additional dependencies:\n\
907 Add to Cargo.toml:\n\
908 resvg = \"0.35\"\n\
909 usvg = \"0.35\"\n\
910 tiny-skia = \"0.11\"\n\
911 base64 = \"0.21\"\n\
912 \n\
913 SVG content available:\n\
914 {} bytes -->\n\
915 iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
916 svg_content.len()
917 ))
918 }
919
920 fn export_to_mermaid(&self, graph: &FxGraph) -> String {
921 crate::visualization::GraphDebugger::new(graph.clone())
922 .visualize_mermaid(&crate::visualization::VisualizationOptions::default())
923 }
924
925 fn export_to_onnx(&self, graph: &FxGraph) -> Result<String> {
926 use crate::onnx_export::OnnxExporter;
928
929 let exporter = OnnxExporter::new().with_model_name("exported_model".to_string());
930 let onnx_model = exporter.export(graph)?;
931
932 let json = serde_json::to_string_pretty(&onnx_model).map_err(|e| {
934 torsh_core::error::TorshError::SerializationError(format!(
935 "Failed to serialize ONNX model: {}",
936 e
937 ))
938 })?;
939
940 Ok(json)
941 }
942
943 fn import_from_onnx(&self, data: &str) -> Result<FxGraph> {
945 use crate::onnx_export::OnnxModel;
948
949 let onnx_model: OnnxModel = serde_json::from_str(data).map_err(|e| {
950 torsh_core::error::TorshError::InvalidArgument(format!(
951 "Failed to parse ONNX model: {}",
952 e
953 ))
954 })?;
955
956 let mut fx_graph = FxGraph::new();
958
959 for input in &onnx_model.graph.input {
961 let input_node = crate::Node::Input(input.name.clone());
962 let node_idx = fx_graph.add_node(input_node);
963 fx_graph.add_input(node_idx);
964 }
965
966 for node in &onnx_model.graph.node {
968 let op_node = crate::Node::Call(node.op_type.clone(), node.input.clone());
969 fx_graph.add_node(op_node);
970 }
971
972 let output_node = crate::Node::Output;
974 let output_idx = fx_graph.add_node(output_node);
975 fx_graph.add_output(output_idx);
976
977 Ok(fx_graph)
978 }
979
980 fn import_from_torchscript(&self, data: &str) -> Result<FxGraph> {
981 let graph_data: serde_json::Value = serde_json::from_str(data).map_err(|e| {
986 torsh_core::error::TorshError::InvalidArgument(format!(
987 "Failed to parse TorchScript model: {}",
988 e
989 ))
990 })?;
991
992 let mut graph = FxGraph::new();
994
995 if let Some(nodes) = graph_data
998 .get("graph")
999 .and_then(|g| g.get("nodes"))
1000 .and_then(|n| n.as_array())
1001 {
1002 for node in nodes {
1003 if let Some(op_type) = node.get("op").and_then(|o| o.as_str()) {
1004 let inputs = node
1005 .get("inputs")
1006 .and_then(|i| i.as_array())
1007 .map(|arr| {
1008 arr.iter()
1009 .filter_map(|v| v.as_str().map(String::from))
1010 .collect()
1011 })
1012 .unwrap_or_else(Vec::new);
1013
1014 let fx_node = crate::Node::Call(op_type.to_string(), inputs);
1015 graph.add_node(fx_node);
1016 }
1017 }
1018 }
1019
1020 let input_idx = graph.add_node(crate::Node::Input("input".to_string()));
1022 graph.add_input(input_idx);
1023
1024 let output_idx = graph.add_node(crate::Node::Output);
1025 graph.add_output(output_idx);
1026
1027 Ok(graph)
1028 }
1029
1030 fn import_from_tensorflow(&self, data: &str) -> Result<FxGraph> {
1031 let graph_data: serde_json::Value = serde_json::from_str(data).map_err(|e| {
1035 torsh_core::error::TorshError::InvalidArgument(format!(
1036 "Failed to parse TensorFlow model: {}",
1037 e
1038 ))
1039 })?;
1040
1041 let mut graph = FxGraph::new();
1043
1044 if let Some(node_defs) = graph_data.get("node").and_then(|n| n.as_array()) {
1046 for node_def in node_defs {
1047 if let Some(op) = node_def.get("op").and_then(|o| o.as_str()) {
1048 let name = node_def
1049 .get("name")
1050 .and_then(|n| n.as_str())
1051 .unwrap_or("unknown");
1052
1053 let inputs = node_def
1054 .get("input")
1055 .and_then(|i| i.as_array())
1056 .map(|arr| {
1057 arr.iter()
1058 .filter_map(|v| v.as_str().map(String::from))
1059 .collect()
1060 })
1061 .unwrap_or_else(Vec::new);
1062
1063 let fx_node = match op {
1065 "Placeholder" => crate::Node::Input(name.to_string()),
1066 _ => crate::Node::Call(op.to_string(), inputs),
1067 };
1068
1069 let node_idx = graph.add_node(fx_node);
1070
1071 if op == "Placeholder" {
1073 graph.add_input(node_idx);
1074 }
1075 }
1076 }
1077 }
1078
1079 let output_idx = graph.add_node(crate::Node::Output);
1081 graph.add_output(output_idx);
1082
1083 Ok(graph)
1084 }
1085}
1086
1087#[derive(Debug, Clone)]
1089pub enum ExportFormat {
1090 Json,
1091 Dot,
1092 Svg,
1093 Png,
1094 Mermaid,
1095 Onnx,
1096}
1097
1098#[derive(Debug, Clone)]
1100pub enum ImportFormat {
1101 Json,
1102 Onnx,
1103 TorchScript,
1104 TensorFlow,
1105}
1106
1107#[derive(Debug, Clone, Serialize, Deserialize)]
1109pub struct PerformanceMetrics {
1110 pub graph_execution_time: Duration,
1111 pub node_execution_times: HashMap<String, Duration>,
1112 pub memory_usage_mb: f64,
1113 pub compilation_time: Duration,
1114 pub fps: f64,
1115 pub active_users: usize,
1116}
1117
1118pub struct EditorServer {
1120 #[allow(dead_code)]
1121 graph: Arc<RwLock<FxGraph>>,
1122 #[allow(dead_code)]
1123 performance_monitor: Arc<Mutex<PerformanceMonitor>>,
1124 #[allow(dead_code)]
1125 history: Arc<Mutex<EditHistory>>,
1126 #[allow(dead_code)]
1127 collaboration_state: Arc<RwLock<CollaborationState>>,
1128}
1129
1130impl EditorServer {
1131 pub fn new(
1132 graph: Arc<RwLock<FxGraph>>,
1133 performance_monitor: Arc<Mutex<PerformanceMonitor>>,
1134 history: Arc<Mutex<EditHistory>>,
1135 collaboration_state: Arc<RwLock<CollaborationState>>,
1136 ) -> Self {
1137 Self {
1138 graph,
1139 performance_monitor,
1140 history,
1141 collaboration_state,
1142 }
1143 }
1144
1145 pub async fn start(&self, port: u16) -> Result<()> {
1146 println!("š Interactive Graph Editor starting on port {}", port);
1147 println!(
1148 "š Real-time visualization: http://localhost:{}/editor",
1149 port
1150 );
1151 println!("š¤ Collaboration API: http://localhost:{}/api", port);
1152
1153 println!("\nš” To implement the web server, add one of these dependencies:");
1175 println!(" - actix-web = \"4.0\" (mature, battle-tested)");
1176 println!(" - axum = \"0.7\" (modern, ergonomic)");
1177 println!(" - warp = \"0.3\" (functional style)");
1178 println!(
1179 "\nš The server is configured to run on http://0.0.0.0:{}",
1180 port
1181 );
1182
1183 Ok(())
1184 }
1185}
1186
1187impl PerformanceMonitor {
1189 fn new() -> Self {
1190 Self {
1191 node_timings: HashMap::new(),
1192 memory_usage: HashMap::new(),
1193 compilation_history: VecDeque::with_capacity(100),
1194 update_frequency: Duration::from_millis(100),
1195 last_update: Instant::now(),
1196 }
1197 }
1198
1199 fn update(&mut self) {
1200 self.last_update = Instant::now();
1201
1202 for (node_id, timings) in &self.node_timings {
1211 if let Some(last_timing) = timings.last() {
1213 let estimated_memory = last_timing.as_millis() as u64 * 1024; self.memory_usage.insert(*node_id, estimated_memory);
1215 }
1216 }
1217
1218 let compilation_duration = Duration::from_millis(0); self.compilation_history.push_back(compilation_duration);
1221
1222 while self.compilation_history.len() > 100 {
1224 self.compilation_history.pop_front();
1225 }
1226 }
1227
1228 fn get_current_metrics(&self) -> PerformanceMetrics {
1229 PerformanceMetrics {
1230 graph_execution_time: Duration::from_millis(0),
1231 node_execution_times: HashMap::new(),
1232 memory_usage_mb: 0.0,
1233 compilation_time: Duration::from_millis(0),
1234 fps: 60.0,
1235 active_users: 0,
1236 }
1237 }
1238}
1239
1240impl EditHistory {
1241 fn new() -> Self {
1242 Self {
1243 history: Vec::new(),
1244 current_position: 0,
1245 max_history_size: 100,
1246 operations: Vec::new(),
1247 }
1248 }
1249
1250 fn add_snapshot(&mut self, snapshot: GraphSnapshot) {
1251 self.history.truncate(self.current_position);
1253
1254 self.history.push(snapshot);
1256 self.current_position = self.history.len();
1257
1258 if self.history.len() > self.max_history_size {
1260 self.history.remove(0);
1261 self.current_position = self.history.len();
1262 }
1263 }
1264
1265 fn can_undo(&self) -> bool {
1266 self.current_position > 1
1267 }
1268
1269 fn can_redo(&self) -> bool {
1270 self.current_position < self.history.len()
1271 }
1272
1273 fn undo(&mut self) -> &GraphSnapshot {
1274 self.current_position = self.current_position.saturating_sub(1);
1275 &self.history[self.current_position.saturating_sub(1)]
1276 }
1277
1278 fn redo(&mut self) -> &GraphSnapshot {
1279 let snapshot = &self.history[self.current_position];
1280 self.current_position = (self.current_position + 1).min(self.history.len());
1281 snapshot
1282 }
1283}
1284
1285impl CollaborationState {
1286 fn new() -> Self {
1287 Self {
1288 active_users: HashMap::new(),
1289 edit_locks: HashMap::new(),
1290 user_selections: HashMap::new(),
1291 recent_edits: VecDeque::with_capacity(1000),
1292 node_positions: HashMap::new(),
1293 }
1294 }
1295}
1296
1297impl Default for AutoSaveConfig {
1299 fn default() -> Self {
1300 Self {
1301 enabled: true,
1302 interval: Duration::from_secs(30),
1303 max_auto_saves: 10,
1304 save_location: "/tmp".to_string(),
1305 compression: false,
1306 }
1307 }
1308}
1309
1310impl Default for VisualizationConfig {
1311 fn default() -> Self {
1312 Self {
1313 theme: VisualizationTheme::Light,
1314 layout_algorithm: LayoutAlgorithm::ForceDirected,
1315 animation_settings: AnimationSettings::default(),
1316 performance_overlay: true,
1317 collaborative_cursors: true,
1318 grid_settings: GridSettings::default(),
1319 }
1320 }
1321}
1322
1323impl Default for AnimationSettings {
1324 fn default() -> Self {
1325 Self {
1326 enabled: true,
1327 duration: Duration::from_millis(300),
1328 easing: EasingFunction::EaseInOut,
1329 fps_limit: 60,
1330 }
1331 }
1332}
1333
1334impl Default for GridSettings {
1335 fn default() -> Self {
1336 Self {
1337 enabled: true,
1338 size: 20.0,
1339 color: "#e0e0e0".to_string(),
1340 opacity: 0.3,
1341 snap_to_grid: false,
1342 }
1343 }
1344}
1345
1346pub async fn launch_interactive_editor(graph: FxGraph, port: Option<u16>) -> Result<()> {
1348 let editor = InteractiveGraphEditor::new(graph);
1349 let port = port.unwrap_or(8080);
1350
1351 println!("šØ Launching Interactive Graph Editor...");
1352 println!("⨠Features: Real-time visualization, collaborative editing, performance monitoring");
1353
1354 editor.start_server(port).await
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359 use super::*;
1360 use crate::tracer::ModuleTracer;
1361
1362 #[test]
1363 fn test_interactive_editor_creation() {
1364 let mut tracer = ModuleTracer::new();
1365 tracer.add_input("x");
1366 tracer.add_call("relu", vec!["x".to_string()]);
1367 tracer.add_output("node_0");
1368 let graph = tracer.finalize();
1369
1370 let editor = InteractiveGraphEditor::new(graph);
1371
1372 assert!(editor.graph.read().is_ok());
1374 assert!(editor.performance_monitor.lock().is_ok());
1375 assert!(editor.history.lock().is_ok());
1376 }
1377
1378 #[test]
1379 fn test_edit_operations() {
1380 let mut tracer = ModuleTracer::new();
1381 tracer.add_input("x");
1382 let graph = tracer.finalize();
1383
1384 let editor = InteractiveGraphEditor::new(graph);
1385
1386 let add_op = EditOperation::AddNode {
1388 node_type: "call".to_string(),
1389 position: (100.0, 100.0),
1390 parameters: {
1391 let mut params = HashMap::new();
1392 params.insert("operation".to_string(), "relu".to_string());
1393 params.insert("args".to_string(), "x".to_string());
1394 params
1395 },
1396 };
1397
1398 assert!(editor
1399 .apply_edit(add_op, Some("test_user".to_string()))
1400 .is_ok());
1401 }
1402
1403 #[test]
1404 fn test_undo_redo_functionality() {
1405 let mut tracer = ModuleTracer::new();
1406 tracer.add_input("x");
1407 let graph = tracer.finalize();
1408
1409 let editor = InteractiveGraphEditor::new(graph);
1410
1411 editor.create_snapshot("Initial state").unwrap();
1413
1414 let add_op = EditOperation::AddNode {
1416 node_type: "call".to_string(),
1417 position: (100.0, 100.0),
1418 parameters: HashMap::new(),
1419 };
1420
1421 assert!(editor.apply_edit(add_op, None).is_ok());
1422
1423 assert!(editor.undo().is_ok());
1425
1426 assert!(editor.redo().is_ok());
1428 }
1429
1430 #[test]
1431 fn test_export_import() {
1432 let mut tracer = ModuleTracer::new();
1433 tracer.add_input("x");
1434 tracer.add_call("relu", vec!["x".to_string()]);
1435 tracer.add_output("node_0");
1436 let graph = tracer.finalize();
1437
1438 let editor = InteractiveGraphEditor::new(graph);
1439
1440 let exported = editor.export_graph(ExportFormat::Json);
1442 assert!(exported.is_ok());
1443
1444 if let Ok(data) = exported {
1446 assert!(data.contains("node_count"));
1447 assert!(data.contains("edge_count"));
1448 assert!(data.contains("fx_graph"));
1449
1450 assert!(editor.import_graph(&data, ImportFormat::Json).is_ok());
1452 }
1453 }
1454
1455 #[test]
1456 fn test_collaboration_features() {
1457 let mut tracer = ModuleTracer::new();
1458 tracer.add_input("x");
1459 let graph = tracer.finalize();
1460
1461 let editor = InteractiveGraphEditor::new(graph);
1462
1463 let user = UserSession {
1465 user_id: "test_user".to_string(),
1466 username: "Test User".to_string(),
1467 cursor_position: Some((0.0, 0.0)),
1468 selected_nodes: vec![],
1469 last_activity: std::time::SystemTime::now(),
1470 color: "#ff0000".to_string(),
1471 };
1472
1473 let session_id = editor.start_collaboration(user);
1474 assert!(session_id.is_ok());
1475
1476 if let Ok(id) = session_id {
1478 assert!(editor.stop_collaboration(&id).is_ok());
1479 }
1480 }
1481
1482 #[test]
1483 fn test_performance_monitoring() {
1484 let mut tracer = ModuleTracer::new();
1485 tracer.add_input("x");
1486 let graph = tracer.finalize();
1487
1488 let editor = InteractiveGraphEditor::new(graph);
1489
1490 let metrics = editor.get_performance_metrics();
1492 assert_eq!(metrics.fps, 60.0);
1493 assert_eq!(metrics.active_users, 0);
1494 }
1495}