Add shellgen UI and safety modules
This commit is contained in:
163
app/shellgen/ui/console.py
Normal file
163
app/shellgen/ui/console.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""Console UI using Rich library."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.text import Text
|
||||||
|
from rich.syntax import Syntax
|
||||||
|
from rich.prompt import Prompt, Confirm
|
||||||
|
from rich.table import Table
|
||||||
|
from rich import box
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleUI:
|
||||||
|
"""Rich-based console interface for ShellGen."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize console with Rich."""
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def print_header(self) -> None:
|
||||||
|
"""Print the application header."""
|
||||||
|
header = Text()
|
||||||
|
header.append("ShellGen", style="bold magenta")
|
||||||
|
header.append(" - ", style="dim")
|
||||||
|
header.append("Natural Language to Shell Command", style="italic")
|
||||||
|
|
||||||
|
self.console.print(Panel(header, expand=False))
|
||||||
|
|
||||||
|
def display_generated_command(
|
||||||
|
self, command: str, explanation: str, language: str = "bash"
|
||||||
|
) -> None:
|
||||||
|
"""Display the generated command with explanation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: The generated shell command.
|
||||||
|
explanation: Brief explanation of the command.
|
||||||
|
language: Syntax highlighting language.
|
||||||
|
"""
|
||||||
|
self.console.print("\n[bold green]Generated Command:[/bold green]")
|
||||||
|
|
||||||
|
syntax = Syntax(command, language, theme="monokai", line_numbers=False)
|
||||||
|
self.console.print(Panel(syntax, expand=False, border_style="green"))
|
||||||
|
|
||||||
|
self.console.print(f"\n[bold]Explanation:[/bold] {explanation}\n")
|
||||||
|
|
||||||
|
def display_history(self, entries: List[dict]) -> None:
|
||||||
|
"""Display command history.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entries: List of history entry dictionaries.
|
||||||
|
"""
|
||||||
|
if not entries:
|
||||||
|
self.console.print("[yellow]No command history found.[/yellow]")
|
||||||
|
return
|
||||||
|
|
||||||
|
table = Table(
|
||||||
|
title="Command History",
|
||||||
|
show_header=True,
|
||||||
|
header_style="bold magenta",
|
||||||
|
box=box.ROUNDED,
|
||||||
|
)
|
||||||
|
|
||||||
|
table.add_column("ID", width=5, justify="right")
|
||||||
|
table.add_column("Prompt", width=40)
|
||||||
|
table.add_column("Command", width=40)
|
||||||
|
table.add_column("Shell", width=10)
|
||||||
|
table.add_column("Executed", width=10)
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
executed = "✓" if entry.get("executed") else "✗"
|
||||||
|
style = "green" if entry.get("executed") else "red"
|
||||||
|
|
||||||
|
table.add_row(
|
||||||
|
str(entry.get("id", 0)),
|
||||||
|
entry.get("prompt", "")[:37] + "..." if len(entry.get("prompt", "")) > 40 else entry.get("prompt", ""),
|
||||||
|
entry.get("command", "")[:37] + "..." if len(entry.get("command", "")) > 40 else entry.get("command", ""),
|
||||||
|
entry.get("shell", ""),
|
||||||
|
f"[{style}]{executed}[/{style}]",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.console.print(table)
|
||||||
|
|
||||||
|
def confirm_execution(self) -> bool:
|
||||||
|
"""Prompt user for execution confirmation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if user confirmed, False otherwise.
|
||||||
|
"""
|
||||||
|
return Confirm.ask("Execute this command?")
|
||||||
|
|
||||||
|
def print_safety_warning(self, warning: str) -> None:
|
||||||
|
"""Print a safety warning.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
warning: The warning message.
|
||||||
|
"""
|
||||||
|
self.console.print(
|
||||||
|
Panel(
|
||||||
|
Text(warning, style="bold red"),
|
||||||
|
title="⚠️ Safety Warning",
|
||||||
|
border_style="red",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def print_cancelled(self) -> None:
|
||||||
|
"""Print cancelled message."""
|
||||||
|
self.console.print("[yellow]Command execution cancelled.[/yellow]")
|
||||||
|
|
||||||
|
def print_error(self, message: str) -> None:
|
||||||
|
"""Print an error message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Error message to display.
|
||||||
|
"""
|
||||||
|
self.console.print(
|
||||||
|
Panel(
|
||||||
|
Text(message, style="bold red"),
|
||||||
|
title="Error",
|
||||||
|
border_style="red",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute_command(self, command: str) -> None:
|
||||||
|
"""Execute a command and show output.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: Command to execute.
|
||||||
|
"""
|
||||||
|
self.console.print(
|
||||||
|
f"\n[bold]Executing:[/bold] [cyan]{command}[/cyan]\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
command,
|
||||||
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.stdout:
|
||||||
|
self.console.print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
self.console.print(f"[red]{result.stderr}[/red]", highlight=False)
|
||||||
|
|
||||||
|
self.console.print(
|
||||||
|
f"\n[dim]Exit code: {result.returncode}[/dim]\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self.console.print("[yellow]Command timed out after 30 seconds[/yellow]")
|
||||||
|
except Exception as e:
|
||||||
|
self.console.print(f"[red]Execution error: {e}[/red]")
|
||||||
|
|
||||||
|
def print_feedback_submitted(self) -> None:
|
||||||
|
"""Print feedback submission confirmation."""
|
||||||
|
self.console.print(
|
||||||
|
"[green]✓[/green] Feedback submitted successfully"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user