Update TUI

This commit is contained in:
Dita Aji Pratama 2026-09-07 09:23:50 +07:00
parent 0383410c84
commit 6703468dd8
4 changed files with 86 additions and 43 deletions

View File

@ -76,7 +76,7 @@ ACTIONS = {
},
"send_prompt": {
"key": KEY_CTRL_ENTER,
"label": "Send Prompt", "mnemonic": "P", "shortcut": "Ctrl+Enter",
"label": "Send Prompt", "mnemonic": "P", "shortcut": "Ctrl+Return",
"handler": _send_prompt, "enabled": _ready,
},
"cancel_inference": {

View File

@ -24,6 +24,10 @@ class HendrikTUI:
self.agent_max_iterations = agent_max_iterations
self.character_name = config.AGENT_CHARACTER or "AI Agent"
# Nama model set aktif (dipakai status bar). Diisi dari default set
# saat startup, lalu diperbarui setiap kali user ganti model set.
self.model_set_name = self._lookup_model_set_name()
self.messages = None
self.log = []
self.input_buffer = [""]
@ -47,6 +51,31 @@ class HendrikTUI:
self.session_mgr = NeoSessionManager()
self.current_session: NeoSession | None = None
def _lookup_model_set_name(self) -> str:
"""Nama model set yang sedang aktif (untuk status bar).
Mulai dari default set tipe 'llm'; jika model yang sedang dipakai
ternyata milik set lain, cari set yang memuat model tersebut."""
try:
default_id = moop.get_default_set_id(None, "llm")
default_set = moop.get_set(None, default_id) if default_id else None
model = getattr(self.llm, "model", "") or ""
if default_set and (
not model
or model in {c["model"] for c in moop.resolve_chain(None, default_id)}
):
return default_set["name"] or "-"
if default_set:
for cand in moop.list_sets(None):
if cand["id"] == default_id:
continue
if model in {c["model"] for c in moop.resolve_chain(None, cand["id"])}:
return cand["name"] or "-"
return default_set["name"] or "-"
return "-"
except Exception:
return "-"
def switch_model_set(self, set_id):
default_chain = moop.default_chain(None, "llm")
target = moop.get_set(None, set_id)
@ -59,6 +88,7 @@ class HendrikTUI:
if not default_chain:
moop.set_default(None, "llm", set_id)
self.llm.set_chains({"llm": chain})
self.model_set_name = target.get("name") or self._lookup_model_set_name()
if self.current_session:
self.session_mgr.update_model_info(
self.current_session.doc_id, self._model_info()

View File

@ -9,6 +9,7 @@ import json
from tools.coder import get_current_workspace
from . import theme
from .actions import ACTIONS
from .menubar import draw_menubar, draw_menu_dropdown
# Alias warna agar kode drawing di bawah tetap ringkas.
@ -16,6 +17,7 @@ C_USER = theme.C_USER
C_AI = theme.C_AI
C_SYSTEM = theme.C_SYSTEM
C_INPUT = theme.C_INPUT
C_STATUS = theme.C_STATUS
C_STATUS_READY = theme.C_STATUS_READY
C_STATUS_PROC = theme.C_STATUS_PROC
C_ERROR = theme.C_ERROR
@ -344,55 +346,66 @@ def draw_input(app, stdscr):
def draw_status(app, stdscr):
# Status bar di baris h-9: mode, model, workspace, session
# Status bar di baris h-9.
# kiri : "STATUS nama_model_set nama_model workspace"
# kanan : "nama_character Ctrl+Return:Send"
# STATUS = mode READY/PROCESSING (berwarna). Bila terminal sempit, bagian
# workspace dipotong dari depan (sisa akhir path) agar sisi kanan utuh.
h, w = app.h, app.w
y = h - 9
ws = get_current_workspace()
session_tag = ""
if app.current_session:
session_tag = f" {app.current_session.name} "
model = app.llm.model
mode = " PROCESSING " if app.processing else " READY "
# Format: [MODE] model workspace session (menggunakan spasi sebagai pemisah)
status_text = f"{mode} {model} {ws} {session_tag}"
# Jika terlalu panjang, potong bagian workspace-nya saja agar tetap readable
ws_display = ws
if len(status_text) > w:
max_ws_len = w - len(mode) - len(model) - len(session_tag) - 3
if len(ws) > max_ws_len:
ws_display = ".." + ws[-(max_ws_len - 2):]
status_text = f"{mode} {model} {ws_display} {session_tag}"
set_name = (getattr(app, "model_set_name", "") or "-").strip() or "-"
model = (app.llm.model or "-").strip() or "-"
ws = get_current_workspace() or "-"
# Gambar background dasar
full_status = status_text.ljust(w)[:w]
stdscr.addstr(y, 0, full_status, curses.color_pair(C_STATUS_INFO))
send_key = ACTIONS["send_prompt"].get("shortcut") or "Ctrl+Return"
right = f" {app.character_name} {send_key}:Send "
# Sisi kiri hanya boleh memakai ruang selebar w - len(right) - 1 (pemisah).
avail = max(0, w - len(right) - 1)
fixed = len(mode) + len(set_name) + len(model) + 3 # 3 = spasi pemisah
ws_display = ws
if fixed + len(ws_display) > avail:
budget = max(0, avail - fixed)
ws_display = (".." + ws)[-budget:] if budget > 0 else ""
left = f"{mode} {set_name} {model} {ws_display}"[:avail]
def _clip(text, x):
# Potong teks agar tidak melewati batas area kiri (kolom `avail`).
if x >= avail:
return ""
return text[: avail - x]
# Background penuh sepanjang baris
try:
stdscr.addstr(y, 0, " " * w, curses.color_pair(C_STATUS_INFO))
except curses.error:
pass
# Highlight mode dengan warna berbeda (Hijau/Kuning)
mode_attr = curses.color_pair(C_STATUS_READY) if not app.processing else curses.color_pair(C_STATUS_PROC)
stdscr.addstr(y, 0, mode, mode_attr | curses.A_BOLD)
# Highlight Model, Workspace dan Session dengan warna Putih-Bold
highlight_attr = curses.color_pair(C_STATUS_INFO) | curses.A_BOLD
try:
# Mode sudah digambar, kita cari posisi setelah mode
model_start = len(mode) + 1 # melewati mode + 1 spasi
ws_start = len(mode) + len(model) + 2
ws_len = len(ws_display)
# Gambar Model
stdscr.addstr(y, model_start, model, highlight_attr)
# Gambar Workspace
stdscr.addstr(y, ws_start, ws_display, highlight_attr)
# Gambar Session
session_start = ws_start + ws_len + 1 # melewati workspace + 1 spasi
if session_tag:
stdscr.addstr(y, session_start, session_tag, highlight_attr)
stdscr.addstr(y, 0, left, highlight_attr)
# Mode ditimpa lebih dulu dengan warnanya sendiri
stdscr.addstr(y, 0, mode, mode_attr | curses.A_BOLD)
# Posisi model set, model, dan workspace (mengikuti isi `left`)
set_start = len(mode) + 1
stdscr.addstr(y, set_start, _clip(set_name, set_start), highlight_attr)
model_start = set_start + len(set_name) + 1
stdscr.addstr(y, model_start, _clip(model, model_start), highlight_attr)
ws_start = model_start + len(model) + 1
if ws_display:
stdscr.addstr(y, ws_start, _clip(ws_display, ws_start), highlight_attr)
except curses.error:
pass
# Sisi kanan: character + petunjuk tombol kirim
right_attr = curses.color_pair(C_STATUS) | curses.A_BOLD
try:
stdscr.addstr(y, w - len(right), right, right_attr)
except curses.error:
pass

View File

@ -10,7 +10,7 @@ C_USER = 2 # user message: cyan
C_AI = 3 # AI response: hijau
C_SYSTEM = 4 # system log: kuning
C_INPUT = 5 # text input: putih
C_STATUS = 6 # status bar: hitam di atas kuning
C_STATUS = 6 # status bar: hitam di atas biru
C_STATUS_READY = 10 # status READY: hijau
C_STATUS_PROC = 11 # status PROCESSING: kuning
C_SEP = 7 # separator line: magenta
@ -38,7 +38,7 @@ def init_colors():
curses.init_pair(C_AI, curses.COLOR_GREEN, -1)
curses.init_pair(C_SYSTEM, curses.COLOR_YELLOW, -1)
curses.init_pair(C_INPUT, curses.COLOR_WHITE, -1)
curses.init_pair(C_STATUS, curses.COLOR_BLACK, curses.COLOR_YELLOW)
curses.init_pair(C_STATUS, curses.COLOR_BLACK, curses.COLOR_BLUE)
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_INFO, curses.COLOR_BLACK, curses.COLOR_WHITE)