PicoBot/src/command/handlers/list_topics.rs
oudecheng 598d425c28 feat: 话题添加描述字段,工具消息按 tool_call_id 合并展示
- TopicSummary 新增 description 字段,侧边栏优先显示描述
- ToolPanel 使用 toolCallId 将 tool_call 和 tool_result 配对合并展示
- 保存消息时同步更新 topics 表的 message_count 和 last_active_at
- ChatMessage 新增 toolCallId 字段

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 14:30:21 +08:00

99 lines
2.8 KiB
Rust

use crate::command::context::CommandContext;
use crate::command::handler::{CommandHandler, CommandMetadata};
use crate::command::response::{CommandError, CommandResponse, MessageKind};
use crate::command::Command;
use crate::storage::SessionStore;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
/// Topic 摘要信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopicSummary {
pub topic_id: String,
pub session_id: String,
pub title: String,
pub description: Option<String>,
pub message_count: i64,
pub created_at: i64,
pub last_active_at: i64,
}
/// 列出 Session 的 Topics 命令处理器
pub struct ListTopicsCommandHandler {
store: Arc<SessionStore>,
}
impl ListTopicsCommandHandler {
pub fn new(store: Arc<SessionStore>) -> Self {
Self { store }
}
}
#[async_trait]
impl CommandHandler for ListTopicsCommandHandler {
fn can_handle(&self, cmd: &Command) -> bool {
matches!(cmd, Command::ListTopics { .. })
}
fn metadata(&self) -> Option<CommandMetadata> {
Some(CommandMetadata {
name: "topics",
description: "列出 Session 的所有 Topics",
usage: "/topics <session_id>",
})
}
async fn handle(
&self,
cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
match cmd {
Command::ListTopics { session_id } => {
handle_list_topics(self, session_id, ctx).await
}
_ => unreachable!(),
}
}
}
async fn handle_list_topics(
handler: &ListTopicsCommandHandler,
session_id: String,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
let topics = handler
.store
.list_topics(&session_id)
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
let summaries: Vec<TopicSummary> = topics
.into_iter()
.map(|t| TopicSummary {
topic_id: t.id,
session_id: t.session_id,
title: t.title,
description: t.description.filter(|d| !d.is_empty()),
message_count: t.message_count,
created_at: t.created_at,
last_active_at: t.last_active_at,
})
.collect();
let topics_json = serde_json::to_string(&summaries)
.map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?;
let message = format!(
"Found {} topic(s) in session '{}'",
summaries.len(),
session_id
);
Ok(CommandResponse::success(ctx.request_id)
.with_message(MessageKind::Notification, &message)
.with_metadata("topics", &topics_json)
.with_metadata("session_id", &session_id)
.with_metadata("count", &summaries.len().to_string()))
}