Skip to content

Commit b4df935

Browse files
committed
save and restore both, api_messages and display_messages
1 parent 7c62b09 commit b4df935

2 files changed

Lines changed: 120 additions & 10 deletions

File tree

src/history.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ const SESSIONS_DIR: &str = "sessions";
1010
const INDEX_FILE: &str = "index.json";
1111
const MAX_PREVIEW_LENGTH: usize = 120;
1212

13+
#[derive(Debug, Clone, Serialize, Deserialize)]
14+
pub enum DisplayMessage {
15+
UserMessage {
16+
content: String,
17+
},
18+
AssistantMessage {
19+
content: String,
20+
},
21+
ToolExecution {
22+
tool_name: String,
23+
tool_input: serde_json::Value,
24+
tool_output: String,
25+
},
26+
}
27+
1328
#[derive(Debug, Clone, Serialize, Deserialize)]
1429
pub struct SessionMetadata {
1530
pub id: String,
@@ -22,7 +37,12 @@ pub struct SessionMetadata {
2237
#[derive(Debug, Serialize, Deserialize)]
2338
pub struct Session {
2439
pub id: String,
25-
pub messages: Vec<Message>,
40+
/// Messages in API format (for continuing the conversation with Claude)
41+
#[serde(default, rename = "messages", alias = "api_messages")]
42+
pub api_messages: Vec<Message>,
43+
/// Messages in display format (for reconstructing the original UI)
44+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
45+
pub display_messages: Vec<DisplayMessage>,
2646
pub system_prompt: String,
2747
pub created_at: u64,
2848
pub updated_at: u64,
@@ -118,6 +138,7 @@ impl HistoryManager {
118138
&self,
119139
session_id: &str,
120140
messages: &[Message],
141+
display_messages: &[DisplayMessage],
121142
system_prompt: &str,
122143
) -> Result<()> {
123144
let now = SystemTime::now()
@@ -129,7 +150,8 @@ impl HistoryManager {
129150

130151
let session = Session {
131152
id: session_id.to_string(),
132-
messages: messages.to_vec(),
153+
api_messages: messages.to_vec(),
154+
display_messages: display_messages.to_vec(),
133155
system_prompt: system_prompt.to_string(),
134156
created_at: if session_path.exists() {
135157
let existing: Session = serde_json::from_str(&fs::read_to_string(&session_path)?)?;
@@ -158,13 +180,13 @@ impl HistoryManager {
158180
}
159181
};
160182

161-
let preview = Self::extract_preview(&session.messages);
183+
let preview = Self::extract_preview(&session.api_messages);
162184
let metadata = SessionMetadata {
163185
id: session.id.clone(),
164186
preview,
165187
created_at: session.created_at,
166188
updated_at: session.updated_at,
167-
message_count: session.messages.len(),
189+
message_count: session.api_messages.len(),
168190
};
169191

170192
if let Some(pos) = index.sessions.iter().position(|s| s.id == session.id) {
@@ -256,12 +278,12 @@ mod tests {
256278
let system_prompt = "Test system prompt";
257279

258280
manager
259-
.save_session(&session_id, &messages, system_prompt)
281+
.save_session(&session_id, &messages, &[], system_prompt)
260282
.unwrap();
261283

262284
let loaded = manager.load_session(&session_id).unwrap();
263285
assert_eq!(loaded.id, session_id);
264-
assert_eq!(loaded.messages.len(), 1);
286+
assert_eq!(loaded.api_messages.len(), 1);
265287
assert_eq!(loaded.system_prompt, system_prompt);
266288
}
267289

@@ -272,14 +294,14 @@ mod tests {
272294

273295
let session_id1 = HistoryManager::generate_session_id();
274296
manager
275-
.save_session(&session_id1, &[Message::user("First session")], "System")
297+
.save_session(&session_id1, &[Message::user("First session")], &[], "System")
276298
.unwrap();
277299

278300
std::thread::sleep(std::time::Duration::from_secs(1));
279301

280302
let session_id2 = HistoryManager::generate_session_id();
281303
manager
282-
.save_session(&session_id2, &[Message::user("Second session")], "System")
304+
.save_session(&session_id2, &[Message::user("Second session")], &[], "System")
283305
.unwrap();
284306

285307
let sessions = manager.list_sessions().unwrap();

src/repl.rs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct Repl {
2525
max_tokens: u32,
2626
recursion_depth: u32,
2727
session_id: String,
28+
display_messages: Vec<crate::history::DisplayMessage>,
2829
}
2930

3031
impl Repl {
@@ -62,6 +63,7 @@ impl Repl {
6263
max_tokens,
6364
recursion_depth: 0,
6465
session_id,
66+
display_messages: Vec::new(),
6567
})
6668
}
6769

@@ -93,6 +95,7 @@ impl Repl {
9395
}
9496
"clear" => {
9597
self.conversation.clear();
98+
self.display_messages.clear();
9699
self.session_id = HistoryManager::generate_session_id();
97100
println!("{}", "Conversation history cleared.".bright_yellow());
98101
continue;
@@ -150,6 +153,11 @@ impl Repl {
150153

151154
fn process_message(&mut self, user_input: &str) -> Result<()> {
152155
self.conversation.add_user_message(user_input.to_string());
156+
157+
// Track display message
158+
self.display_messages.push(crate::history::DisplayMessage::UserMessage {
159+
content: user_input.to_string(),
160+
});
153161

154162
let request = CreateMessageRequest {
155163
model: self.model.clone(),
@@ -230,6 +238,12 @@ impl Repl {
230238
println!("{}", highlighted);
231239
}
232240
println!();
241+
242+
// Track assistant display message
243+
let combined_text = text_output.join("\n");
244+
self.display_messages.push(crate::history::DisplayMessage::AssistantMessage {
245+
content: combined_text,
246+
});
233247
}
234248

235249
// Store the full assistant response with content blocks
@@ -291,6 +305,13 @@ impl Repl {
291305

292306
println!("{}", output.dimmed());
293307
println!();
308+
309+
// Track tool execution in display_messages
310+
self.display_messages.push(crate::history::DisplayMessage::ToolExecution {
311+
tool_name: tool_name.clone(),
312+
tool_input: tool_input.clone(),
313+
tool_output: output.clone(),
314+
});
294315

295316
// Collect tool result instead of adding immediately
296317
tool_results.push(crate::api::MessageContentBlock::ToolResult {
@@ -315,6 +336,13 @@ impl Repl {
315336
let error_msg = format!("Tool execution failed: {}", e);
316337
eprintln!("{} {}", "Error:".bright_red().bold(), error_msg);
317338
println!();
339+
340+
// Track tool execution error in display_messages
341+
self.display_messages.push(crate::history::DisplayMessage::ToolExecution {
342+
tool_name: tool_name.clone(),
343+
tool_input: tool_input.clone(),
344+
tool_output: error_msg.clone(),
345+
});
318346

319347
// Collect error as tool result
320348
tool_results.push(crate::api::MessageContentBlock::ToolResult {
@@ -543,6 +571,7 @@ impl Repl {
543571
self.history_manager.save_session(
544572
&self.session_id,
545573
self.conversation.messages(),
574+
&self.display_messages,
546575
self.conversation.system_prompt(),
547576
)?;
548577

@@ -577,15 +606,74 @@ impl Repl {
577606

578607
self.session_id = session.id.clone();
579608
self.conversation.clear();
580-
self.conversation.restore_messages(session.messages.clone());
609+
self.conversation.restore_messages(session.api_messages.clone());
610+
self.display_messages = session.display_messages.clone();
581611

582612
println!(
583613
"{} {} ({} messages)",
584614
"Loaded session:".bright_green(),
585615
session.id,
586-
session.messages.len()
616+
session.api_messages.len()
587617
);
618+
println!();
619+
620+
// Display the original conversation
621+
self.display_session(&session);
588622

589623
Ok(())
590624
}
625+
626+
fn display_session(&self, session: &crate::history::Session) {
627+
if session.display_messages.is_empty() {
628+
println!("{}", "Note: This is a legacy session without display history. Only API messages are available.".dimmed());
629+
println!();
630+
return;
631+
}
632+
633+
println!("{}", "═".repeat(80).bright_cyan());
634+
println!("{}", "Previous Conversation:".bright_cyan().bold());
635+
println!("{}", "═".repeat(80).bright_cyan());
636+
println!();
637+
638+
for display_msg in &session.display_messages {
639+
match display_msg {
640+
crate::history::DisplayMessage::UserMessage { content } => {
641+
println!("{} {}", ">>>".bright_green(), content);
642+
println!();
643+
}
644+
crate::history::DisplayMessage::AssistantMessage { content } => {
645+
println!("{}", "Assistant:".bright_blue().bold());
646+
let highlighted = self.highlighter.highlight_text(content);
647+
println!("{}", highlighted);
648+
println!();
649+
}
650+
crate::history::DisplayMessage::ToolExecution { tool_name, tool_input: _, tool_output } => {
651+
if tool_name == "execute_bash" {
652+
if let Ok(input_val) = serde_json::from_value::<serde_json::Value>(
653+
serde_json::to_value(&tool_output).unwrap_or_default()
654+
) {
655+
if let Some(command) = input_val.get("command").and_then(|v| v.as_str()) {
656+
println!(
657+
"{} {}",
658+
"Executing:".bright_green().bold(),
659+
command.bright_cyan()
660+
);
661+
}
662+
}
663+
} else {
664+
println!(
665+
"{} {}",
666+
"Using tool:".bright_yellow().bold(),
667+
tool_name.bright_yellow()
668+
);
669+
}
670+
println!("{}", tool_output.dimmed());
671+
println!();
672+
}
673+
}
674+
}
675+
676+
println!("{}", "═".repeat(80).bright_cyan());
677+
println!();
678+
}
591679
}

0 commit comments

Comments
 (0)