AIencoder commited on
Commit
9d6dfd1
·
verified ·
1 Parent(s): 69804c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +365 -455
app.py CHANGED
@@ -1,546 +1,456 @@
 
 
1
  import gradio as gr
2
- import os
 
3
  import sys
4
  import subprocess
 
 
 
 
 
 
 
5
  import threading
6
- import time
7
- import ast
8
  import re
9
- import signal
10
- import shutil
11
  import tempfile
12
- import atexit
13
- from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # ═══════════════════════════════════════
16
- # 1. HELPER CLASSES
17
  # ═══════════════════════════════════════
18
-
19
- class VirtualDisplayManager:
20
- """Manages a virtual display (Xvfb) for capturing GUI output."""
21
  def __init__(self):
22
- self.display = None
23
- self.is_active = False
24
- self.check_xvfb()
25
-
26
- def check_xvfb(self):
27
- # Check if Xvfb is installed (Linux only)
28
- self.has_xvfb = shutil.which('Xvfb') is not None
29
-
30
- def start(self):
31
- if not self.has_xvfb:
32
- print("Xvfb not found. Virtual display disabled.")
33
- return
34
- try:
35
- from pyvirtualdisplay import Display
36
- self.display = Display(visible=0, size=(1280, 720))
37
- self.display.start()
38
- os.environ["DISPLAY"] = f":{self.display.display}"
39
- self.is_active = True
40
- print(f"Virtual Display started on {os.environ['DISPLAY']}")
41
- except Exception as e:
42
- print(f"Failed to start virtual display: {e}")
43
 
44
- def capture(self):
45
- if not self.is_active:
46
- return None
47
  try:
48
- # Capture screenshot of the virtual display
49
- # Using xwd and converting to png is standard, but let's try PIL grab if possible
50
- # or a simple screenshot tool if installed.
51
- # Fallback: ImageGrab works on Linux if DISPLAY is set correctly and dependencies exist.
52
- from PIL import ImageGrab
53
- img = ImageGrab.grab()
54
-
55
- # Save to a temporary path for Gradio to display
56
- fd, path = tempfile.mkstemp(suffix=".png")
57
- os.close(fd)
58
- img.save(path)
59
- return path
60
- except Exception as e:
61
- print(f"Capture failed: {e}")
62
- return None
63
-
64
- def cleanup(self):
65
- if self.display:
66
- self.display.stop()
67
-
68
- class FileSystem:
69
- """Manages file operations."""
70
- def __init__(self, root="."):
71
- self.root = os.path.abspath(root)
72
- self.current_file = None
73
- self.files = []
74
- self.refresh()
75
-
76
- def refresh(self):
77
- # List only .py files for simplicity, or all text files
78
- self.files = [f for f in os.listdir(self.root) if f.endswith('.py') or f.endswith('.txt') or f.endswith('.md')]
79
- self.files.sort()
80
- if not self.current_file and self.files:
81
- self.current_file = self.files[0]
82
- return self.files
83
-
84
- def get_all_files(self):
85
- self.refresh()
86
- return self.files
87
-
88
- def set_current_file(self, filename):
89
- if filename in self.files:
90
- self.current_file = filename
91
-
92
- def get_current_file_content(self):
93
- if not self.current_file: return ""
94
- try:
95
- with open(os.path.join(self.root, self.current_file), 'r', encoding='utf-8') as f:
96
- return f.read()
97
- except:
98
- return "Error reading file."
99
-
100
- def save_file(self, content):
101
- if not self.current_file: return
102
- with open(os.path.join(self.root, self.current_file), 'w', encoding='utf-8') as f:
103
- f.write(content)
104
-
105
- def create_file(self, name):
106
- if not name: return
107
- path = os.path.join(self.root, name)
108
- if not os.path.exists(path):
109
- with open(path, 'w') as f:
110
- f.write("# New file\n")
111
- self.refresh()
112
- self.current_file = name
113
-
114
- def delete_file(self, name):
115
- path = os.path.join(self.root, name)
116
- if os.path.exists(path):
117
- os.remove(path)
118
- self.current_file = None
119
- self.refresh()
120
- return True
121
- return False
122
-
123
- class TerminalManager:
124
- """Manages running shell commands."""
125
- def __init__(self):
126
- self.history = []
127
-
128
- def run_command(self, command):
129
- if not command.strip(): return "No command"
130
-
131
- try:
132
- # Capture output
133
- result = subprocess.run(
134
- command,
135
- shell=True,
136
- cwd=os.getcwd(),
137
- stdout=subprocess.PIPE,
138
- stderr=subprocess.PIPE,
139
- text=True,
140
- timeout=10
141
  )
142
- output = f"$ {command}\n{result.stdout}\n{result.stderr}"
 
 
 
 
 
143
  except Exception as e:
144
- output = f"$ {command}\nError: {str(e)}"
145
-
146
- self.history.append(command)
147
- return output
148
 
149
- def get_history_choices(self):
150
- return list(set(self.history)) # Unique history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  def cleanup(self):
153
- pass
 
 
 
154
 
155
- class GUIManager:
156
- """Manages running the user's Python script as a subprocess."""
 
 
 
 
157
  def __init__(self):
158
  self.process = None
159
-
160
  def launch(self, code):
