mohamedgamal04
QueryQuest — Hugging Face Spaces deploy (Docker)
0cac9cf
Raw
History Blame Contribute Delete
5.31 kB
"""Application entrypoint for QueryQuest.
Author: mohamedgamal04
"""
import sys
import time
from pathlib import Path
from dotenv import load_dotenv
from rich.console import Console
from rich.prompt import Prompt
from rich.panel import Panel
from rich import box
from .chat_session import run_chat_session
from .cli import normalize_prompt_input, parse_args, print_banner
from .config import EXCEL_INFO_FORMAT_VERSION, PROVIDERS_BY_NAME, SYSTEM_PROMPT
from .excel.context import (
build_excel_files_info,
describe_excel_snapshot_changes,
format_excel_context,
get_excel_snapshot,
list_excel_files,
normalize_excel_dir,
)
from .setup_flow import run_setup
from .state import load_state, save_state
CONSOLE = Console()
SNAPSHOT_CHECK_INTERVAL_SECONDS = 2.0
MAX_EXCEL_CONTEXT_CHARS = 5000
def main() -> None:
"""Initialize configuration and run the interactive chat session."""
load_dotenv()
print_banner(CONSOLE)
options = parse_args(sys.argv[1:], CONSOLE)
state = load_state()
if options.setup or state is None:
state = run_setup(CONSOLE)
excel_default = state.get("excel_dir") if state else None
excel_dir_input = Prompt.ask(
"Excel files location",
default=excel_default or str(normalize_excel_dir()),
console=CONSOLE,
).strip()
excel_dir = normalize_excel_dir(Path(excel_dir_input))
config = PROVIDERS_BY_NAME[state["provider"]]
CONSOLE.print(
Panel(
f"[bold]Provider[/bold]: [cyan]{config.name}[/cyan]\n"
f"[bold]Model[/bold]: [bright_white]{state['model']}[/bright_white]",
border_style="bright_cyan",
box=box.ROUNDED,
padding=(0, 1),
)
)
excel_info_cache = {
"signature": state.get("excel_signature") if state else None,
"info": state.get("excel_info") if state else None,
"format_version": state.get("excel_info_format_version") if state else None,
"snapshot": None,
"change_note": "",
"last_snapshot_check_at": 0.0,
}
is_info_stale = excel_info_cache["format_version"] != EXCEL_INFO_FORMAT_VERSION
if not excel_info_cache["signature"] or not excel_info_cache["info"] or is_info_stale:
excel_info_cache["info"], excel_info_cache["signature"], excel_info_cache["snapshot"] = build_excel_files_info(excel_dir)
excel_info_cache["format_version"] = EXCEL_INFO_FORMAT_VERSION
save_state(
state["provider"],
state["api_key"],
state["model"],
excel_dir=str(excel_dir),
excel_signature=excel_info_cache["signature"],
excel_info=excel_info_cache["info"],
excel_info_format_version=EXCEL_INFO_FORMAT_VERSION,
)
else:
_, excel_info_cache["snapshot"] = get_excel_snapshot(excel_dir)
excel_info_cache["last_snapshot_check_at"] = time.monotonic()
def get_system_prompt() -> str:
"""Return a system prompt enriched with current Excel metadata.
The prompt cache is refreshed when the file snapshot signature changes.
"""
now = time.monotonic()
should_refresh_snapshot = (now - excel_info_cache["last_snapshot_check_at"]) >= SNAPSHOT_CHECK_INTERVAL_SECONDS
if should_refresh_snapshot or excel_info_cache["snapshot"] is None:
current_signature, current_snapshot = get_excel_snapshot(excel_dir)
excel_info_cache["last_snapshot_check_at"] = now
if current_signature != excel_info_cache["signature"]:
# Rebuild context when workbook set/content changed on disk.
previous_snapshot = excel_info_cache["snapshot"]
info, signature, snapshot = build_excel_files_info(excel_dir)
excel_info_cache["signature"] = signature
excel_info_cache["info"] = info
excel_info_cache["format_version"] = EXCEL_INFO_FORMAT_VERSION
excel_info_cache["snapshot"] = snapshot
excel_info_cache["change_note"] = describe_excel_snapshot_changes(previous_snapshot, snapshot)
save_state(
state["provider"],
state["api_key"],
state["model"],
excel_dir=str(excel_dir),
excel_signature=signature,
excel_info=info,
excel_info_format_version=EXCEL_INFO_FORMAT_VERSION,
)
elif excel_info_cache["snapshot"] is None:
excel_info_cache["snapshot"] = current_snapshot
prompt_parts = [SYSTEM_PROMPT]
if excel_info_cache["change_note"]:
prompt_parts.append(excel_info_cache["change_note"])
if excel_info_cache["info"]:
excel_context = format_excel_context(excel_info_cache["info"])
if len(excel_context) > MAX_EXCEL_CONTEXT_CHARS:
excel_context = f"{excel_context[:MAX_EXCEL_CONTEXT_CHARS]}\n... [excel context truncated for speed]"
prompt_parts.append(excel_context)
return "\n\n".join(part for part in prompt_parts if part)
prompt, prompt_flag_only = normalize_prompt_input(options.prompt or "")
if prompt_flag_only:
CONSOLE.print("Please provide prompt text after -p/--prompt.")
prompt = ""
if options.setup and not prompt:
CONSOLE.print(
Panel(
"[green]Setup complete.[/green] Type prompts below.\n"
"Use [cyan]QQ -q[/cyan] or [cyan]QQ -quit[/cyan] to exit.",
border_style="bright_green",
box=box.ROUNDED,
padding=(0, 1),
)
)
run_chat_session(
console=CONSOLE,
provider_name=config.name,
provider_base_url=config.base_url,
model_name=state["model"],
api_key=state["api_key"],
initial_prompt=prompt,
system_prompt_provider=get_system_prompt,
excel_file_count_provider=lambda: len(list_excel_files(excel_dir)),
excel_dir=excel_dir,
)
if __name__ == "__main__":
main()