Skip to main content

mermaid_cli/tui/state/
generation.rs

1//! Generation state machine
2//!
3//! Tracks the application lifecycle during model interactions.
4
5use std::time::Instant;
6
7/// Generation status for the status line
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum GenerationStatus {
10    /// Not currently generating
11    Idle,
12    /// Message sent upstream, waiting for model to start responding
13    Sending,
14    /// Waiting for first token from model (thinking/reasoning)
15    Thinking,
16    /// Actively receiving and displaying tokens
17    Streaming,
18}
19
20impl GenerationStatus {
21    pub fn display_text(&self) -> &str {
22        match self {
23            GenerationStatus::Idle => "Idle",
24            GenerationStatus::Sending => "Sending",
25            GenerationStatus::Thinking => "Thinking",
26            GenerationStatus::Streaming => "Streaming",
27        }
28    }
29}
30
31/// Comprehensive state machine for the application lifecycle
32/// Impossible states become impossible to represent
33#[derive(Debug, Clone)]
34pub enum AppState {
35    /// Idle - not doing anything, ready for input
36    Idle,
37
38    /// Currently generating a response from the model
39    Generating {
40        status: GenerationStatus,
41        start_time: Instant,
42        tokens_received: usize,
43        abort_handle: Option<tokio::task::AbortHandle>,
44        /// Accumulated streaming response text for this model call
45        response_buffer: String,
46        /// True once `response_buffer` has been truncated to the size cap.
47        /// Subsequent `push_response` calls become no-ops to avoid quadratic
48        /// re-truncation and duplicated `[TRUNCATED…]` markers.
49        response_truncated: bool,
50    },
51}
52
53impl AppState {
54    /// Get generation status if we're generating
55    pub fn generation_status(&self) -> Option<GenerationStatus> {
56        match self {
57            AppState::Generating { status, .. } => Some(*status),
58            _ => None,
59        }
60    }
61
62    /// Check if we're currently generating
63    pub fn is_generating(&self) -> bool {
64        matches!(self, AppState::Generating { .. })
65    }
66
67    /// Check if we're idle
68    pub fn is_idle(&self) -> bool {
69        matches!(self, AppState::Idle)
70    }
71
72    /// Get generation start time if we're generating
73    pub fn generation_start_time(&self) -> Option<Instant> {
74        match self {
75            AppState::Generating { start_time, .. } => Some(*start_time),
76            _ => None,
77        }
78    }
79
80    /// Get tokens received if we're generating
81    pub fn tokens_received(&self) -> Option<usize> {
82        match self {
83            AppState::Generating {
84                tokens_received, ..
85            } => Some(*tokens_received),
86            _ => None,
87        }
88    }
89
90    /// Get abort handle if we're generating
91    pub fn abort_handle(&self) -> Option<&tokio::task::AbortHandle> {
92        match self {
93            AppState::Generating { abort_handle, .. } => abort_handle.as_ref(),
94            _ => None,
95        }
96    }
97}