7.4 KiB
7.4 KiB
TUI (Terminal User Interface)
Dibangun di atas ratatui + crossterm. Kode ada di apps/interfaces/tui/src/.
Struktur Source
apps/interfaces/tui/src/
├── run.rs # Event loop utama
├── state.rs # AppStateRest — single source of truth
├── action.rs # apply_action(): satu-satunya mutator state
├── turn.rs # Spawn agent turn di background thread
├── lib.rs # Re-export publik
├── controller/
│ ├── input.rs # Key handler → Vec<Action>
│ └── command.rs # Slash command parser
├── view/
│ ├── mod.rs # Layout + pre_render() + draw()
│ ├── chat.rs # Chat transcript panel (dengan display cache)
│ ├── sidebar.rs # Sidebar: workflow, tasks, usage
│ ├── status.rs # Status bar satu baris
│ ├── markdown.rs # Markdown → styled Span (pulldown-cmark)
│ ├── workflow.rs # Workflow/hive-mind progress panel
│ ├── theme.rs # Tokyo Night color palette (const)
│ └── overlays/ # 16 overlay panel
└── model/ # Data model lokal TUI
Render Pipeline (Per Frame)
run_loop_inner() [50ms in-flight / 200ms idle]
│
├── drain expired toasts (1x, bukan 2x)
│
├── if dirty:
│ view::pre_render(&mut state) ← update cache (markdown, token count)
│ terminal.draw(|f| view::draw(f, &state))
│ state.dirty = false
│
└── poll events → apply_action → Action::Tick
Optimasi Performa
| Masalah lama | Solusi saat ini |
|---|---|
count_tokens (tiktoken) setiap frame |
Cache cached_token_count, update hanya saat pesan baru |
render_markdown ulang setiap frame |
display_lines_cache di AppStateRest, rebuild saat transcript_cache.dirty |
Vec::remove(0) untuk evict pesan lama |
VecDeque::pop_front() — O(1) |
Mutex<bool> untuk turn_in_flight |
Arc<AtomicBool> — lock-free |
| Render terus meski idle | Skip terminal.draw() jika dirty == false |
| Poll 50ms konstan | Adaptif: 50ms saat in-flight, 200ms saat idle |
drain_expired_toasts 2x per iterasi |
Sekali saja di run_loop_inner |
State (AppStateRest)
AppStateRest di state.rs adalah satu-satunya sumber kebenaran TUI:
AppStateRest {
settings: Settings // provider, model, dll
app_config: AppConfig // endpoint, env vars
workspace_roots: Vec<PathBuf> // working directories
session_dir / session_id // path sesi aktif
memory_dir // direktori memory
session_runtime: Option<SessionRuntime> // history pesan, usage stats
transcript_cache: TranscriptCache // VecDeque<ChatMessageDisplay>
scroll: ScrollState // offset scroll pane chat
input: InputState // buffer, cursor, history, autocomplete
misc: MiscState // overlay aktif, toasts, flags
turn_events: Arc<Mutex<VecDeque<TurnEvent>>> // queue event dari agent
turn_in_flight_flag: Arc<AtomicBool> // apakah agent sedang jalan
abort_flag: Arc<AtomicBool> // sinyal abort oleh user
// Cache performa
display_lines_cache: Vec<Line<'static>> // hasil render markdown
cached_token_count: usize // token count terkini
token_count_dirty: bool // perlu hitung ulang?
last_render_width: u16 // lebar terminal saat render terakhir
dirty: bool // perlu render ulang?
quit: bool // keluar dari loop?
}
Aturan mutasi:
- Dimutasi hanya dari
action.rs::apply_action()danrun.rs(untuk dirty/quit) - Semua fungsi
view/*bersifat read-only terhadap state pre_render_chat()boleh mutasi hanya field cache (display_lines_cache,cached_token_count,token_count_dirty)
Input & Actions
controller/input.rs::handle_key() → Vec<Action> → apply_action(&mut state, action)
Semua mutasi state melewati satu titik: apply_action. Controller tidak tahu bagaimana state diubah, hanya action apa yang dihasilkan.
Action Utama
| Action | Efek |
|---|---|
SubmitInput(text) |
Push ke transcript, spawn agent turn |
Tick |
Drain TurnEvent queue, update state dari hasil agent |
ScrollUp/Down |
Ubah scroll.offset |
OpenOverlay(v) |
Set misc.overlay = v |
Resize(w, h) |
Invalidasi cache display, set last_render_width |
AbortTurn |
Store true ke abort_flag |
ForceQuit |
Set quit = true |
Overlays (16 Panel)
| Overlay | File | Fungsi |
|---|---|---|
Help |
overlays/help.rs |
Daftar shortcut keyboard |
Settings |
overlays/settings.rs |
Panel pengaturan |
Bash |
overlays/bash.rs |
Background shell jobs |
QuitConfirm |
overlays/quit_confirm.rs |
Konfirmasi keluar |
KeyInput |
overlays/key_input.rs |
Capture key binding |
Editor |
overlays/editor.rs |
File editor inline |
Effort |
overlays/effort.rs |
Pilih level reasoning LLM |
Mcp |
overlays/mcp.rs |
Manajemen MCP server |
Todo |
overlays/todo.rs |
Daftar TODO |
Rewind |
overlays/rewind.rs |
Navigasi history pesan |
Learning |
overlays/learning.rs |
Viewer lesson |
Usage |
overlays/usage.rs |
Statistik token |
Loading |
overlays/loading.rs |
Spinner generik |
ModelSelector |
overlays/model_selector.rs |
Pilih model LLM |
ClearConfirm |
overlays/clear_confirm.rs |
Konfirmasi clear chat |
Layout Terminal
┌───────────────────────────────────────────────┐
│ │
│ Chat Transcript Sidebar (≥90) │
│ (view/chat.rs) ┌────────────┐ │
│ VecDeque messages │ Workflow │ │
│ + markdown cache ├────────────┤ │
│ scrollable │ Tasks │ │
│ ├────────────┤ │
│ │ Usage │ │
│ └────────────┘ │
├───────────────────────────────────────────────┤
│ ❯ Input Bar + Autocomplete dropdown │
├───────────────────────────────────────────────┤
│ ⚡zesdex READY │ ...center... │ tok · model │
└───────────────────────────────────────────────┘
Sidebar hanya tampil jika lebar terminal ≥ 90 kolom.
Theme
view/theme.rs mendefinisikan palette Tokyo Night sebagai const Color:
PRIMARY, BG, SURFACE, SURFACE_ELEVATED, BORDER, TEXT, TEXT_DIM, TEXT_MUTED, SUCCESS, WARNING, ERROR, INFO, HIGHLIGHT, CODE_BG, dll.
Markdown Rendering
view/markdown.rs::render_markdown(text, width, dim):
- Parse dengan
pulldown-cmark - Hasilkan
Vec<Span<'static>>dengan styling - Support: heading, code block, diff block (warna +/-/@@), list, blockquote, table, inline code, link
dim=true→ semua span memakaiTEXT_DIM+ italic (untuk tool output)- Hasil di-cache di
AppStateRest::display_lines_cache