Merge branch 'master' of https://gitea.ditaajipratama.net/aji/hendrik
This commit is contained in:
commit
9d617b4702
@ -111,6 +111,7 @@ class HendrikTUI:
|
|||||||
curses.use_default_colors()
|
curses.use_default_colors()
|
||||||
init_colors()
|
init_colors()
|
||||||
stdscr.keypad(True)
|
stdscr.keypad(True)
|
||||||
|
curses.raw() # Ctrl+C sebagai key code 3, bukan SIGINT → KeyboardInterrupt
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
|
|
||||||
self.messages = [{"role": "system", "content": self.build_system_prompt(
|
self.messages = [{"role": "system", "content": self.build_system_prompt(
|
||||||
@ -140,7 +141,12 @@ class HendrikTUI:
|
|||||||
try:
|
try:
|
||||||
key = stdscr.getch()
|
key = stdscr.getch()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
break
|
if self.processing:
|
||||||
|
self.llm.cancel_requested = True
|
||||||
|
self.agent_done.set()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
key = -1
|
||||||
|
|
||||||
handle_key(self, stdscr, key)
|
handle_key(self, stdscr, key)
|
||||||
|
|
||||||
|
|||||||
@ -39,12 +39,11 @@ def handle_key(app, stdscr, key):
|
|||||||
processing = app.processing
|
processing = app.processing
|
||||||
|
|
||||||
# -- Always allowed (even during processing) --
|
# -- Always allowed (even during processing) --
|
||||||
if key == 3: # Ctrl+C → cancel stream jika processing, exit jika tidak
|
# Check for Ctrl+C (key 3) and also potential curses representation of Ctrl+C
|
||||||
|
if key == 3 or key == 26: # 3 is Ctrl+C, 26 is Ctrl+Z (sometimes mapped)
|
||||||
if processing:
|
if processing:
|
||||||
# Cancel stream yang sedang berjalan
|
|
||||||
app.llm.cancel_requested = True
|
app.llm.cancel_requested = True
|
||||||
log(app, "system", " Stream cancelled by user")
|
log(app, "system", " Stream cancelled by user")
|
||||||
app.scroll = 999999
|
|
||||||
else:
|
else:
|
||||||
app.running = False
|
app.running = False
|
||||||
elif key == curses.KEY_PPAGE:
|
elif key == curses.KEY_PPAGE:
|
||||||
|
|||||||
@ -36,7 +36,7 @@ def init_colors():
|
|||||||
curses.init_pair(C_STATUS, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
curses.init_pair(C_STATUS, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
||||||
curses.init_pair(C_STATUS_READY, curses.COLOR_BLACK, curses.COLOR_GREEN + 8)
|
curses.init_pair(C_STATUS_READY, curses.COLOR_BLACK, curses.COLOR_GREEN + 8)
|
||||||
curses.init_pair(C_STATUS_PROC, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
curses.init_pair(C_STATUS_PROC, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
||||||
curses.init_pair(C_STATUS_INFO, curses.COLOR_WHITE, -1)
|
curses.init_pair(C_STATUS_INFO, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||||
curses.init_pair(C_SEP, curses.COLOR_MAGENTA, -1)
|
curses.init_pair(C_SEP, curses.COLOR_MAGENTA, -1)
|
||||||
curses.init_pair(C_ERROR, curses.COLOR_RED, -1)
|
curses.init_pair(C_ERROR, curses.COLOR_RED, -1)
|
||||||
curses.init_pair(C_INPUT_BORDER, curses.COLOR_BLUE, -1)
|
curses.init_pair(C_INPUT_BORDER, curses.COLOR_BLUE, -1)
|
||||||
@ -57,27 +57,52 @@ def draw(app, stdscr):
|
|||||||
|
|
||||||
|
|
||||||
def draw_header(app, stdscr):
|
def draw_header(app, stdscr):
|
||||||
# Baris paling atas: " Hendrik AI Agent ─────── <model> "
|
# Baris 1: " Hendrik AI Agent ─────── <model> "
|
||||||
w = app.w
|
w = app.w
|
||||||
name = " Hendrik AI Agent "
|
name = " Hendrik AI Agent "
|
||||||
model = f" {app.llm.model} "
|
model = f" {app.llm.model} "
|
||||||
|
# Perbaiki kalkulasi agar line1 tepat mengisi w kolom
|
||||||
mid = w - len(model) - 1
|
mid = w - len(model) - 1
|
||||||
pad = max(1, mid - len(name) - 1)
|
pad = max(1, mid - len(name) - 1)
|
||||||
line = name + "\u2500" * pad + " " + model
|
line1 = name + "\u2500" * pad + " " + model
|
||||||
attr = curses.color_pair(C_HEADER) | curses.A_BOLD
|
# Gunakan ljust(w) untuk memastikan background biru penuh sampai ujung kanan
|
||||||
stdscr.addstr(0, 0, line[:w], attr)
|
full_line1 = line1.ljust(w)
|
||||||
|
attr1 = curses.color_pair(C_HEADER) | curses.A_BOLD
|
||||||
|
stdscr.addstr(0, 0, full_line1[:w], attr1)
|
||||||
|
|
||||||
|
# Baris 2: Shortcut hints
|
||||||
|
# Tampilkan hanya shortcut yang aktif sesuai status processing
|
||||||
|
if app.processing:
|
||||||
|
# Hanya ^C (cancel) yang aktif saat processing
|
||||||
|
hints = " ^C:cancel "
|
||||||
|
else:
|
||||||
|
# Semua shortcut aktif saat READY
|
||||||
|
hints = " ^N:new ^O:open ^R:rename ^D:send ^E:model ^W:workspace ^C:exit "
|
||||||
|
|
||||||
|
# Align left and fill the rest of the width with spaces to keep background color
|
||||||
|
full_line = hints.ljust(w)
|
||||||
|
# Menggunakan C_HINT_DISABLED untuk warna abu-abu cerah
|
||||||
|
attr2 = curses.color_pair(C_HINT_DISABLED)
|
||||||
|
stdscr.addstr(1, 0, full_line[:w], attr2)
|
||||||
|
|
||||||
|
|
||||||
def draw_chat(app, stdscr):
|
def draw_chat(app, stdscr):
|
||||||
# Area chat — dari baris 1 sampai baris (h - 10).
|
# Area chat — dari baris 2 sampai baris (h - 11).
|
||||||
# Bisa di-scroll dengan Page Up / Page Down.
|
# Bisa di-scroll dengan Page Up / Page Down.
|
||||||
# app.log berisi daftar item (role, text, time) untuk display.
|
# app.log berisi daftar item (role, text, time) untuk display.
|
||||||
h, w = app.h, app.w
|
h, w = app.h, app.w
|
||||||
chat_top = 1
|
chat_top = 2
|
||||||
chat_h = h - 10
|
chat_h = h - 11
|
||||||
if chat_h <= 0:
|
if chat_h <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Auto-scroll: Jika agen tidak sedang processing (READY),
|
||||||
|
# pastikan scroll berada di posisi paling bawah agar respon terbaru terlihat.
|
||||||
|
if not app.processing:
|
||||||
|
# Kita akan hitung total rendered rows nanti,
|
||||||
|
# tapi kita bisa memberi hint atau melakukan adjustment di sini.
|
||||||
|
pass
|
||||||
|
|
||||||
# Render log ke list of rows; setiap row = list of (color, text) segments.
|
# Render log ke list of rows; setiap row = list of (color, text) segments.
|
||||||
# Ini memungkinkan satu baris punya multi-warna (misal label tool_call).
|
# 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.
|
||||||
@ -216,6 +241,7 @@ def draw_chat(app, stdscr):
|
|||||||
# Clamp scroll agar tidak melebihi total baris
|
# Clamp scroll agar tidak melebihi total baris
|
||||||
total = len(rendered)
|
total = len(rendered)
|
||||||
max_scroll = max(0, total - chat_h)
|
max_scroll = max(0, total - chat_h)
|
||||||
|
|
||||||
if app.scroll > max_scroll:
|
if app.scroll > max_scroll:
|
||||||
app.scroll = max_scroll
|
app.scroll = max_scroll
|
||||||
app.scroll = max(0, app.scroll)
|
app.scroll = max(0, app.scroll)
|
||||||
@ -348,50 +374,50 @@ def draw_input(app, stdscr):
|
|||||||
|
|
||||||
|
|
||||||
def draw_status(app, stdscr):
|
def draw_status(app, stdscr):
|
||||||
# Status bar di baris h-9: [session] workspace, mode (READY/PROCESSING), shortcut hints
|
# Status bar di baris h-9: mode, workspace, session
|
||||||
h, w = app.h, app.w
|
h, w = app.h, app.w
|
||||||
y = h - 9
|
y = h - 9
|
||||||
ws = os.getcwd()
|
ws = os.getcwd()
|
||||||
|
|
||||||
session_tag = ""
|
session_tag = ""
|
||||||
if app.current_session:
|
if app.current_session:
|
||||||
sname = app.current_session.name
|
session_tag = f" {app.current_session.name} "
|
||||||
if len(sname) > 20:
|
|
||||||
sname = sname[:17] + "..."
|
|
||||||
session_tag = f"[{sname}] "
|
|
||||||
|
|
||||||
mode = " PROCESSING " if app.processing else " READY "
|
mode = " PROCESSING " if app.processing else " READY "
|
||||||
if app.processing:
|
|
||||||
hints = " ^N:new ^O:open ^R:rename ^E:model ^W:ws ^C:cancel "
|
# Format: [MODE] workspace session (menggunakan spasi sebagai pemisah)
|
||||||
else:
|
status_text = f"{mode} {ws} {session_tag}"
|
||||||
hints = " ^N:new ^O:open ^R:rename ^D:send ^E:model ^W:ws ^C:exit "
|
|
||||||
max_left = w - len(mode) - len(hints) - 4
|
# Jika terlalu panjang, potong bagian workspace-nya saja agar tetap readable
|
||||||
left = session_tag + ws
|
ws_display = ws
|
||||||
if len(left) > max_left:
|
if len(status_text) > w:
|
||||||
left = ".." + left[-(max_left - 2):]
|
max_ws_len = w - len(mode) - len(session_tag) - 2
|
||||||
|
if len(ws) > max_ws_len:
|
||||||
|
ws_display = ".." + ws[-(max_ws_len - 2):]
|
||||||
|
status_text = f"{mode} {ws_display} {session_tag}"
|
||||||
|
|
||||||
status = (f" {left} \u2502{mode}\u2502{hints}").ljust(w)[:w]
|
# Gambar background dasar
|
||||||
stdscr.addstr(y, 0, status, curses.color_pair(C_STATUS_INFO))
|
full_status = status_text.ljust(w)[:w]
|
||||||
|
stdscr.addstr(y, 0, full_status, curses.color_pair(C_STATUS_INFO))
|
||||||
|
|
||||||
# Highlight mode dengan warna berbeda
|
# Highlight mode dengan warna berbeda (Hijau/Kuning)
|
||||||
mode_start = len(f" {left} \u2502")
|
|
||||||
mode_end = mode_start + len(mode)
|
|
||||||
mode_attr = curses.color_pair(C_STATUS_READY) if not app.processing else curses.color_pair(C_STATUS_PROC)
|
mode_attr = curses.color_pair(C_STATUS_READY) if not app.processing else curses.color_pair(C_STATUS_PROC)
|
||||||
stdscr.addstr(y, mode_start, mode, mode_attr | curses.A_BOLD)
|
stdscr.addstr(y, 0, mode, mode_attr | curses.A_BOLD)
|
||||||
|
|
||||||
# Hint shortcuts di kanan — tombol tertentu abu-abu saat processing
|
# Highlight Workspace dan Session dengan warna Putih-Bold
|
||||||
x = mode_end + 2
|
highlight_attr = curses.color_pair(C_STATUS_INFO) | curses.A_BOLD
|
||||||
hints_parts = [
|
|
||||||
("^N:new", True),
|
try:
|
||||||
(" ^O:open", True),
|
# Mode sudah digambar, kita cari posisi setelah mode
|
||||||
(" ^R:rename", True),
|
ws_start = len(mode) + 1 # melewati mode + 1 spasi
|
||||||
(" ^D:send", not app.processing),
|
ws_len = len(ws_display)
|
||||||
(" ^E:model", True),
|
|
||||||
(" ^W:ws", True),
|
# Gambar Workspace
|
||||||
(" ^C:cancel", app.processing),
|
stdscr.addstr(y, ws_start, ws_display, highlight_attr)
|
||||||
(" ^C:exit", not app.processing),
|
|
||||||
]
|
# Gambar Session
|
||||||
for text, enabled in hints_parts:
|
session_start = ws_start + ws_len + 1 # melewati workspace + 1 spasi
|
||||||
attr = curses.color_pair(C_STATUS_INFO) | curses.A_BOLD if enabled else curses.color_pair(C_HINT_DISABLED)
|
if session_tag:
|
||||||
stdscr.addstr(y, x, text, attr)
|
stdscr.addstr(y, session_start, session_tag, highlight_attr)
|
||||||
x += len(text)
|
except curses.error:
|
||||||
|
pass
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user