matrixcode_core/command/handlers/load.rs
1//! /load 命令实现
2//!
3//! 加载指定会话,包括完整消息历史和项目记忆。
4
5use super::super::backend_context::BackendContext;
6use super::super::command_trait::Command;
7use crate::{AgentEvent, ContentBlock, EventData, EventType, HistoryMessage, MessageContent, Role};
8use std::path::PathBuf;
9
10/// Load 命令
11///
12/// 用法:
13/// - /load <session_id> - 加载指定会话(包括消息历史和记忆)
14pub struct Load;
15
16impl Command for Load {
17 fn name(&self) -> &'static str {
18 "load"
19 }
20
21 fn help(&self) -> Option<&'static str> {
22 Some("加载指定会话(包括消息历史和记忆)。用法: /load <session_id>")
23 }
24
25 fn execute<'a>(
26 &'a self,
27 ctx: &'a mut BackendContext<'_>,
28 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
29 Box::pin(async move {
30 let session_id = ctx.message.strip_prefix("/load ").unwrap_or("");
31
32 if let Some(mgr) = ctx.session_mgr {
33 if mgr.resume(session_id).is_ok() {
34 // Load full message history for display and agent
35 let full_messages = mgr
36 .full_messages()
37 .map(|msgs| msgs.to_vec())
38 .unwrap_or_default();
39
40 if !full_messages.is_empty() {
41 // Set full messages to agent
42 ctx.agent.set_messages(full_messages.clone());
43
44 // Load memory for this session's project path
45 let project_path: Option<PathBuf> = mgr
46 .current_metadata()
47 .and_then(|m| m.project_path.clone())
48 .map(PathBuf::from);
49
50 if let Some(ref path) = project_path {
51 // Reload memory storage with session's project path
52 if let Ok(new_storage) = crate::memory::MemoryStorage::new(Some(path)) {
53 *ctx.memory_storage = Some(new_storage);
54 log::info!("Loaded memory for project: {}", path.display());
55 }
56 }
57
58 // Convert messages to history format for TUI display
59 // Each message can produce multiple history entries (text + thinking)
60 let history: Vec<HistoryMessage> = full_messages
61 .iter()
62 .filter_map(|m| {
63 let base_role = match m.role {
64 Role::User => "user",
65 Role::Assistant => "assistant",
66 _ => return None, // Skip system/tool messages
67 };
68
69 match &m.content {
70 MessageContent::Text(text) => {
71 Some(vec![HistoryMessage {
72 role: base_role.to_string(),
73 content: text.clone(),
74 is_thinking: false,
75 }])
76 }
77 MessageContent::Blocks(blocks) => {
78 // Extract text and thinking from blocks
79 // Thinking blocks become separate messages
80 let entries: Vec<HistoryMessage> = blocks
81 .iter()
82 .filter_map(|b| match b {
83 ContentBlock::Text { text } => Some(HistoryMessage {
84 role: base_role.to_string(),
85 content: text.clone(),
86 is_thinking: false,
87 }),
88 ContentBlock::Thinking { thinking, .. } => {
89 Some(HistoryMessage {
90 role: base_role.to_string(),
91 content: thinking.clone(),
92 is_thinking: true,
93 })
94 }
95 _ => None,
96 })
97 .collect();
98 if entries.is_empty() {
99 None
100 } else {
101 Some(entries)
102 }
103 }
104 }
105 })
106 .flatten()
107 .collect();
108
109 // Send history to TUI for display
110 if !history.is_empty() {
111 let _ = ctx
112 .event_tx
113 .send(AgentEvent::with_data(
114 EventType::HistoryLoaded,
115 EventData::HistoryMessages { messages: history },
116 ))
117 .await;
118 }
119
120 // Build success message
121 let msg = match project_path {
122 Some(path) => format!(
123 "✓ Session '{}' loaded: {} messages (project: {})",
124 session_id,
125 full_messages.len(),
126 path.display()
127 ),
128 None => format!(
129 "✓ Session '{}' loaded: {} messages",
130 session_id,
131 full_messages.len()
132 ),
133 };
134
135 let _ = ctx.event_tx.send(AgentEvent::progress(msg, None)).await;
136 } else {
137 let _ = ctx
138 .event_tx
139 .send(AgentEvent::progress(
140 format!("⚠️ Session '{}' has no messages", session_id),
141 None,
142 ))
143 .await;
144 }
145 } else {
146 let _ = ctx
147 .event_tx
148 .send(AgentEvent::progress(
149 format!("❌ Session '{}' not found", session_id),
150 None,
151 ))
152 .await;
153 }
154 } else {
155 let _ = ctx
156 .event_tx
157 .send(AgentEvent::progress(
158 "❌ Session manager not available",
159 None,
160 ))
161 .await;
162 }
163
164 false // 不转发给 agent
165 })
166 }
167}