| |
| """ |
| Project Jarvis — Command Line Interface |
| Interactive CLI for testing Jarvis from the terminal. |
| Supports both text and voice modes. |
| """ |
|
|
| import sys |
| import os |
| import time |
|
|
| |
| sys.path.insert(0, os.path.dirname(__file__)) |
|
|
| from rich.console import Console |
| from rich.panel import Panel |
| from rich.text import Text |
| from rich.table import Table |
| from rich.live import Live |
| from rich import box |
|
|
|
|
| console = Console() |
|
|
|
|
| def print_banner(): |
| banner = """ |
| ╔══════════════════════════════════════════════════╗ |
| ║ ║ |
| ║ ██╗ █████╗ ██████╗ ██╗ ██╗██╗███████╗ ║ |
| ║ ██║██╔══██╗██╔══██╗██║ ██║██║██╔════╝ ║ |
| ║ ██║███████║██████╔╝██║ ██║██║███████╗ ║ |
| ║ ██ ██║██╔══██║██╔══██╗╚██╗ ██╔╝██║╚════██║ ║ |
| ║ ╚█████╔╝██║ ██║██║ ██║ ╚████╔╝ ██║███████║ ║ |
| ║ ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚══════╝ ║ |
| ║ ║ |
| ║ Personal AI Assistant — 100% Local ║ |
| ║ ║ |
| ╚══════════════════════════════════════════════════╝ |
| """ |
| console.print(banner, style="bold blue") |
|
|
|
|
| def init_services(): |
| """Initialize all Jarvis services.""" |
| console.print("\n[bold yellow]Initializing Jarvis...[/bold yellow]\n") |
|
|
| from app.core import brain |
| from config import settings |
|
|
| table = Table(box=box.ROUNDED, title="Service Status", title_style="bold") |
| table.add_column("Service", style="cyan") |
| table.add_column("Status", style="green") |
| table.add_column("Detail") |
|
|
| try: |
| brain.init() |
| statuses = brain.get_all_status() |
| for svc in statuses: |
| status_style = { |
| "ready": "[green]✓ Ready[/green]", |
| "error": "[red]✗ Error[/red]", |
| "not_loaded": "[yellow]◌ Not loaded[/yellow]", |
| "unavailable": "[dim]— Unavailable[/dim]", |
| }.get(svc["status"], f"[dim]{svc['status']}[/dim]") |
|
|
| table.add_row(svc["name"].upper(), status_style, svc.get("detail", "")) |
|
|
| console.print(table) |
| console.print() |
| return True |
| except Exception as e: |
| console.print(f"[bold red]Initialization error: {e}[/bold red]") |
| return False |
|
|
|
|
| def text_mode(): |
| """Interactive text command mode.""" |
| from app.core import brain |
|
|
| console.print("[bold green]Text mode active.[/bold green] Type your commands below.") |
| console.print("[dim]Type 'quit' to exit, 'voice' to switch to voice mode, 'status' for service status.[/dim]\n") |
|
|
| session_id = "cli-session" |
|
|
| while True: |
| try: |
| user_input = console.input("[bold cyan]You:[/bold cyan] ").strip() |
|
|
| if not user_input: |
| continue |
|
|
| if user_input.lower() in ("quit", "exit", "q"): |
| console.print("[dim]Goodbye, sir.[/dim]") |
| break |
|
|
| if user_input.lower() == "voice": |
| voice_mode() |
| continue |
|
|
| if user_input.lower() == "status": |
| show_status() |
| continue |
|
|
| if user_input.lower() == "clear": |
| console.clear() |
| print_banner() |
| continue |
|
|
| |
| start = time.time() |
| result = brain.process_text(user_input, session_id=session_id) |
| elapsed = (time.time() - start) * 1000 |
|
|
| response = result.get("response_text", "I couldn't process that.") |
| intent = result.get("intent", "unknown") |
|
|
| console.print() |
| console.print(f"[bold magenta]Jarvis:[/bold magenta] {response}") |
| console.print(f"[dim] intent: {intent} | {elapsed:.0f}ms[/dim]\n") |
|
|
| except KeyboardInterrupt: |
| console.print("\n[dim]Interrupted. Type 'quit' to exit.[/dim]") |
| except EOFError: |
| break |
|
|
|
|
| def voice_mode(): |
| """Voice command mode — records, transcribes, processes, speaks.""" |
| from app.core import brain |
| from app.services import tts |
|
|
| console.print("\n[bold green]Voice mode active.[/bold green] Speak after the prompt.") |
| console.print("[dim]Press Ctrl+C to stop and return to text mode.[/dim]\n") |
|
|
| session_id = "cli-voice" |
|
|
| while True: |
| try: |
| console.print("[bold yellow]🎤 Listening...[/bold yellow] (speak now)") |
|
|
| |
| result = brain.voice_loop_once() |
|
|
| transcription = result.get("transcription", "") |
| response = result.get("response_text", "") |
| intent = result.get("intent", "unknown") |
|
|
| if transcription: |
| console.print(f"[bold cyan]You said:[/bold cyan] {transcription}") |
| console.print(f"[bold magenta]Jarvis:[/bold magenta] {response}") |
| console.print(f"[dim] intent: {intent} | {result.get('timing', {}).get('total_ms', 0):.0f}ms[/dim]\n") |
|
|
| except KeyboardInterrupt: |
| console.print("\n[dim]Returning to text mode...[/dim]\n") |
| break |
|
|
|
|
| def show_status(): |
| """Show service status.""" |
| from app.core import brain |
|
|
| table = Table(box=box.ROUNDED, title="Service Status") |
| table.add_column("Service", style="cyan") |
| table.add_column("Status") |
| table.add_column("Detail") |
|
|
| for svc in brain.get_all_status(): |
| status_icon = {"ready": "✓", "error": "✗", "unavailable": "—"}.get(svc["status"], "?") |
| status_style = {"ready": "green", "error": "red", "unavailable": "dim"}.get(svc["status"], "yellow") |
| table.add_row( |
| svc["name"].upper(), |
| f"[{status_style}]{status_icon} {svc['status']}[/{status_style}]", |
| svc.get("detail", ""), |
| ) |
|
|
| console.print(table) |
| console.print() |
|
|
|
|
| def main(): |
| print_banner() |
|
|
| if not init_services(): |
| console.print("[bold red]Failed to initialize. Make sure Ollama is running.[/bold red]") |
| console.print("[dim]Run: ollama serve[/dim]") |
| sys.exit(1) |
|
|
| |
| if len(sys.argv) > 1: |
| if sys.argv[1] == "--voice": |
| voice_mode() |
| return |
| else: |
| |
| from app.core import brain |
| text = " ".join(sys.argv[1:]) |
| result = brain.process_text(text) |
| console.print(f"[bold magenta]Jarvis:[/bold magenta] {result['response_text']}") |
| return |
|
|
| |
| text_mode() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|