Compare commits
	
		
			14 Commits
		
	
	
		
			main
			...
			Developmen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 28c24d5057 | |||
| 2e2a18272a | |||
| 49d93096d4 | |||
| 3f527022e6 | |||
| e0f597f21a | |||
| 1e1890b7e9 | |||
| 054db0753d | |||
| 9c5e7ae3b4 | |||
| 2d76d82572 | |||
| 31cd4ddfd3 | |||
| a83fba1a23 | |||
| 4e3d3466f1 | |||
| d619fc0f3d | |||
| 9003d676ac | 
							
								
								
									
										197
									
								
								api_interface.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								api_interface.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,197 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					Module: api_interface.py
 | 
				
			||||||
 | 
					--------------------------
 | 
				
			||||||
 | 
					Obsahuje funkce pro komunikaci s OpenAI API a správu konverzačního vlákna.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import openai
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					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. 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. Zadejte 'assistant_id' do [OpenAI].")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Globální vlákno konverzace
 | 
				
			||||||
 | 
					thread_id = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def vytvor_nove_vlakno():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Vytvoří nové vlákno konverzace s asistentem pomocí OpenAI API.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global 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",
 | 
				
			||||||
 | 
					        content=prompt
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    run = openai.beta.threads.runs.create(
 | 
				
			||||||
 | 
					        thread_id=thread_id,
 | 
				
			||||||
 | 
					        assistant_id=ASSISTANT_ID
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
 | 
				
			||||||
 | 
					        if run_status.status == "requires_action":
 | 
				
			||||||
 | 
					            handle_required_action(run_status, run.id, thread_id)
 | 
				
			||||||
 | 
					        elif run_status.status == "completed":
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        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 = ""
 | 
				
			||||||
 | 
					    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 (např. cmd:).
 | 
				
			||||||
 | 
					    Ošetřuje stavy podobně jako posli_dotaz_do_assistenta.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global thread_id
 | 
				
			||||||
 | 
					    if thread_id is None:
 | 
				
			||||||
 | 
					        vytvor_nove_vlakno()
 | 
				
			||||||
 | 
					    logger.debug(f"Odesílám příkaz k vykonání: {command}")
 | 
				
			||||||
 | 
					    run = openai.beta.threads.runs.create(
 | 
				
			||||||
 | 
					        thread_id=thread_id,
 | 
				
			||||||
 | 
					        assistant_id=ASSISTANT_ID,
 | 
				
			||||||
 | 
					        instructions=f"Prosím, spusť tento příkaz: {command}"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
 | 
				
			||||||
 | 
					        if run_status.status == "requires_action":
 | 
				
			||||||
 | 
					            handle_required_action(run_status, run.id, thread_id)
 | 
				
			||||||
 | 
					        elif run_status.status == "completed":
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        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 = ""
 | 
				
			||||||
 | 
					    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 nebo chybovou hlášku.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    result_container = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def worker():
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            odpoved = posli_dotaz_do_assistenta(prompt)
 | 
				
			||||||
 | 
					            result_container['answer'] = odpoved
 | 
				
			||||||
 | 
					        except Exception as ex:
 | 
				
			||||||
 | 
					            result_container['answer'] = f"Chyba při volání asistenta: {str(ex)}"
 | 
				
			||||||
 | 
					            logger.exception("Chyba v worker funkci volani_asistenta.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    thread = threading.Thread(target=worker)
 | 
				
			||||||
 | 
					    thread.start()
 | 
				
			||||||
 | 
					    idx = 0
 | 
				
			||||||
 | 
					    spinner = ["|", "/", "-", "\\"]
 | 
				
			||||||
 | 
					    while thread.is_alive():
 | 
				
			||||||
 | 
					        if spinner_func:
 | 
				
			||||||
 | 
					            spinner_func(spinner[idx % len(spinner)])
 | 
				
			||||||
 | 
					        time.sleep(0.2)
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
							
								
								
									
										27
									
								
								functions/create_script.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								functions/create_script.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "create_script",
 | 
				
			||||||
 | 
					  "description": "Vytvoří skript na základě popisu činnosti. Pokud je zadán popis, asistent vygeneruje obsah skriptu odpovídající tomuto popisu; pokud je zadán i přímý obsah, použije se ten. Skript bude uložen do zadaného souboru a nastaven jako spustitelný.",
 | 
				
			||||||
 | 
					  "strict": false,
 | 
				
			||||||
 | 
					  "parameters": {
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
					    "properties": {
 | 
				
			||||||
 | 
					      "file_name": {
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "description": "Název souboru (nebo cesta), do kterého se skript uloží."
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "description": {
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "description": "Popis činnosti, kterou má skript vykonávat. Asistent na základě tohoto popisu vygeneruje obsah skriptu."
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "content": {
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "description": "Nepovinný parametr. Pokud je explicitně zadán, bude použit jako obsah skriptu místo generovaného textu."
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "required": [
 | 
				
			||||||
 | 
					      "file_name",
 | 
				
			||||||
 | 
					      "description"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "additionalProperties": false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								functions/execute_shell_command.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								functions/execute_shell_command.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "execute_shell_command",
 | 
				
			||||||
 | 
					  "description": "Spustí shellový příkaz a vrátí jeho výstup.",
 | 
				
			||||||
 | 
					  "strict": true,
 | 
				
			||||||
 | 
					  "parameters": {
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
					    "properties": {
 | 
				
			||||||
 | 
					      "command": {
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "description": "Shellový příkaz k vykonání."
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "additionalProperties": false,
 | 
				
			||||||
 | 
					    "required": [
 | 
				
			||||||
 | 
					      "command"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								functions/get_system_load.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								functions/get_system_load.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "get_system_load",
 | 
				
			||||||
 | 
					  "description": "Získá aktuální zátěž systému (load average).",
 | 
				
			||||||
 | 
					  "strict": true,
 | 
				
			||||||
 | 
					  "parameters": {
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
					    "properties": {},
 | 
				
			||||||
 | 
					    "additionalProperties": false,
 | 
				
			||||||
 | 
					    "required": []
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								functions/read_file.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								functions/read_file.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "read_file",
 | 
				
			||||||
 | 
					  "description": "Načte soubor o maximální velikosti 10kB a vrátí jeho obsah (případně zkrácený).",
 | 
				
			||||||
 | 
					  "strict": true,
 | 
				
			||||||
 | 
					  "parameters": {
 | 
				
			||||||
 | 
					    "type": "object",
 | 
				
			||||||
 | 
					    "required": [
 | 
				
			||||||
 | 
					      "path"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "properties": {
 | 
				
			||||||
 | 
					      "path": {
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "description": "Cesta k souboru, který se má přečíst."
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "additionalProperties": false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					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", 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()
 | 
				
			||||||
							
								
								
									
										77
									
								
								markdown_parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								markdown_parser.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					Module: markdown_parser.py
 | 
				
			||||||
 | 
					----------------------------
 | 
				
			||||||
 | 
					Rozšířený Markdown parser pro curses.
 | 
				
			||||||
 | 
					...
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import curses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_markdown(text):
 | 
				
			||||||
 | 
					    segments = []
 | 
				
			||||||
 | 
					    lines = text.splitlines()
 | 
				
			||||||
 | 
					    in_code_block = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for line in lines:
 | 
				
			||||||
 | 
					        stripped = line.strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if stripped.startswith("```"):
 | 
				
			||||||
 | 
					            in_code_block = not in_code_block
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if in_code_block:
 | 
				
			||||||
 | 
					            # celé řádky ve vícerádkovém kódu
 | 
				
			||||||
 | 
					            segments.append((line + "\n", curses.A_REVERSE))
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Nadpisy
 | 
				
			||||||
 | 
					        if stripped.startswith("#"):
 | 
				
			||||||
 | 
					            hash_count = 0
 | 
				
			||||||
 | 
					            for ch in line:
 | 
				
			||||||
 | 
					                if ch == '#':
 | 
				
			||||||
 | 
					                    hash_count += 1
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            content = line[hash_count:].strip()
 | 
				
			||||||
 | 
					            segments.append((content + "\n", curses.A_BOLD | curses.A_UNDERLINE))
 | 
				
			||||||
 | 
					        # Odrážky
 | 
				
			||||||
 | 
					        elif stripped.startswith("-") or stripped.startswith("*"):
 | 
				
			||||||
 | 
					            content = line[1:].strip()
 | 
				
			||||||
 | 
					            segments.append(("  • " + content + "\n", curses.A_BOLD))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Běžný řádek
 | 
				
			||||||
 | 
					            inline_segments = parse_inline(line)
 | 
				
			||||||
 | 
					            for seg_text, seg_attr in inline_segments:
 | 
				
			||||||
 | 
					                segments.append((seg_text, seg_attr))
 | 
				
			||||||
 | 
					            segments.append(("\n", 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return segments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_inline(text):
 | 
				
			||||||
 | 
					    segments = []
 | 
				
			||||||
 | 
					    pos = 0
 | 
				
			||||||
 | 
					    pattern = re.compile(r'(\*\*.*?\*\*|\*.*?\*|`.*?`)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for match in pattern.finditer(text):
 | 
				
			||||||
 | 
					        start, end = match.span()
 | 
				
			||||||
 | 
					        if start > pos:
 | 
				
			||||||
 | 
					            segments.append((text[pos:start], 0))
 | 
				
			||||||
 | 
					        token = match.group(0)
 | 
				
			||||||
 | 
					        if token.startswith("**") and token.endswith("**"):
 | 
				
			||||||
 | 
					            content = token[2:-2]
 | 
				
			||||||
 | 
					            segments.append((content, curses.A_BOLD))
 | 
				
			||||||
 | 
					        elif token.startswith("*") and token.endswith("*"):
 | 
				
			||||||
 | 
					            content = token[1:-1]
 | 
				
			||||||
 | 
					            segments.append((content, curses.A_UNDERLINE))
 | 
				
			||||||
 | 
					        elif token.startswith("`") and token.endswith("`"):
 | 
				
			||||||
 | 
					            content = token[1:-1]
 | 
				
			||||||
 | 
					            segments.append((content, curses.A_REVERSE))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            segments.append((token, 0))
 | 
				
			||||||
 | 
					        pos = end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if pos < len(text):
 | 
				
			||||||
 | 
					        segments.append((text[pos:], 0))
 | 
				
			||||||
 | 
					    return segments
 | 
				
			||||||
							
								
								
									
										411
									
								
								robovojtik.py
									
									
									
									
									
								
							
							
						
						
									
										411
									
								
								robovojtik.py
									
									
									
									
									
								
							@ -1,411 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
Robovojtík – Linuxový shell asistent s vylepšeným interaktivním rozhraním
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Funkce:
 | 
					 | 
				
			||||||
  • Rozhraní s barevným oddělením:
 | 
					 | 
				
			||||||
      - Levá strana je rozdělena na hlavičku (s trvalými instrukcemi a spinnerem) a chat (historie).
 | 
					 | 
				
			||||||
      - Pravá strana zobrazuje výstup příkazů.
 | 
					 | 
				
			||||||
      - Mezi levým a pravým panelem je vertikální oddělovač.
 | 
					 | 
				
			||||||
      - Dolní část je vstupní oblast (o dva řádky).
 | 
					 | 
				
			||||||
  • Spinner se zobrazuje v hlavičce, uprostřed, vždy když čekáme na odpověď asistenta.
 | 
					 | 
				
			||||||
  • Do chatu se zaznamenává i uživatelské potvrzení.
 | 
					 | 
				
			||||||
  • Logování (při spuštění s --log) běží asynchronně a loguje vše na úrovni DEBUG do souboru "robovojtik.log".
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import openai
 | 
					 | 
				
			||||||
import subprocess
 | 
					 | 
				
			||||||
import curses
 | 
					 | 
				
			||||||
import configparser
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
import threading
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import argparse
 | 
					 | 
				
			||||||
import queue
 | 
					 | 
				
			||||||
import logging.handlers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# === 1. Načtení konfigurace z config.ini ===
 | 
					 | 
				
			||||||
config = configparser.ConfigParser()
 | 
					 | 
				
			||||||
config.read("config.ini")
 | 
					 | 
				
			||||||
openai.api_key = config["OpenAI"]["api_key"]
 | 
					 | 
				
			||||||
ASSISTANT_ID = config["OpenAI"]["assistant_id"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Globální proměnné
 | 
					 | 
				
			||||||
automode = False       # Automatický režim
 | 
					 | 
				
			||||||
log_enabled = False    # Zapnutí logování přes --log
 | 
					 | 
				
			||||||
thread_id = None       # ID vlákna konverzace s OpenAI
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Asynchroní logování
 | 
					 | 
				
			||||||
logger = logging.getLogger("robovojtik")
 | 
					 | 
				
			||||||
logger.setLevel(logging.DEBUG)
 | 
					 | 
				
			||||||
log_queue = None
 | 
					 | 
				
			||||||
listener = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# === 2. Definice funkcí pro volání OpenAI API ===
 | 
					 | 
				
			||||||
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": "get_system_load",
 | 
					 | 
				
			||||||
        "description": "Získá aktuální zátěž systému (load average).",
 | 
					 | 
				
			||||||
        "parameters": {
 | 
					 | 
				
			||||||
            "type": "object",
 | 
					 | 
				
			||||||
            "properties": {},
 | 
					 | 
				
			||||||
            "required": [],
 | 
					 | 
				
			||||||
            "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
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# === 3. (System prompt není načítán ze zdrojového kódu) ===
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# === 4. Pomocné funkce pro komunikaci s OpenAI API ===
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def vytvor_nove_vlakno():
 | 
					 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def clean_command(command):
 | 
					 | 
				
			||||||
    return command.replace("`", "").strip()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def volani_asistenta(prompt, spinner_func=None):
 | 
					 | 
				
			||||||
    result_container = {}
 | 
					 | 
				
			||||||
    def worker():
 | 
					 | 
				
			||||||
        odpoved = posli_dotaz_do_assistenta(prompt)
 | 
					 | 
				
			||||||
        result_container['answer'] = odpoved
 | 
					 | 
				
			||||||
    thread = threading.Thread(target=worker)
 | 
					 | 
				
			||||||
    thread.start()
 | 
					 | 
				
			||||||
    idx = 0
 | 
					 | 
				
			||||||
    spinner = ["|", "/", "-", "\\"]
 | 
					 | 
				
			||||||
    while thread.is_alive():
 | 
					 | 
				
			||||||
        if spinner_func:
 | 
					 | 
				
			||||||
            spinner_func(spinner[idx % len(spinner)])
 | 
					 | 
				
			||||||
        time.sleep(0.2)
 | 
					 | 
				
			||||||
        idx += 1
 | 
					 | 
				
			||||||
    thread.join()
 | 
					 | 
				
			||||||
    return result_container.get('answer', "")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def posli_dotaz_do_assistenta(prompt):
 | 
					 | 
				
			||||||
    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",
 | 
					 | 
				
			||||||
        content=prompt
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    run = openai.beta.threads.runs.create(
 | 
					 | 
				
			||||||
        thread_id=thread_id,
 | 
					 | 
				
			||||||
        assistant_id=ASSISTANT_ID
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
 | 
					 | 
				
			||||||
        if run_status.status == "completed":
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
        time.sleep(1)
 | 
					 | 
				
			||||||
    messages = openai.beta.threads.messages.list(thread_id=thread_id)
 | 
					 | 
				
			||||||
    answer = messages.data[0].content[0].text.value
 | 
					 | 
				
			||||||
    logger.debug(f"Asistent odpověděl: {answer}")
 | 
					 | 
				
			||||||
    return answer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def posli_prikaz_do_assistenta(command):
 | 
					 | 
				
			||||||
    command = clean_command(command)
 | 
					 | 
				
			||||||
    global thread_id
 | 
					 | 
				
			||||||
    if thread_id is None:
 | 
					 | 
				
			||||||
        vytvor_nove_vlakno()
 | 
					 | 
				
			||||||
    logger.debug(f"Odesílám příkaz k vykonání: {command}")
 | 
					 | 
				
			||||||
    run = openai.beta.threads.runs.create(
 | 
					 | 
				
			||||||
        thread_id=thread_id,
 | 
					 | 
				
			||||||
        assistant_id=ASSISTANT_ID,
 | 
					 | 
				
			||||||
        instructions=f"Prosím, spusť tento příkaz: {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
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        elif run_status.status == "completed":
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
        time.sleep(1)
 | 
					 | 
				
			||||||
    messages = openai.beta.threads.messages.list(thread_id=thread_id)
 | 
					 | 
				
			||||||
    answer = messages.data[0].content[0].text.value
 | 
					 | 
				
			||||||
    logger.debug(f"Výsledek spuštěného příkazu: {answer}")
 | 
					 | 
				
			||||||
    return answer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def is_command_response(response):
 | 
					 | 
				
			||||||
    return response.strip().lower().startswith("navrhovaný příkaz:")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_first_command_proposal(response):
 | 
					 | 
				
			||||||
    """Vrátí první řádek, který začíná 'Navrhovaný příkaz:'; jinak None."""
 | 
					 | 
				
			||||||
    lines = response.splitlines()
 | 
					 | 
				
			||||||
    for line in lines:
 | 
					 | 
				
			||||||
        if line.strip().lower().startswith("navrhovaný příkaz:"):
 | 
					 | 
				
			||||||
            return line
 | 
					 | 
				
			||||||
    return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# === 5. Funkce pro spouštění příkazů a report ===
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def spust_prikaz(command):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        logger.debug(f"Lokálně spouštím příkaz: {command}")
 | 
					 | 
				
			||||||
        output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
 | 
					 | 
				
			||||||
        return output.strip()
 | 
					 | 
				
			||||||
    except subprocess.CalledProcessError as e:
 | 
					 | 
				
			||||||
        return f"Chyba při vykonávání příkazu:\n{e.output}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def vytvor_skript(nazev, obsah):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        with open(nazev, "w") 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:
 | 
					 | 
				
			||||||
        return f"Nastala chyba při tvorbě skriptu: {str(e)}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def run_command_locally_and_report(command):
 | 
					 | 
				
			||||||
    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 = posli_dotaz_do_assistenta(report_text)
 | 
					 | 
				
			||||||
    return output, answer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# === 6. Vylepšené interaktivní rozhraní s curses ===
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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   # oddělovač má 1 sloupec
 | 
					 | 
				
			||||||
    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' nebo 'skript: nazev; obsah'.",
 | 
					 | 
				
			||||||
        "Pro ukončení zadejte 'vypni' nebo 'exit'."
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    for idx, line in enumerate(header_text):
 | 
					 | 
				
			||||||
        header_win.addstr(idx, 1, line)
 | 
					 | 
				
			||||||
    header_win.refresh()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chat_win.scrollok(True)
 | 
					 | 
				
			||||||
    output_win.scrollok(True)
 | 
					 | 
				
			||||||
    prompt_win.scrollok(True)
 | 
					 | 
				
			||||||
    output_win.box()
 | 
					 | 
				
			||||||
    output_win.refresh()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Funkce pro spinner v hlavičce (uprostřed)
 | 
					 | 
				
			||||||
    def spinner_func(ch):
 | 
					 | 
				
			||||||
        mid_x = left_width // 2 - 5
 | 
					 | 
				
			||||||
        header_win.addstr(1, mid_x, f"Čekám... {ch}", curses.color_pair(5) | curses.A_BOLD)
 | 
					 | 
				
			||||||
        header_win.refresh()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    chat_history = []
 | 
					 | 
				
			||||||
    def add_to_chat(text):
 | 
					 | 
				
			||||||
        chat_history.append(text)
 | 
					 | 
				
			||||||
        chat_win.addstr(text + "\n")
 | 
					 | 
				
			||||||
        chat_win.refresh()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    add_to_chat("Historie chatu:")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        prompt_win.erase()
 | 
					 | 
				
			||||||
        prompt_win.addstr(">> ", curses.A_BOLD)
 | 
					 | 
				
			||||||
        prompt_win.refresh()
 | 
					 | 
				
			||||||
        curses.echo()
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            user_input = prompt_win.getstr().decode("utf-8").strip()
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
        curses.noecho()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not user_input:
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        add_to_chat("Ty: " + user_input)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if user_input.lower() in ["vypni", "exit"]:
 | 
					 | 
				
			||||||
            add_to_chat("Ukončuji Robovojtíka...")
 | 
					 | 
				
			||||||
            time.sleep(1)
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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)
 | 
					 | 
				
			||||||
                nazev, obsah = rest.split(";", 1)
 | 
					 | 
				
			||||||
                nazev = nazev.strip()
 | 
					 | 
				
			||||||
                obsah = obsah.strip()
 | 
					 | 
				
			||||||
                vysledek = vytvor_skript(nazev, obsah)
 | 
					 | 
				
			||||||
                add_to_chat(vysledek)
 | 
					 | 
				
			||||||
            except Exception as e:
 | 
					 | 
				
			||||||
                add_to_chat(f"Chyba při vytváření skriptu: {str(e)}")
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if user_input.startswith("cmd:"):
 | 
					 | 
				
			||||||
            command = user_input[4:].strip()
 | 
					 | 
				
			||||||
            add_to_chat(f"Rozpoznán přímý příkaz: {command}")
 | 
					 | 
				
			||||||
            if not automode:
 | 
					 | 
				
			||||||
                prompt_win.erase()
 | 
					 | 
				
			||||||
                prompt_win.addstr("Spustit tento příkaz? (y/n): ", curses.A_BOLD)
 | 
					 | 
				
			||||||
                prompt_win.refresh()
 | 
					 | 
				
			||||||
                potvrzeni = prompt_win.getstr().decode("utf-8").strip().lower()
 | 
					 | 
				
			||||||
                add_to_chat("Ty: " + potvrzeni)
 | 
					 | 
				
			||||||
                if potvrzeni != "y":
 | 
					 | 
				
			||||||
                    add_to_chat("Příkaz nebyl spuštěn.")
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
            output, response = run_command_locally_and_report(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)
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        assistant_response = volani_asistenta(user_input, spinner_func=spinner_func)
 | 
					 | 
				
			||||||
        add_to_chat("Robovojtík odpovídá:\n" + assistant_response)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        proposal_line = get_first_command_proposal(assistant_response)
 | 
					 | 
				
			||||||
        if proposal_line:
 | 
					 | 
				
			||||||
            navrhovany_prikaz = proposal_line[len("Navrhovaný příkaz:"):].strip()
 | 
					 | 
				
			||||||
            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)
 | 
					 | 
				
			||||||
                prompt_win.refresh()
 | 
					 | 
				
			||||||
                potvrzeni = prompt_win.getstr().decode("utf-8").strip().lower()
 | 
					 | 
				
			||||||
                add_to_chat("Ty: " + potvrzeni)
 | 
					 | 
				
			||||||
                if potvrzeni != "y":
 | 
					 | 
				
			||||||
                    add_to_chat("Příkaz nebyl spuštěn.")
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
            output, response = run_command_locally_and_report(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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    prompt_win.erase()
 | 
					 | 
				
			||||||
    prompt_win.refresh()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    global log_enabled, listener, log_queue
 | 
					 | 
				
			||||||
    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()
 | 
					 | 
				
			||||||
    if args.log:
 | 
					 | 
				
			||||||
        log_enabled = True
 | 
					 | 
				
			||||||
        log_queue = queue.Queue(-1)
 | 
					 | 
				
			||||||
        qh = logging.handlers.QueueHandler(log_queue)
 | 
					 | 
				
			||||||
        logger.addHandler(qh)
 | 
					 | 
				
			||||||
        logger.setLevel(logging.DEBUG)
 | 
					 | 
				
			||||||
        fh = logging.FileHandler("robovojtik.log")
 | 
					 | 
				
			||||||
        fh.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
 | 
					 | 
				
			||||||
        listener = logging.handlers.QueueListener(log_queue, fh)
 | 
					 | 
				
			||||||
        listener.start()
 | 
					 | 
				
			||||||
        logger.debug("Logování zapnuto.")
 | 
					 | 
				
			||||||
    curses.wrapper(main_curses)
 | 
					 | 
				
			||||||
    if listener:
 | 
					 | 
				
			||||||
        listener.stop()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
							
								
								
									
										78
									
								
								shell_functions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								shell_functions.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return command.replace("`", "").strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def spust_prikaz(command):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Spustí příkaz lokálně a vrátí jeho výstup.
 | 
				
			||||||
 | 
					    V případě chyby vrací chybovou hlášku.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        logger.debug(f"Lokálně spouštím příkaz: {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", 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 read_file_content(path: str) -> str:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
 | 
					    return output
 | 
				
			||||||
							
								
								
									
										109
									
								
								system_prompt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								system_prompt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					Jsi Linuxový shell asistent jménem Robovojtík. Tvým úkolem je pomáhat uživateli vykonávat příkazy v systému, klidně složitějšího charakteru (pipe, grep...), analyzovat soubory a usnadnit práci se skripty. Můžeš také generovat skripty podle zadání a analyzovat obsah souborů.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					🔹 **Obecné chování**:
 | 
				
			||||||
 | 
					- Pokud se tě uživatel ptá na cokoli ohledně systému, souborů nebo příkazů, nejprve mu **vysvětli řešení a navrhni vhodný příkaz**.
 | 
				
			||||||
 | 
					- **Nikdy neprováděj příkaz automaticky**, pokud není zapnutý **automatický režim (automód)**.
 | 
				
			||||||
 | 
					- Pokud je **automód aktivní**, příkaz provedeš rovnou a oznámíš výsledek.
 | 
				
			||||||
 | 
					- Pokud **automód není aktivní**, vždy čekáš na potvrzení uživatele před spuštěním příkazu.
 | 
				
			||||||
 | 
					- **Používáš Markdown formátování** pro odpovědi, aby byly přehledné. Používej i barvy, píšeš na černé pozadí.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🔹 **Práce se soubory**
 | 
				
			||||||
 | 
					- Pokud se tě uživatel zeptá na obsah souboru, zavoláš funkci **read_file**.
 | 
				
			||||||
 | 
					- Umíš **načíst soubor do velikosti 10 kB**. Pokud je větší, načteš jen prvních 10 kB a připojíš upozornění:  
 | 
				
			||||||
 | 
					  `⚠️ Upozornění: Soubor byl delší než 10 kB, zobrazujeme pouze prvních 10 kB!`
 | 
				
			||||||
 | 
					- Pokud je soubor **binární**, odpovíš:  
 | 
				
			||||||
 | 
					  `❌ Tento soubor nelze přečíst jako text.`  
 | 
				
			||||||
 | 
					- Po načtení souboru se ho pokusíš **analyzovat**, například:
 | 
				
			||||||
 | 
					  - Pokud jde o skript, vysvětlíš, co dělá.
 | 
				
			||||||
 | 
					  - Pokud jde o konfigurační soubor, shrneš jeho nastavení.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🔹 **Vykonávání příkazů**
 | 
				
			||||||
 | 
					- Pokud uživatel **zadá dotaz**, navrhneš odpovídající shellový příkaz.  
 | 
				
			||||||
 | 
					  **Odpověď začínáš prefixem**:
 | 
				
			||||||
 | 
					🖥️ Navrhovaný příkaz: ls -la
 | 
				
			||||||
 | 
					Analyzuj následující výstup příkazu. Neposkytuj žádné návrhy příkazů, jen shrň výsledky a uveď komentář.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					markdown
 | 
				
			||||||
 | 
					Zkopírovat
 | 
				
			||||||
 | 
					Upravit
 | 
				
			||||||
 | 
					- Pokud je automód vypnutý, vždy čekáš na odpověď „**y**“ nebo „**n**“.
 | 
				
			||||||
 | 
					- Pokud se příkaz vykoná, **analyzuješ výstup** a dáš uživateli zpětnou vazbu.
 | 
				
			||||||
 | 
					- Pokud dojde k chybě, pokusíš se pomoci uživateli ji opravit.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🔹 **Práce se skripty**
 | 
				
			||||||
 | 
					- Uživatel může chtít vytvořit skript. Pomůžeš mu tím, že:
 | 
				
			||||||
 | 
					- **Navrhneš kód skriptu** na základě popisu uživatele.
 | 
				
			||||||
 | 
					- Zeptáš se na jméno souboru, pokud ho uživatel nezadal.
 | 
				
			||||||
 | 
					- Po potvrzení vytvoříš soubor a nastavíš ho jako spustitelný.
 | 
				
			||||||
 | 
					- **Pokud uživatel zadá:**  
 | 
				
			||||||
 | 
					`"Vytvoř skript, který vypíše nejnovější soubor"`  
 | 
				
			||||||
 | 
					**Odpovíš:**  
 | 
				
			||||||
 | 
					📝 Navrhovaný skript:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bash
 | 
				
			||||||
 | 
					Zkopírovat
 | 
				
			||||||
 | 
					Upravit
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					ls -t | head -n 1
 | 
				
			||||||
 | 
					"Chceš tento skript vytvořit?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Po potvrzení zavoláš funkci create_script.
 | 
				
			||||||
 | 
					🔹 Automód (automatický režim)
 | 
				
			||||||
 | 
					Pokud je zapnutý automód, příkazy ihned provádíš bez potvrzení.
 | 
				
			||||||
 | 
					Po provedení popíšeš výsledek.
 | 
				
			||||||
 | 
					Pokud je vypnutý automód, vždy požádáš o potvrzení.
 | 
				
			||||||
 | 
					🔹 Odpovědi a formátování
 | 
				
			||||||
 | 
					Používáš Markdown, abys zpřehlednil odpovědi:
 | 
				
			||||||
 | 
					Tučné písmo pro důležité části.
 | 
				
			||||||
 | 
					🔹 Emoji pro vizuální odlišení sekcí.
 | 
				
			||||||
 | 
					Bloky kódu pro příkazy a skripty:
 | 
				
			||||||
 | 
					bash
 | 
				
			||||||
 | 
					Zkopírovat
 | 
				
			||||||
 | 
					Upravit
 | 
				
			||||||
 | 
					ls -la
 | 
				
			||||||
 | 
					Pokud výstup obsahuje chybu, použiješ:
 | 
				
			||||||
 | 
					yaml
 | 
				
			||||||
 | 
					Zkopírovat
 | 
				
			||||||
 | 
					Upravit
 | 
				
			||||||
 | 
					❌ Chyba: Příkaz nebyl úspěšný. Možné řešení: ...
 | 
				
			||||||
 | 
					Příklad interakce
 | 
				
			||||||
 | 
					Uživatel:
 | 
				
			||||||
 | 
					„Vypiš mi obsah složky.“
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Robovojtík:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bash
 | 
				
			||||||
 | 
					Zkopírovat
 | 
				
			||||||
 | 
					Upravit
 | 
				
			||||||
 | 
					🖥️ Navrhovaný příkaz: ls -l
 | 
				
			||||||
 | 
					Chceš tento příkaz vykonat? (y/n)
 | 
				
			||||||
 | 
					(Pokud uživatel odpoví y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Robovojtík:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sql
 | 
				
			||||||
 | 
					Zkopírovat
 | 
				
			||||||
 | 
					Upravit
 | 
				
			||||||
 | 
					📄 Výpis složky:
 | 
				
			||||||
 | 
					total 4
 | 
				
			||||||
 | 
					-rw-r--r-- 1 user user  120 Mar 20 12:34 config.ini
 | 
				
			||||||
 | 
					-rwxr-xr-x 1 user user  204 Mar 20 12:35 script.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					✅ Příkaz proběhl úspěšně.
 | 
				
			||||||
 | 
					🛠️ Tvůj hlavní úkol je:
 | 
				
			||||||
 | 
					Analyzovat dotazy a nabídnout nejlepší možné řešení.
 | 
				
			||||||
 | 
					Používat funkce (execute_shell_command, create_script, read_file).
 | 
				
			||||||
 | 
					Vždy dávat smysluplnou zpětnou vazbu.
 | 
				
			||||||
 | 
					Používat formátování Markdown pro přehledné odpovědi.
 | 
				
			||||||
 | 
					Respektovat automód – v automatickém režimu pracuješ sám, jinak čekáš na potvrzení.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pokud zpráva začíná "Výsledek příkazu:", uživatel žádá pouze stručné shrnutí a komentář bez jakéhokoliv návrhu příkazů.
 | 
				
			||||||
							
								
								
									
										262
									
								
								ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								ui.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,262 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					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)}")
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user