161
- self.stop() # Stop any existing process
162
-
163
- # Write code to a temp file to run it
164
- fd, path = tempfile.mkstemp(suffix=".py")
165
- with os.fdopen(fd, 'w') as f:
166
- f.write(code)
167
-
168
  try:
169
- # Run asynchronously
170
- self.process = subprocess.Popen(
171
- [sys.executable, path],
172
- stdout=subprocess.PIPE,
173
- stderr=subprocess.PIPE,
174
- text=True,
175
- cwd=os.getcwd()
176
- )
177
- return f"Started process PID: {self.process.pid}"
178
- except Exception as e:
179
- return f"Failed to start: {e}"
180
 
181
  def stop(self):
182
- if self.process:
183
- self.process.terminate()
184
- self.process = None
185
- return "Process stopped."
186
- return "No process running."
 
 
 
187
 
188
- class CodeAnalyzer:
189
- """Parses Python code to extract structure (Classes/Functions)."""
190
- def analyze(self, code):
191
- if not code: return "No code."
192
- try:
193
- tree = ast.parse(code)
194
- lines = []
195
- for node in ast.walk(tree):
196
- if isinstance(node, ast.ClassDef):
197
- lines.append(f"📦 Class: {node.name}")
198
- for item in node.body:
199
- if isinstance(item, ast.FunctionDef):
200
- lines.append(f" 🔹 def {item.name}(...)")
201
- elif isinstance(node, ast.FunctionDef):
202
- # Only list top-level functions here (those not in a class)
203
- # This logic is simplified; ast.walk flattens everything.
204
- # A proper visitor is better, but for a simple view:
205
- pass
206
-
207
- # Simple Regex fallback for cleaner hierarchy if AST is too messy
208
- summary = []
209
- for line in code.splitlines():
210
- if line.strip().startswith("class "):
211
- summary.append(f"📦 {line.split(':')[0]}")
212
- elif line.strip().startswith("def "):
213
- indent = " " if line.startswith(" ") else ""
214
- summary.append(f"{indent}🔹 {line.split(':')[0]}")
215
-
216
- return "\n".join(summary) if summary else "No classes/functions found."
217
- except SyntaxError:
218
- return "❌ Syntax Error"
219
 
220
- class SnippetManager:
221
- """Manages code snippets."""
 
 
222
  def __init__(self):
223
- self.snippets = {
224
- "Hello World": "print('Hello World')",
225
- "Tkinter Basic": "import tkinter as tk\nroot = tk.Tk()\nroot.mainloop()",
226
- "Gradio Demo": "import gradio as gr\ndef greet(n):\n return 'Hi '+n\ngr.Interface(fn=greet, inputs='text', outputs='text').launch()"
227
  }
 
 
 
228
 
229
- def get_names(self):
230
- return list(self.snippets.keys())
 
 
 
231
 
232
- def get(self, name):
233
- return self.snippets.get(name, "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
- def add(self, name, code, tags):
236
- self.snippets[name] = code
237
- return "Snippet saved."
238
 
239
- class FindReplace:
240
- """Handles Text Finding and Replacing."""
241
- def find_in_file(self, code, query, match_case):
242
- if not query: return "Empty query."
243
- flags = 0 if match_case else re.IGNORECASE
244
- matches = list(re.finditer(re.escape(query), code, flags))
245
- return f"Found {len(matches)} occurrences."
246
-
247
- def find_in_all_files(self, file_list, query):
248
- results = []
249
- for filename in file_list:
250
  try:
251
- with open(filename, 'r') as f:
252
- content = f.read()
253
- if query in content:
254
- results.append(filename)
255
- except: pass
256
- return "\n".join(results) if results else "No matches in other files."
257
-
258
- def replace_in_file(self, code, find, replace, match_case):
259
- if not find: return code, "Empty search."
260
- flags = 0 if match_case else re.IGNORECASE
261
- new_code, count = re.subn(re.escape(find), replace, code, flags=flags)
262
- return new_code, f"Replaced {count} occurrences."
263
 
264
  # ═══════════════════════════════════════
265
- # 2. INSTANTIATE MANAGERS
266
  # ═══════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
- vdisplay = VirtualDisplayManager()
269
- vdisplay.start()
270
-
271
- fs = FileSystem()
272
- terminal = TerminalManager()
273
- gui_mgr = GUIManager()
274
  analyzer = CodeAnalyzer()
275
- snippets = SnippetManager()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  finder = FindReplace()
277
 
278
  # ═══════════════════════════════════════
279
- # 3. UI THEME & CSS
280
  # ═══════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- theme = gr.themes.Soft(
283
- primary_hue="orange",
284
- secondary_hue="slate",
285
- neutral_hue="slate",
286
- )
287
 
