fix: resolve CI/CD issues with proper package structure and imports
This commit is contained in:
212
src/local_api_docs_search/cli/interactive.py
Normal file
212
src/local_api_docs_search/cli/interactive.py
Normal file
@@ -0,0 +1,212 @@
|
||||
"""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 = []
|
||||
Reference in New Issue
Block a user