|
| 1 | + |
| 2 | +import os |
| 3 | +import time |
| 4 | +import pyperclip |
| 5 | +from rich.console import Console |
| 6 | +from rich.panel import Panel |
| 7 | +from rich.markdown import Markdown |
| 8 | +from rich.live import Live |
| 9 | +from rich.table import Table |
| 10 | +from rich.spinner import Spinner |
| 11 | +from rich.align import Align |
| 12 | +from ..config import Config |
| 13 | +from ..core.extractor import CodeExtractor |
| 14 | +from .banner import Banner |
| 15 | + |
| 16 | +class UI: |
| 17 | + """Advanced Terminal User Interface using Rich""" |
| 18 | + |
| 19 | + def __init__(self): |
| 20 | + self.console = Console() |
| 21 | + |
| 22 | + def clear(self): |
| 23 | + os.system('cls' if os.name == 'nt' else 'clear') |
| 24 | + |
| 25 | + def banner(self): |
| 26 | + self.clear() |
| 27 | + Banner.print_banner(self.console) |
| 28 | + |
| 29 | + def main_menu(self): |
| 30 | + table = Table(show_header=False, box=None, padding=(0, 2)) |
| 31 | + table.add_column("Icon", style="bold yellow", justify="right") |
| 32 | + table.add_column("Option", style="bold white") |
| 33 | + |
| 34 | + table.add_row("[1]", "Initialize Uplink (Start Chat)") |
| 35 | + table.add_row("[2]", "Configure Security Keys (API Setup)") |
| 36 | + table.add_row("[3]", "System Manifesto (About)") |
| 37 | + table.add_row("[4]", "Terminate Session (Exit)") |
| 38 | + |
| 39 | + panel = Panel( |
| 40 | + Align.center(table), |
| 41 | + title="[bold cyan]MAIN MENU[/bold cyan]", |
| 42 | + border_style="bright_blue", |
| 43 | + padding=(1, 5) |
| 44 | + ) |
| 45 | + self.console.print(panel) |
| 46 | + |
| 47 | + def show_msg(self, title: str, content: str, color: str = "white"): |
| 48 | + self.console.print(Panel(content, title=f"[bold]{title}[/]", border_style=color)) |
| 49 | + |
| 50 | + def get_input(self, label: str = "COMMAND") -> str: |
| 51 | + prompt_style = Config.Colors.USER_PROMPT |
| 52 | + self.console.print(f"[{prompt_style}]┌──({label})-[~][/]") |
| 53 | + return self.console.input(f"[{prompt_style}]└─> [/]") |
| 54 | + |
| 55 | + def stream_markdown(self, title: str, content_generator): |
| 56 | + """ |
| 57 | + Renders Markdown content in real-time as it streams. |
| 58 | + """ |
| 59 | + full_response = "" |
| 60 | + |
| 61 | + with Live( |
| 62 | + Panel(Spinner("dots", text="Decryption in progress..."), title=title, border_style="cyan"), |
| 63 | + console=self.console, |
| 64 | + refresh_per_second=12, |
| 65 | + transient=False |
| 66 | + ) as live: |
| 67 | + |
| 68 | + for chunk in content_generator: |
| 69 | + full_response += chunk |
| 70 | + |
| 71 | + # Clean format for display |
| 72 | + display_text = full_response.replace("[HacxGPT]:", "").replace("[CODE]:", "").strip() |
| 73 | + if not display_text: display_text = "..." |
| 74 | + |
| 75 | + md = Markdown(display_text, code_theme=Config.CODE_THEME) |
| 76 | + |
| 77 | + live.update( |
| 78 | + Panel( |
| 79 | + md, |
| 80 | + title=f"[bold cyan]{title}[/bold cyan] [dim](Stream Active)[/dim]", |
| 81 | + border_style="cyan" |
| 82 | + ) |
| 83 | + ) |
| 84 | + |
| 85 | + display_text = full_response.replace("[HacxGPT]:", "").replace("[CODE]:", "").strip() |
| 86 | + live.update( |
| 87 | + Panel( |
| 88 | + Markdown(display_text, code_theme=Config.CODE_THEME), |
| 89 | + title=f"[bold green]{title}[/bold green] [bold]✓[/]", |
| 90 | + border_style="green" |
| 91 | + ) |
| 92 | + ) |
| 93 | + |
| 94 | + return full_response |
| 95 | + |
| 96 | + def handle_code_blocks(self, response_text: str): |
| 97 | + """Handle code block extraction and user actions with Pro UI""" |
| 98 | + code_blocks = CodeExtractor.extract_code_blocks(response_text) |
| 99 | + |
| 100 | + if not code_blocks: |
| 101 | + return |
| 102 | + |
| 103 | + self.console.print(Panel(f"[bold yellow]🔍 Detected {len(code_blocks)} code block(s)[/]", border_style="yellow")) |
| 104 | + |
| 105 | + # Display code blocks info |
| 106 | + table = Table(show_header=True, header_style="bold magenta", border_style="dim white", expand=True) |
| 107 | + table.add_column("#", style="cyan", justify="center", width=4) |
| 108 | + table.add_column("Language", style="green") |
| 109 | + table.add_column("Preview", style="dim white") |
| 110 | + table.add_column("Lines", style="yellow", justify="right") |
| 111 | + |
| 112 | + for idx, (lang, code) in enumerate(code_blocks, 1): |
| 113 | + lines = code.split('\n') |
| 114 | + preview = lines[0].strip()[:50] + "..." if len(lines[0]) > 50 else lines[0].strip() |
| 115 | + table.add_row(str(idx), lang.upper(), preview, str(len(lines))) |
| 116 | + |
| 117 | + self.console.print(table) |
| 118 | + |
| 119 | + # Pro Menu |
| 120 | + menu_text = """ |
| 121 | +[bold cyan]Options:[/bold cyan] |
| 122 | +[bold green][1][/] Save All [bold green][2][/] Copy All [bold green][3][/] Save One [bold green][4][/] Copy One [bold red][SEMICOLON/Space][/] Skip |
| 123 | +""" |
| 124 | + self.console.print(Panel(menu_text.strip(), border_style="blue", title="[bold]Action Menu[/]")) |
| 125 | + self.console.print("[dim]Press the corresponding key...[/]") |
| 126 | + |
| 127 | + while True: |
| 128 | + import msvcrt |
| 129 | + key = msvcrt.getch() |
| 130 | + try: |
| 131 | + char = key.decode("utf-8").lower() |
| 132 | + except: |
| 133 | + continue |
| 134 | + |
| 135 | + if char == '1': |
| 136 | + self._save_all_blocks(code_blocks) |
| 137 | + break |
| 138 | + elif char == '2': |
| 139 | + self._copy_all_blocks(code_blocks) |
| 140 | + break |
| 141 | + elif char == '3': |
| 142 | + self._save_specific_block_interactive(code_blocks) |
| 143 | + break |
| 144 | + elif char == '4': |
| 145 | + self._copy_specific_block_interactive(code_blocks) |
| 146 | + break |
| 147 | + elif char == ' ' or char == ';': |
| 148 | + self.console.print("[yellow]Skipped.[/]") |
| 149 | + break |
| 150 | + |
| 151 | + def _save_specific_block_interactive(self, code_blocks): |
| 152 | + self.console.print("[bold cyan]Press the number of the block to save (1-9)...[/]") |
| 153 | + while True: |
| 154 | + import msvcrt |
| 155 | + key = msvcrt.getch() |
| 156 | + try: |
| 157 | + char = key.decode("utf-8") |
| 158 | + if char.isdigit() and 1 <= int(char) <= len(code_blocks): |
| 159 | + idx = int(char) - 1 |
| 160 | + lang, code = code_blocks[idx] |
| 161 | + filepath = CodeExtractor.save_code_block(code, lang, idx) |
| 162 | + self.console.print(f"[bold green]✓ Saved Block {char} to: {filepath}[/]") |
| 163 | + break |
| 164 | + except: |
| 165 | + pass |
| 166 | + |
| 167 | + def _copy_specific_block_interactive(self, code_blocks): |
| 168 | + self.console.print("[bold cyan]Press the number of the block to copy (1-9)...[/]") |
| 169 | + while True: |
| 170 | + import msvcrt |
| 171 | + key = msvcrt.getch() |
| 172 | + try: |
| 173 | + char = key.decode("utf-8") |
| 174 | + if char.isdigit() and 1 <= int(char) <= len(code_blocks): |
| 175 | + idx = int(char) - 1 |
| 176 | + lang, code = code_blocks[idx] |
| 177 | + pyperclip.copy(code) |
| 178 | + self.console.print(f"[bold green]✓ Block {char} copied to clipboard![/]") |
| 179 | + break |
| 180 | + except: |
| 181 | + pass |
0 commit comments