Zřejmě to stále funguje

This commit is contained in:
sinuhet 2025-03-20 02:01:54 +01:00
parent 4e3d3466f1
commit a83fba1a23
2 changed files with 149 additions and 75 deletions

View File

@ -1,62 +1,128 @@
"""
Module: markdown_parser.py
----------------------------
Jednoduchý Markdown parser pro curses.
Rozpozná základní znaèky:
- **tuènì** curses.A_BOLD
- *kurzíva* curses.A_UNDERLINE
- `kód` curses.A_REVERSE (pro zvýraznìní kódu)
Vrací seznam segmentù ve tvaru (text, atribut), které lze postupnì vypsat pomocí curses.
"""
import re
import curses
def parse_markdown(text):
"""
Rozparsuje zadaný text obsahující jednoduché Markdown znaèky a vrátí seznam
dvojic (segment_text, attribute). Nepodporuje vnoøené znaèky.
Podporované znaèky:
**text** tuènì (A_BOLD)
*text* kurzíva (A_UNDERLINE)
`text` kód (A_REVERSE)
Pokud se znaèky nepárují, vrací zbytek textu s atributem 0.
"""
segments = []
pos = 0
pattern = re.compile(r'(\*\*.*?\*\*|\*.*?\*|`.*?`)')
for match in pattern.finditer(text):
start, end = match.span()
# Normalní text pøed znaèkou
if start > pos:
segments.append((text[pos:start], 0))
token = match.group(0)
# Rozpoznání typu tokenu
if token.startswith("**") and token.endswith("**"):
# Bold odstraníme dvojité hvìzdièky
content = token[2:-2]
segments.append((content, curses.A_BOLD))
elif token.startswith("*") and token.endswith("*"):
# Italic odstraníme hvìzdièky
content = token[1:-1]
segments.append((content, curses.A_UNDERLINE))
elif token.startswith("`") and token.endswith("`"):
# Inline kód odstraníme zpìtné apostrofy a použijeme reverzní barvu
content = token[1:-1]
segments.append((content, curses.A_REVERSE))
else:
segments.append((token, 0))
pos = end
# Zbytek textu
if pos < len(text):
segments.append((text[pos:], 0))
return segments
if __name__ == "__main__":
# Jednoduchý test
sample = "Toto je **tuèný text**, toto je *kurzíva* a toto je `kód`."
segments = parse_markdown(sample)
for seg, attr in segments:
print(f"Segment: '{seg}', Attr: {attr}")
"""
Module: markdown_parser.py
----------------------------
Rozšířený Markdown parser pro curses.
Podporované funkce:
1. Nadpisy (řádky začínající #) -> zobrazeny tučně a podtrženě
2. Odrážky (řádky začínající - a *) -> zobrazeny s tučným textem a symbolickou tečkou
3. Víceřádkové bloky kódu (```):
vše mezi trojitými backticky je zobrazeno s curses.A_REVERSE
4. Inline formátování:
**text** -> curses.A_BOLD
*text* -> curses.A_UNDERLINE
`text` -> curses.A_REVERSE
Poznámka: nepodporujeme vnořené značky, a parser je pouze zjednodušený.
"""
import re
import curses
def parse_markdown(text):
"""
Hlavní funkce pro rozparsování zadaného textu na seznam (segment_text, attribute).
Respektuje víceřádkové bloky kódu (```), nadpisy, odrážky a volá parse_inline pro
zpracování běžných řádků.
"""
segments = []
lines = text.splitlines()
in_code_block = False # Indikátor, zda se nacházíme uvnitř bloku ```.
for line in lines:
stripped = line.strip()
# Kontrola začátku/konce bloku kódu (```).
if stripped.startswith("```"):
# Přepínáme stav
in_code_block = not in_code_block
if in_code_block:
# Řádek obsahující samotné ```
# Pokud tam je něco navíc, ořežeme
code_block_marker = stripped[3:].strip()
# Můžeme sem zařadit logiku pro detekci jazyka, pokud by to bylo potřeba
else:
# Konec bloku kódu
pass
continue
if in_code_block:
# Ve víceřádkovém kódu každou linku zobrazíme s curses.A_REVERSE
segments.append((line + "\n", curses.A_REVERSE))
continue
# Pokud nejsme v bloku kódu, zpracujeme nadpisy, odrážky a inline formát.
if stripped.startswith("#"):
# Nadpis: zjistíme, kolik # tam je
hash_count = 0
for ch in line:
if ch == '#':
hash_count += 1
else:
break
# Obsah nadpisu za #...
content = line[hash_count:].strip()
# Atribut: tučný a podtržený
segments.append((content + "\n", curses.A_BOLD | curses.A_UNDERLINE))
elif stripped.startswith("-") or stripped.startswith("*"):
# Odrážka
content = line[1:].strip()
segments.append(("" + content + "\n", curses.A_BOLD))
else:
# Běžný řádek -> inline formát
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):
"""
Rozparsuje inline Markdown značky (tučný, kurzíva, inline kód) v jednom řádku,
vrací seznam (text, attr).
"""
segments = []
pos = 0
# Regulární výraz pro zachycení **text**, *text*, `text`
pattern = re.compile(r'(\*\*.*?\*\*|\*.*?\*|`.*?`)')
for match in pattern.finditer(text):
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
if __name__ == "__main__":
# Krátký test
sample = """# Nadpis 1
Toto je normální text s **tučným** a *kurzívou*.
- Odrážka 1
* Odrážka 2
A teď blok kódu:
ls -la echo "Hello"
Další text s `inline kódem` a ## menším nadpisem?
## Nadpis 2
"""
segs = parse_markdown(sample)
for seg, attr in segs:
# Ukázkové vypsání v terminálu (bez curses) pro debug
print(f"{repr(seg)} attr={attr}")

