limit_cli/tui/commands/
session.rs1use super::{Command, CommandContext, CommandResult};
6use crate::error::CliError;
7
8pub struct SessionCommand {
10 }
12
13impl SessionCommand {
14 pub fn new() -> Self {
15 Self {}
16 }
17
18 fn handle_list(&self, ctx: &CommandContext) -> Result<CommandResult, CliError> {
19 let session_manager = ctx.session_manager.lock().unwrap();
20 let current_session_id = ctx.session_id.clone();
21
22 match session_manager.list_sessions() {
23 Ok(sessions) => {
24 if sessions.is_empty() {
25 ctx.add_system_message("No sessions found.".to_string());
26 } else {
27 let mut output = vec!["Sessions (most recent first):".to_string()];
28 for (i, session) in sessions.iter().enumerate() {
29 let current = if session.id == current_session_id {
30 " (current)"
31 } else {
32 ""
33 };
34 let short_id = if session.id.len() > 8 {
35 &session.id[..8]
36 } else {
37 &session.id
38 };
39 output.push(format!(
40 " {}. {}{} - {} messages, {} in tokens, {} out tokens",
41 i + 1,
42 short_id,
43 current,
44 session.message_count,
45 session.total_input_tokens,
46 session.total_output_tokens
47 ));
48 }
49 ctx.add_system_message(output.join("\n"));
50 }
51 }
52 Err(e) => {
53 ctx.add_system_message(format!("Error listing sessions: {}", e));
54 }
55 }
56
57 Ok(CommandResult::Continue)
58 }
59
60 fn handle_new(&self, ctx: &mut CommandContext) -> Result<CommandResult, CliError> {
61 let save_result = {
63 let session_manager = ctx.session_manager.lock().unwrap();
64 let messages = ctx.messages.lock().unwrap().clone();
65 let input_tokens = *ctx.total_input_tokens.lock().unwrap();
66 let output_tokens = *ctx.total_output_tokens.lock().unwrap();
67
68 session_manager.save_session(&ctx.session_id, &messages, input_tokens, output_tokens)
69 };
70
71 if let Err(e) = save_result {
72 tracing::error!("Failed to save current session: {}", e);
73 ctx.add_system_message(format!("⚠ Warning: Failed to save current session: {}", e));
74 }
75
76 let new_session_id = {
78 let session_manager = ctx.session_manager.lock().map_err(|e| {
79 CliError::ConfigError(format!("Failed to acquire session manager lock: {}", e))
80 })?;
81
82 session_manager
83 .create_new_session()
84 .map_err(|e| CliError::ConfigError(format!("Failed to create session: {}", e)))?
85 };
86
87 ctx.session_id = new_session_id.clone();
89
90 ctx.messages.lock().unwrap().clear();
92 *ctx.total_input_tokens.lock().unwrap() = 0;
93 *ctx.total_output_tokens.lock().unwrap() = 0;
94
95 ctx.chat_view.lock().unwrap().clear();
97
98 tracing::info!("Created new session: {}", new_session_id);
99
100 let session_short_id = if new_session_id.len() > 8 {
102 &new_session_id[new_session_id.len().saturating_sub(8)..]
103 } else {
104 &new_session_id
105 };
106 ctx.add_system_message(format!("🆕 New session created: {}", session_short_id));
107
108 Ok(CommandResult::NewSession)
109 }
110
111 fn handle_load(
112 &self,
113 session_id: &str,
114 ctx: &mut CommandContext,
115 ) -> Result<CommandResult, CliError> {
116 tracing::info!("Session load command detected for session: {}", session_id);
117
118 let save_result = {
120 let session_manager = ctx.session_manager.lock().unwrap();
121 let messages = ctx.messages.lock().unwrap().clone();
122 let input_tokens = *ctx.total_input_tokens.lock().unwrap();
123 let output_tokens = *ctx.total_output_tokens.lock().unwrap();
124
125 session_manager.save_session(&ctx.session_id, &messages, input_tokens, output_tokens)
126 };
127
128 if let Err(e) = save_result {
129 tracing::error!("Failed to save current session: {}", e);
130 ctx.add_system_message(format!("⚠ Warning: Failed to save current session: {}", e));
131 }
132
133 let (full_session_id, session_info, messages) = {
135 let session_manager = ctx.session_manager.lock().map_err(|e| {
136 CliError::ConfigError(format!("Failed to acquire session manager lock: {}", e))
137 })?;
138
139 let sessions = session_manager
140 .list_sessions()
141 .map_err(|e| CliError::ConfigError(format!("Failed to list sessions: {}", e)))?;
142
143 let matched_session = if session_id.len() >= 8 {
144 sessions
146 .iter()
147 .find(|s| s.id == session_id)
148 .or_else(|| sessions.iter().find(|s| s.id.starts_with(session_id)))
150 } else {
151 sessions.iter().find(|s| s.id.starts_with(session_id))
153 };
154
155 match matched_session {
156 Some(info) => {
157 let full_id = info.id.clone();
158 let msgs = session_manager.load_session(&full_id).map_err(|e| {
159 CliError::ConfigError(format!("Failed to load session {}: {}", full_id, e))
160 })?;
161 (full_id, info.clone(), msgs)
162 }
163 None => {
164 ctx.add_system_message(format!("❌ Session not found: {}", session_id));
165 return Ok(CommandResult::Continue);
166 }
167 }
168 };
169
170 ctx.session_id = full_session_id.clone();
172 *ctx.total_input_tokens.lock().unwrap() = session_info.total_input_tokens;
173 *ctx.total_output_tokens.lock().unwrap() = session_info.total_output_tokens;
174 *ctx.messages.lock().unwrap() = messages.clone();
175
176 {
178 let mut chat = ctx.chat_view.lock().unwrap();
179 chat.clear();
180
181 for msg in &messages {
182 match msg.role {
183 limit_llm::Role::User => {
184 let content = msg.content.as_deref().unwrap_or("");
185 let chat_msg = limit_tui::components::Message::user(content.to_string());
186 chat.add_message(chat_msg);
187 }
188 limit_llm::Role::Assistant => {
189 let content = msg.content.as_deref().unwrap_or("");
190 let chat_msg =
191 limit_tui::components::Message::assistant(content.to_string());
192 chat.add_message(chat_msg);
193 }
194 _ => {}
195 }
196 }
197 }
198
199 tracing::info!(
200 "Loaded session: {} ({} messages)",
201 full_session_id,
202 messages.len()
203 );
204
205 let session_short_id = if full_session_id.len() > 8 {
207 &full_session_id[full_session_id.len().saturating_sub(8)..]
208 } else {
209 &full_session_id
210 };
211 ctx.add_system_message(format!(
212 "📂 Loaded session: {} ({} messages, {} in tokens, {} out tokens)",
213 session_short_id,
214 messages.len(),
215 session_info.total_input_tokens,
216 session_info.total_output_tokens
217 ));
218
219 Ok(CommandResult::LoadSession(full_session_id))
220 }
221}
222
223impl Command for SessionCommand {
224 fn name(&self) -> &str {
225 "session"
226 }
227
228 fn description(&self) -> &str {
229 "Manage conversation sessions"
230 }
231
232 fn usage(&self) -> Vec<&str> {
233 vec!["/session list", "/session new", "/session load <id>"]
234 }
235
236 fn execute(&self, args: &str, ctx: &mut CommandContext) -> Result<CommandResult, CliError> {
237 let args = args.trim();
238
239 if args == "list" {
240 self.handle_list(ctx)
241 } else if args == "new" {
242 self.handle_new(ctx)
243 } else if args.starts_with("load ") {
244 let session_id = args.strip_prefix("load ").unwrap();
245 self.handle_load(session_id, ctx)
246 } else {
247 ctx.add_system_message(
248 "Usage: /session list, /session new, /session load <id>".to_string(),
249 );
250 Ok(CommandResult::Continue)
251 }
252 }
253}
254
255impl Default for SessionCommand {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_session_command() {
267 let cmd = SessionCommand::new();
268 assert_eq!(cmd.name(), "session");
269 }
270}