Compare commits
No commits in common. "78387899adf2688805f36f18d1eb09612bc128df" and "f121c6cbb034db1b026dddb233e7cfed1ebe9166" have entirely different histories.
78387899ad
...
f121c6cbb0
21
tui/agent.py
21
tui/agent.py
@ -3,16 +3,8 @@ import threading
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from scripts import ntro
|
from scripts import ntro
|
||||||
|
|
||||||
WELCOME_ART = """
|
WELCOME_ART = """\
|
||||||
__ __ _______ __ _ ______ ______ ___ ___ _
|
\n\
|
||||||
| | | || || | | || | | _ | | | | | | |
|
|
||||||
| |_| || ___|| |_| || _ || | || | | | |_| |
|
|
||||||
| || |___ | || | | || |_||_ | | | _|
|
|
||||||
| || ___|| _ || |_| || __ || | | |_
|
|
||||||
| _ || |___ | | | || || | | || | | _ |
|
|
||||||
|__| |__||_______||_| |__||______| |___| |_||___| |___| |_|
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
╔══════════════════════════════════════════╗
|
╔══════════════════════════════════════════╗
|
||||||
║ ║
|
║ ║
|
||||||
║ /\\_/\\ ║
|
║ /\\_/\\ ║
|
||||||
@ -21,8 +13,7 @@ WELCOME_ART = """
|
|||||||
║ ( ) AI Agent ║
|
║ ( ) AI Agent ║
|
||||||
║ (___) ║
|
║ (___) ║
|
||||||
║ ║
|
║ ║
|
||||||
╚══════════════════════════════════════════╝
|
╚══════════════════════════════════════════╝"""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def log(app, role, text):
|
def log(app, role, text):
|
||||||
@ -83,11 +74,7 @@ def _agent_loop(app):
|
|||||||
app.scroll = 999999
|
app.scroll = 999999
|
||||||
for tc in response.tool_calls:
|
for tc in response.tool_calls:
|
||||||
tname = tc["function"]["name"]
|
tname = tc["function"]["name"]
|
||||||
targs = tc["function"]["arguments"]
|
log(app, "system", f" \u2192 {tname}")
|
||||||
log(app, "tool_call", json.dumps({
|
|
||||||
"name": tname,
|
|
||||||
"arguments": targs,
|
|
||||||
}))
|
|
||||||
app.scroll = 999999
|
app.scroll = 999999
|
||||||
execute_tool(app, tc)
|
execute_tool(app, tc)
|
||||||
else:
|
else:
|
||||||
|
|||||||
102
tui/render.py
102
tui/render.py
@ -3,7 +3,6 @@
|
|||||||
# lalu membaca state dari `app` untuk menggambar di layar.
|
# lalu membaca state dari `app` untuk menggambar di layar.
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
@ -22,8 +21,6 @@ C_INPUT_BORDER = 9 # border input box: biru
|
|||||||
C_STATUS_INFO = 12 # status info (workspace/hints): putih
|
C_STATUS_INFO = 12 # status info (workspace/hints): putih
|
||||||
C_HINT_DISABLED = 13 # hint disabled (abu-abu)
|
C_HINT_DISABLED = 13 # hint disabled (abu-abu)
|
||||||
C_WELCOME = 14 # welcome art: light blue
|
C_WELCOME = 14 # welcome art: light blue
|
||||||
C_TOOL_CALL = 15 # tool call: kuning terang
|
|
||||||
C_TOOL_RESULT = 16 # tool result: magenta muda
|
|
||||||
|
|
||||||
|
|
||||||
def init_colors():
|
def init_colors():
|
||||||
@ -43,8 +40,6 @@ def init_colors():
|
|||||||
curses.init_pair(C_INPUT_BORDER, curses.COLOR_BLUE, -1)
|
curses.init_pair(C_INPUT_BORDER, curses.COLOR_BLUE, -1)
|
||||||
curses.init_pair(C_HINT_DISABLED, 8, -1) # abu-abu di atas bg default
|
curses.init_pair(C_HINT_DISABLED, 8, -1) # abu-abu di atas bg default
|
||||||
curses.init_pair(C_WELCOME, curses.COLOR_BLUE + 8, -1) # light blue
|
curses.init_pair(C_WELCOME, curses.COLOR_BLUE + 8, -1) # light blue
|
||||||
curses.init_pair(C_TOOL_CALL, curses.COLOR_YELLOW + 8, -1) # bright yellow
|
|
||||||
curses.init_pair(C_TOOL_RESULT, curses.COLOR_MAGENTA + 8, -1) # bright magenta
|
|
||||||
|
|
||||||
|
|
||||||
def draw(app, stdscr):
|
def draw(app, stdscr):
|
||||||
@ -79,113 +74,67 @@ def draw_chat(app, stdscr):
|
|||||||
if chat_h <= 0:
|
if chat_h <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Render log ke list of rows; setiap row = list of (color, text) segments.
|
# Render log ke list of (color, text) agar scroll calculation akurat
|
||||||
# Ini memungkinkan satu baris punya multi-warna (misal label tool_call).
|
# Setiap baris di-wrap sesuai lebar terminal
|
||||||
# Setiap baris di-wrap sesuai lebar terminal.
|
rendered = []
|
||||||
rendered = [] # list of list of (color, text)
|
|
||||||
|
|
||||||
def _add_row(segments):
|
|
||||||
# segments: list of (color, text)
|
|
||||||
rendered.append(segments)
|
|
||||||
|
|
||||||
def _add_blank():
|
|
||||||
rendered.append([(None, "")])
|
|
||||||
|
|
||||||
def _wrap_render(text, indent=0, color=C_INPUT):
|
def _wrap_render(text, indent=0, color=C_INPUT):
|
||||||
available = w - indent - 1 # sisakan 1 kolom margin kanan
|
available = w - indent - 1 # sisakan 1 kolom margin kanan
|
||||||
if available <= 0:
|
if available <= 0:
|
||||||
_add_row([(color, " " * indent)])
|
rendered.append((color, " " * indent))
|
||||||
return
|
return
|
||||||
for line in text.split("\n"):
|
for line in text.split("\n"):
|
||||||
if not line:
|
if not line:
|
||||||
_add_row([(color, " " * indent)])
|
rendered.append((color, " " * indent))
|
||||||
continue
|
continue
|
||||||
start = 0
|
start = 0
|
||||||
while start < len(line):
|
while start < len(line):
|
||||||
chunk = line[start:start + available]
|
chunk = line[start:start + available]
|
||||||
_add_row([(color, " " * indent + chunk)])
|
rendered.append((color, " " * indent + chunk))
|
||||||
start += available
|
start += available
|
||||||
|
|
||||||
for idx, item in enumerate(app.log):
|
for idx, item in enumerate(app.log):
|
||||||
role, text = item["role"], item["text"]
|
role, text = item["role"], item["text"]
|
||||||
if role == "sep":
|
if role == "sep":
|
||||||
_add_blank()
|
rendered.append((None, ""))
|
||||||
_add_blank()
|
rendered.append((None, ""))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Tambah blank line sebelum system log setelah user/ai response
|
# Tambah blank line sebelum system log setelah user/ai response
|
||||||
if role == "system" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"):
|
if role == "system" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"):
|
||||||
_add_blank()
|
rendered.append((None, ""))
|
||||||
|
|
||||||
# Tambah blank line sebelum ai response setelah user (langsung, tanpa tools)
|
# Tambah blank line sebelum ai response setelah user (langsung, tanpa tools)
|
||||||
if role == "ai" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"):
|
if role == "ai" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"):
|
||||||
_add_blank()
|
rendered.append((None, ""))
|
||||||
|
|
||||||
# Tambah blank line sebelum ai response setelah system log
|
# Tambah blank line sebelum ai response setelah system log
|
||||||
if role == "ai" and idx > 0 and app.log[idx - 1]["role"] == "system":
|
if role == "ai" and idx > 0 and app.log[idx - 1]["role"] == "system":
|
||||||
_add_blank()
|
rendered.append((None, ""))
|
||||||
|
|
||||||
if role == "user":
|
if role == "user":
|
||||||
label = f" You ({item['time']}) "
|
label = f" You ({item['time']}) "
|
||||||
_add_row([(C_USER, label)])
|
rendered.append((C_USER, label))
|
||||||
_wrap_render(text, indent=1, color=C_INPUT)
|
_wrap_render(text, indent=1, color=C_INPUT)
|
||||||
elif role == "ai":
|
elif role == "ai":
|
||||||
label = f" Hendrik ({item['time']}) "
|
label = f" Hendrik ({item['time']}) "
|
||||||
_add_row([(C_AI, label)])
|
rendered.append((C_AI, label))
|
||||||
_wrap_render(text, indent=1, color=C_INPUT)
|
_wrap_render(text, indent=1, color=C_INPUT)
|
||||||
elif role == "system":
|
elif role == "system":
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
_add_row([(C_SYSTEM, lines[0])])
|
rendered.append((C_SYSTEM, lines[0]))
|
||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
_add_row([(C_SYSTEM, " " + line)])
|
rendered.append((C_SYSTEM, " " + line))
|
||||||
elif role == "welcome":
|
elif role == "welcome":
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
for line in lines:
|
for line in lines:
|
||||||
_add_row([(C_WELCOME, " " + line)])
|
rendered.append((C_WELCOME, " " + line))
|
||||||
elif role == "tool_call":
|
|
||||||
# Format:
|
|
||||||
# (blank line)
|
|
||||||
# Hendrik run_bash (HH:MM) ← "Hendrik" hijau, "run_bash (HH:MM)" kuning
|
|
||||||
# { ← arguments indent 1 spasi, kuning
|
|
||||||
# "command": "ls -la"
|
|
||||||
# }
|
|
||||||
# (blank line)
|
|
||||||
# Blank line di atas (kecuali sebelumnya sudah blank dari role lain)
|
|
||||||
if idx > 0 and app.log[idx - 1]["role"] not in ("sep", "welcome"):
|
|
||||||
_add_blank()
|
|
||||||
try:
|
|
||||||
tc = json.loads(text)
|
|
||||||
tname = tc["name"]
|
|
||||||
targs_raw = tc["arguments"]
|
|
||||||
# Pretty-print arguments
|
|
||||||
try:
|
|
||||||
targs = json.loads(targs_raw) if isinstance(targs_raw, str) else targs_raw
|
|
||||||
args_str = json.dumps(targs, indent=2, ensure_ascii=False)
|
|
||||||
except Exception:
|
|
||||||
args_str = str(targs_raw)
|
|
||||||
|
|
||||||
# Label: "Hendrik" hijau + "tool_name" kuning + "(HH:MM)" hijau
|
|
||||||
_add_row([
|
|
||||||
(C_AI, " Hendrik "),
|
|
||||||
(C_TOOL_CALL, tname),
|
|
||||||
(C_AI, f" ({item['time']}) "),
|
|
||||||
])
|
|
||||||
for aline in args_str.split("\n"):
|
|
||||||
_add_row([(C_INPUT, " " + aline)])
|
|
||||||
except Exception:
|
|
||||||
_add_row([
|
|
||||||
(C_AI, " Hendrik "),
|
|
||||||
(C_TOOL_CALL, "unknown"),
|
|
||||||
(C_AI, f" ({item['time']}) "),
|
|
||||||
])
|
|
||||||
# Blank line di bawah
|
|
||||||
_add_blank()
|
|
||||||
elif role == "error":
|
elif role == "error":
|
||||||
label = " \u2717 "
|
label = " \u2717 "
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
_add_row([(C_ERROR, label + (lines[0] if lines else ""))])
|
rendered.append((C_ERROR, label + (lines[0] if lines else "")))
|
||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
_add_row([(C_ERROR, " " + line)])
|
rendered.append((C_ERROR, " " + line))
|
||||||
|
|
||||||
# Clamp scroll agar tidak melebihi total baris
|
# Clamp scroll agar tidak melebihi total baris
|
||||||
total = len(rendered)
|
total = len(rendered)
|
||||||
@ -204,21 +153,14 @@ def draw_chat(app, stdscr):
|
|||||||
|
|
||||||
y = chat_top
|
y = chat_top
|
||||||
for i in range(app.scroll, min(app.scroll + chat_h, total)):
|
for i in range(app.scroll, min(app.scroll + chat_h, total)):
|
||||||
segments = rendered[i]
|
color, text = rendered[i]
|
||||||
x = 0
|
|
||||||
for color, text in segments:
|
|
||||||
if not text:
|
|
||||||
continue
|
|
||||||
attr = curses.color_pair(color) | curses.A_BOLD if color else curses.A_NORMAL
|
attr = curses.color_pair(color) | curses.A_BOLD if color else curses.A_NORMAL
|
||||||
remaining = w - x
|
if len(text) > w:
|
||||||
if remaining <= 0:
|
text = text[:w]
|
||||||
break
|
|
||||||
display = text[:remaining]
|
|
||||||
try:
|
try:
|
||||||
stdscr.addstr(y, x, display, attr)
|
stdscr.addstr(y, 0, text, attr)
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
x += len(display)
|
|
||||||
y += 1
|
y += 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user