34
ui.py
View File

@ -2,6 +2,8 @@
Module: ui.py
-------------
Obsahuje interaktivní rozhraní Robovojtíka založené na knihovně curses.
Implementuje barevné oddělení, trvalou hlavičku, historii chatu s formátováním Markdown,
spinner pro indikaci čekání a vstupní oblast.
"""
import curses
@ -19,14 +21,15 @@ logger = logging.getLogger("robovojtik.ui")
automode = False
def main_curses(stdscr):
# Inicializace barev
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Hlavička
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.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)
@ -36,7 +39,7 @@ def main_curses(stdscr):
header_height = 3
prompt_height = 2
left_width = width // 2
right_width = width - left_width - 1 # oddělovač = 1 sloupec
right_width = width - left_width - 1 # oddělovač
chat_height = height - header_height - prompt_height
header_win = curses.newwin(header_height, left_width, 0, 0)
@ -83,16 +86,20 @@ def main_curses(stdscr):
def spinner_func(ch):
mid_x = left_width // 2 - 5
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()
# Vypíšeme spinner do hlavičky
try:
header_win.addnstr(1, mid_x, f"Čekám... {ch}", left_width - mid_x - 1, curses.color_pair(5) | curses.A_BOLD)
header_win.refresh()
except curses.error:
pass
chat_history = []
def add_to_chat(text):
# Použijeme markdown_parser pro formátování
# Nové volání parseru parse_markdown z markdown_parser
segments = markdown_parser.parse_markdown(text)
for seg_text, attr in segments:
chat_win.addstr(seg_text, attr)
chat_win.addstr("\n")
for seg_text, seg_attr in segments:
chat_win.addstr(seg_text, seg_attr)
chat_win.refresh()
add_to_chat("Historie chatu:")
@ -163,13 +170,14 @@ def main_curses(stdscr):
add_to_chat("Robovojtík odpovídá:\n" + response)
continue
# Ostatní dotazy
assistant_response = api_interface.volani_asistenta(user_input, spinner_func=spinner_func)
add_to_chat("Robovojtík odpovídá:\n" + assistant_response)
if assistant_response.strip().lower().startswith("navrhovaný příkaz:"):
lines = assistant_response.splitlines()
proposal_line = lines[0]
navrhovany_prikaz = proposal_line[len("Navrhovaný příkaz:"):].strip()
navrhovany_prikaz = proposal_line[len("navrhovaný příkaz:"):].strip()
add_to_chat(f"Navrhovaný příkaz: {navrhovany_prikaz}")
if not automode:
prompt_win.erase()