use crate::tui::app::state::{DisplayMessage, StreamingTpsTracker, ToolCallGroup};
use std::time::Instant;
#[derive(Debug, Default, Clone)]
pub struct BackgroundSessionState {
pub streaming_response: Option<String>,
pub streaming_reasoning: Option<String>,
pub active_tool_group: Option<ToolCallGroup>,
pub is_processing: bool,
pub processing_started_at: Option<Instant>,
pub last_input_tokens: Option<u32>,
pub streaming_output_tokens: u32,
pub display_token_count: usize,
pub tps_tracker: StreamingTpsTracker,
pub pending_messages: Vec<DisplayMessage>,
}
impl BackgroundSessionState {
pub fn has_live_state(&self) -> bool {
self.streaming_response.is_some()
|| self.streaming_reasoning.is_some()
|| self.active_tool_group.is_some()
|| self.is_processing
|| self.streaming_output_tokens > 0
|| !self.pending_messages.is_empty()
}
}
pub enum SessionStateMut<'a> {
Foreground(&'a mut crate::tui::app::App),
Background(&'a mut BackgroundSessionState),
}
impl SessionStateMut<'_> {
pub fn append_streaming_chunk(&mut self, text: &str) {
match self {
Self::Foreground(app) => app.append_streaming_chunk(text.to_string()),
Self::Background(bg) => {
bg.streaming_reasoning = None;
bg.streaming_response
.get_or_insert_with(String::new)
.push_str(text);
}
}
}
pub fn append_reasoning_chunk(&mut self, text: &str) {
let is_whitespace = text.trim().is_empty();
if text.is_empty() {
return;
}
match self {
Self::Foreground(app) => {
if let Some(ref mut existing) = app.streaming_reasoning {
existing.push_str(text);
} else if !is_whitespace {
app.streaming_reasoning = Some(text.to_string());
}
if app.auto_scroll {
app.scroll_offset = 0;
}
}
Self::Background(bg) => {
if let Some(ref mut existing) = bg.streaming_reasoning {
existing.push_str(text);
} else if !is_whitespace {
bg.streaming_reasoning = Some(text.to_string());
}
}
}
}
pub fn set_processing(&mut self, processing: bool) {
match self {
Self::Foreground(app) => {
app.is_processing = processing;
if processing && app.processing_started_at.is_none() {
app.processing_started_at = Some(Instant::now());
} else if !processing {
app.processing_started_at = None;
}
}
Self::Background(bg) => {
bg.is_processing = processing;
if processing && bg.processing_started_at.is_none() {
bg.processing_started_at = Some(Instant::now());
} else if !processing {
bg.processing_started_at = None;
}
}
}
}
pub fn add_streaming_output_tokens(&mut self, tokens: u32) {
match self {
Self::Foreground(app) => {
app.streaming_output_tokens += tokens;
app.advance_streaming_window();
}
Self::Background(bg) => {
bg.streaming_output_tokens += tokens;
bg.tps_tracker.advance(Instant::now());
}
}
}
pub fn set_active_tool_group(&mut self, group: Option<ToolCallGroup>) {
match self {
Self::Foreground(app) => app.active_tool_group = group,
Self::Background(bg) => bg.active_tool_group = group,
}
}
pub fn active_tool_group_mut(&mut self) -> Option<&mut ToolCallGroup> {
match self {
Self::Foreground(app) => app.active_tool_group.as_mut(),
Self::Background(bg) => bg.active_tool_group.as_mut(),
}
}
pub fn set_display_token_count(&mut self, count: usize) {
match self {
Self::Foreground(app) => app.display_token_count = count,
Self::Background(bg) => bg.display_token_count = count,
}
}
pub fn set_last_input_tokens(&mut self, count: u32) {
match self {
Self::Foreground(app) => app.last_input_tokens = Some(count),
Self::Background(bg) => bg.last_input_tokens = Some(count),
}
}
pub fn take_active_tool_group(&mut self) -> Option<ToolCallGroup> {
match self {
Self::Foreground(app) => app.active_tool_group.take(),
Self::Background(bg) => bg.active_tool_group.take(),
}
}
pub fn push_message(&mut self, msg: DisplayMessage) {
match self {
Self::Foreground(app) => app.messages.push(msg),
Self::Background(bg) => bg.pending_messages.push(msg),
}
}
pub fn last_message_mut(&mut self) -> Option<&mut DisplayMessage> {
match self {
Self::Foreground(app) => app.messages.last_mut(),
Self::Background(bg) => bg.pending_messages.last_mut(),
}
}
pub fn reset_processing_clock(&mut self) {
if let Self::Foreground(app) = self {
app.processing_started_at = Some(Instant::now());
}
}
pub fn clear_turn_state(&mut self) {
match self {
Self::Foreground(app) => {
app.streaming_response = None;
app.streaming_reasoning = None;
app.active_tool_group = None;
app.is_processing = false;
app.processing_started_at = None;
app.streaming_output_tokens = 0;
}
Self::Background(bg) => {
bg.streaming_response = None;
bg.streaming_reasoning = None;
bg.active_tool_group = None;
bg.is_processing = false;
bg.processing_started_at = None;
bg.streaming_output_tokens = 0;
}
}
}
}