"""Interactive search mode with Rich-powered UI.""" from typing import List, Optional from rich.console import Console from rich.prompt import Prompt from rich.text import Text from rich.panel import Panel from rich import box from local_api_docs_search.models.document import SearchResult from local_api_docs_search.search.searcher import Searcher from local_api_docs_search.utils.formatters import get_source_style console = Console() class InteractiveSession: """Interactive search session with history and navigation.""" def __init__(self): """Initialize the interactive session.""" self._searcher = Searcher() self._history: List[str] = [] self._history_index: int = -1 self._results: List[SearchResult] = [] self._result_index: int = 0 self._current_query: str = "" def run(self): """Run the interactive session.""" self._print_welcome() while True: try: query = self._get_input() if query is None: break if not query.strip(): continue self._history.append(query) self._history_index = len(self._history) self._execute_search(query) except KeyboardInterrupt: console.print("\n[italic]Use 'exit' or 'quit' to leave[/]") except EOFError: break console.print("\n[italic]Goodbye![/]") def _print_welcome(self): """Print welcome message.""" welcome_text = Text.assemble( ("Local API Docs Search\n", "bold cyan"), ("-" * 40, "dim\n"), ("Type your query and press Enter to search.\n", "white"), ("Commands:\n", "bold yellow"), (" :q, quit, exit - Leave interactive mode\n", "dim"), (" :h, help - Show this help\n", "dim"), (" :c, clear - Clear search results\n", "dim"), (" :n, next - Next result\n", "dim"), (" :p, prev - Previous result\n", "dim"), (" ↑/↓ - History navigation\n", "dim"), ) panel = Panel(welcome_text, title="Welcome", expand=False) console.print(panel) def _get_input(self) -> Optional[str]: """Get user input with history navigation.""" prompt = Prompt.ask( "[bold cyan]Search[/]", default="", show_default=False, accept_default=False, ) if prompt in (":q", ":quit", "quit", "exit", "exit()"): return None if prompt in (":h", ":help", "help"): self._print_welcome() return "" if prompt in (":c", ":clear", "clear"): self._results = [] console.print("[italic]Results cleared[/]") return "" if prompt in (":n", ":next", "next"): self._navigate_results(1) return "" if prompt in (":p", ":prev", "previous"): self._navigate_results(-1) return "" return prompt def _execute_search(self, query: str): """Execute search and display results.""" self._current_query = query self._result_index = 0 with console.status("Searching..."): self._results = self._searcher.hybrid_search(query, limit=10) if not self._results: console.print("[italic]No results found[/]\n") return console.print(f"\n[bold]Found {len(self._results)} result(s)[/]\n") self._display_current_result() def _display_current_result(self): """Display the current result.""" if not self._results: return result = self._results[self._result_index] source_style = get_source_style(result.document.source_type) content = Text() content.append(f"Result {self._result_index + 1}/{len(self._results)}\n", "bold yellow") content.append(f"Title: {result.document.title}\n", "bold") content.append(f"Type: {result.document.source_type.value}\n", source_style) content.append(f"Score: {result.score:.4f}\n\n", "dim") preview = result.document.content[:500] if len(result.document.content) > 500: preview += "..." content.append(preview) if result.document.file_path: content.append(f"\n\n[dim]File: {result.document.file_path}[/]") panel = Panel( content, title=f"Result {self._result_index + 1}", expand=False, box=box.ROUNDED, ) console.print(panel) if result.highlights: console.print("\n[bold]Highlights:[/]") for highlight in result.highlights[:3]: console.print(f" [dim]{highlight}[/]") console.print() def _navigate_results(self, direction: int): """Navigate through search results.""" if not self._results: console.print("[italic]No results to navigate[/]") return new_index = self._result_index + direction if new_index < 0: new_index = 0 elif new_index >= len(self._results): new_index = len(self._results) - 1 self._result_index = new_index self._display_current_result() def run_interactive(): """Run the interactive search mode.""" session = InteractiveSession() session.run() class InteractiveSearch: """Legacy interactive search class for compatibility.""" def __init__(self): """Initialize the interactive search.""" self._searcher = Searcher() self._history: List[str] = [] def search(self, query: str) -> List[SearchResult]: """Execute search. Args: query: Search query Returns: List of search results """ self._history.append(query) return self._searcher.hybrid_search(query) def get_history(self) -> List[str]: """Get search history. Returns: List of past queries """ return self._history def clear_history(self): """Clear search history.""" self._history = []