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