PicoBot/src/command/handlers/get_current.rs
oudecheng cda14360af chore: 建立工程化基线(rustfmt + clippy + CI + eslint + prettier)
配置:
- rustfmt.toml: 固化 max_width=100 / 4 空格缩进,cargo fmt 全量格式化
- Cargo.toml: 配置 [lints.rust] 与 [lints.clippy] 渐进式规则
- .github/workflows/ci.yml: Rust(fmt+clippy+test) + 前端(eslint+tsc+test) 双平台 CI
- Makefile: 新增 check/fmt/fix 目标,clippy 对齐 --all-targets --all-features
- web: eslint flat config + prettier 配置 + package.json 脚本与依赖
- src/main.rs: loop→while 修复 clippy::never_loop

对抗性审查发现并修复:
- eslint 缺 caughtErrorsIgnorePattern 导致 catch(_) 误报为 error
- 前端 lint 未接入 CI,现已补上 Lint 步骤
- Makefile 与 CI 的 clippy flags 不一致,已对齐
2026-08-03 23:24:02 +08:00

166 lines
5.3 KiB
Rust

use crate::agent::context_compressor::estimate_tokens;
use crate::agent::{SystemPromptContext, SystemPromptProvider};
use crate::command::Command;
use crate::command::context::CommandContext;
use crate::command::handler::{CommandHandler, CommandMetadata};
use crate::command::response::{CommandError, CommandResponse, MessageKind};
use crate::storage::SessionStore;
use async_trait::async_trait;
use std::sync::Arc;
/// 获取当前话题命令处理器
pub struct GetCurrentSessionCommandHandler {
store: Arc<SessionStore>,
system_prompt_provider: Option<Arc<dyn SystemPromptProvider>>,
}
impl GetCurrentSessionCommandHandler {
pub fn new(store: Arc<SessionStore>) -> Self {
Self {
store,
system_prompt_provider: None,
}
}
pub fn with_system_prompt_provider(mut self, provider: Arc<dyn SystemPromptProvider>) -> Self {
self.system_prompt_provider = Some(provider);
self
}
}
#[async_trait]
impl CommandHandler for GetCurrentSessionCommandHandler {
fn can_handle(&self, cmd: &Command) -> bool {
matches!(cmd, Command::GetCurrentSession)
}
fn metadata(&self) -> Option<CommandMetadata> {
Some(CommandMetadata {
name: "current",
description: "获取当前话题信息",
usage: "/current",
})
}
async fn handle(
&self,
cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
match cmd {
Command::GetCurrentSession => handle_get_current_session(self, ctx).await,
_ => unreachable!(),
}
}
}
async fn handle_get_current_session(
handler: &GetCurrentSessionCommandHandler,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
let topic_id = ctx
.topic_id
.as_deref()
.ok_or_else(|| CommandError::new("NO_CURRENT_TOPIC", "No current topic"))?;
let chat_id = ctx
.chat_id
.as_deref()
.ok_or_else(|| CommandError::new("NO_CHAT_ID", "No chat id".to_string()))?;
let topic = handler
.store
.get_topic(topic_id)
.map_err(|e| CommandError::new("GET_TOPIC_ERROR", e.to_string()))?
.ok_or_else(|| {
CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id))
})?;
// 直读 DB 按话题加载消息(不依赖 SessionManager 内存,重启后也能正确获取历史)
let messages = handler
.store
.load_messages_for_topic(topic_id, Some(&topic.session_id))
.map_err(|e| CommandError::new("LOAD_MESSAGES_ERROR", e.to_string()))?;
let actual_message_count = messages.len();
let message_tokens = estimate_tokens(&messages);
// Calculate system prompt tokens if provider is available
let system_prompt_tokens = if let Some(ref provider) = handler.system_prompt_provider {
let user_message_count = messages.iter().filter(|m| m.role == "user").count();
let system_prompt_context = SystemPromptContext {
session_id: ctx.session_id.clone(),
chat_id: chat_id.to_string(),
user_message_count,
};
provider
.build(&system_prompt_context)
.map(|sp| {
use crate::bus::ChatMessage;
let system_msg = ChatMessage::system(&sp.content);
estimate_tokens(&[system_msg])
})
.unwrap_or(0)
} else {
0
};
let total_tokens = system_prompt_tokens + message_tokens;
let last_active = format_time_ago(topic.last_active_at);
let created_at = format_time_ago(topic.created_at);
let description_line = if let Some(ref desc) = topic.description {
if !desc.is_empty() {
format!("\n Description: {}", desc)
} else {
String::new()
}
} else {
String::new()
};
let message = format!(
"Current Topic:\n\n Topic ID: {}\n Title: {}{}\n Messages: {}\n Tokens: ~{} (系统提示词: ~{}, 用户消息: ~{})\n Created: {}\n Last Active: {}",
topic.id,
topic.title,
description_line,
actual_message_count,
total_tokens,
system_prompt_tokens,
message_tokens,
created_at,
last_active
);
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", &actual_message_count.to_string())
.with_metadata("estimated_tokens", &total_tokens.to_string())
.with_metadata("system_prompt_tokens", &system_prompt_tokens.to_string())
.with_metadata("message_tokens", &message_tokens.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)
}
}