288
- css = """
289
- .gradio-container { min-height: 100vh; }
290
- textarea { font-family: 'Consolas', 'Monaco', monospace !important; }
291
- .cm-scroller { overflow: auto !important; }
292
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
  # ═══════════════════════════════════════
295
- # 4. UI CONSTRUCTION
296
  # ═══════════════════════════════════════
 
 
 
 
297
 
298
- with gr.Blocks(theme=theme, css=css, title="Python IDE") as demo:
299
-
300
- # State variables
301
- # (Gradio 4.x usually handles state via component values, but we can use State objects if needed)
 
 
302
 
303
  with gr.Row():
304
- # ─── LEFT SIDEBAR ───
305
- with gr.Column(scale=1, min_width=250):
306
- gr.Markdown("### 📂 Explorer")
307
-
308
- with gr.Row():
309
- new_file_name = gr.Textbox(placeholder="NewFile.py", show_label=False, scale=3)
310
- new_file_btn = gr.Button("➕", scale=1, min_width=10)
 
 
 
 
 
311
 
312
- file_list = gr.Dropdown(choices=fs.get_all_files(), label="Files", value=fs.current_file, interactive=True)
 
313
 
314
- with gr.Row():
315
- refresh_btn = gr.Button("🔄 Refresh")
316
- del_file_btn = gr.Button("🗑 Delete", variant="stop")
317
-
318
- gr.Markdown("### 🌳 Structure")
319
- structure_view = gr.Code(language="markdown", label="AST Outline", interactive=False, lines=15)
320
-
321
- # ─── MAIN AREA ───
 
322
  with gr.Column(scale=4):
 
323
 
324
- # Toolbar
325
- with gr.Row():
326
- run_btn = gr.Button("▶ RUN (Console)", variant="primary")
327
- run_gui_btn = gr.Button("🖼 RUN (GUI)", variant="secondary")
328
- stop_gui_btn = gr.Button("⏹ STOP", variant="stop")
329
- save_btn = gr.Button("💾 SAVE")
330
-
331
- # Editor
332
- editor = gr.Code(
333
- value=fs.get_current_file_content(),
334
- language="python",
335
- label=f"Editor - {fs.current_file}",
336
- lines=20,
337
- interactive=True
338
- )
339
-
340
- # AI/Tools Row (Placeholder for future expansion)
341
  with gr.Row():
342
- complete_btn = gr.Button(" Auto-Complete")
343
- explain_btn = gr.Button("🧐 Explain")
344
- refactor_btn = gr.Button("🛠 Refactor")
 
 
345
 
346
- # Bottom Tabs
347
  with gr.Tabs():
348
-
349
- # Terminal Tab
350
- with gr.Tab("💻 TERMINAL"):
351
- terminal_output = gr.Textbox(label="Output", lines=10, max_lines=20, elem_classes="term-out")
352
- with gr.Row():
353
- terminal_input = gr.Textbox(show_label=False, placeholder="Type command here...", scale=4)
354
- terminal_run_btn = gr.Button("Run", scale=1)
355
- with gr.Row():
356
- clear_btn = gr.Button("Clear Output")
357
- history_refresh = gr.Button("History")
358
- history_dd = gr.Dropdown(label="Command History", choices=[])
359
 
360
- # ─ Find/Replace Tab
361
- with gr.Tab("🔍 FIND"):
362
- with gr.Row():
363
- find_query = gr.Textbox(label="Find", placeholder="Text to find...")
364
- replace_query = gr.Textbox(label="Replace", placeholder="Replacement text...")
365
-
366
  with gr.Row():
367
- find_case = gr.Checkbox(label="Match Case")
368
-
 
369
  with gr.Row():
370
- find_btn = gr.Button("FIND", variant="primary")
371
- find_all_btn = gr.Button("FIND ALL")
372
- replace_btn = gr.Button("REPLACE")
373
-
374
  find_results = gr.Textbox(label="Results", lines=5)
375
 
376
- # ─ Snippets Tab
377
- with gr.Tab("🧩 SNIPPETS"):
378
  with gr.Row():
379
- snippet_list = gr.Dropdown(choices=snippets.get_names(), label="Load Snippet")
380
- snippet_insert_btn = gr.Button("Insert into Editor")
381
-
382
- gr.Markdown("---")
383
- with gr.Row():
384
- snippet_name = gr.Textbox(label="Snippet Name")
385
- snippet_tags = gr.Textbox(label="Tags (comma sep)")
386
- snippet_save_btn = gr.Button("Save Selection as Snippet")
387
- snippet_status = gr.Markdown("")
388
-
389
- # ─ Display Tab (Xvfb) ─
390
- with gr.Tab("🖥 DISPLAY"):
391
- display_image = gr.Image(label="Xvfb Capture", type="filepath", interactive=False, height=400)
392
- with gr.Row():
393
- capture_btn = gr.Button("📸 CAPTURE SCREEN", variant="primary")
394
- auto_capture = gr.Checkbox(label="Auto-refresh (2s)")
395
-
396
  auto_timer = gr.Timer(value=2.0, active=False)
397
 
398
  # ═══════════════════════════════════════
399
- # 5. LOGIC FUNCTIONS
400
  # ═══════════════════════════════════════
401
-
402
- # --- File System Wrappers ---
403
- def load_file_content(f):
404
  fs.set_current_file(f)
405
- content = fs.get_current_file_content()
406
- return content, f"Editor - {f}", analyzer.analyze(content)
407
 
408
- def refresh_file_list():
409
- files = fs.refresh()
410
- return gr.update(choices=files, value=fs.current_file)
411
 
412
- def save_file_content(code):
413
  fs.save_file(code)
414
- return "File saved."
415
-
416
- def create_new_file(name):
417
- if not name: return gr.update(), "Name required", ""
418
- fs.create_file(name)
419
- content = fs.get_current_file_content()
420
- return gr.update(choices=fs.get_all_files(), value=name), f"Editor - {name}", analyzer.analyze(content)
421
-
422
- def delete_current_file(name):
423
- if fs.delete_file(name):
424
- next_file = fs.current_file
425
- content = fs.get_current_file_content()
426
- return (
427
- gr.update(choices=fs.get_all_files(), value=next_file),
428
- content,
429
- f"Editor - {next_file}",
430
- analyzer.analyze(content)
431
- )
432
- return gr.update(), gr.update(), gr.update(), gr.update()
433
-
434
- # --- Execution Wrappers ---
435
- def run_console_code(code):
436
- # Simply run the code and return stdout
437
- # Note: This runs in the same environment, simplistic.
438
- fd, path = tempfile.mkstemp(suffix=".py")
439
- with os.fdopen(fd, 'w') as f:
440
- f.write(code)
441
- try:
442
- res = subprocess.run([sys.executable, path], capture_output=True, text=True, timeout=5)
443
- return f"OUTPUT:\n{res.stdout}\nERRORS:\n{res.stderr}"
444
- except Exception as e:
445
- return str(e)
446
 
447
- def run_gui_code(code):
 
448
  return gui_mgr.launch(code)
449
 
450
- def stop_gui_code():
451
- return gui_mgr.stop()
 
452
 
453
- def terminal_submit(cmd):
454
- return terminal.run_command(cmd), gr.update(value="")
 
455
 
456
- # --- Bridge Functions (Find/Display) ---
457
- def do_find(code, query, case):
458
- return finder.find_in_file(code, query, case)
 
 
 
 
 
459
 
460
- def do_find_all(query):
461
- return finder.find_in_all_files(fs.files, query)
 
 
 
 
 
 
462
 
463
- def do_replace(code, find, replace, case):
464
- new_code, msg = finder.replace_in_file(code, find, replace, case)
465
- # Update editor, results box, AND structure view
466
- fs.save_file(new_code) # Auto-save on replace
467
- new_structure = analyzer.analyze(new_code)
468
- return new_code, msg, new_structure
 
469
 
470
- def capture_display():
 
471
  path = vdisplay.capture()
472
  return path if path else None
473
 
474
- def toggle_auto(checkbox_state):
475
- return gr.Timer(active=checkbox_state)
476
-
477
- def auto_capture_tick():
478
- return capture_display()
479
-
480
- # --- Snippet Wrappers ---
481
- def insert_snippet(code, snippet_name):
482
- s_code = snippets.get(snippet_name)
483
- if not s_code: return code
484
- return (code + "\n" + s_code) if code else s_code
485
-
486
- def save_snippet_ui(name, code, tags):
487
- return snippets.add(name, code, tags), gr.update(choices=snippets.get_names())
488
-
489
- # --- AI Mock functions ---
490
- def mock_ai_action(code, action):
491
- return f"# {action} applied to code (AI Mock)\n" + code
492
-
493
- # ═══════════════════════════════════════
494
- # 6. EVENT WIRING
495
- # ═══════════════════════════════════════
496
-
497
- # File Explorer
498
- file_list.change(fn=load_file_content, inputs=file_list, outputs=[editor, editor.label, structure_view])
499
- refresh_btn.click(fn=refresh_file_list, inputs=None, outputs=file_list)
500
- new_file_btn.click(fn=create_new_file, inputs=new_file_name, outputs=[file_list, editor.label, structure_view])
501
- save_btn.click(fn=save_file_content, inputs=editor, outputs=None)
502
- del_file_btn.click(fn=delete_current_file, inputs=file_list, outputs=[file_list, editor, editor.label, structure_view])
503
-
504
- # Editor Actions
505
- run_btn.click(fn=run_console_code, inputs=editor, outputs=terminal_output)
506
- run_gui_btn.click(fn=run_gui_code, inputs=editor, outputs=terminal_output)
507
- stop_gui_btn.click(fn=stop_gui_code, inputs=None, outputs=terminal_output)
508
-
509
- # AI Actions
510
- complete_btn.click(fn=lambda c: mock_ai_action(c, "Completion"), inputs=editor, outputs=editor)
511
- explain_btn.click(fn=lambda c: mock_ai_action(c, "Explanation"), inputs=editor, outputs=terminal_output)
512
- refactor_btn.click(fn=lambda c: mock_ai_action(c, "Refactor"), inputs=editor, outputs=editor)
513
-
514
- # Terminal
515
- terminal_run_btn.click(fn=terminal_submit, inputs=terminal_input, outputs=[terminal_output, terminal_input])
516
- terminal_input.submit(fn=terminal_submit, inputs=terminal_input, outputs=[terminal_output, terminal_input])
517
- clear_btn.click(fn=lambda: terminal.run_command("clear"), inputs=None, outputs=terminal_output)
518
- history_refresh.click(fn=lambda: gr.update(choices=terminal.get_history_choices()), inputs=None, outputs=history_dd)
519
- history_dd.change(fn=lambda x: x, inputs=history_dd, outputs=terminal_input)
520
-
521
- # Find & Replace
522
- find_btn.click(fn=do_find, inputs=[editor, find_query, find_case], outputs=find_results)
523
- find_all_btn.click(fn=do_find_all, inputs=[find_query], outputs=find_results)
524
- replace_btn.click(fn=do_replace,
525
- inputs=[editor, find_query, replace_query, find_case],
526
- outputs=[editor, find_results, structure_view])
527
-
528
- # Display / Capture
529
- capture_btn.click(fn=capture_display, inputs=None, outputs=display_image)
530
- auto_capture.change(fn=toggle_auto, inputs=auto_capture, outputs=auto_timer)
531
- auto_timer.tick(fn=auto_capture_tick, inputs=None, outputs=display_image)
532
-
533
- # Snippets
534
- snippet_insert_btn.click(fn=insert_snippet, inputs=[editor, snippet_list], outputs=editor)
535
- snippet_save_btn.click(fn=save_snippet_ui, inputs=[snippet_name, editor, snippet_tags], outputs=[snippet_status, snippet_list])
536
 
537
  if __name__ == "__main__":
538
- # Register cleanup handlers
539
  atexit.register(terminal.cleanup)
540
  atexit.register(gui_mgr.stop)
541
  atexit.register(vdisplay.cleanup)
542
 
543
- # Launch
544
  demo.queue().launch(
545
  server_name="0.0.0.0",
546
  server_port=7860,
 
1
+ import warnings
2
+ warnings.filterwarnings("ignore", category=FutureWarning, message=".*torch.distributed.reduce_op.*")
3
  import gradio as gr
4
+ import torch
5
+ import time
6
  import sys
7
  import subprocess
8
+ import os
9
+ import pty
10
+ import select
11
+ import signal
12
+ import fcntl
13
+ import struct
14
+ import termios
15
  import threading
 
 
16
  import re
17
+ import ast
18
+ import json
19
  import tempfile
20
+ from pathlib import Path
21
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, GenerationConfig
22
+ from typing import List, Dict
23
+ from functools import lru_cache
24
+
25
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
26
+
27
+ MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
28
+ DEVICE = "cpu"
29
+ DTYPE = torch.float32
30
+ DISPLAY_NUM = ":99"
31
+ SCREEN_W, SCREEN_H = 800, 600
32
+ SNIPPETS_FILE = "/tmp/axon_snippets.json"
33
 
34
  # ═══════════════════════════════════════
35
+ # Virtual Display
36
  # ═══════════════════════════════════════
37
+ class VirtualDisplay:
 
 
38
  def __init__(self):
39
+ self.xvfb_proc = None
40
+ self.display = DISPLAY_NUM
41
+ self._start_xvfb()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ def _start_xvfb(self):
 
 
44
  try:
45
+ subprocess.run(["pkill", "-f", f"Xvfb {self.display}"], capture_output=True, timeout=5)
46
+ time.sleep(0.2)
47
+ self.xvfb_proc = subprocess.Popen(
48
+ ["Xvfb", self.display, "-screen", "0", f"{SCREEN_W}x{SCREEN_H}x24", "-ac", "-nolisten", "tcp"],
49
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  )
51
+ time.sleep(0.5)
52
+ if self.xvfb_proc.poll() is None:
53
+ print(f"[Xvfb] Running on {self.display}")
54
+ os.environ["DISPLAY"] = self.display
55
+ else:
56
+ self.xvfb_proc = None
57
  except Exception as e:
58
+ print(f"[Xvfb] {e}"); self.xvfb_proc = None
 
 
 
59
 
60
+ def capture(self):
61
+ if not self.is_running: return None
62
+ tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False); tmp.close()
63
+ # Try multiple capture methods
64
+ cmds = [
65
+ ["scrot", "--display", self.display, tmp.name],
66
+ f"import -window root -display {self.display} {tmp.name}"
67
+ ]
68
+ for cmd in cmds:
69
+ try:
70
+ kw = {"shell": True} if isinstance(cmd, str) else {}
71
+ r = subprocess.run(cmd, capture_output=True, timeout=2, env={**os.environ, "DISPLAY": self.display}, **kw)
72
+ if r.returncode == 0 and os.path.exists(tmp.name) and os.path.getsize(tmp.name) > 100:
73
+ return tmp.name
74
+ except: pass
75
+ return None
76
+
77
+ @property
78
+ def is_running(self): return self.xvfb_proc is not None and self.xvfb_proc.poll() is None
79
 
80
  def cleanup(self):
81
+ if self.xvfb_proc:
82
+ self.xvfb_proc.terminate()
83
+ try: self.xvfb_proc.wait(timeout=3)
84
+ except: self.xvfb_proc.kill()
85
 
86
+ vdisplay = VirtualDisplay()
87
+
88
+ # ═══════════════════════════════════════
89
+ # GUI Process Manager
90
+ # ═══════════════════════════════════════
91
+ class GUIProcessManager:
92
  def __init__(self):
93
  self.process = None
94
+
95
  def launch(self, code):
96
+ self.stop()
97
+ tmp = "/tmp/_axon_gui_run.py"
98
+ with open(tmp, "w") as f: f.write(code)
99
+ env = os.environ.copy()
100
+ env.update({"DISPLAY": DISPLAY_NUM, "SDL_VIDEODRIVER": "x11"})
 
 
101
  try:
102
+ self.process = subprocess.Popen([sys.executable, tmp], stdout=subprocess.PIPE,
103
+ stderr=subprocess.PIPE, env=env, preexec_fn=os.setsid)
104
+ time.sleep(0.5)
105
+ if self.process.poll() is not None:
106
+ out, err = self.process.communicate(timeout=2)
107
+ msg = (out.decode(errors="replace") + "\n" + err.decode(errors="replace")).strip()
108
+ return f"[Exited immediately]\n{msg}" if msg else "[Exited]"
109
+ return f"[GUI launched — PID {self.process.pid}] Switch to DISPLAY tab to view."
110
+ except Exception as e: return f"[Error] {e}"
 
 
111
 
112
  def stop(self):
113
+ if self.process and self.process.poll() is None:
114
+ pid = self.process.pid
115
+ try: os.killpg(os.getpgid(pid), signal.SIGTERM); self.process.wait(timeout=3)
116
+ except:
117
+ try: os.killpg(os.getpgid(pid), signal.SIGKILL)
118
+ except: pass
119
+ self.process = None; return f"[Stopped PID {pid}]"
120
+ self.process = None; return "[No process]"
121
 
122
+ gui_mgr = GUIProcessManager()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ # ═══════════════════════════════════════
125
+ # File System
126
+ # ═══════════════════════════════════════
127
+ class FileSystem:
128
  def __init__(self):
129
+ self.files: Dict[str, str] = {
130
+ "main.py": '# Start coding here\nprint("Hello World")',
131
+ "utils.py": "def add(a, b): return a + b",
 
132
  }
133
+ self.current_file = "main.py"
134
+ self._sync_dir = "/tmp/axon_workspace"
135
+ self._sync_to_disk()
136
 
137
+ def _sync_to_disk(self):
138
+ os.makedirs(self._sync_dir, exist_ok=True)
139
+ for name, content in self.files.items():
140
+ path = os.path.join(self._sync_dir, name)
141
+ with open(path, "w") as f: f.write(content)
142
 
143
+ def save_file(self, content):
144
+ if self.current_file:
145
+ self.files[self.current_file] = content
146
+ path = os.path.join(self._sync_dir, self.current_file)
147
+ os.makedirs(os.path.dirname(path) if "/" in self.current_file else self._sync_dir, exist_ok=True)
148
+ with open(path, "w") as f: f.write(content)
149
+
150
+ def get_current_file_content(self): return self.files.get(self.current_file, "")
151
+ def set_current_file(self, f):
152
+ if f in self.files: self.current_file = f
153
+ def create_file(self, f, c=""):
154
+ if f and f not in self.files:
155
+ self.files[f] = c; self.current_file = f
156
+ self._sync_to_disk()
157
+ def delete_file(self, f):
158
+ if f in self.files and len(self.files) > 1:
159
+ del self.files[f]
160
+ path = os.path.join(self._sync_dir, f)
161
+ if os.path.exists(path): os.unlink(path)
162
+ self.current_file = list(self.files.keys())[0]
163
+ return True
164
+ return False
165
+ def get_all_files(self): return list(self.files.keys())
166
 
167
+ fs = FileSystem()
 
 
168
 
169
+ # ═══════════════════════════════════════
170
+ # Snippet Library
171
+ # ═══════════════════════════════════════
172
+ class SnippetLibrary:
173
+ def __init__(self):
174
+ self.snippets = {}
175
+ self._load()
176
+ def _load(self):
177
+ if os.path.exists(SNIPPETS_FILE):
 
 
178
  try:
179
+ with open(SNIPPETS_FILE) as f: self.snippets = json.load(f)
180
+ except: self.snippets = {}
181
+ def _save(self):
182
+ with open(SNIPPETS_FILE, "w") as f: json.dump(self.snippets, f)
183
+ def add(self, name, code, tags=""):
184
+ if not name.strip(): return "Error: name required"
185
+ self.snippets[name.strip()] = {"code": code, "tags": tags, "created": time.strftime("%Y-%m-%d")}
186
+ self._save(); return f"Saved: {name}"
187
+ def get(self, name): return self.snippets.get(name, {}).get("code", "")
188
+ def get_names(self): return list(self.snippets.keys())
189
+
190
+ snippets = SnippetLibrary()
191
 
192
  # ═══════════════════════════════════════
193
+ # Code Analyzer & FindReplace
194
  # ═══════════════════════════════════════
195
+ class CodeAnalyzer:
196
+ @staticmethod
197
+ def analyze(code: str) -> str:
198
+ if not code.strip(): return "Empty file"
199
+ try: tree = ast.parse(code)
200
+ except SyntaxError as e: return f"⚠ Syntax error: line {e.lineno}: {e.msg}"
201
+ lines = [f"📊 {len(code.splitlines())} lines"]
202
+ for node in ast.walk(tree):
203
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
204
+ lines.append(f"⚡ def {node.name}(...)")
205
+ elif isinstance(node, ast.ClassDef):
206
+ lines.append(f"🏗 class {node.name}")
207
+ return "\n".join(lines)
208
 
 
 
 
 
 
 
209
  analyzer = CodeAnalyzer()
210
+
211
+ class FindReplace:
212
+ @staticmethod
213
+ def find_in_file(code, query, case):
214
+ if not query: return ""
215
+ res = []
216
+ for i, line in enumerate(code.splitlines(), 1):
217
+ if (query in line) if case else (query.lower() in line.lower()):
218
+ res.append(f"L{i}: {line.strip()}")
219
+ return "\n".join(res) if res else "No matches."
220
+
221
+ @staticmethod
222
+ def replace_in_file(code, find, replace, case):
223
+ if not find: return code, "Empty find."
224
+ flags = 0 if case else re.IGNORECASE
225
+ new_code, count = re.subn(re.escape(find), replace, code, flags=flags)
226
+ return new_code, f"Replaced {count}."
227
+
228
  finder = FindReplace()
229
 
230
  # ═══════════════════════════════════════
231
+ # Terminal
232
  # ═══════════════════════════════════════
233
+ class PTYTerminal:
234
+ def __init__(self):
235
+ self.master_fd = None; self.pid = None
236
+ self.lock = threading.Lock(); self.log = []
237
+ self._spawn()
238
+ def _spawn(self):
239
+ try:
240
+ pid, fd = pty.openpty(); self.pid = os.fork()
241
+ if self.pid == 0:
242
+ os.setsid(); os.dup2(fd,0); os.dup2(fd,1); os.dup2(fd,2)
243
+ if fd>2: os.close(fd)
244
+ os.execvpe("/bin/bash", ["/bin/bash","--norc","-i"], {**os.environ, "PS1":"$ ", "TERM":"dumb"})
245
+ else:
246
+ os.close(fd); self.master_fd = pid
247
+ fcntl.fcntl(self.master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
248
+ except: pass
249
+ def read(self):
250
+ if not self.master_fd: return ""
251
+ try:
252
+ out = b""
253
+ while True:
254
+ r,_,_ = select.select([self.master_fd],[],[],0.01)
255
+ if not r: break
256
+ out += os.read(self.master_fd, 1024)
257
+ if out:
258
+ txt = out.decode(errors="replace")
259
+ self.log.append(txt)
260
+ return "".join(self.log)[-2000:]
261
+ except: return "".join(self.log)[-2000:]
262
+ return "".join(self.log)[-2000:]
263
+ def write(self, cmd):
264
+ if self.master_fd: os.write(self.master_fd, (cmd+"\n").encode())
265
+ def cleanup(self):
266
+ if self.pid: os.kill(self.pid, signal.SIGTERM)
267
 
268
+ terminal = PTYTerminal()
 
 
 
 
269
 
270
+ # ═══════════════════════════════════════
271
+ # AI Model
272
+ # ═════════��═════════════════════════════
273
+ @lru_cache(maxsize=1)
274
+ def load_model():
275
+ print("Loading Model...")
276
+ try:
277
+ tok = AutoTokenizer.from_pretrained(MODEL_NAME)
278
+ mdl = AutoModelForCausalLM.from_pretrained(MODEL_NAME, dtype=DTYPE, device_map=DEVICE)
279
+ return pipeline("text-generation", model=mdl, tokenizer=tok)
280
+ except: return None
281
+
282
+ def ai_gen(prompt, code):
283
+ pipe = load_model()
284
+ if not pipe: return "Model load failed."
285
+ formatted = f"<|system|>You are a Python coding assistant.</s><|user|>{prompt}\n{code}</s><|assistant|>"
286
+ try: return pipe(formatted, max_new_tokens=256, temperature=0.2)[0]["generated_text"].split("<|assistant|>")[-1]
287
+ except Exception as e: return str(e)
288
 
289
  # ═══════════════════════════════════════
290
+ # UI Setup
291
  # ═══════════════════════════════════════
292
+ theme = gr.themes.Default(
293
+ primary_hue="cyan", neutral_hue="gray",
294
+ font=[gr.themes.GoogleFont("IBM Plex Mono"), "monospace"]
295
+ ).set(body_background_fill="#080808", body_text_color="#e0e0e0")
296
 
297
+ css = """
298
+ .gradio-container { max-width: 100% !important; }
299
+ textarea { font-family: 'IBM Plex Mono', monospace !important; font-size: 12px !important; }
300
+ """
301
+
302
+ with gr.Blocks(title="Axon Pro", theme=theme, css=css) as demo:
303
 
304
  with gr.Row():
305
+ gr.Markdown("# AXON PRO v4")
306
+
307
+ with gr.Row():
308
+ # Left Sidebar
309
+ with gr.Column(scale=1, min_width=200):
310
+ with gr.Accordion("📁 EXPLORER", open=True):
311
+ file_list = gr.Dropdown(choices=fs.get_all_files(), value=fs.current_file, label="Files")
312
+ new_file_txt = gr.Textbox(placeholder="new_file.py", show_label=False)
313
+ with gr.Row():
314
+ new_btn = gr.Button("NEW", size="sm")
315
+ save_btn = gr.Button("SAVE", size="sm")
316
+ del_btn = gr.Button("DEL", size="sm", variant="stop")
317
 
318
+ with gr.Accordion("🗺 STRUCTURE", open=True):
319
+ structure_view = gr.Textbox(label="", lines=10, interactive=False)
320
 
321
+ with gr.Accordion("📋 SNIPPETS", open=False):
322
+ snip_list = gr.Dropdown(choices=snippets.get_names(), label="Saved")
323
+ snip_name = gr.Textbox(placeholder="Name", show_label=False)
324
+ with gr.Row():
325
+ snip_save_btn = gr.Button("SAVE", size="sm")
326
+ snip_load_btn = gr.Button("INSERT", size="sm")
327
+ status_msg = gr.Markdown("")
328
+
329
+ # Main Editor Area
330
  with gr.Column(scale=4):
331
+ editor = gr.Code(value=fs.get_current_file_content(), language="python", lines=20)
332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  with gr.Row():
334
+ run_btn = gr.Button(" RUN CLI", variant="primary")
335
+ run_gui_btn = gr.Button("🖥 RUN GUI", variant="primary")
336
+ stop_btn = gr.Button(" STOP", variant="stop")
337
+ ai_complete = gr.Button("✨ COMPLETE")
338
+ ai_explain = gr.Button("🤔 EXPLAIN")
339
 
 
340
  with gr.Tabs():
341
+ with gr.Tab("TERMINAL"):
342
+ term_out = gr.Textbox(lines=12, label="Output", interactive=False)
343
+ term_in = gr.Textbox(placeholder="$ Command...", show_label=False)
344
+ term_run = gr.Button("Enter")
 
 
 
 
 
 
 
345
 
346
+ with gr.Tab("FIND"):
 
 
 
 
 
347
  with gr.Row():
348
+ find_query = gr.Textbox(label="Find")
349
+ replace_query = gr.Textbox(label="Replace")
350
+ find_case = gr.Checkbox(label="Case Sensitive")
351
  with gr.Row():
352
+ find_btn = gr.Button("Find")
353
+ replace_btn = gr.Button("Replace All")
 
 
354
  find_results = gr.Textbox(label="Results", lines=5)
355
 
356
+ with gr.Tab("DISPLAY"):
357
+ display_image = gr.Image(label="Virtual Display", interactive=False, type="filepath")
358
  with gr.Row():
359
+ capture_btn = gr.Button("📸 Refresh Screen")
360
+ auto_capture = gr.Checkbox(label="Auto Refresh (2s)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  auto_timer = gr.Timer(value=2.0, active=False)
362
 
363
  # ═══════════════════════════════════════
364
+ # LOGIC & WIRING
365
  # ═══════════════════════════════════════
366
+
367
+ # --- File System Handlers ---
368
+ def load_file(f):
369
  fs.set_current_file(f)
370
+ code = fs.get_current_file_content()
371
+ return code, analyzer.analyze(code)
372
 
373
+ def create_file(name):
374
+ fs.create_file(name)
375
+ return gr.Dropdown(choices=fs.get_all_files(), value=name), "", ""
376
 
377
+ def save_file(code):
378
  fs.save_file(code)
379
+ return "Saved."
380
+
381
+ def delete_file(f):
382
+ fs.delete_file(f)
383
+ new_f = fs.current_file
384
+ new_c = fs.get_current_file_content()
385
+ return gr.Dropdown(choices=fs.get_all_files(), value=new_f), new_c
386
+
387
+ file_list.change(load_file, file_list, [editor, structure_view])
388
+ new_btn.click(create_file, new_file_txt, [file_list, new_file_txt, editor])
389
+ save_btn.click(save_file, editor, status_msg)
390
+ del_btn.click(delete_file, file_list, [file_list, editor])
391
+ editor.change(lambda c: analyzer.analyze(c), editor, structure_view)
392
+
393
+ # --- Execution Handlers ---
394
+ def run_cli(code):
395
+ fs.save_file(code)
396
+ cmd = f"python3 {fs._sync_dir}/{fs.current_file}"
397
+ terminal.write(cmd)
398
+ time.sleep(0.5)
399
+ return terminal.read()
 
 
 
 
 
 
 
 
 
 
 
400
 
401
+ def run_gui(code):
402
+ fs.save_file(code)
403
  return gui_mgr.launch(code)
404
 
405
+ run_btn.click(run_cli, editor, term_out)
406
+ run_gui_btn.click(run_gui, editor, term_out)
407
+ stop_btn.click(gui_mgr.stop, None, term_out)
408
 
409
+ # --- AI Handlers ---
410
+ ai_complete.click(lambda c: ai_gen("Complete this code:", c), editor, editor)
411
+ ai_explain.click(lambda c: ai_gen("Explain this code:", c), editor, term_out)
412
 
413
+ # --- Terminal Handlers ---
414
+ def term_cmd(cmd):
415
+ terminal.write(cmd)
416
+ time.sleep(0.2)
417
+ return "", terminal.read()
418
+
419
+ term_run.click(term_cmd, term_in, [term_in, term_out])
420
+ term_in.submit(term_cmd, term_in, [term_in, term_out])
421
 
422
+ # --- Find/Replace Handlers ---
423
+ find_btn.click(finder.find_in_file, [editor, find_query, find_case], find_results)
424
+
425
+ def replace_h(c, f, r, case):
426
+ new_c, msg = finder.replace_in_file(c, f, r, case)
427
+ return new_c, msg
428
+
429
+ replace_btn.click(replace_h, [editor, find_query, replace_query, find_case], [editor, find_results])
430
 
431
+ # --- Snippet Handlers ---
432
+ snip_save_btn.click(snippets.add, [snip_name, editor], status_msg)
433
+
434
+ def insert_snip(name, code):
435
+ return code + "\n" + snippets.get(name)
436
+
437
+ snip_load_btn.click(insert_snip, [snip_list, editor], editor)
438
 
439
+ # --- Display Handlers ---
440
+ def capture():
441
  path = vdisplay.capture()
442
  return path if path else None
443
 
444
+ capture_btn.click(capture, None, display_image)
445
+ auto_capture.change(lambda x: gr.Timer(active=x), auto_capture, auto_timer)
446
+ auto_timer.tick(capture, None, display_image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
 
448
  if __name__ == "__main__":
449
+ import atexit
450
  atexit.register(terminal.cleanup)
451
  atexit.register(gui_mgr.stop)
452
  atexit.register(vdisplay.cleanup)
453
 
 
454
  demo.queue().launch(
455
  server_name="0.0.0.0",
456
  server_port=7860,