steer_tui/tui/
model.rs

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