Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ea3d361b1 | ||
|
|
271721694b |
@@ -1,3 +1,10 @@
|
||||
## [1.18.4](https://github.com/asepharyana/zesdex/compare/v1.18.3...v1.18.4) (2026-08-27)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **tui:** render streaming token secara inkremental + kurangi redraw sia-sia ([2717216](https://github.com/asepharyana/zesdex/commit/271721694beb62e9fa5ff932312b293aa1d56823))
|
||||
|
||||
## [1.18.3](https://github.com/asepharyana/zesdex/compare/v1.18.2...v1.18.3) (2026-08-27)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.18.3"
|
||||
version = "1.18.4"
|
||||
edition = "2021"
|
||||
authors = ["asepharyana <superaseph@gmail.com>"]
|
||||
|
||||
|
||||
@@ -112,9 +112,12 @@ fn run_loop_inner(
|
||||
}
|
||||
|
||||
// Drain expired toasts exactly once per loop iteration (was being
|
||||
// done here AND in Action::Tick before this fix).
|
||||
// done here AND in Action::Tick before this fix). Skip the clock
|
||||
// syscall + Vec scan when there are no toasts at all.
|
||||
if !state.misc.toasts.is_empty() {
|
||||
let now_ms = chrono::Utc::now().timestamp_millis();
|
||||
state.misc.drain_expired_toasts(now_ms);
|
||||
}
|
||||
|
||||
// Skip render when nothing has changed — avoids expensive markdown
|
||||
// re-parse and layout recalculation every cycle while idle.
|
||||
|
||||
@@ -179,16 +179,9 @@ impl MiscState {
|
||||
self.toasts.push(toast);
|
||||
}
|
||||
|
||||
/// Remove and return all toasts whose lifetime has expired at `now_ms`.
|
||||
pub fn drain_expired_toasts(&mut self, now_ms: i64) -> Vec<Toast> {
|
||||
let expired: Vec<_> = self
|
||||
.toasts
|
||||
.iter()
|
||||
.filter(|t| t.expired(now_ms))
|
||||
.cloned()
|
||||
.collect();
|
||||
/// Remove all toasts whose lifetime has expired at `now_ms`.
|
||||
pub fn drain_expired_toasts(&mut self, now_ms: i64) {
|
||||
self.toasts.retain(|t| !t.expired(now_ms));
|
||||
expired
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,13 @@ pub struct AppStateRest {
|
||||
pub last_render_width: u16,
|
||||
/// Number of messages that were in the cache when it was last built.
|
||||
pub cached_msg_count: usize,
|
||||
/// Cache index where the last message's rendered lines begin. Used to
|
||||
/// splice streaming updates (only re-render the trailing message).
|
||||
pub cached_last_start: usize,
|
||||
/// Content+reasoning byte length of the last message when it was last
|
||||
/// rendered. Guards the streaming branch from re-rendering on ticks
|
||||
/// where no new token arrived (spinner-only frames).
|
||||
pub cached_last_len: usize,
|
||||
/// Terminal width at the time of the last full cache build.
|
||||
pub render_width_at_cache: u16,
|
||||
/// Atomic flag set when the user aborts the current turn.
|
||||
@@ -180,6 +187,8 @@ impl Default for AppStateRest {
|
||||
token_count_dirty: true,
|
||||
last_render_width: 0,
|
||||
cached_msg_count: 0,
|
||||
cached_last_start: 0,
|
||||
cached_last_len: 0,
|
||||
render_width_at_cache: 0,
|
||||
}
|
||||
}
|
||||
@@ -230,6 +239,8 @@ impl AppStateRest {
|
||||
token_count_dirty: true,
|
||||
last_render_width: 0,
|
||||
cached_msg_count: 0,
|
||||
cached_last_start: 0,
|
||||
cached_last_len: 0,
|
||||
render_width_at_cache: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,8 @@ impl Component for ChatComponent {
|
||||
}
|
||||
state.display_lines_cache = all_lines;
|
||||
state.cached_msg_count = msg_count;
|
||||
state.cached_last_start = state.display_lines_cache.len();
|
||||
state.cached_last_len = last_msg_len(state);
|
||||
state.render_width_at_cache = state.last_render_width;
|
||||
state.transcript_cache.dirty = false;
|
||||
} else if state.transcript_cache.dirty && msg_count > cached_count {
|
||||
@@ -205,6 +207,27 @@ impl Component for ChatComponent {
|
||||
state.display_lines_cache.extend(msg_lines);
|
||||
}
|
||||
state.cached_msg_count = msg_count;
|
||||
state.cached_last_start = state.display_lines_cache.len();
|
||||
state.cached_last_len = last_msg_len(state);
|
||||
state.transcript_cache.dirty = false;
|
||||
} else if state.transcript_cache.dirty {
|
||||
// Streaming update: message count unchanged, but the last
|
||||
// assistant message's content grew (StreamToken). Re-render only
|
||||
// that message and splice its lines back into the cache — this
|
||||
// keeps updates O(new content), not O(whole history) per token.
|
||||
// Without this branch, streamed tokens would never appear (the
|
||||
// cache is only rebuilt on new-message or resize paths).
|
||||
let cur_len = last_msg_len(state);
|
||||
if cur_len != state.cached_last_len {
|
||||
if let Some(last_msg) = state.transcript_cache.messages.back() {
|
||||
if state.cached_last_start <= state.display_lines_cache.len() {
|
||||
let new_lines = render_one_message(last_msg, content_width);
|
||||
state.display_lines_cache.truncate(state.cached_last_start);
|
||||
state.display_lines_cache.extend(new_lines);
|
||||
}
|
||||
}
|
||||
state.cached_last_len = cur_len;
|
||||
}
|
||||
state.transcript_cache.dirty = false;
|
||||
}
|
||||
|
||||
@@ -326,3 +349,15 @@ fn split_spans_into_lines(spans: Vec<Span<'_>>) -> Vec<Line<'_>> {
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
/// Byte length of the last message's content + reasoning. Used as a cheap
|
||||
/// change detector for the streaming render branch — avoids re-rendering on
|
||||
/// spinner-only ticks where no new token arrived.
|
||||
fn last_msg_len(state: &crate::state::AppStateRest) -> usize {
|
||||
state
|
||||
.transcript_cache
|
||||
.messages
|
||||
.back()
|
||||
.map(|m| m.content.len() + m.reasoning.len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
@@ -86,6 +86,10 @@ pub fn draw(frame: &mut Frame, state: &AppStateRest) {
|
||||
}
|
||||
|
||||
fn render_toasts(frame: &mut Frame, state: &AppStateRest) {
|
||||
// Fast path: nothing to render.
|
||||
if state.misc.toasts.is_empty() {
|
||||
return;
|
||||
}
|
||||
let now_ms = chrono::Utc::now().timestamp_millis();
|
||||
let active: Vec<&zesdex_infrastructure::Toast> = state
|
||||
.misc
|
||||
|
||||
Reference in New Issue
Block a user