File size: 14,760 Bytes
5374a2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
from typing import Dict, Any, Optional
import json
import tkinter as tk
from tkinter import ttk

class WorkFlowJSONEditorGUI:
    """GUI JSON Editor GUI based on tkinter"""
    
    def __init__(self, json_data: Dict[str, Any]):
        self.json_data = json_data
        self.result = None
        self.root = None
        
    def edit_json(self) -> Optional[Dict[str, Any]]:
        """start the json editor and return the modified data"""
        try:
            import tkinter as tk
            from tkinter import ttk, scrolledtext
        except ImportError:
            print("⚠️  tkinter is not available, use the text editor")
            return self._edit_json_text()
        
        self.root = tk.Tk()
        self.root.title("WorkFlow JSON Editor")
        self.root.geometry("800x600")
        
        # create the main frame
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # configure the grid weight
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(1, weight=1)
        
        # title
        title_label = ttk.Label(main_frame, text="Edit WorkFlow JSON Structure", font=("Arial", 14, "bold"))
        title_label.grid(row=0, column=0, columnspan=2, pady=(0, 10))
        
        # left button frame
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=1, column=0, sticky=(tk.W, tk.N), padx=(0, 10))
        
        # buttons
        ttk.Button(button_frame, text="πŸ“ Format", command=self._format_json).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="βœ… Validate", command=self._validate_json).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="πŸ”„ Reset", command=self._reset_json).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="πŸ“‹ Copy", command=self._copy_json).pack(fill=tk.X, pady=2)
        
        ttk.Separator(button_frame, orient='horizontal').pack(fill=tk.X, pady=10)
        
        # quick operation buttons
        ttk.Label(button_frame, text="Quick Operations:", font=("Arial", 10, "bold")).pack(anchor=tk.W)
        ttk.Button(button_frame, text="βž• Add Node", command=self._add_node_quick).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="πŸ”— Add Edge", command=self._add_edge_quick).pack(fill=tk.X, pady=2)
        ttk.Button(button_frame, text="πŸ“„ Template", command=self._insert_template).pack(fill=tk.X, pady=2)
        
        # right text editor area
        text_frame = ttk.Frame(main_frame)
        text_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
        text_frame.columnconfigure(0, weight=1)
        text_frame.rowconfigure(0, weight=1)
        
        # text editor
        self.text_area = scrolledtext.ScrolledText(
            text_frame, 
            wrap=tk.WORD, 
            width=60, 
            height=30,
            font=("Consolas", 10)
        )
        self.text_area.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # insert json data
        self.text_area.insert(tk.END, json.dumps(self.json_data, indent=2, ensure_ascii=False))
        
        # bottom button frame
        bottom_frame = ttk.Frame(main_frame)
        bottom_frame.grid(row=2, column=0, columnspan=2, pady=(10, 0))
        
        # confirm and cancel buttons
        ttk.Button(bottom_frame, text="πŸ’Ύ Save and Close", command=self._save_and_close).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(bottom_frame, text="❌ Cancel", command=self._cancel).pack(side=tk.LEFT, padx=(0, 5))
        
        # status label
        self.status_label = ttk.Label(bottom_frame, text="Ready", foreground="green")
        self.status_label.pack(side=tk.RIGHT)
        
        # start the gui
        self.root.mainloop()
        return self.result
    
    def _format_json(self):
        """format the json"""
        try:
            text = self.text_area.get(1.0, tk.END)
            data = json.loads(text)
            formatted = json.dumps(data, indent=2, ensure_ascii=False)
            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(tk.END, formatted)
            self.status_label.config(text="βœ… Formatting completed", foreground="green")
        except json.JSONDecodeError as e:
            self.status_label.config(text=f"❌ JSON format error: {e}", foreground="red")
    
    def _validate_json(self):
        """validate the json"""
        try:
            text = self.text_area.get(1.0, tk.END)
            data = json.loads(text)
            
            # validate the workflow structure
            if not isinstance(data, dict):
                raise ValueError("The root node must be a dictionary")
            
            if 'nodes' not in data or not isinstance(data['nodes'], list):
                raise ValueError("Must contain nodes array")
            
            node_names = set()
            for node in data['nodes']:
                if not isinstance(node, dict) or 'name' not in node:
                    raise ValueError("Each node must contain name field")
                
                name = node['name']
                if name in node_names:
                    raise ValueError(f"Node name duplicate: {name}")
                node_names.add(name)
            
            # validate the edges
            if 'edges' in data:
                for edge in data['edges']:
                    if not isinstance(edge, dict):
                        continue
                    source = edge.get('source')
                    target = edge.get('target')
                    if source and source not in node_names:
                        raise ValueError(f"The source node of the edge does not exist: {source}")
                    if target and target not in node_names:
                        raise ValueError(f"The target node of the edge does not exist: {target}")
            
            self.status_label.config(text="βœ… JSON structure is valid", foreground="green")
            
        except (json.JSONDecodeError, ValueError) as e:
            self.status_label.config(text=f"❌ Validation failed: {e}", foreground="red")
    
    def _reset_json(self):
        """reset the json"""
        self.text_area.delete(1.0, tk.END)
        self.text_area.insert(tk.END, json.dumps(self.json_data, indent=2, ensure_ascii=False))
        self.status_label.config(text="πŸ”„ Reset", foreground="blue")
    
    def _copy_json(self):
        """copy the json to the clipboard"""
        try:
            text = self.text_area.get(1.0, tk.END)
            self.root.clipboard_clear()
            self.root.clipboard_append(text)
            self.status_label.config(text="πŸ“‹ Copied to clipboard", foreground="blue")
        except Exception as e:
            self.status_label.config(text=f"❌ Copy failed: {e}", foreground="red")
    
    def _add_node_quick(self):
        """quick add node"""
        try:
            import tkinter.simpledialog as sd
            
            name = sd.askstring("Add Node", "Node name:")
            if not name:
                return
            
            desc = sd.askstring("Add Node", "Node description:")
            if not desc:
                desc = f"The description of the node {name}"
            
            node_template = {
                "class_name": "WorkFlowNode",
                "name": name,
                "description": desc,
                "inputs": [],
                "outputs": [],
                "agents": [],
                "status": "pending"
            }
            
            # get the current json
            current_text = self.text_area.get(1.0, tk.END)
            try:
                data = json.loads(current_text)
                data.setdefault('nodes', []).append(node_template)
                
                # update the text area
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(tk.END, json.dumps(data, indent=2, ensure_ascii=False))
                self.status_label.config(text=f"βœ… Added node: {name}", foreground="green")
                
            except json.JSONDecodeError:
                self.status_label.config(text="❌ Current JSON format error, cannot add node", foreground="red")
                
        except ImportError:
            self.status_label.config(text="❌ Cannot use dialog", foreground="red")
    
    def _add_edge_quick(self):
        """quick add edge"""
        try:
            import tkinter.simpledialog as sd
            
            # get the current node list
            current_text = self.text_area.get(1.0, tk.END)
            try:
                data = json.loads(current_text)
                nodes = data.get('nodes', [])
                node_names = [node.get('name') for node in nodes if node.get('name')]
                
                if len(node_names) < 2:
                    self.status_label.config(text="❌ At least 2 nodes are required to add edge", foreground="red")
                    return
                
                source = sd.askstring("Add Edge", f"Source node (optional: {', '.join(node_names)}):")
                if not source or source not in node_names:
                    self.status_label.config(text="❌ Source node invalid", foreground="red")
                    return
                
                target = sd.askstring("Add Edge", f"Target node (optional: {', '.join(node_names)}):")
                if not target or target not in node_names:
                    self.status_label.config(text="❌ Target node invalid", foreground="red")
                    return
                
                edge_template = {
                    "class_name": "WorkFlowEdge",
                    "source": source,
                    "target": target,
                    "priority": 0
                }
                
                data.setdefault('edges', []).append(edge_template)
                
                # update the text area
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(tk.END, json.dumps(data, indent=2, ensure_ascii=False))
                self.status_label.config(text=f"βœ… Added edge: {source} -> {target}", foreground="green")
                
            except json.JSONDecodeError:
                self.status_label.config(text="❌ Current JSON format error, cannot add edge", foreground="red")
                
        except ImportError:
            self.status_label.config(text="❌ Cannot use dialog", foreground="red")
    
    def _insert_template(self):
        """insert template"""
        templates = {
            "Simple Node": {
                "class_name": "WorkFlowNode",
                "name": "new_node",
                "description": "New node description",
                "inputs": [{"class_name": "Parameter", "name": "input1", "type": "string", "description": "Input parameter", "required": True}],
                "outputs": [{"class_name": "Parameter", "name": "output1", "type": "string", "description": "Output parameter", "required": True}],
                "agents": [],
                "status": "pending"
            },
            "CustomizeAgent": {
                "name": "my_agent",
                "description": "Customize Agent",
                "inputs": [{"name": "input1", "type": "string", "description": "Input", "required": True}],
                "outputs": [{"name": "output1", "type": "string", "description": "Output", "required": True}],
                "prompt": "Process input: {input1}",
                "parse_mode": "str"
            }
        }
        
        # create the template selection window
        template_window = tk.Toplevel(self.root)
        template_window.title("Select Template")
        template_window.geometry("400x300")
        
        ttk.Label(template_window, text="Select the template to insert:").pack(pady=10)
        
        template_listbox = tk.Listbox(template_window)
        template_listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        for template_name in templates.keys():
            template_listbox.insert(tk.END, template_name)
        
        def insert_selected():
            selection = template_listbox.curselection()
            if selection:
                template_name = template_listbox.get(selection[0])
                template_json = json.dumps(templates[template_name], indent=2, ensure_ascii=False)
                self.text_area.insert(tk.INSERT, f"\n{template_json}\n")
                self.status_label.config(text=f"βœ… Inserted template: {template_name}", foreground="green")
                template_window.destroy()
        
        ttk.Button(template_window, text="Insert", command=insert_selected).pack(pady=10)
        ttk.Button(template_window, text="Cancel", command=template_window.destroy).pack()
    
    def _save_and_close(self):
        """save and close"""
        try:
            text = self.text_area.get(1.0, tk.END)
            self.result = json.loads(text)
            self.root.destroy()
        except json.JSONDecodeError as e:
            self.status_label.config(text=f"❌ JSON format error: {e}", foreground="red")
    
    def _cancel(self):
        """cancel"""
        self.result = None
        self.root.destroy()
    
    def _edit_json_text(self) -> Optional[Dict[str, Any]]:
        """use the text editor to edit the json (backup solution)"""
        import tempfile
        import subprocess
        import os
        
        # create the temporary file
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
            json.dump(self.json_data, f, indent=2, ensure_ascii=False)
            temp_file = f.name
        
        try:
            print(f"πŸ“ Opening file editor: {temp_file}")
            print("πŸ’‘ Please save the file and close the editor after editing")
            
            # select the editor based on the operating system
            if os.name == 'nt':  # Windows
                subprocess.run(['notepad', temp_file])
            elif os.name == 'posix':  # Linux/Mac
                subprocess.run(['nano', temp_file])
            
            # read the edited file
            with open(temp_file, 'r', encoding='utf-8') as f:
                result = json.load(f)
            
            return result
            
        except Exception as e:
            print(f"❌ Editor opening failed: {e}")
            return None
        finally:
            # clean up the temporary file
            os.unlink(temp_file)