AIencoder commited on
Commit
15b3e55
·
verified ·
1 Parent(s): 4952e75

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +489 -314
app.py CHANGED
@@ -14,10 +14,12 @@ import struct
14
  import termios
15
  import threading
16
  import re
 
 
17
  from io import StringIO
18
- import contextlib
19
  from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, GenerationConfig
20
- from typing import List, Tuple
21
  from functools import lru_cache
22
 
23
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
@@ -26,6 +28,101 @@ os.environ["TOKENIZERS_PARALLELISM"] = "false"
26
  MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
27
  DEVICE = "cpu"
28
  DTYPE = torch.float32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  # --- File System ---
31
  class FileSystem:
@@ -58,14 +155,8 @@ class FileSystem:
58
 
59
  fs = FileSystem()
60
 
61
- # --- PTY Terminal Engine ---
62
  class PTYTerminal:
63
- """
64
- Real PTY-based terminal. Spawns an actual bash shell so every command
65
- that works in a normal Debian/Ubuntu terminal works here — pip, npm,
66
- apt-get, git, curl, python, node, you name it.
67
- """
68
-
69
  STRIP_ANSI = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b\[.*?[@-~]|\r')
70
 
71
  def __init__(self):
@@ -75,22 +166,18 @@ class PTYTerminal:
75
  self.log_lines: List[str] = []
76
  self.max_lines = 800
77
  self._spawn_shell()
78
- self._append_line("Axon Pro Terminal v2.0 — Full PTY Shell")
79
- self._append_line("────────────────────────────────────────")
80
- self._append_line("This is a real bash shell. Everything works:")
81
- self._append_line(" pip install <pkg> npm install <pkg>")
82
- self._append_line(" apt-get install <pkg> git clone <url>")
83
- self._append_line(" python script.py node app.js")
84
- self._append_line(" curl, wget, ls, cat, grep, etc.")
85
- self._append_line("────────────────────────────────────────")
86
 
87
  def _spawn_shell(self):
88
- """Fork a real bash process attached to a PTY."""
89
  try:
90
  pid, fd = pty.openpty()
91
  self.pid = os.fork()
92
  if self.pid == 0:
93
- # Child process — become the shell
94
  os.close(pid)
95
  os.setsid()
96
  slave_fd = fd
@@ -104,9 +191,9 @@ class PTYTerminal:
104
  env["TERM"] = "dumb"
105
  env["PS1"] = "$ "
106
  env["DEBIAN_FRONTEND"] = "noninteractive"
 
107
  os.execvpe("/bin/bash", ["/bin/bash", "--norc", "--noprofile", "-i"], env)
108
  else:
109
- # Parent process
110
  os.close(fd)
111
  self.master_fd = pid
112
  flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
@@ -116,11 +203,10 @@ class PTYTerminal:
116
  time.sleep(0.3)
117
  self._read_output()
118
  except Exception as e:
119
- self._append_line(f"[Terminal Error] Failed to spawn shell: {e}")
120
  self.master_fd = None
121
 
122
  def _read_output(self, timeout: float = 0.1) -> str:
123
- """Read all available output from the PTY."""
124
  if self.master_fd is None:
125
  return ""
126
  output = []
@@ -146,7 +232,6 @@ class PTYTerminal:
146
  return "".join(output)
147
 
148
  def _clean_output(self, text: str) -> str:
149
- """Strip ANSI escape codes and control characters."""
150
  cleaned = self.STRIP_ANSI.sub("", text)
151
  cleaned = "".join(ch for ch in cleaned if ch == "\n" or ch == "\t" or (ord(ch) >= 32))
152
  return cleaned
@@ -160,102 +245,80 @@ class PTYTerminal:
160
  return "\n".join(self.log_lines)
161
 
162
  def run_command(self, cmd: str) -> str:
163
- """Send a command to the real bash shell and capture output."""
164
  cmd = cmd.strip()
165
  if not cmd:
166
  return self.get_log()
167
-
168
  with self.lock:
169
  if cmd.lower() == "clear":
170
  self.log_lines = []
171
  return ""
172
-
173
  if self.master_fd is None:
174
  self._append_line(f"$ {cmd}")
175
- self._append_line("[Terminal Error] No shell process. Restart the Space.")
176
  return self.get_log()
177
-
178
- # Drain any leftover output
179
  self._read_output(0.05)
180
-
181
  self._append_line(f"$ {cmd}")
182
-
183
  try:
184
  os.write(self.master_fd, (cmd + "\n").encode())
185
  except OSError as e:
186
  self._append_line(f"[Write Error] {e}")
187
  return self.get_log()
188
-
189
  parts = cmd.split()
190
  base = parts[0].lower() if parts else ""
191
- long_commands = ["pip", "pip3", "npm", "npx", "apt-get", "apt", "git", "wget",
192
- "curl", "make", "cmake", "cargo", "yarn", "conda"]
193
-
194
- if base in long_commands:
195
- wait_time = 180
196
- elif base in ("python", "python3", "node"):
197
- wait_time = 60
198
- else:
199
- wait_time = 15
200
-
201
  all_output = []
202
  start = time.time()
203
- idle_count = 0
204
-
205
- while time.time() - start < wait_time:
206
  chunk = self._read_output(0.3)
207
  if chunk:
208
- idle_count = 0
209
  all_output.append(chunk)
210
- combined = "".join(all_output)
211
- if combined.rstrip().endswith("$"):
212
  break
213
  else:
214
- idle_count += 1
215
- if base not in long_commands and idle_count >= 3:
216
  break
217
- if base in long_commands and idle_count >= 10:
218
  break
219
-
220
  raw = "".join(all_output)
221
  cleaned = self._clean_output(raw)
222
-
223
  lines = cleaned.split("\n")
224
  filtered = []
225
  skip_echo = True
226
  for line in lines:
227
- stripped = line.strip()
228
- if skip_echo and stripped == cmd.strip():
229
  skip_echo = False
230
  continue
231
- if stripped == "$" or stripped == "$ ":
232
  continue
233
  filtered.append(line)
234
-
235
  result = "\n".join(filtered).strip()
236
  if result:
237
  self._append_line(result)
238
-
239
  return self.get_log()
240
 
241
  def run_editor_code(self, code: str) -> str:
242
- """Execute code from the editor by writing to a temp file."""
243
  with self.lock:
244
  self._append_line("$ python [editor]")
245
-
246
- tmp_path = "/tmp/_axon_editor_run.py"
247
  try:
248
- with open(tmp_path, "w") as f:
249
  f.write(code)
250
  except Exception as e:
251
  with self.lock:
252
- self._append_line(f"[Error] Could not write temp file: {e}")
253
  return self.get_log()
254
-
255
  try:
 
 
256
  result = subprocess.run(
257
- [sys.executable, tmp_path],
258
- capture_output=True, text=True, timeout=30
259
  )
260
  with self.lock:
261
  if result.stdout.strip():
@@ -266,11 +329,10 @@ class PTYTerminal:
266
  self._append_line("(No output)")
267
  except subprocess.TimeoutExpired:
268
  with self.lock:
269
- self._append_line("[Timed out after 30s]")
270
  except Exception as e:
271
  with self.lock:
272
  self._append_line(f"[Error] {e}")
273
-
274
  return self.get_log()
275
 
276
  def cleanup(self):
@@ -290,335 +352,448 @@ terminal = PTYTerminal()
290
  # --- Model Loading ---
291
  @lru_cache(maxsize=1)
292
  def load_model():
293
- print("Loading TinyLlama model for CPU...")
294
- start_time = time.time()
295
  try:
296
- tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
297
- model = AutoModelForCausalLM.from_pretrained(
298
- MODEL_NAME,
299
- dtype=DTYPE,
300
- device_map=DEVICE,
301
- low_cpu_mem_usage=True
302
- )
303
- gen_config = GenerationConfig(
304
- max_new_tokens=256,
305
- temperature=0.2,
306
- top_p=0.95,
307
- do_sample=True,
308
- )
309
- pipe = pipeline(
310
- "text-generation",
311
- model=model,
312
- tokenizer=tokenizer,
313
- generation_config=gen_config,
314
- )
315
- print(f"Model loaded in {time.time() - start_time:.2f}s")
316
  return pipe
317
  except Exception as e:
318
- print(f"Error loading model: {e}")
319
  return None
320
 
321
- # --- AI Functions ---
322
-
323
- def generate_completion(code: str) -> str:
324
- model = load_model()
325
- if not model:
326
- return "# Error: Model failed to load."
327
- prompt = f"<|system|>You are a Python expert. Complete the code. No explanations.</s><|user|>\n{code}\n</s><|assistant|>"
328
  try:
329
- result = model(prompt, num_return_sequences=1, return_full_text=False)
330
- return result[0]["generated_text"].strip()
331
- except Exception as e:
332
- return f"# Error: {str(e)}"
333
-
334
- def explain_code(code: str) -> str:
335
- model = load_model()
336
- if not model:
337
- return "Error loading model."
338
- prompt = f"<|system|>Explain this Python code concisely.</s><|user|>\n{code}\n</s><|assistant|>"
339
- try:
340
- result = model(prompt, max_new_tokens=512)
341
- return result[0]["generated_text"].strip()
342
  except Exception as e:
343
  return str(e)
344
 
345
- def refactor_code(code: str) -> str:
346
- model = load_model()
347
- if not model:
348
- return "Error loading model."
349
- prompt = f"<|system|>Refactor this Python code for PEP 8 and best practices.</s><|user|>\n{code}\n</s><|assistant|>"
350
- try:
351
- result = model(prompt, max_new_tokens=512)
352
- return result[0]["generated_text"].strip()
353
- except Exception as e:
354
- return str(e)
355
-
356
- def generate_code(prompt_text: str) -> str:
357
- model = load_model()
358
- if not model:
359
- return "Error loading model."
360
- prompt = f"<|system|>Write Python code for: {prompt_text}</s><|assistant|>"
361
- try:
362
- result = model(prompt, max_new_tokens=512)
363
- return result[0]["generated_text"].strip()
364
- except Exception as e:
365
- return str(e)
366
 
367
  def create_diff_view(original: str, modified: str) -> str:
368
  import difflib
369
- diff = difflib.unified_diff(original.splitlines(), modified.splitlines(), lineterm="")
370
- return "\n".join(diff)
371
 
372
- # --- Gradio UI ---
 
 
373
 
374
  theme = gr.themes.Default(
375
- font=[gr.themes.GoogleFont("JetBrains Mono"), "monospace"],
376
- primary_hue="blue",
377
  neutral_hue="gray",
378
  ).set(
379
- button_primary_background_fill="*primary_500",
380
- button_primary_background_fill_hover="*primary_600",
381
- body_background_fill="*neutral_900",
382
- body_text_color="*neutral_50",
 
 
 
 
 
 
 
 
 
 
 
383
  )
384
 
385
  css = """
386
- * { border-radius: 0 !important; }
387
- .terminal-box textarea {
388
- background-color: #0a0a0a !important;
389
- color: #00ff41 !important;
390
- font-family: 'JetBrains Mono', monospace !important;
391
- font-size: 13px !important;
392
- line-height: 1.4 !important;
393
- }
394
- .terminal-input input {
395
- background-color: #0a0a0a !important;
396
- color: #00ff41 !important;
397
- font-family: 'JetBrains Mono', monospace !important;
398
- border: 1px solid #1a3a1a !important;
399
- font-size: 13px !important;
400
- }
401
- .terminal-input input::placeholder {
402
- color: #2a6a2a !important;
403
- }
404
- .diff-view { height: 40vh; }
405
- .code-wrap { white-space: pre-wrap !important; }
406
- .status-bar { font-size: 12px; opacity: 0.7; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  """
408
 
409
- with gr.Blocks(title="Axon Pro - Python IDE") as demo:
410
-
411
- gr.Markdown("# 🐍 Axon Pro — Python AI IDE")
412
-
413
- with gr.Tabs() as main_tabs:
414
- with gr.Tab("Editor", id="editor-tab"):
415
- with gr.Row(equal_height=True):
416
- with gr.Column(scale=1, min_width=200):
417
- gr.Markdown("### 📁 Explorer")
418
- file_list = gr.Dropdown(
419
- choices=fs.get_all_files(),
420
- value=fs.current_file,
421
- label="Files",
422
- interactive=True,
423
- container=False,
424
- )
425
- with gr.Row():
426
- new_file_btn = gr.Button("➕ New", variant="secondary")
427
- save_btn = gr.Button("💾 Save", variant="secondary")
428
- new_file_name = gr.Textbox(
429
- placeholder="script.py", label="New File", container=False
430
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
 
432
- with gr.Column(scale=4):
433
- editor = gr.Code(
434
- value=fs.get_current_file_content(),
435
- label="Code Editor",
436
- language="python",
437
- lines=20,
438
- interactive=True,
439
- elem_classes="code-wrap",
 
 
 
 
 
 
440
  )
441
-
442
  with gr.Row():
443
- run_btn = gr.Button("▶ Run", variant="primary")
444
- complete_btn = gr.Button(" Complete", variant="secondary")
445
- explain_btn = gr.Button("📝 Explain", variant="secondary")
446
- refactor_btn = gr.Button("🔧 Refactor", variant="secondary")
447
-
448
- with gr.Tabs():
449
- with gr.Tab("Terminal", id="terminal-tab"):
450
- terminal_output = gr.Textbox(
451
- value=terminal.get_log(),
452
- lines=12,
453
- max_lines=30,
454
- interactive=False,
455
- elem_classes="terminal-box",
456
- label="Terminal",
457
- show_label=False,
458
- )
459
- with gr.Row():
460
- terminal_input = gr.Textbox(
461
- placeholder="$ type any command: pip install pygame, npm i, git clone, ls ...",
462
- scale=8,
463
- container=False,
464
- elem_classes="terminal-input",
465
- )
466
- terminal_run_btn = gr.Button(
467
- "⏎", variant="primary", scale=1, min_width=50
468
- )
469
- clear_btn = gr.Button(
470
- "Clear", variant="secondary", size="sm"
471
- )
472
-
473
- with gr.Tab("AI Chat", id="chat-tab"):
474
- chat_history = gr.Chatbot(
475
- label="Axon AI", height=300
476
- )
477
- with gr.Row():
478
- chat_input = gr.Textbox(
479
- placeholder="Ask to generate Python code...",
480
- scale=7,
481
- container=False,
482
- )
483
- send_btn = gr.Button(
484
- "Generate", variant="primary", scale=1
485
- )
486
-
487
- with gr.Tab("Diff View", id="diff-tab"):
488
- diff_view = gr.Code(
489
- label="AI Changes",
490
- language="python",
491
- elem_classes="diff-view code-wrap",
492
- interactive=False,
493
- )
494
- with gr.Row():
495
- apply_btn = gr.Button("Apply Changes", variant="primary")
496
- discard_btn = gr.Button("Discard Changes", variant="secondary")
497
-
498
  status_bar = gr.Markdown(
499
- "**AXON PRO v2.0** | Python 3.x | CPU Mode | TinyLlama-1.1B | Full PTY Terminal",
 
 
500
  elem_classes="status-bar",
501
  )
502
 
503
- # State
504
- current_file_state = gr.State(fs.current_file)
505
  diff_original_state = gr.State("")
506
  diff_modified_state = gr.State("")
507
- diff_mode_state = gr.State(False)
508
 
509
- # --- Handlers ---
 
 
 
510
  def update_file(content):
511
  fs.save_file(content)
512
- return fs.get_current_file_content()
513
 
514
  def load_file(filename):
515
  fs.set_current_file(filename)
516
- return fs.get_current_file_content(), filename
517
 
518
- def create_file(name):
519
  if name and "." in name:
520
  fs.create_file(name)
521
- return (
522
- gr.update(choices=fs.get_all_files(), value=name),
 
 
523
  fs.get_current_file_content(),
524
- )
525
- return gr.update(choices=fs.get_all_files()), fs.get_current_file_content()
526
 
527
- def terminal_command(cmd):
528
  log = terminal.run_command(cmd)
529
  return log, ""
530
 
531
- def clear_terminal_fn():
532
  terminal.log_lines = []
533
  return ""
534
 
535
- def run_editor_wrapper(content):
536
  return terminal.run_editor_code(content)
537
 
538
- def complete_wrapper(content):
539
- comp = generate_completion(content)
540
- return content + "\n" + comp
541
 
542
- def explain_wrapper(content):
543
  explanation = explain_code(content)
544
  fs.save_file(content)
545
- diff_orig = content
546
- diff_mod = f'"""\nEXPLANATION:\n{explanation}\n"""\n\n{content}'
547
- diff = create_diff_view(diff_orig, diff_mod)
548
- return diff, diff_orig, diff_mod, True
549
 
550
- def refactor_wrapper(content):
551
  refactored = refactor_code(content)
552
  fs.save_file(content)
553
- diff = create_diff_view(content, refactored)
554
- return diff, content, refactored, True
555
 
556
- def generate_wrapper(prompt_text, history):
557
  generated = generate_code(prompt_text)
558
  diff = create_diff_view("", generated)
559
  new_history = history + [
560
  {"role": "user", "content": prompt_text},
561
- {"role": "assistant", "content": "Generated code is available in the Diff View tab."},
562
  ]
563
- return diff, "", generated, True, new_history, ""
 
 
 
 
 
 
 
 
 
 
564
 
565
- # --- Wiring ---
566
  editor.change(update_file, editor, None)
567
- file_list.change(load_file, file_list, [editor, current_file_state])
568
- new_file_btn.click(create_file, new_file_name, [file_list, editor])
569
 
570
- terminal_input.submit(
571
- terminal_command, terminal_input, [terminal_output, terminal_input]
572
- )
573
- terminal_run_btn.click(
574
- terminal_command, terminal_input, [terminal_output, terminal_input]
575
- )
576
- clear_btn.click(clear_terminal_fn, None, terminal_output)
577
 
578
- run_btn.click(run_editor_wrapper, editor, terminal_output)
579
- complete_btn.click(complete_wrapper, editor, editor)
 
580
 
581
  explain_btn.click(
582
- explain_wrapper,
583
- editor,
584
- [diff_view, diff_original_state, diff_modified_state, diff_mode_state],
585
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
586
 
587
  refactor_btn.click(
588
- refactor_wrapper,
589
- editor,
590
- [diff_view, diff_original_state, diff_modified_state, diff_mode_state],
591
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
592
 
 
593
  chat_input.submit(
594
- generate_wrapper,
595
- [chat_input, chat_history],
596
- [diff_view, diff_original_state, diff_modified_state, diff_mode_state, chat_history, chat_input],
597
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
598
 
599
  send_btn.click(
600
- generate_wrapper,
601
- [chat_input, chat_history],
602
- [diff_view, diff_original_state, diff_modified_state, diff_mode_state, chat_history, chat_input],
603
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
604
 
 
605
  apply_btn.click(
606
- lambda mod: (mod, False), diff_modified_state, [editor, diff_mode_state]
607
- ).then(lambda: gr.Tabs(selected="editor-tab"), None, main_tabs)
608
 
609
  discard_btn.click(
610
- lambda: (gr.update(), False), None, [editor, diff_mode_state]
611
- ).then(lambda: gr.Tabs(selected="editor-tab"), None, main_tabs)
 
 
 
612
 
613
 
614
  if __name__ == "__main__":
615
  import atexit
616
  atexit.register(terminal.cleanup)
 
617
 
618
  demo.launch(
619
- server_name="0.0.0.0",
620
- server_port=7860,
621
- theme=theme,
622
- css=css,
623
- ssr_mode=False,
624
  )
 
14
  import termios
15
  import threading
16
  import re
17
+ import base64
18
+ import tempfile
19
  from io import StringIO
20
+ from pathlib import Path
21
  from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, GenerationConfig
22
+ from typing import List
23
  from functools import lru_cache
24
 
25
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
 
28
  MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
29
  DEVICE = "cpu"
30
  DTYPE = torch.float32
31
+ DISPLAY_NUM = ":99"
32
+ SCREEN_W, SCREEN_H = 800, 600
33
+
34
+ # --- Virtual Display Setup ---
35
+ class VirtualDisplay:
36
+ """Manages Xvfb virtual framebuffer for capturing GUI app output."""
37
+
38
+ def __init__(self):
39
+ self.xvfb_proc = None
40
+ self.display = DISPLAY_NUM
41
+ self.width = SCREEN_W
42
+ self.height = SCREEN_H
43
+ self._start_xvfb()
44
+
45
+ def _start_xvfb(self):
46
+ """Start Xvfb virtual framebuffer."""
47
+ try:
48
+ # Kill any existing Xvfb on this display
49
+ subprocess.run(["pkill", "-f", f"Xvfb {self.display}"],
50
+ capture_output=True, timeout=5)
51
+ time.sleep(0.2)
52
+
53
+ self.xvfb_proc = subprocess.Popen(
54
+ ["Xvfb", self.display, "-screen", "0",
55
+ f"{self.width}x{self.height}x24", "-ac", "-nolisten", "tcp"],
56
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
57
+ )
58
+ time.sleep(0.5)
59
+
60
+ # Verify it started
61
+ if self.xvfb_proc.poll() is not None:
62
+ print("[VirtualDisplay] Xvfb failed to start, trying to install...")
63
+ subprocess.run(["apt-get", "update", "-qq"], capture_output=True, timeout=60)
64
+ subprocess.run(["apt-get", "install", "-y", "-qq", "xvfb", "x11-utils",
65
+ "imagemagick"], capture_output=True, timeout=120)
66
+ self.xvfb_proc = subprocess.Popen(
67
+ ["Xvfb", self.display, "-screen", "0",
68
+ f"{self.width}x{self.height}x24", "-ac", "-nolisten", "tcp"],
69
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
70
+ )
71
+ time.sleep(0.5)
72
+
73
+ if self.xvfb_proc.poll() is None:
74
+ print(f"[VirtualDisplay] Xvfb running on {self.display}")
75
+ os.environ["DISPLAY"] = self.display
76
+ else:
77
+ print("[VirtualDisplay] Xvfb could not start")
78
+ self.xvfb_proc = None
79
+
80
+ except FileNotFoundError:
81
+ print("[VirtualDisplay] Xvfb not found, installing...")
82
+ try:
83
+ subprocess.run(["apt-get", "update", "-qq"], capture_output=True, timeout=60)
84
+ subprocess.run(["apt-get", "install", "-y", "-qq", "xvfb", "x11-utils",
85
+ "imagemagick"], capture_output=True, timeout=120)
86
+ self._start_xvfb()
87
+ except Exception as e:
88
+ print(f"[VirtualDisplay] Install failed: {e}")
89
+ self.xvfb_proc = None
90
+ except Exception as e:
91
+ print(f"[VirtualDisplay] Error: {e}")
92
+ self.xvfb_proc = None
93
+
94
+ def capture_screenshot(self) -> str | None:
95
+ """Capture current display as base64 PNG. Returns None on failure."""
96
+ if self.xvfb_proc is None or self.xvfb_proc.poll() is not None:
97
+ return None
98
+ try:
99
+ tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
100
+ tmp.close()
101
+ # Try xdotool + import (ImageMagick)
102
+ result = subprocess.run(
103
+ ["import", "-window", "root", "-display", self.display, tmp.name],
104
+ capture_output=True, timeout=5
105
+ )
106
+ if result.returncode == 0 and os.path.getsize(tmp.name) > 100:
107
+ return tmp.name
108
+ os.unlink(tmp.name)
109
+ except Exception:
110
+ pass
111
+ return None
112
+
113
+ @property
114
+ def is_running(self) -> bool:
115
+ return self.xvfb_proc is not None and self.xvfb_proc.poll() is None
116
+
117
+ def cleanup(self):
118
+ if self.xvfb_proc:
119
+ self.xvfb_proc.terminate()
120
+ try:
121
+ self.xvfb_proc.wait(timeout=3)
122
+ except subprocess.TimeoutExpired:
123
+ self.xvfb_proc.kill()
124
+
125
+ vdisplay = VirtualDisplay()
126
 
127
  # --- File System ---
128
  class FileSystem:
 
155
 
156
  fs = FileSystem()
157
 
158
+ # --- PTY Terminal ---
159
  class PTYTerminal:
 
 
 
 
 
 
160
  STRIP_ANSI = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b\[.*?[@-~]|\r')
161
 
162
  def __init__(self):
 
166
  self.log_lines: List[str] = []
167
  self.max_lines = 800
168
  self._spawn_shell()
169
+ self._append_line("AXON TERMINAL v3.0")
170
+ self._append_line("══════════════════════════════════════")
171
+ self._append_line("Full PTY shell everything works:")
172
+ self._append_line(" pip/npm/apt-get/git/curl/wget/make")
173
+ self._append_line(" python/node GUI apps render to Display")
174
+ self._append_line("══════════════════════════════════════")
 
 
175
 
176
  def _spawn_shell(self):
 
177
  try:
178
  pid, fd = pty.openpty()
179
  self.pid = os.fork()
180
  if self.pid == 0:
 
181
  os.close(pid)
182
  os.setsid()
183
  slave_fd = fd
 
191
  env["TERM"] = "dumb"
192
  env["PS1"] = "$ "
193
  env["DEBIAN_FRONTEND"] = "noninteractive"
194
+ env["DISPLAY"] = DISPLAY_NUM
195
  os.execvpe("/bin/bash", ["/bin/bash", "--norc", "--noprofile", "-i"], env)
196
  else:
 
197
  os.close(fd)
198
  self.master_fd = pid
199
  flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
 
203
  time.sleep(0.3)
204
  self._read_output()
205
  except Exception as e:
206
+ self._append_line(f"[Shell Error] {e}")
207
  self.master_fd = None
208
 
209
  def _read_output(self, timeout: float = 0.1) -> str:
 
210
  if self.master_fd is None:
211
  return ""
212
  output = []
 
232
  return "".join(output)
233
 
234
  def _clean_output(self, text: str) -> str:
 
235
  cleaned = self.STRIP_ANSI.sub("", text)
236
  cleaned = "".join(ch for ch in cleaned if ch == "\n" or ch == "\t" or (ord(ch) >= 32))
237
  return cleaned
 
245
  return "\n".join(self.log_lines)
246
 
247
  def run_command(self, cmd: str) -> str:
 
248
  cmd = cmd.strip()
249
  if not cmd:
250
  return self.get_log()
 
251
  with self.lock:
252
  if cmd.lower() == "clear":
253
  self.log_lines = []
254
  return ""
 
255
  if self.master_fd is None:
256
  self._append_line(f"$ {cmd}")
257
+ self._append_line("[Error] No shell. Restart Space.")
258
  return self.get_log()
 
 
259
  self._read_output(0.05)
 
260
  self._append_line(f"$ {cmd}")
 
261
  try:
262
  os.write(self.master_fd, (cmd + "\n").encode())
263
  except OSError as e:
264
  self._append_line(f"[Write Error] {e}")
265
  return self.get_log()
 
266
  parts = cmd.split()
267
  base = parts[0].lower() if parts else ""
268
+ long_cmds = ["pip", "pip3", "npm", "npx", "apt-get", "apt", "git",
269
+ "wget", "curl", "make", "cmake", "cargo", "yarn", "conda"]
270
+ wait = 180 if base in long_cmds else (60 if base in ("python", "python3", "node") else 15)
 
 
 
 
 
 
 
271
  all_output = []
272
  start = time.time()
273
+ idle = 0
274
+ while time.time() - start < wait:
 
275
  chunk = self._read_output(0.3)
276
  if chunk:
277
+ idle = 0
278
  all_output.append(chunk)
279
+ if "".join(all_output).rstrip().endswith("$"):
 
280
  break
281
  else:
282
+ idle += 1
283
+ if base not in long_cmds and idle >= 3:
284
  break
285
+ if base in long_cmds and idle >= 10:
286
  break
 
287
  raw = "".join(all_output)
288
  cleaned = self._clean_output(raw)
 
289
  lines = cleaned.split("\n")
290
  filtered = []
291
  skip_echo = True
292
  for line in lines:
293
+ s = line.strip()
294
+ if skip_echo and s == cmd.strip():
295
  skip_echo = False
296
  continue
297
+ if s in ("$", "$ "):
298
  continue
299
  filtered.append(line)
 
300
  result = "\n".join(filtered).strip()
301
  if result:
302
  self._append_line(result)
 
303
  return self.get_log()
304
 
305
  def run_editor_code(self, code: str) -> str:
 
306
  with self.lock:
307
  self._append_line("$ python [editor]")
308
+ tmp = "/tmp/_axon_run.py"
 
309
  try:
310
+ with open(tmp, "w") as f:
311
  f.write(code)
312
  except Exception as e:
313
  with self.lock:
314
+ self._append_line(f"[Error] {e}")
315
  return self.get_log()
 
316
  try:
317
+ env = os.environ.copy()
318
+ env["DISPLAY"] = DISPLAY_NUM
319
  result = subprocess.run(
320
+ [sys.executable, tmp], capture_output=True, text=True,
321
+ timeout=30, env=env
322
  )
323
  with self.lock:
324
  if result.stdout.strip():
 
329
  self._append_line("(No output)")
330
  except subprocess.TimeoutExpired:
331
  with self.lock:
332
+ self._append_line("[Timed out 30s]")
333
  except Exception as e:
334
  with self.lock:
335
  self._append_line(f"[Error] {e}")
 
336
  return self.get_log()
337
 
338
  def cleanup(self):
 
352
  # --- Model Loading ---
353
  @lru_cache(maxsize=1)
354
  def load_model():
355
+ print("Loading TinyLlama...")
356
+ t0 = time.time()
357
  try:
358
+ tok = AutoTokenizer.from_pretrained(MODEL_NAME)
359
+ mdl = AutoModelForCausalLM.from_pretrained(
360
+ MODEL_NAME, dtype=DTYPE, device_map=DEVICE, low_cpu_mem_usage=True)
361
+ gc = GenerationConfig(max_new_tokens=256, temperature=0.2, top_p=0.95, do_sample=True)
362
+ pipe = pipeline("text-generation", model=mdl, tokenizer=tok, generation_config=gc)
363
+ print(f"Model loaded in {time.time()-t0:.1f}s")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  return pipe
365
  except Exception as e:
366
+ print(f"Model error: {e}")
367
  return None
368
 
369
+ # --- AI helpers ---
370
+ def _llm(system: str, user: str, max_tok: int = 512) -> str:
371
+ m = load_model()
372
+ if not m:
373
+ return "Error: model not loaded"
374
+ prompt = f"<|system|>{system}</s><|user|>\n{user}\n</s><|assistant|>"
 
375
  try:
376
+ r = m(prompt, max_new_tokens=max_tok)
377
+ return r[0]["generated_text"].strip()
 
 
 
 
 
 
 
 
 
 
 
378
  except Exception as e:
379
  return str(e)
380
 
381
+ def generate_completion(code): return _llm("Complete this Python code. No explanations.", code, 256)
382
+ def explain_code(code): return _llm("Explain this Python code concisely.", code)
383
+ def refactor_code(code): return _llm("Refactor for PEP 8 and best practices.", code)
384
+ def generate_code(prompt): return _llm(f"Write Python code for: {prompt}", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
  def create_diff_view(original: str, modified: str) -> str:
387
  import difflib
388
+ return "\n".join(difflib.unified_diff(original.splitlines(), modified.splitlines(), lineterm=""))
 
389
 
390
+ # ═══════════════════════════════════════════════════════════
391
+ # GRADIO UI — Industrial Hacker Aesthetic
392
+ # ═══════════════════════════════════════════════════════════
393
 
394
  theme = gr.themes.Default(
395
+ font=[gr.themes.GoogleFont("IBM Plex Mono"), "monospace"],
396
+ primary_hue="cyan",
397
  neutral_hue="gray",
398
  ).set(
399
+ body_background_fill="#080808",
400
+ body_text_color="#e0e0e0",
401
+ button_primary_background_fill="#0ff",
402
+ button_primary_background_fill_hover="#0dd",
403
+ button_primary_text_color="#000",
404
+ button_secondary_background_fill="#1a1a1a",
405
+ button_secondary_background_fill_hover="#2a2a2a",
406
+ button_secondary_text_color="#0ff",
407
+ button_secondary_border_color="#0ff40",
408
+ block_background_fill="#0d0d0d",
409
+ block_border_color="#1a1a1a",
410
+ block_label_text_color="#0f8",
411
+ input_background_fill="#0a0a0a",
412
+ input_border_color="#1a3a2a",
413
+ input_text_color="#0f8",
414
  )
415
 
416
  css = """
417
+ /* ═══ GLOBAL ═══ */
418
+ * { border-radius: 0 !important; }
419
+ .gradio-container { max-width: 100% !important; }
420
+
421
+ /* ═══ HEADER ═══ */
422
+ .axon-header {
423
+ background: linear-gradient(90deg, #080808 0%, #0a1a1a 50%, #080808 100%);
424
+ border-bottom: 1px solid #0ff3;
425
+ padding: 12px 20px !important;
426
+ margin-bottom: 8px;
427
+ }
428
+ .axon-header h1 {
429
+ font-family: 'IBM Plex Mono', monospace !important;
430
+ background: linear-gradient(90deg, #0ff, #0f8);
431
+ -webkit-background-clip: text;
432
+ -webkit-text-fill-color: transparent;
433
+ font-size: 1.4em !important;
434
+ letter-spacing: 3px;
435
+ margin: 0 !important;
436
+ }
437
+ .axon-header p {
438
+ color: #0f84 !important;
439
+ font-size: 0.75em;
440
+ letter-spacing: 1px;
441
+ }
442
+
443
+ /* ═══ PANELS ═══ */
444
+ .panel-section {
445
+ border: 1px solid #1a2a2a !important;
446
+ background: #0a0a0a !important;
447
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
448
+ }
449
+ .panel-section:hover {
450
+ border-color: #0ff3 !important;
451
+ }
452
+
453
+ /* ═══ TERMINAL ═══ */
454
+ .terminal-box textarea {
455
+ background: #050505 !important;
456
+ color: #00ff41 !important;
457
+ font-family: 'IBM Plex Mono', monospace !important;
458
+ font-size: 12px !important;
459
+ line-height: 1.5 !important;
460
+ border: 1px solid #0f31 !important;
461
+ text-shadow: 0 0 4px #0f32;
462
+ }
463
+ .terminal-input input {
464
+ background: #050505 !important;
465
+ color: #00ff41 !important;
466
+ font-family: 'IBM Plex Mono', monospace !important;
467
+ border: 1px solid #0f32 !important;
468
+ font-size: 12px !important;
469
+ caret-color: #0f8 !important;
470
+ }
471
+ .terminal-input input::placeholder { color: #0f83 !important; }
472
+ .terminal-input input:focus { border-color: #0ff !important; box-shadow: 0 0 8px #0ff2 !important; }
473
+
474
+ /* ═══ CODE EDITOR ═══ */
475
+ .code-editor textarea, .code-editor .cm-editor {
476
+ font-family: 'IBM Plex Mono', monospace !important;
477
+ font-size: 13px !important;
478
+ }
479
+
480
+ /* ═══ FILE EXPLORER ═══ */
481
+ .file-explorer {
482
+ border-right: 1px solid #1a2a2a !important;
483
+ }
484
+ .file-explorer .wrap { border-color: #0f32 !important; }
485
+
486
+ /* ═══ DISPLAY PANEL ═══ */
487
+ .display-panel {
488
+ border: 1px solid #1a2a2a !important;
489
+ background: #050505 !important;
490
+ min-height: 200px;
491
+ }
492
+ .display-panel img {
493
+ image-rendering: pixelated;
494
+ }
495
+
496
+ /* ═══ TOOLBAR ═══ */
497
+ .toolbar-btn {
498
+ font-size: 0.8em !important;
499
+ padding: 6px 12px !important;
500
+ letter-spacing: 0.5px;
501
+ text-transform: uppercase;
502
+ font-weight: 600 !important;
503
+ border: 1px solid transparent !important;
504
+ transition: all 0.2s ease !important;
505
+ }
506
+ .toolbar-btn:hover {
507
+ border-color: #0ff !important;
508
+ box-shadow: 0 0 12px #0ff2 !important;
509
+ }
510
+
511
+ /* ═══ COLLAPSIBLE SECTIONS (Accordion) ═══ */
512
+ .gr-accordion { border: 1px solid #1a2a2a !important; background: #0a0a0a !important; }
513
+ .gr-accordion > .label-wrap {
514
+ background: #0d0d0d !important;
515
+ border-bottom: 1px solid #1a2a2a !important;
516
+ transition: background 0.2s ease;
517
+ }
518
+ .gr-accordion > .label-wrap:hover { background: #121a1a !important; }
519
+ .gr-accordion > .label-wrap span {
520
+ color: #0ff !important;
521
+ font-family: 'IBM Plex Mono', monospace !important;
522
+ letter-spacing: 1px;
523
+ font-size: 0.85em;
524
+ }
525
+
526
+ /* ═══ TABS ═══ */
527
+ .tabs > .tab-nav > button {
528
+ font-family: 'IBM Plex Mono', monospace !important;
529
+ letter-spacing: 1px;
530
+ font-size: 0.8em;
531
+ text-transform: uppercase;
532
+ color: #888 !important;
533
+ border-bottom: 2px solid transparent !important;
534
+ transition: all 0.2s ease;
535
+ }
536
+ .tabs > .tab-nav > button.selected {
537
+ color: #0ff !important;
538
+ border-bottom-color: #0ff !important;
539
+ text-shadow: 0 0 8px #0ff4;
540
+ }
541
+
542
+ /* ═══ STATUS BAR ═══ */
543
+ .status-bar {
544
+ background: #0a0a0a !important;
545
+ border-top: 1px solid #1a2a2a;
546
+ padding: 4px 12px !important;
547
+ font-size: 11px !important;
548
+ color: #0f84 !important;
549
+ }
550
+ .status-bar strong { color: #0ff !important; }
551
+
552
+ /* ═══ SCROLLBAR ═══ */
553
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
554
+ ::-webkit-scrollbar-track { background: #0a0a0a; }
555
+ ::-webkit-scrollbar-thumb { background: #0f83; }
556
+ ::-webkit-scrollbar-thumb:hover { background: #0ff; }
557
+
558
+ /* ═══ CHATBOT ═══ */
559
+ .chatbot-container .message { border-radius: 0 !important; font-family: 'IBM Plex Mono', monospace !important; }
560
  """
561
 
562
+ # ═══════════════════════════════════════════════════════════
563
+
564
+ with gr.Blocks(title="Axon Pro — Python AI IDE") as demo:
565
+
566
+ # ── HEADER ──
567
+ with gr.Row(elem_classes="axon-header"):
568
+ gr.Markdown("# ⬡ AXON PRO\n\nPYTHON AI IDE — v3.0")
569
+
570
+ # ── MAIN LAYOUT ──
571
+ with gr.Row(equal_height=False):
572
+
573
+ # ══ LEFT: FILE EXPLORER (Collapsible) ══
574
+ with gr.Column(scale=1, min_width=180, elem_classes="file-explorer"):
575
+ with gr.Accordion("📁 EXPLORER", open=True):
576
+ file_list = gr.Dropdown(
577
+ choices=fs.get_all_files(), value=fs.current_file,
578
+ label="", interactive=True, container=False,
579
+ )
580
+ new_file_name = gr.Textbox(
581
+ placeholder="filename.py", label="", container=False
582
+ )
583
+ with gr.Row():
584
+ new_file_btn = gr.Button("+ NEW", variant="secondary", size="sm",
585
+ elem_classes="toolbar-btn")
586
+ save_btn = gr.Button("SAVE", variant="secondary", size="sm",
587
+ elem_classes="toolbar-btn")
588
+
589
+ # ══ CENTER: EDITOR + TERMINAL ══
590
+ with gr.Column(scale=4):
591
+
592
+ # Code Editor
593
+ editor = gr.Code(
594
+ value=fs.get_current_file_content(),
595
+ label=f" {fs.current_file}",
596
+ language="python", lines=22, interactive=True,
597
+ elem_classes="code-editor",
598
+ )
599
 
600
+ # Toolbar
601
+ with gr.Row():
602
+ run_btn = gr.Button("▶ RUN", variant="primary", elem_classes="toolbar-btn")
603
+ complete_btn = gr.Button(" COMPLETE", variant="secondary", elem_classes="toolbar-btn")
604
+ explain_btn = gr.Button("◈ EXPLAIN", variant="secondary", elem_classes="toolbar-btn")
605
+ refactor_btn = gr.Button("⟲ REFACTOR", variant="secondary", elem_classes="toolbar-btn")
606
+
607
+ # Bottom panels (Collapsible)
608
+ with gr.Tabs() as bottom_tabs:
609
+ with gr.Tab("⌘ TERMINAL", id="terminal-tab"):
610
+ terminal_output = gr.Textbox(
611
+ value=terminal.get_log(), lines=10, max_lines=25,
612
+ interactive=False, elem_classes="terminal-box",
613
+ label="", show_label=False,
614
  )
 
615
  with gr.Row():
616
+ terminal_input = gr.Textbox(
617
+ placeholder="$ pip install pygame | npm i | git clone | ls ...",
618
+ scale=9, container=False, elem_classes="terminal-input",
619
+ )
620
+ terminal_run_btn = gr.Button("⏎", variant="primary", scale=1, min_width=45)
621
+ clear_btn = gr.Button("CLEAR", variant="secondary", size="sm",
622
+ elem_classes="toolbar-btn")
623
+
624
+ with gr.Tab("💬 AI CHAT", id="chat-tab"):
625
+ chat_history = gr.Chatbot(label="", height=250,
626
+ elem_classes="chatbot-container")
627
+ with gr.Row():
628
+ chat_input = gr.Textbox(
629
+ placeholder="Describe the code you want...",
630
+ scale=8, container=False,
631
+ )
632
+ send_btn = gr.Button("GEN", variant="primary", scale=1,
633
+ elem_classes="toolbar-btn")
634
+
635
+ with gr.Tab("⇄ DIFF", id="diff-tab"):
636
+ diff_view = gr.Code(label="AI Changes", language="python",
637
+ interactive=False, lines=15)
638
+ with gr.Row():
639
+ apply_btn = gr.Button("✓ APPLY", variant="primary",
640
+ elem_classes="toolbar-btn")
641
+ discard_btn = gr.Button("✗ DISCARD", variant="secondary",
642
+ elem_classes="toolbar-btn")
643
+
644
+ # ══ RIGHT: DISPLAY PANEL (Collapsible) ══
645
+ with gr.Column(scale=2, min_width=250):
646
+ with gr.Accordion("🖥 DISPLAY OUTPUT", open=True):
647
+ gr.Markdown(
648
+ "<small style='color:#0f84'>GUI apps (pygame, tkinter, etc.) "
649
+ "render here via virtual display</small>"
650
+ )
651
+ display_image = gr.Image(
652
+ label="", type="filepath", interactive=False,
653
+ elem_classes="display-panel", height=400,
654
+ )
655
+ with gr.Row():
656
+ capture_btn = gr.Button("📸 CAPTURE", variant="secondary", size="sm",
657
+ elem_classes="toolbar-btn")
658
+ auto_refresh = gr.Checkbox(label="Auto-refresh", value=False)
659
+
660
+ display_status = gr.Markdown(
661
+ f"<small style='color:#0f84'>Xvfb: "
662
+ f"{'● running' if vdisplay.is_running else '○ stopped'} "
663
+ f"| {SCREEN_W}x{SCREEN_H}</small>"
664
+ )
665
+
666
+ # ── STATUS BAR ──
 
 
 
 
667
  status_bar = gr.Markdown(
668
+ f"**AXON PRO v3.0** Python {sys.version.split()[0]} CPU "
669
+ f"TinyLlama-1.1B │ PTY Shell │ "
670
+ f"Xvfb {'ON' if vdisplay.is_running else 'OFF'}",
671
  elem_classes="status-bar",
672
  )
673
 
674
+ # ── STATE ──
 
675
  diff_original_state = gr.State("")
676
  diff_modified_state = gr.State("")
 
677
 
678
+ # ══════════════════════════════════════════
679
+ # HANDLERS
680
+ # ══════════════════════════════════════════
681
+
682
  def update_file(content):
683
  fs.save_file(content)
 
684
 
685
  def load_file(filename):
686
  fs.set_current_file(filename)
687
+ return fs.get_current_file_content(), gr.update(label=f" {filename}")
688
 
689
+ def create_file_fn(name):
690
  if name and "." in name:
691
  fs.create_file(name)
692
+ return (gr.update(choices=fs.get_all_files(), value=name),
693
+ fs.get_current_file_content(),
694
+ gr.update(label=f" {name}"))
695
+ return (gr.update(choices=fs.get_all_files()),
696
  fs.get_current_file_content(),
697
+ gr.update())
 
698
 
699
+ def terminal_cmd(cmd):
700
  log = terminal.run_command(cmd)
701
  return log, ""
702
 
703
+ def clear_term():
704
  terminal.log_lines = []
705
  return ""
706
 
707
+ def run_editor(content):
708
  return terminal.run_editor_code(content)
709
 
710
+ def complete_fn(content):
711
+ return content + "\n" + generate_completion(content)
 
712
 
713
+ def explain_fn(content):
714
  explanation = explain_code(content)
715
  fs.save_file(content)
716
+ orig = content
717
+ mod = f'"""\nEXPLANATION:\n{explanation}\n"""\n\n{content}'
718
+ return create_diff_view(orig, mod), orig, mod
 
719
 
720
+ def refactor_fn(content):
721
  refactored = refactor_code(content)
722
  fs.save_file(content)
723
+ return create_diff_view(content, refactored), content, refactored
 
724
 
725
+ def generate_fn(prompt_text, history):
726
  generated = generate_code(prompt_text)
727
  diff = create_diff_view("", generated)
728
  new_history = history + [
729
  {"role": "user", "content": prompt_text},
730
+ {"role": "assistant", "content": "Code generated check DIFF tab"},
731
  ]
732
+ return diff, "", generated, new_history, ""
733
+
734
+ def capture_display():
735
+ path = vdisplay.capture_screenshot()
736
+ if path:
737
+ return path
738
+ return None
739
+
740
+ # ══════════════════════════════════════════
741
+ # WIRING
742
+ # ══════════════════════════════════════════
743
 
 
744
  editor.change(update_file, editor, None)
745
+ file_list.change(load_file, file_list, [editor, editor])
746
+ new_file_btn.click(create_file_fn, new_file_name, [file_list, editor, editor])
747
 
748
+ # Terminal
749
+ terminal_input.submit(terminal_cmd, terminal_input, [terminal_output, terminal_input])
750
+ terminal_run_btn.click(terminal_cmd, terminal_input, [terminal_output, terminal_input])
751
+ clear_btn.click(clear_term, None, terminal_output)
 
 
 
752
 
753
+ # Editor actions
754
+ run_btn.click(run_editor, editor, terminal_output)
755
+ complete_btn.click(complete_fn, editor, editor)
756
 
757
  explain_btn.click(
758
+ explain_fn, editor,
759
+ [diff_view, diff_original_state, diff_modified_state]
760
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
 
761
 
762
  refactor_btn.click(
763
+ refactor_fn, editor,
764
+ [diff_view, diff_original_state, diff_modified_state]
765
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
 
766
 
767
+ # Chat
768
  chat_input.submit(
769
+ generate_fn, [chat_input, chat_history],
770
+ [diff_view, diff_original_state, diff_modified_state, chat_history, chat_input],
771
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
 
772
 
773
  send_btn.click(
774
+ generate_fn, [chat_input, chat_history],
775
+ [diff_view, diff_original_state, diff_modified_state, chat_history, chat_input],
776
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
 
777
 
778
+ # Diff actions
779
  apply_btn.click(
780
+ lambda mod: mod, diff_modified_state, editor
781
+ ).then(lambda: gr.Tabs(selected="terminal-tab"), None, bottom_tabs)
782
 
783
  discard_btn.click(
784
+ lambda: gr.update(), None, editor
785
+ ).then(lambda: gr.Tabs(selected="terminal-tab"), None, bottom_tabs)
786
+
787
+ # Display capture
788
+ capture_btn.click(capture_display, None, display_image)
789
 
790
 
791
  if __name__ == "__main__":
792
  import atexit
793
  atexit.register(terminal.cleanup)
794
+ atexit.register(vdisplay.cleanup)
795
 
796
  demo.launch(
797
+ server_name="0.0.0.0", server_port=7860,
798
+ theme=theme, css=css, ssr_mode=False,
 
 
 
799
  )