Files
local-api-docs-search/src/local_api_docs_search/cli/interactive.py
7000pctAUTO 2716c44094
Some checks failed
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / build (push) Has been cancelled
fix: resolve CI/CD issues with proper package structure and imports
2026-02-03 03:54:36 +00:00

213 lines
6.2 KiB
Python

"""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 = []