From 2e2a18272afb1fb0ff00a83417c56332c731b591 Mon Sep 17 00:00:00 2001 From: sinuhet Date: Sat, 22 Mar 2025 23:24:23 +0100 Subject: [PATCH] =?UTF-8?q?Oprava,=20po=20kter=C3=A9=20to=20ale=20blbne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Po příkazu se ptá stále dokola --- api_interface.py | 134 ++++++++++++++++++++++++++---------- main.py | 68 ++++++++++++++++++- markdown_parser.py | 63 ++--------------- shell_functions.py | 43 +++++++++--- ui.py | 166 ++++++++++++++++++++++++++++++--------------- 5 files changed, 315 insertions(+), 159 deletions(-) diff --git a/api_interface.py b/api_interface.py index eba9721..4171b3b 100644 --- a/api_interface.py +++ b/api_interface.py @@ -14,16 +14,17 @@ from configparser import ConfigParser logger = logging.getLogger("robovojtik.api_interface") config = ConfigParser() config.read("config.ini") + try: openai.api_key = config["OpenAI"]["api_key"] except KeyError: - raise ValueError("API key not found in config.ini. Please set the 'api_key' under [OpenAI] section or set OPENAI_API_KEY environment variable.") - + raise ValueError("API key not found in config.ini. Zadejte 'api_key' do [OpenAI].") + ASSISTANT_ID = config["OpenAI"].get("assistant_id", None) if not ASSISTANT_ID: - raise ValueError("assistant_id not found in config.ini. Please set the 'assistant_id' under [OpenAI] section.") + raise ValueError("assistant_id not found in config.ini. Zadejte 'assistant_id' do [OpenAI].") -# Globální proměnná pro uchování ID konverzačního vlákna +# Globální vlákno konverzace thread_id = None def vytvor_nove_vlakno(): @@ -31,19 +32,26 @@ def vytvor_nove_vlakno(): Vytvoří nové vlákno konverzace s asistentem pomocí OpenAI API. """ global thread_id - thread = openai.beta.threads.create() - thread_id = thread.id - logger.debug(f"Vytvořeno nové vlákno: {thread_id}") - return thread_id + try: + thread = openai.beta.threads.create() + thread_id = thread.id + logger.debug(f"Vytvořeno nové vlákno: {thread_id}") + return thread_id + except Exception as e: + logger.exception("Chyba při vytváření nového vlákna konverzace.") + raise def posli_dotaz_do_assistenta(prompt): """ Odesílá dotaz v přirozeném jazyce do asistenta a vrací jeho odpověď. + Ošetřuje stav "queued", "running", "pending" i "in_progress" tak, aby cyklus čekal na dokončení. + Vrací poslední zprávu od asistenta. """ global thread_id if thread_id is None: vytvor_nove_vlakno() logger.debug(f"Odesílám dotaz: {prompt}") + openai.beta.threads.messages.create( thread_id=thread_id, role="user", @@ -55,20 +63,75 @@ def posli_dotaz_do_assistenta(prompt): ) while True: run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id) - if run_status.status == "completed": + if run_status.status == "requires_action": + handle_required_action(run_status, run.id, thread_id) + elif run_status.status == "completed": break - time.sleep(1) + elif run_status.status in ["queued", "running", "pending", "in_progress"]: + time.sleep(1) + else: + logger.warning(f"Neočekávaný stav run_status: {run_status.status}") + time.sleep(1) + break + messages = openai.beta.threads.messages.list(thread_id=thread_id) - answer = messages.data[0].content[0].text.value + answer = "" + for msg in reversed(messages.data): + if msg.role == "assistant" and msg.content and msg.content[0].text: + answer = msg.content[0].text.value + break logger.debug(f"Asistent odpověděl: {answer}") return answer +def handle_required_action(run_status, run_id, thread_id): + """ + Vyřídí volání funkcí, které asistent vyžaduje (execute_shell_command, create_script, read_file). + """ + from shell_functions import spust_prikaz, vytvor_skript, read_file_content + + try: + tool_calls = run_status.required_action.submit_tool_outputs.tool_calls + tool_outputs = [] + for tool_call in tool_calls: + tool_name = tool_call.function.name + logger.debug(f"Asistent volá funkci: {tool_name}") + try: + arguments = json.loads(tool_call.function.arguments) + except Exception as e: + logger.exception("Chyba při parsování argumentů pro funkci.") + tool_outputs.append({ + "tool_call_id": tool_call.id, + "output": "Chyba: Nepodařilo se přečíst argumenty." + }) + continue + + if tool_name == "execute_shell_command": + vysledek = spust_prikaz(arguments["command"]) + elif tool_name == "create_script": + vysledek = vytvor_skript(arguments["file_name"], arguments["content"]) + elif tool_name == "read_file": + vysledek = read_file_content(arguments["path"]) + else: + vysledek = "Neznámá funkce." + + tool_outputs.append({ + "tool_call_id": tool_call.id, + "output": vysledek + }) + + openai.beta.threads.runs.submit_tool_outputs( + thread_id=thread_id, + run_id=run_id, + tool_outputs=tool_outputs + ) + except Exception as e: + logger.exception("Chyba při handle_required_action.") + def posli_prikaz_do_assistenta(command): """ - Odesílá schválený příkaz k vykonání do asistenta a vrací jeho odpověď. - Před odesláním se příkaz vyčistí. + Odesílá schválený příkaz k vykonání do asistenta (např. cmd:). + Ošetřuje stavy podobně jako posli_dotaz_do_assistenta. """ - from shell_functions import spust_prikaz, vytvor_skript # Importujeme zde, abychom předešli cyklickým závislostem. global thread_id if thread_id is None: vytvor_nove_vlakno() @@ -81,38 +144,28 @@ def posli_prikaz_do_assistenta(command): while True: run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id) if run_status.status == "requires_action": - tool_calls = run_status.required_action.submit_tool_outputs.tool_calls - tool_outputs = [] - for tool_call in tool_calls: - tool_name = tool_call.function.name - arguments = json.loads(tool_call.function.arguments) - if tool_name == "execute_shell_command": - vysledek = spust_prikaz(arguments["command"]) - elif tool_name == "create_script": - vysledek = vytvor_skript(arguments["file_name"], arguments["content"]) - else: - vysledek = "Neznámá funkce." - tool_outputs.append({ - "tool_call_id": tool_call.id, - "output": vysledek - }) - openai.beta.threads.runs.submit_tool_outputs( - thread_id=thread_id, - run_id=run.id, - tool_outputs=tool_outputs - ) + handle_required_action(run_status, run.id, thread_id) elif run_status.status == "completed": break - time.sleep(1) + elif run_status.status in ["queued", "running", "pending", "in_progress"]: + time.sleep(1) + else: + logger.warning(f"Neočekávaný stav run_status: {run_status.status}") + time.sleep(1) + break messages = openai.beta.threads.messages.list(thread_id=thread_id) - answer = messages.data[0].content[0].text.value + answer = "" + for msg in reversed(messages.data): + if msg.role == "assistant" and msg.content and msg.content[0].text: + answer = msg.content[0].text.value + break logger.debug(f"Výsledek spuštěného příkazu: {answer}") return answer def volani_asistenta(prompt, spinner_func=None): """ Spustí dotaz do asistenta v samostatném vlákně a během čekání volá spinner_func. - Vrací odpověď asistenta. Pokud dojde k chybě, vrací prázdný řetězec. + Vrací odpověď asistenta nebo chybovou hlášku. """ result_container = {} @@ -135,3 +188,10 @@ def volani_asistenta(prompt, spinner_func=None): idx += 1 thread.join() return result_container.get('answer', "") + +def reset_thread(): + """ + Resetuje konverzační vlákno tím, že nastaví globální thread_id na None. + """ + global thread_id + thread_id = None diff --git a/main.py b/main.py index 0a21ff4..61ea06e 100644 --- a/main.py +++ b/main.py @@ -3,33 +3,97 @@ Module: main.py --------------- Hlavní spouštěcí skript pro Robovojtíka. Importuje moduly pro API komunikaci, shellové funkce a uživatelské rozhraní. +Zde také definujeme seznam OpenAI funkcí (FUNCTIONS) včetně read_file. """ import argparse import queue import logging import logging.handlers + import ui +# Definice OpenAI funkcí, které asistent může volat +FUNCTIONS = [ + { + "name": "execute_shell_command", + "description": "Spustí shellový příkaz a vrátí jeho výstup.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Shellový příkaz k vykonání." + } + }, + "required": ["command"], + "additionalProperties": False + } + }, + { + "name": "create_script", + "description": "Vytvoří skript s daným obsahem v souboru a nastaví ho na spustitelný.", + "parameters": { + "type": "object", + "properties": { + "file_name": { + "type": "string", + "description": "Název souboru (nebo cesta), do kterého se skript uloží." + }, + "content": { + "type": "string", + "description": "Obsah skriptu, který se má uložit." + } + }, + "required": ["file_name", "content"], + "additionalProperties": False + } + }, + { + "name": "read_file", + "description": "Načte soubor o maximální velikosti 10kB a vrátí jeho obsah (případně zkrácený).", + "parameters": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Cesta k souboru, který se má přečíst." + } + }, + "required": ["path"], + "additionalProperties": False + } + } +] + + def main(): parser = argparse.ArgumentParser(description="Robovojtík – interaktivní shell asistent") parser.add_argument("--log", action="store_true", help="Zapne logování do souboru robovojtik.log") args = parser.parse_args() + listener = None if args.log: + import logging.handlers log_queue = queue.Queue(-1) logger = logging.getLogger("robovojtik") logger.setLevel(logging.DEBUG) qh = logging.handlers.QueueHandler(log_queue) logger.addHandler(qh) - fh = logging.FileHandler("robovojtik.log") - fh.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) + fh = logging.FileHandler("robovojtik.log", mode="a", encoding="utf-8") + fh.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s")) listener = logging.handlers.QueueListener(log_queue, fh) listener.start() logger.debug("Logování zapnuto.") + + # Předáme seznam FUNCTIONS do UI + ui.set_functions(FUNCTIONS) + ui.main_ui() + if listener: listener.stop() + if __name__ == "__main__": main() diff --git a/markdown_parser.py b/markdown_parser.py index bb830f8..681b110 100644 --- a/markdown_parser.py +++ b/markdown_parser.py @@ -2,74 +2,45 @@ Module: markdown_parser.py ---------------------------- Rozšířený Markdown parser pro curses. - -Podporované funkce: -1. Nadpisy (řádky začínající #) -> zobrazeny tučně a podtrženě -2. Odrážky (řádky začínající - a *) -> zobrazeny s tučným textem a symbolickou tečkou -3. Víceřádkové bloky kódu (```): - vše mezi trojitými backticky je zobrazeno s curses.A_REVERSE -4. Inline formátování: - **text** -> curses.A_BOLD - *text* -> curses.A_UNDERLINE - `text` -> curses.A_REVERSE - -Poznámka: nepodporujeme vnořené značky, a parser je pouze zjednodušený. +... """ import re import curses def parse_markdown(text): - """ - Hlavní funkce pro rozparsování zadaného textu na seznam (segment_text, attribute). - Respektuje víceřádkové bloky kódu (```), nadpisy, odrážky a volá parse_inline pro - zpracování běžných řádků. - """ segments = [] lines = text.splitlines() - in_code_block = False # Indikátor, zda se nacházíme uvnitř bloku ```. + in_code_block = False for line in lines: stripped = line.strip() - # Kontrola začátku/konce bloku kódu (```). if stripped.startswith("```"): - # Přepínáme stav in_code_block = not in_code_block - if in_code_block: - # Řádek obsahující samotné ``` - # Pokud tam je něco navíc, ořežeme - code_block_marker = stripped[3:].strip() - # Můžeme sem zařadit logiku pro detekci jazyka, pokud by to bylo potřeba - else: - # Konec bloku kódu - pass continue if in_code_block: - # Ve víceřádkovém kódu každou linku zobrazíme s curses.A_REVERSE + # celé řádky ve vícerádkovém kódu segments.append((line + "\n", curses.A_REVERSE)) continue - # Pokud nejsme v bloku kódu, zpracujeme nadpisy, odrážky a inline formát. + # Nadpisy if stripped.startswith("#"): - # Nadpis: zjistíme, kolik # tam je hash_count = 0 for ch in line: if ch == '#': hash_count += 1 else: break - # Obsah nadpisu za #... content = line[hash_count:].strip() - # Atribut: tučný a podtržený segments.append((content + "\n", curses.A_BOLD | curses.A_UNDERLINE)) + # Odrážky elif stripped.startswith("-") or stripped.startswith("*"): - # Odrážka content = line[1:].strip() segments.append((" • " + content + "\n", curses.A_BOLD)) else: - # Běžný řádek -> inline formát + # Běžný řádek inline_segments = parse_inline(line) for seg_text, seg_attr in inline_segments: segments.append((seg_text, seg_attr)) @@ -79,14 +50,8 @@ def parse_markdown(text): def parse_inline(text): - """ - Rozparsuje inline Markdown značky (tučný, kurzíva, inline kód) v jednom řádku, - vrací seznam (text, attr). - """ segments = [] pos = 0 - - # Regulární výraz pro zachycení **text**, *text*, `text` pattern = re.compile(r'(\*\*.*?\*\*|\*.*?\*|`.*?`)') for match in pattern.finditer(text): @@ -110,19 +75,3 @@ def parse_inline(text): if pos < len(text): segments.append((text[pos:], 0)) return segments - -if __name__ == "__main__": - # Krátký test - sample = """# Nadpis 1 -Toto je normální text s **tučným** a *kurzívou*. -- Odrážka 1 -* Odrážka 2 -A teď blok kódu: -ls -la echo "Hello" -Další text s `inline kódem` a ## menším nadpisem? -## Nadpis 2 -""" - segs = parse_markdown(sample) - for seg, attr in segs: - # Ukázkové vypsání v terminálu (bez curses) pro debug - print(f"{repr(seg)} attr={attr}") diff --git a/shell_functions.py b/shell_functions.py index 40c6701..7351c5b 100644 --- a/shell_functions.py +++ b/shell_functions.py @@ -2,13 +2,17 @@ Module: shell_functions.py ---------------------------- Obsahuje funkce pro vykonávání lokálních shellových příkazů a tvorbu skriptů. +Přidána funkce read_file_content pro načtení souboru. """ import subprocess import logging +import os logger = logging.getLogger("robovojtik.shell_functions") +MAX_FILE_SIZE = 10 * 1024 # 10 kB + def clean_command(command): """ Odstraní backticky a nepotřebné mezery z příkazu. @@ -25,31 +29,50 @@ def spust_prikaz(command): output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True) return output.strip() except subprocess.CalledProcessError as e: + logger.exception("Chyba při vykonávání příkazu.") return f"Chyba při vykonávání příkazu:\n{e.output}" + except Exception as e: + logger.exception("Obecná chyba při vykonávání příkazu.") + return f"Chyba při vykonávání příkazu: {str(e)}" def vytvor_skript(nazev, obsah): """ Zapíše obsah do souboru s daným názvem a nastaví jej jako spustitelný. """ try: - with open(nazev, "w") as f: + with open(nazev, "w", encoding="utf-8") as f: f.write(obsah) subprocess.call(f"chmod +x {nazev}", shell=True) logger.debug(f"Skript {nazev} vytvořen a nastaven jako spustitelný.") return f"Skript {nazev} byl úspěšně vytvořen." except Exception as e: + logger.exception("Chyba při tvorbě skriptu.") return f"Nastala chyba při tvorbě skriptu: {str(e)}" -def run_command_locally_and_report(command, api_interface): +def read_file_content(path: str) -> str: """ - Spustí příkaz lokálně, odešle jeho výstup jako report (s prefixem) - do asistenta a vrátí tuple (output, assistant_response). + Načte soubor na adrese 'path' do max. 10 kB. + Pokud je soubor větší, vrátí jen 10 kB a poznámku. + """ + try: + if not os.path.isfile(path): + return f"Chyba: Soubor '{path}' neexistuje nebo není souborem." + size = os.path.getsize(path) + partial = (size > MAX_FILE_SIZE) + with open(path, "rb") as f: + data = f.read(MAX_FILE_SIZE) + text = data.decode("utf-8", errors="replace") + if partial: + text += "\n\n[POZOR: Soubor byl delší než 10 kB, zobrazujeme pouze prvních 10 kB!]" + return text + except Exception as e: + logger.exception("Chyba při čtení souboru") + return f"Chyba při čtení souboru: {str(e)}" + +def run_command_locally(command): + """ + Spustí příkaz lokálně a vrátí jeho výstup. """ command = clean_command(command) output = spust_prikaz(command) - if not output.startswith("Chyba při vykonávání příkazu:"): - report_text = f"Výstup příkazu (příkaz proběhl úspěšně):\n{output}" - else: - report_text = f"Výstup příkazu:\n{output}" - answer = api_interface.posli_dotaz_do_assistenta(report_text) - return output, answer + return output diff --git a/ui.py b/ui.py index 9cf72f5..95133f9 100644 --- a/ui.py +++ b/ui.py @@ -2,14 +2,15 @@ Module: ui.py ------------- Obsahuje interaktivní rozhraní Robovojtíka založené na knihovně curses. -Implementuje barevné oddělení, trvalou hlavičku, historii chatu s formátováním Markdown, -spinner pro indikaci čekání a vstupní oblast. +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 @@ -17,11 +18,19 @@ import markdown_parser logger = logging.getLogger("robovojtik.ui") -# Definujeme globální proměnnou automode +# Globální funkce – pro integraci s main.py +FUNCTIONS = [] + + +def set_functions(funcs): + global FUNCTIONS + FUNCTIONS = funcs + + automode = False + def main_curses(stdscr): - # Inicializace barev curses.start_color() curses.use_default_colors() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Hlavička @@ -39,7 +48,7 @@ def main_curses(stdscr): header_height = 3 prompt_height = 2 left_width = width // 2 - right_width = width - left_width - 1 # oddělovač + right_width = width - left_width - 1 chat_height = height - header_height - prompt_height header_win = curses.newwin(header_height, left_width, 0, 0) @@ -64,8 +73,7 @@ def main_curses(stdscr): header_text = [ "Vítejte v Robovojtikovi!", - "Zadejte dotaz, příkaz (prefix 'cmd:'), 'automat',", - "nebo napiš 'napiš mi skript, ...' pro generování skriptu.", + "Zadejte dotaz, příkaz (prefix 'cmd:'), 'automat', 'readfile:' apod.", "Pro ukončení zadejte 'vypni' nebo 'exit'." ] max_chars = max(0, left_width - 2) @@ -82,31 +90,34 @@ def main_curses(stdscr): output_win.box() output_win.refresh() - spinner_chars = ["|", "/", "-", "\\"] - def spinner_func(ch): mid_x = left_width // 2 - 5 - # Vypíšeme spinner do hlavičky 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.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 - chat_history = [] - def add_to_chat(text): - # Nové volání parseru parse_markdown z markdown_parser segments = markdown_parser.parse_markdown(text) for seg_text, seg_attr in segments: - chat_win.addstr(seg_text, seg_attr) + 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() - prompt_win.addstr(">> ", curses.A_BOLD) + try: + prompt_win.addstr(">> ", curses.A_BOLD) + except curses.error: + pass prompt_win.refresh() curses.echo() try: @@ -120,83 +131,132 @@ def main_curses(stdscr): 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": - global automode automode = not automode stav = "zapnut" if automode else "vypnut" add_to_chat(f"Automód byl nyní {stav}.") continue - if user_input.lower().startswith("skript:"): - try: - _, rest = user_input.split("skript:", 1) - parts = rest.split(";", 1) - if len(parts) < 2: - add_to_chat("Pro vytvoření skriptu uveďte název a popis oddělené středníkem.") - continue - file_name = parts[0].strip() - description = parts[1].strip() - add_to_chat(f"Vytvářím skript '{file_name}' na základě popisu: {description}") - generated_content = api_interface.posli_dotaz_do_assistenta("Vygeneruj skript podle popisu: " + description) - add_to_chat("Generovaný obsah skriptu:\n" + generated_content) - result = shell_functions.vytvor_skript(file_name, generated_content) - add_to_chat(result) - except Exception as e: - add_to_chat(f"Chyba při vytváření skriptu: {str(e)}") + # 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římý příkaz: {command}") + add_to_chat(f"Rozpoznán příkaz: {command}") if not automode: prompt_win.erase() - prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD) + try: + prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD) + except curses.error: + pass prompt_win.refresh() - potvrzeni = prompt_win.getstr().decode("utf-8").strip().lower() + 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 != "y": + if potvrzeni not in ("y", "yes", "ano"): add_to_chat("Příkaz nebyl spuštěn.") continue - output, response = shell_functions.run_command_locally_and_report(command, api_interface) + output = shell_functions.run_command_locally(command) output_win.erase() - output_win.addstr(1, 1, output) - output_win.box() - output_win.refresh() - add_to_chat("Robovojtík odpovídá:\n" + response) + 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 + # 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) - if assistant_response.strip().lower().startswith("navrhovaný příkaz:"): + # 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() - prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD) + try: + prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD) + except curses.error: + pass prompt_win.refresh() - potvrzeni = prompt_win.getstr().decode("utf-8").strip().lower() + 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 != "y": + if potvrzeni not in ("y", "yes", "ano"): add_to_chat("Příkaz nebyl spuštěn.") continue - output, response = shell_functions.run_command_locally_and_report(navrhovany_prikaz, api_interface) + output = shell_functions.run_command_locally(navrhovany_prikaz) output_win.erase() - output_win.addstr(1, 1, output) - output_win.box() - output_win.refresh() - add_to_chat("Robovojtík odpovídá:\n" + response) + 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(): - curses.wrapper(main_curses) + try: + curses.wrapper(main_curses) + except Exception as e: + logger.exception("Neočekávaná chyba v curses wrapperu.") + print(f"Chyba: {str(e)}")