Initial commit: Add Local API Docs Search CLI tool
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-03 01:18:59 +00:00
parent 6be0dde4fb
commit a597ecabeb

216
src/cli/interactive.py Normal file
View File

@@ -0,0 +1,216 @@
"""Interactive search mode with Rich-powered UI."""
import os
from pathlib import Path
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.table import Table
from rich import box
from src.models.document import SourceType, Document, SearchResult
from src.search.searcher import Searcher
from src.utils.config import get_config
from src.utils.formatters import format_search_results, 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 = []