feat: implement Component trait for modular UI components and refactor TUI views to use it

This commit is contained in:
asepharyana
2026-07-21 06:08:00 +07:00
parent 6ab532018a
commit d59713d3e3
10 changed files with 326 additions and 272 deletions
+8 -13
View File
@@ -7,6 +7,7 @@
//! - `mark_dirty()` di Tick hanya dipanggil jika ada event aktif atau spinner berjalan
use super::theme::Theme;
use crate::components::Component;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
@@ -14,6 +15,9 @@ use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
use ratatui::Frame;
use zesdex_domain::core::Role;
#[derive(Default)]
pub struct ChatComponent;
const PREFIX_WIDTH: usize = 15;
fn role_accent_color(role: &Role) -> Color {
@@ -160,14 +164,8 @@ fn render_one_message(
lines
}
/// Pre-render hook: update cache secara incremental.
///
/// Strategi:
/// - Cache menyimpan jumlah pesan saat terakhir di-render (`cached_msg_count`)
/// - Jika pesan bertambah → hanya render pesan BARU, append ke cache
/// - Jika pesan berkurang (eviction) atau width berubah → full rebuild
/// - Token count dihitung lazily hanya jika `token_count_dirty`
pub fn pre_render_chat(state: &mut crate::state::AppStateRest) {
impl Component for ChatComponent {
fn pre_render(&mut self, state: &mut crate::state::AppStateRest) {
let content_width = state.last_render_width.saturating_sub(PREFIX_WIDTH as u16 + 2);
let msg_count = state.transcript_cache.messages.len();
let cached_count = state.cached_msg_count;
@@ -220,11 +218,7 @@ pub fn pre_render_chat(state: &mut crate::state::AppStateRest) {
}
}
/// Render chat transcript.
///
/// TIDAK melakukan clone seluruh cache — hanya mengambil slice window
/// yang visible (biasanya 30-50 baris) via reference langsung ke cache.
pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::state::AppStateRest) {
fn draw(&mut self, frame: &mut Frame, area: Rect, state: &crate::state::AppStateRest) {
let messages = &state.transcript_cache.messages;
let scroll_offset = state.scroll.offset;
let max_visible = (area.height as usize).saturating_sub(3);
@@ -300,6 +294,7 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::state::AppStateRe
.style(Style::default().bg(Theme::BG));
frame.render_widget(paragraph, area);
}
}
fn split_spans_into_lines(spans: Vec<Span<'_>>) -> Vec<Line<'_>> {