Source code for klea_utils.ui.tui.repl

#!/usr/bin/env python3
"""
Shared async REPL for Klea chat interfaces.

File: klea_utils/ui/tui/repl.py

Copyright 2026 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
"""

import logging


[docs] async def run_repl( url: str, title: str, single_query: str = "", app_prefix: str = "klea", app_name: str = "klea-tui", ) -> None: """Run an interactive or single-query chat REPL. Connects to the API server's SSE ``/query/stream`` endpoint and displays progress labels and the final answer. In interactive mode each query is sent as the user types it; the REPL does not batch queries. :param url: Base URL of the API server (e.g. ``http://127.0.0.1:8005``) :param title: Application title displayed on start :param single_query: If set, run one query and exit instead of REPL loop :param app_prefix: Prefix for user/assistant labels (e.g. ``"klea"``) :param app_name: Log identity for this frontend, used as the log file name so each process keeps its own logs (e.g. ``"klea-rag-tui"``) """ # Configure process-wide logging for this client process. Lazy: # platformdirs / plogging imports are cheap, and everything else is # deferred below so --help stays fast. from platformdirs import PlatformDirs from klea_utils.plogging import setup_root_logger setup_root_logger( app_name, stderr_level=logging.INFO, log_dir=PlatformDirs(app_name).user_data_dir, ) # Lazy: avoids importing yaspin (and its deps) at module level import coolname from yaspin import yaspin from klea_utils.api.sse import stream_events from klea_utils.api.utils import check_api_is_ready chat_id = coolname.generate_slug(2) with yaspin(text="Waiting for API..."): await check_api_is_ready(f"{url}/health/ready") async def _query_one(query: str) -> None: full_response = "" error_msg = "" print() with yaspin(text="Working ...", timer=True) as spinner: async for event in stream_events(query, chat_id, url): if event["type"] == "progress": spinner.text = event["node"] elif event["type"] == "complete": full_response = event.get("message_for_user", "") spinner.ok("[OK]") elif event["type"] == "error": error_msg = event.get("message", "Unknown server error") spinner.fail("[ERROR]") break output = error_msg or full_response label = "(ERROR)" if error_msg else "(AI)" print(f"{app_prefix} {label} >>> {output}") print("\n" + "-" * 40 + "\n") if single_query: print(f"{app_prefix} (USER) >>> {single_query}") await _query_one(single_query) return print(f"*** {title} ({url}) ***") print("Please note that answers are generated by LLMs and may be incorrect.") print() print("Type 'quit' to exit.") print("\n" + "-" * 40 + "\n") while True: q = input(f"{app_prefix} (USER) >>> ") if q.lower() == "quit": break await _query_one(q) print(f"\n{app_prefix} >>> Bye!")