From 6703468dd8644d9bc2119b0e90ba00bc2755a39b Mon Sep 17 00:00:00 2001 From: Dita Aji Pratama Date: Mon, 7 Sep 2026 09:23:50 +0700 Subject: [PATCH] Update TUI --- interfaces/tui/actions.py | 2 +- interfaces/tui/app.py | 30 +++++++++++++ interfaces/tui/render.py | 93 ++++++++++++++++++++++----------------- interfaces/tui/theme.py | 4 +- 4 files changed, 86 insertions(+), 43 deletions(-) diff --git a/interfaces/tui/actions.py b/interfaces/tui/actions.py index 73c11e5..775286c 100644 --- a/interfaces/tui/actions.py +++ b/interfaces/tui/actions.py @@ -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": { diff --git a/interfaces/tui/app.py b/interfaces/tui/app.py index 77b2996..b0ca2a9 100644 --- a/interfaces/tui/app.py +++ b/interfaces/tui/app.py @@ -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() diff --git a/interfaces/tui/render.py b/interfaces/tui/render.py index 4371987..51d3215 100644 --- a/interfaces/tui/render.py +++ b/interfaces/tui/render.py @@ -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 diff --git a/interfaces/tui/theme.py b/interfaces/tui/theme.py index 2186ce1..d2c1ad7 100644 --- a/interfaces/tui/theme.py +++ b/interfaces/tui/theme.py @@ -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)