Skip to main content

steer_tui/tui/
model.rs

1use crate::tui::core_commands::{CommandResponse as CoreCommandResponse, CoreCommandType};
2use steer_grpc::client_api::{McpServerInfo, Message};
3use steer_tools::ToolCall;
4use time::OffsetDateTime;
5
6pub type RowId = String;
7
8/// Severity levels for system notices
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum NoticeLevel {
11    Error,
12    Warn,
13    Info,
14}
15
16/// All rows that can appear in the scrollback panel
17#[derive(Debug, Clone)]
18pub enum ChatItemData {
19    /// Conversation message
20    Message(Message),
21
22    /// A tool call that is in progress
23    PendingToolCall {
24        id: RowId,
25        tool_call: ToolCall,
26        ts: OffsetDateTime,
27    },
28
29    /// Raw slash command entered by the user
30    SlashInput {
31        id: RowId,
32        raw: String,
33        ts: OffsetDateTime,
34    },
35
36    /// Internal notices (errors, warnings, info)
37    SystemNotice {
38        id: RowId,
39        level: NoticeLevel,
40        text: String,
41        ts: OffsetDateTime,
42    },
43
44    CoreCmdResponse {
45        id: RowId,
46        command: CoreCommandType,
47        response: CoreCommandResponse,
48        ts: OffsetDateTime,
49    },
50
51    /// TUI command response (e.g., /help, /theme, /auth)
52    TuiCommandResponse {
53        id: RowId,
54        command: String,
55        response: TuiCommandResponse,
56        ts: OffsetDateTime,
57    },
58
59    InFlightOperation {
60        id: RowId,
61        operation_id: uuid::Uuid,
62        label: String,
63        ts: OffsetDateTime,
64    },
65}
66
67#[derive(Debug, Clone)]
68pub enum TuiCommandResponse {
69    Text(String),
70    Theme { name: String },
71    ListThemes(Vec<String>),
72    ListMcpServers(Vec<McpServerInfo>),
73}
74
75#[derive(Debug, Clone)]
76pub enum CommandResponse {
77    Core(CoreCommandResponse),
78    Tui(TuiCommandResponse),
79}
80impl From<CoreCommandResponse> for CommandResponse {
81    fn from(response: CoreCommandResponse) -> Self {
82        CommandResponse::Core(response)
83    }
84}
85impl From<TuiCommandResponse> for CommandResponse {
86    fn from(response: TuiCommandResponse) -> Self {
87        CommandResponse::Tui(response)
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct ChatItem {
93    pub parent_chat_item_id: Option<RowId>,
94    pub data: ChatItemData,
95}
96
97impl ChatItem {
98    /// Get the unique identifier for this chat item
99    pub fn id(&self) -> &str {
100        match &self.data {
101            ChatItemData::Message(row) => row.id(),
102            ChatItemData::PendingToolCall { id, .. } => id,
103            ChatItemData::SlashInput { id, .. } => id,
104            ChatItemData::CoreCmdResponse { id, .. } => id,
105            ChatItemData::SystemNotice { id, .. } => id,
106            ChatItemData::TuiCommandResponse { id, .. } => id,
107            ChatItemData::InFlightOperation { id, .. } => id,
108        }
109    }
110
111    /// Get the timestamp for this chat item
112    pub fn timestamp(&self) -> OffsetDateTime {
113        match &self.data {
114            ChatItemData::Message(message) => {
115                OffsetDateTime::from_unix_timestamp(message.timestamp() as i64)
116                    .unwrap_or(OffsetDateTime::UNIX_EPOCH)
117            }
118            ChatItemData::PendingToolCall { ts, .. } => *ts,
119            ChatItemData::SlashInput { ts, .. } => *ts,
120            ChatItemData::CoreCmdResponse { ts, .. } => *ts,
121            ChatItemData::SystemNotice { ts, .. } => *ts,
122            ChatItemData::TuiCommandResponse { ts, .. } => *ts,
123            ChatItemData::InFlightOperation { ts, .. } => *ts,
124        }
125    }
126
127    /// Get the parent message ID for filtering
128    pub fn parent_message_id(&self) -> Option<&str> {
129        match &self.data {
130            ChatItemData::Message(msg) => msg.parent_message_id(),
131            _ => None,
132        }
133    }
134}
135
136pub fn generate_row_id() -> RowId {
137    ulid::Ulid::new().to_string()
138}