Zřejmě to stále funguje
This commit is contained in:
parent
4e3d3466f1
commit
a83fba1a23
@ -1,62 +1,128 @@
|
|||||||
"""
|
"""
|
||||||
Module: markdown_parser.py
|
Module: markdown_parser.py
|
||||||
----------------------------
|
----------------------------
|
||||||
Jednoduchý Markdown parser pro curses.
|
Rozšířený Markdown parser pro curses.
|
||||||
Rozpozná základní znaèky:
|
|
||||||
- **tuènì** › curses.A_BOLD
|
Podporované funkce:
|
||||||
- *kurzíva* › curses.A_UNDERLINE
|
1. Nadpisy (řádky začínající #) -> zobrazeny tučně a podtrženě
|
||||||
- `kód` › curses.A_REVERSE (pro zvýraznìní kódu)
|
2. Odrážky (řádky začínající - a *) -> zobrazeny s tučným textem a symbolickou tečkou
|
||||||
Vrací seznam segmentù ve tvaru (text, atribut), které lze postupnì vypsat pomocí curses.
|
3. Víceřádkové bloky kódu (```):
|
||||||
"""
|
vše mezi trojitými backticky je zobrazeno s curses.A_REVERSE
|
||||||
|
4. Inline formátování:
|
||||||
import re
|
**text** -> curses.A_BOLD
|
||||||
import curses
|
*text* -> curses.A_UNDERLINE
|
||||||
|
`text` -> curses.A_REVERSE
|
||||||
def parse_markdown(text):
|
|
||||||
"""
|
Poznámka: nepodporujeme vnořené značky, a parser je pouze zjednodušený.
|
||||||
Rozparsuje zadaný text obsahující jednoduché Markdown znaèky a vrátí seznam
|
"""
|
||||||
dvojic (segment_text, attribute). Nepodporuje vnoøené znaèky.
|
|
||||||
|
import re
|
||||||
Podporované znaèky:
|
import curses
|
||||||
**text** › tuènì (A_BOLD)
|
|
||||||
*text* › kurzíva (A_UNDERLINE)
|
def parse_markdown(text):
|
||||||
`text` › kód (A_REVERSE)
|
"""
|
||||||
|
Hlavní funkce pro rozparsování zadaného textu na seznam (segment_text, attribute).
|
||||||
Pokud se znaèky nepárují, vrací zbytek textu s atributem 0.
|
Respektuje víceřádkové bloky kódu (```), nadpisy, odrážky a volá parse_inline pro
|
||||||
"""
|
zpracování běžných řádků.
|
||||||
segments = []
|
"""
|
||||||
pos = 0
|
segments = []
|
||||||
pattern = re.compile(r'(\*\*.*?\*\*|\*.*?\*|`.*?`)')
|
lines = text.splitlines()
|
||||||
for match in pattern.finditer(text):
|
in_code_block = False # Indikátor, zda se nacházíme uvnitř bloku ```.
|
||||||
start, end = match.span()
|
|
||||||
# Normalní text pøed znaèkou
|
for line in lines:
|
||||||
if start > pos:
|
stripped = line.strip()
|
||||||
segments.append((text[pos:start], 0))
|
|
||||||
token = match.group(0)
|
# Kontrola začátku/konce bloku kódu (```).
|
||||||
# Rozpoznání typu tokenu
|
if stripped.startswith("```"):
|
||||||
if token.startswith("**") and token.endswith("**"):
|
# Přepínáme stav
|
||||||
# Bold – odstraníme dvojité hvìzdièky
|
in_code_block = not in_code_block
|
||||||
content = token[2:-2]
|
if in_code_block:
|
||||||
segments.append((content, curses.A_BOLD))
|
# Řádek obsahující samotné ```
|
||||||
elif token.startswith("*") and token.endswith("*"):
|
# Pokud tam je něco navíc, ořežeme
|
||||||
# Italic – odstraníme hvìzdièky
|
code_block_marker = stripped[3:].strip()
|
||||||
content = token[1:-1]
|
# Můžeme sem zařadit logiku pro detekci jazyka, pokud by to bylo potřeba
|
||||||
segments.append((content, curses.A_UNDERLINE))
|
else:
|
||||||
elif token.startswith("`") and token.endswith("`"):
|
# Konec bloku kódu
|
||||||
# Inline kód – odstraníme zpìtné apostrofy a použijeme reverzní barvu
|
pass
|
||||||
content = token[1:-1]
|
continue
|
||||||
segments.append((content, curses.A_REVERSE))
|
|
||||||
else:
|
if in_code_block:
|
||||||
segments.append((token, 0))
|
# Ve víceřádkovém kódu každou linku zobrazíme s curses.A_REVERSE
|
||||||
pos = end
|
segments.append((line + "\n", curses.A_REVERSE))
|
||||||
# Zbytek textu
|
continue
|
||||||
if pos < len(text):
|
|
||||||
segments.append((text[pos:], 0))
|
# Pokud nejsme v bloku kódu, zpracujeme nadpisy, odrážky a inline formát.
|
||||||
return segments
|
if stripped.startswith("#"):
|
||||||
|
# Nadpis: zjistíme, kolik # tam je
|
||||||
if __name__ == "__main__":
|
hash_count = 0
|
||||||
# Jednoduchý test
|
for ch in line:
|
||||||
sample = "Toto je **tuèný text**, toto je *kurzíva* a toto je `kód`."
|
if ch == '#':
|
||||||
segments = parse_markdown(sample)
|
hash_count += 1
|
||||||
for seg, attr in segments:
|
else:
|
||||||
print(f"Segment: '{seg}', Attr: {attr}")
|
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
34
ui.py
@ -2,6 +2,8 @@
|
|||||||
Module: ui.py
|
Module: ui.py
|
||||||
-------------
|
-------------
|
||||||
Obsahuje interaktivní rozhraní Robovojtíka založené na knihovně curses.
|
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
|
import curses
|
||||||
@ -19,14 +21,15 @@ logger = logging.getLogger("robovojtik.ui")
|
|||||||
automode = False
|
automode = False
|
||||||
|
|
||||||
def main_curses(stdscr):
|
def main_curses(stdscr):
|
||||||
|
# Inicializace barev
|
||||||
curses.start_color()
|
curses.start_color()
|
||||||
curses.use_default_colors()
|
curses.use_default_colors()
|
||||||
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Hlavička
|
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Hlavička
|
||||||
curses.init_pair(2, curses.COLOR_WHITE, -1) # Chat
|
curses.init_pair(2, curses.COLOR_WHITE, -1) # Chat
|
||||||
curses.init_pair(3, curses.COLOR_GREEN, -1) # Výstup
|
curses.init_pair(3, curses.COLOR_GREEN, -1) # Výstup
|
||||||
curses.init_pair(4, curses.COLOR_YELLOW, -1) # Vstup
|
curses.init_pair(4, curses.COLOR_YELLOW, -1) # Vstup
|
||||||
curses.init_pair(5, curses.COLOR_CYAN, -1) # Spinner
|
curses.init_pair(5, curses.COLOR_CYAN, -1) # Spinner
|
||||||
curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE) # Oddělovač
|
curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE) # Oddělovač
|
||||||
|
|
||||||
curses.curs_set(1)
|
curses.curs_set(1)
|
||||||
stdscr.nodelay(False)
|
stdscr.nodelay(False)
|
||||||
@ -36,7 +39,7 @@ def main_curses(stdscr):
|
|||||||
header_height = 3
|
header_height = 3
|
||||||
prompt_height = 2
|
prompt_height = 2
|
||||||
left_width = width // 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
|
chat_height = height - header_height - prompt_height
|
||||||
|
|
||||||
header_win = curses.newwin(header_height, left_width, 0, 0)
|
header_win = curses.newwin(header_height, left_width, 0, 0)
|
||||||
@ -83,16 +86,20 @@ def main_curses(stdscr):
|
|||||||
|
|
||||||
def spinner_func(ch):
|
def spinner_func(ch):
|
||||||
mid_x = left_width // 2 - 5
|
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)
|
# Vypíšeme spinner do hlavičky
|
||||||
header_win.refresh()
|
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 = []
|
chat_history = []
|
||||||
|
|
||||||
def add_to_chat(text):
|
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)
|
segments = markdown_parser.parse_markdown(text)
|
||||||
for seg_text, attr in segments:
|
for seg_text, seg_attr in segments:
|
||||||
chat_win.addstr(seg_text, attr)
|
chat_win.addstr(seg_text, seg_attr)
|
||||||
chat_win.addstr("\n")
|
|
||||||
chat_win.refresh()
|
chat_win.refresh()
|
||||||
|
|
||||||
add_to_chat("Historie chatu:")
|
add_to_chat("Historie chatu:")
|
||||||
@ -163,13 +170,14 @@ def main_curses(stdscr):
|
|||||||
add_to_chat("Robovojtík odpovídá:\n" + response)
|
add_to_chat("Robovojtík odpovídá:\n" + response)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Ostatní dotazy
|
||||||
assistant_response = api_interface.volani_asistenta(user_input, spinner_func=spinner_func)
|
assistant_response = api_interface.volani_asistenta(user_input, spinner_func=spinner_func)
|
||||||
add_to_chat("Robovojtík odpovídá:\n" + assistant_response)
|
add_to_chat("Robovojtík odpovídá:\n" + assistant_response)
|
||||||
|
|
||||||
if assistant_response.strip().lower().startswith("navrhovaný příkaz:"):
|
if assistant_response.strip().lower().startswith("navrhovaný příkaz:"):
|
||||||
lines = assistant_response.splitlines()
|
lines = assistant_response.splitlines()
|
||||||
proposal_line = lines[0]
|
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}")
|
add_to_chat(f"Navrhovaný příkaz: {navrhovany_prikaz}")
|
||||||
if not automode:
|
if not automode:
|
||||||
prompt_win.erase()
|
prompt_win.erase()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user