use crate::command::context::CommandContext; use crate::command::handler::{CommandHandler, CommandMetadata}; use crate::command::response::{CommandError, CommandResponse, MessageKind}; use crate::command::Command; use crate::gateway::session::SessionManager; use crate::storage::SessionStore; use async_trait::async_trait; use std::sync::Arc; /// 切换话题命令处理器 pub struct SwitchSessionCommandHandler { store: Arc, session_manager: Option, } impl SwitchSessionCommandHandler { pub fn new(store: Arc) -> Self { Self { store, session_manager: None } } pub fn with_session_manager(mut self, session_manager: SessionManager) -> Self { self.session_manager = Some(session_manager); self } } #[async_trait] impl CommandHandler for SwitchSessionCommandHandler { fn can_handle(&self, cmd: &Command) -> bool { matches!(cmd, Command::SwitchSession { .. }) } fn metadata(&self) -> Option { Some(CommandMetadata { name: "use", description: "切换到指定话题", usage: "/use ", }) } async fn handle( &self, cmd: Command, ctx: CommandContext, ) -> Result { match cmd { Command::SwitchSession { session_id } => { handle_switch_session(self, session_id, ctx).await } _ => unreachable!(), } } } async fn handle_switch_session( handler: &SwitchSessionCommandHandler, topic_id: String, ctx: CommandContext, ) -> Result { let session_id = ctx.session_id.as_deref() .ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?; let chat_id = ctx.chat_id.as_deref() .ok_or_else(|| CommandError::new("NO_CHAT_ID", "No chat_id in context"))?; // 尝试解析为序号 let target_topic_id = if let Ok(index) = topic_id.parse::() { let topics = handler .store .list_topics(session_id) .map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?; let index = index.saturating_sub(1); if index >= topics.len() { return Err(CommandError::new( "INVALID_TOPIC_INDEX", format!("Topic index {} is out of range (1-{})", index + 1, topics.len()) )); } topics[index].id.clone() } else { topic_id }; // 验证目标话题存在 let topic = handler .store .get_topic(&target_topic_id) .map_err(|e| CommandError::new("SWITCH_TOPIC_ERROR", e.to_string()))? .ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", target_topic_id)))?; // 如果有 SessionManager,实际切换话题历史 if let Some(ref session_manager) = handler.session_manager { if let Some(session) = session_manager.get(&ctx.channel_name).await { let mut session_guard = session.lock().await; session_guard.switch_topic(chat_id, &target_topic_id) .map_err(|e| CommandError::new("SWITCH_TOPIC_ERROR", e.to_string()))?; } } let message = format!( "✓ Switched to topic: {} ({} messages)", topic.title, topic.message_count ); Ok(CommandResponse::success(ctx.request_id) .with_message(MessageKind::Notification, &message) .with_metadata("topic_id", &topic.id) .with_metadata("title", &topic.title) .with_metadata("message_count", &topic.message_count.to_string())) } fn format_time_ago(timestamp_ms: i64) -> String { let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() as i64; let diff_ms = now - timestamp_ms; let diff_secs = diff_ms / 1000; if diff_secs < 60 { "just now".to_string() } else if diff_secs < 3600 { format!("{} mins ago", diff_secs / 60) } else if diff_secs < 86400 { format!("{} hours ago", diff_secs / 3600) } else { format!("{} days ago", diff_secs / 86400) } }