Robovojtik/ui.py
sinuhet 2e2a18272a Oprava, po které to ale blbne
Po příkazu se ptá stále dokola
2025-03-22 23:24:23 +01:00

263 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Module: ui.py
-------------
Obsahuje interaktivní rozhraní Robovojtíka založené na knihovně curses.
Implementuje barevné oddělení, trvalou hlavičku, historii chatu,
spinner pro indikaci čekání a vstupní oblast. Také prefix 'readfile:'.
"""
import curses
import time
import threading
import logging
import re
import api_interface
import shell_functions
import markdown_parser
logger = logging.getLogger("robovojtik.ui")
# Globální funkce pro integraci s main.py
FUNCTIONS = []
def set_functions(funcs):
global FUNCTIONS
FUNCTIONS = funcs
automode = False
def main_curses(stdscr):
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Hlavička
curses.init_pair(2, curses.COLOR_WHITE, -1) # Chat
curses.init_pair(3, curses.COLOR_GREEN, -1) # Výstup
curses.init_pair(4, curses.COLOR_YELLOW, -1) # Vstup
curses.init_pair(5, curses.COLOR_CYAN, -1) # Spinner
curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE) # Oddělovač
curses.curs_set(1)
stdscr.nodelay(False)
stdscr.clear()
height, width = stdscr.getmaxyx()
header_height = 3
prompt_height = 2
left_width = width // 2
right_width = width - left_width - 1
chat_height = height - header_height - prompt_height
header_win = curses.newwin(header_height, left_width, 0, 0)
chat_win = curses.newwin(chat_height, left_width, header_height, 0)
prompt_win = curses.newwin(prompt_height, width, height - prompt_height, 0)
output_win = curses.newwin(height - prompt_height, right_width, 0, left_width + 1)
divider_win = curses.newwin(height - prompt_height, 1, 0, left_width)
header_win.bkgd(' ', curses.color_pair(1))
chat_win.bkgd(' ', curses.color_pair(2))
prompt_win.bkgd(' ', curses.color_pair(4))
output_win.bkgd(' ', curses.color_pair(3))
divider_win.bkgd(' ', curses.color_pair(6))
d_height, _ = divider_win.getmaxyx()
for y in range(d_height):
try:
divider_win.addch(y, 0, curses.ACS_VLINE)
except curses.error:
pass
divider_win.refresh()
header_text = [
"Vítejte v Robovojtikovi!",
"Zadejte dotaz, příkaz (prefix 'cmd:'), 'automat', 'readfile:' apod.",
"Pro ukončení zadejte 'vypni' nebo 'exit'."
]
max_chars = max(0, left_width - 2)
for idx, line in enumerate(header_text):
try:
header_win.addnstr(idx, 1, line, max_chars)
except curses.error:
pass
header_win.refresh()
chat_win.scrollok(True)
output_win.scrollok(True)
prompt_win.scrollok(True)
output_win.box()
output_win.refresh()
def spinner_func(ch):
mid_x = left_width // 2 - 5
try:
header_win.addnstr(1, mid_x, f"Čekám... {ch}", left_width - mid_x - 1,
curses.color_pair(5) | curses.A_BOLD)
header_win.refresh()
except curses.error:
pass
def add_to_chat(text):
segments = markdown_parser.parse_markdown(text)
for seg_text, seg_attr in segments:
try:
chat_win.addstr(seg_text, seg_attr)
except curses.error:
pass
chat_win.refresh()
add_to_chat("Historie chatu:")
global automode
while True:
prompt_win.erase()
try:
prompt_win.addstr(">> ", curses.A_BOLD)
except curses.error:
pass
prompt_win.refresh()
curses.echo()
try:
user_input = prompt_win.getstr().decode("utf-8").strip()
except Exception:
continue
curses.noecho()
if not user_input:
continue
add_to_chat("Ty: " + user_input)
# Ukončení
if user_input.lower() in ["vypni", "exit"]:
add_to_chat("Ukončuji Robovojtíka...")
time.sleep(1)
break
# Přepnutí automódu
if user_input.lower() == "automat":
automode = not automode
stav = "zapnut" if automode else "vypnut"
add_to_chat(f"Automód byl nyní {stav}.")
continue
# readfile:
if user_input.lower().startswith("readfile:"):
path = user_input[9:].strip()
add_to_chat(f"Načítám soubor: {path}")
from shell_functions import read_file_content
content = read_file_content(path)
add_to_chat(f"Obsah souboru:\n{content}")
assistant_response = api_interface.volani_asistenta(
f"Analyzuj obsah tohoto souboru:\n{content}",
spinner_func=spinner_func
)
add_to_chat("Robovojtík odpovídá:\n" + assistant_response)
continue
# Přímý příkaz cmd:
if user_input.startswith("cmd:"):
command = user_input[4:].strip()
add_to_chat(f"Rozpoznán příkaz: {command}")
if not automode:
prompt_win.erase()
try:
prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD)
except curses.error:
pass
prompt_win.refresh()
try:
potvrzeni = prompt_win.getstr().decode("utf-8").strip().lower()
except Exception:
potvrzeni = ""
curses.flushinp() # vyprázdní vstupní buffer
add_to_chat("Ty: " + potvrzeni)
if potvrzeni not in ("y", "yes", "ano"):
add_to_chat("Příkaz nebyl spuštěn.")
continue
output = shell_functions.run_command_locally(command)
output_win.erase()
try:
lines = output.splitlines()
y = 1
for line in lines:
output_win.addstr(y, 1, line)
y += 1
output_win.box()
output_win.refresh()
except curses.error:
pass
summary_prompt = (f"Analyzuj následující výstup příkazu. Neposkytuj žádné návrhy příkazů, "
f"jen shrň výsledky a uveď komentář:\n{output}")
assistant_summary = api_interface.volani_asistenta(summary_prompt, spinner_func=spinner_func)
add_to_chat("Robovojtík shrnuje výstup:\n" + assistant_summary)
continue
# Ostatní dotazy -> Asistent
assistant_response = api_interface.volani_asistenta(user_input, spinner_func=spinner_func)
add_to_chat("Robovojtík odpovídá:\n" + assistant_response)
# Pokud asistent navrhne příkaz, extrahujeme jej
match = re.search(r"`([^`]+)`", assistant_response)
if match:
navrhovany_prikaz = match.group(1)
elif assistant_response.strip().lower().startswith("navrhovaný příkaz:"):
lines = assistant_response.splitlines()
proposal_line = lines[0]
navrhovany_prikaz = proposal_line[len("navrhovaný příkaz:"):].strip()
else:
navrhovany_prikaz = None
if navrhovany_prikaz:
add_to_chat(f"Navrhovaný příkaz: {navrhovany_prikaz}")
if not automode:
prompt_win.erase()
try:
prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD)
except curses.error:
pass
prompt_win.refresh()
try:
potvrzeni = prompt_win.getstr().decode("utf-8").strip().lower()
except Exception:
potvrzeni = ""
curses.flushinp() # vyprázdní vstupní buffer
add_to_chat("Ty: " + potvrzeni)
if potvrzeni not in ("y", "yes", "ano"):
add_to_chat("Příkaz nebyl spuštěn.")
continue
output = shell_functions.run_command_locally(navrhovany_prikaz)
output_win.erase()
try:
lines = output.splitlines()
y = 1
for line in lines:
output_win.addstr(y, 1, line)
y += 1
output_win.box()
output_win.refresh()
except curses.error:
pass
summary_prompt = (
f"Výsledek příkazu:\n{output}\n\n"
"Prosím, stručně shrň výše uvedený výstup příkazu a "
"uveď pouze komentář bez návrhu dalších příkazů."
)
assistant_summary = api_interface.volani_asistenta(summary_prompt, spinner_func=spinner_func)
add_to_chat("Robovojtík shrnuje výstup:\n" + assistant_summary)
continue
prompt_win.erase()
prompt_win.refresh()
def main_ui():
try:
curses.wrapper(main_curses)
except Exception as e:
logger.exception("Neočekávaná chyba v curses wrapperu.")
print(f"Chyba: {str(e)}")