feat: 添加删除话题功能,包括命令处理器和前端交互
This commit is contained in:
parent
bf66c00950
commit
b5e2886068
181
src/command/handlers/delete_topic.rs
Normal file
181
src/command/handlers/delete_topic.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use crate::command::context::CommandContext;
|
||||||
|
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||||
|
use crate::command::handlers::list_topics::TopicSummary;
|
||||||
|
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 DeleteTopicCommandHandler {
|
||||||
|
store: Arc<SessionStore>,
|
||||||
|
session_manager: Option<SessionManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeleteTopicCommandHandler {
|
||||||
|
pub fn new(store: Arc<SessionStore>) -> 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 DeleteTopicCommandHandler {
|
||||||
|
fn can_handle(&self, cmd: &Command) -> bool {
|
||||||
|
matches!(cmd, Command::DeleteTopic { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata(&self) -> Option<CommandMetadata> {
|
||||||
|
Some(CommandMetadata {
|
||||||
|
name: "delete",
|
||||||
|
description: "删除指定话题",
|
||||||
|
usage: "/delete <topic_id>",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(
|
||||||
|
&self,
|
||||||
|
cmd: Command,
|
||||||
|
ctx: CommandContext,
|
||||||
|
) -> Result<CommandResponse, CommandError> {
|
||||||
|
match cmd {
|
||||||
|
Command::DeleteTopic { topic_id } => handle_delete_topic(self, topic_id, ctx).await,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_delete_topic(
|
||||||
|
handler: &DeleteTopicCommandHandler,
|
||||||
|
topic_id: String,
|
||||||
|
ctx: CommandContext,
|
||||||
|
) -> Result<CommandResponse, CommandError> {
|
||||||
|
let session_id = ctx
|
||||||
|
.session_id
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?;
|
||||||
|
|
||||||
|
// 验证话题存在
|
||||||
|
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))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let topic_title = topic.title.clone();
|
||||||
|
|
||||||
|
// 删除话题(存储层方法已存在)
|
||||||
|
handler
|
||||||
|
.store
|
||||||
|
.delete_topic(&topic_id)
|
||||||
|
.map_err(|e| CommandError::new("DELETE_TOPIC_ERROR", e.to_string()))?;
|
||||||
|
|
||||||
|
// 查询更新后的话题列表,返回给前端刷新侧边栏
|
||||||
|
let topics = handler
|
||||||
|
.store
|
||||||
|
.list_topics(session_id)
|
||||||
|
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
|
||||||
|
|
||||||
|
let topic_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(&topic_summaries)
|
||||||
|
.map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?;
|
||||||
|
|
||||||
|
let message = format!("✓ 已删除话题: {}", topic_title);
|
||||||
|
|
||||||
|
Ok(CommandResponse::success(ctx.request_id)
|
||||||
|
.with_message(MessageKind::Notification, &message)
|
||||||
|
.with_metadata("topics", &topics_json)
|
||||||
|
.with_metadata("deleted_topic_id", &topic_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::storage::SessionStore;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
fn create_test_handler() -> DeleteTopicCommandHandler {
|
||||||
|
let store = Arc::new(SessionStore::in_memory().unwrap());
|
||||||
|
DeleteTopicCommandHandler::new(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_topic_success() {
|
||||||
|
let handler = create_test_handler();
|
||||||
|
let store = handler.store.clone();
|
||||||
|
|
||||||
|
// 先创建 session 和 topic
|
||||||
|
let session = store.create_session("test_channel", Some("test")).unwrap();
|
||||||
|
let topic = store
|
||||||
|
.create_topic(&session.id, "test topic", None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let ctx = CommandContext::new("test", "test_channel")
|
||||||
|
.with_session_id(&session.id)
|
||||||
|
.with_chat_id(&session.id);
|
||||||
|
let cmd = Command::DeleteTopic {
|
||||||
|
topic_id: topic.id.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = handler.handle(cmd, ctx).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let resp = result.unwrap();
|
||||||
|
assert!(resp.success);
|
||||||
|
assert!(resp.metadata.contains_key("deleted_topic_id"));
|
||||||
|
|
||||||
|
// 验证话题已被删除
|
||||||
|
let deleted = store.get_topic(&topic.id).unwrap();
|
||||||
|
assert!(deleted.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_nonexistent_topic() {
|
||||||
|
let handler = create_test_handler();
|
||||||
|
let store = handler.store.clone();
|
||||||
|
|
||||||
|
let session = store.create_session("test_channel", Some("test")).unwrap();
|
||||||
|
let ctx = CommandContext::new("test", "test_channel")
|
||||||
|
.with_session_id(&session.id)
|
||||||
|
.with_chat_id(&session.id);
|
||||||
|
let cmd = Command::DeleteTopic {
|
||||||
|
topic_id: "nonexistent".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = handler.handle(cmd, ctx).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_handle() {
|
||||||
|
let handler = create_test_handler();
|
||||||
|
assert!(handler.can_handle(&Command::DeleteTopic {
|
||||||
|
topic_id: "test".to_string()
|
||||||
|
}));
|
||||||
|
assert!(!handler.can_handle(&Command::Help));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
pub mod delete_topic;
|
||||||
pub mod get_current;
|
pub mod get_current;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod list_channels;
|
pub mod list_channels;
|
||||||
|
|||||||
@ -52,6 +52,8 @@ pub enum Command {
|
|||||||
channel: String,
|
channel: String,
|
||||||
chat_id: String,
|
chat_id: String,
|
||||||
},
|
},
|
||||||
|
/// 删除指定话题
|
||||||
|
DeleteTopic { topic_id: String },
|
||||||
/// 停止当前正在执行的 Agent
|
/// 停止当前正在执行的 Agent
|
||||||
StopExecution,
|
StopExecution,
|
||||||
}
|
}
|
||||||
@ -74,6 +76,7 @@ impl Command {
|
|||||||
Command::LoadTaskMessages { .. } => "load_task_messages",
|
Command::LoadTaskMessages { .. } => "load_task_messages",
|
||||||
Command::ListSchedulerJobs => "list_scheduler_jobs",
|
Command::ListSchedulerJobs => "list_scheduler_jobs",
|
||||||
Command::LoadChatMessages { .. } => "load_chat_messages",
|
Command::LoadChatMessages { .. } => "load_chat_messages",
|
||||||
|
Command::DeleteTopic { .. } => "delete_topic",
|
||||||
Command::StopExecution => "stop_execution",
|
Command::StopExecution => "stop_execution",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use crate::bus::{InboundMessage, MessageBus, OutboundMessage};
|
|||||||
use crate::command::adapter::InputAdapter;
|
use crate::command::adapter::InputAdapter;
|
||||||
use crate::command::adapters::channel::ChannelInputAdapter;
|
use crate::command::adapters::channel::ChannelInputAdapter;
|
||||||
use crate::command::handler::CommandRouter;
|
use crate::command::handler::CommandRouter;
|
||||||
|
use crate::command::handlers::delete_topic::DeleteTopicCommandHandler;
|
||||||
use crate::command::handlers::get_current::GetCurrentSessionCommandHandler;
|
use crate::command::handlers::get_current::GetCurrentSessionCommandHandler;
|
||||||
use crate::command::handlers::help::HelpCommandHandler;
|
use crate::command::handlers::help::HelpCommandHandler;
|
||||||
use crate::command::handlers::list_sessions::ListSessionsCommandHandler;
|
use crate::command::handlers::list_sessions::ListSessionsCommandHandler;
|
||||||
@ -97,6 +98,12 @@ impl InboundProcessor {
|
|||||||
system_prompt_provider,
|
system_prompt_provider,
|
||||||
).with_session_manager(session_manager.clone())));
|
).with_session_manager(session_manager.clone())));
|
||||||
|
|
||||||
|
// 注册 delete_topic 处理器
|
||||||
|
command_router.register(Box::new(
|
||||||
|
DeleteTopicCommandHandler::new(store.clone())
|
||||||
|
.with_session_manager(session_manager.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
// 注册 help 处理器(最后注册,获取所有已注册命令的元数据)
|
// 注册 help 处理器(最后注册,获取所有已注册命令的元数据)
|
||||||
let metadata = command_router.metadata_arc();
|
let metadata = command_router.metadata_arc();
|
||||||
command_router.register(Box::new(HelpCommandHandler::new(metadata)));
|
command_router.register(Box::new(HelpCommandHandler::new(metadata)));
|
||||||
|
|||||||
@ -5,6 +5,7 @@ use crate::command::adapter::{InputAdapter, OutputAdapter};
|
|||||||
use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter};
|
use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter};
|
||||||
use crate::command::context::CommandContext;
|
use crate::command::context::CommandContext;
|
||||||
use crate::command::handler::CommandRouter;
|
use crate::command::handler::CommandRouter;
|
||||||
|
use crate::command::handlers::delete_topic::DeleteTopicCommandHandler;
|
||||||
use crate::command::handlers::get_current::GetCurrentSessionCommandHandler;
|
use crate::command::handlers::get_current::GetCurrentSessionCommandHandler;
|
||||||
use crate::command::handlers::help::HelpCommandHandler;
|
use crate::command::handlers::help::HelpCommandHandler;
|
||||||
use crate::command::handlers::list_channels::ListChannelsCommandHandler;
|
use crate::command::handlers::list_channels::ListChannelsCommandHandler;
|
||||||
@ -403,6 +404,11 @@ async fn handle_inbound(
|
|||||||
state.task_repository.clone(),
|
state.task_repository.clone(),
|
||||||
system_prompt_provider.clone(),
|
system_prompt_provider.clone(),
|
||||||
)));
|
)));
|
||||||
|
// 注册 delete_topic 处理器
|
||||||
|
router.register(Box::new(
|
||||||
|
DeleteTopicCommandHandler::new(store.clone())
|
||||||
|
.with_session_manager(state.session_manager.clone()),
|
||||||
|
));
|
||||||
// 注册 help 处理器
|
// 注册 help 处理器
|
||||||
let metadata = router.metadata_arc();
|
let metadata = router.metadata_arc();
|
||||||
router.register(Box::new(HelpCommandHandler::new(metadata)));
|
router.register(Box::new(HelpCommandHandler::new(metadata)));
|
||||||
|
|||||||
@ -52,10 +52,12 @@ function App() {
|
|||||||
// 方法
|
// 方法
|
||||||
handleMessage,
|
handleMessage,
|
||||||
handleCommand,
|
handleCommand,
|
||||||
|
clearMessages,
|
||||||
handleServerMessage,
|
handleServerMessage,
|
||||||
selectTopic,
|
selectTopic,
|
||||||
createTopic,
|
createTopic,
|
||||||
switchTopic,
|
switchTopic,
|
||||||
|
deleteTopic,
|
||||||
requestSessionList,
|
requestSessionList,
|
||||||
requestTopicList,
|
requestTopicList,
|
||||||
enterSubAgentView,
|
enterSubAgentView,
|
||||||
@ -229,6 +231,20 @@ function App() {
|
|||||||
[sendMessage, handleCommand, switchTopic, selectTopic]
|
[sendMessage, handleCommand, switchTopic, selectTopic]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleDeleteTopic = useCallback(
|
||||||
|
(topicId: string) => {
|
||||||
|
const cmd = deleteTopic(topicId)
|
||||||
|
handleCommand(cmd)
|
||||||
|
sendMessage({ type: 'command', payload: JSON.stringify(cmd) })
|
||||||
|
// 如果删除的是当前选中话题,清空选中状态和消息
|
||||||
|
if (topicId === selectedTopic) {
|
||||||
|
selectTopic('')
|
||||||
|
clearMessages()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[sendMessage, handleCommand, deleteTopic, selectedTopic, selectTopic, clearMessages]
|
||||||
|
)
|
||||||
|
|
||||||
const handleNavigateToSubAgent = useCallback(
|
const handleNavigateToSubAgent = useCallback(
|
||||||
(taskId: string, description: string) => {
|
(taskId: string, description: string) => {
|
||||||
const cmd = enterSubAgentView(taskId, description)
|
const cmd = enterSubAgentView(taskId, description)
|
||||||
@ -407,6 +423,7 @@ function App() {
|
|||||||
onCreateTopic={handleCreateTopic}
|
onCreateTopic={handleCreateTopic}
|
||||||
onRefresh={handleRefreshTopics}
|
onRefresh={handleRefreshTopics}
|
||||||
onSwitchTopic={handleSwitchTopic}
|
onSwitchTopic={handleSwitchTopic}
|
||||||
|
onDeleteTopic={handleDeleteTopic}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SchedulerJobList
|
<SchedulerJobList
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Plus, MessageSquare, Layers, Hash, Clock, RefreshCw } from 'lucide-react'
|
import { useState } from 'react'
|
||||||
|
import { Plus, MessageSquare, Layers, Hash, Clock, RefreshCw, Trash2, Check, X } from 'lucide-react'
|
||||||
import type { Topic } from '../../types/protocol'
|
import type { Topic } from '../../types/protocol'
|
||||||
|
|
||||||
interface TopicListProps {
|
interface TopicListProps {
|
||||||
@ -9,6 +10,7 @@ interface TopicListProps {
|
|||||||
onCreateTopic: () => void
|
onCreateTopic: () => void
|
||||||
onRefresh: () => void
|
onRefresh: () => void
|
||||||
onSwitchTopic: (topicId: string) => void
|
onSwitchTopic: (topicId: string) => void
|
||||||
|
onDeleteTopic: (topicId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTime(timestamp: number): string {
|
function formatTime(timestamp: number): string {
|
||||||
@ -35,7 +37,10 @@ export function TopicList({
|
|||||||
onCreateTopic,
|
onCreateTopic,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onSwitchTopic,
|
onSwitchTopic,
|
||||||
|
onDeleteTopic,
|
||||||
}: TopicListProps) {
|
}: TopicListProps) {
|
||||||
|
const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -93,41 +98,83 @@ export function TopicList({
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{topics.map((topic, index) => (
|
{topics.map((topic, index) => (
|
||||||
<button
|
<div key={topic.id} className="group relative">
|
||||||
key={topic.id}
|
<button
|
||||||
onClick={() => onSwitchTopic(topic.id)}
|
onClick={() => onSwitchTopic(topic.id)}
|
||||||
className={`w-full rounded-xl px-3 py-3 text-left text-sm transition-all ${
|
className={`w-full rounded-xl pl-3 pr-8 py-3 text-left text-sm transition-all ${
|
||||||
topic.id === currentTopicId
|
topic.id === currentTopicId
|
||||||
? 'bg-gradient-to-r from-[var(--accent-cyan)]/20 to-transparent border border-[var(--accent-cyan)]/30'
|
? 'bg-gradient-to-r from-[var(--accent-cyan)]/20 to-transparent border border-[var(--accent-cyan)]/30'
|
||||||
: 'hover:bg-[var(--overlay-hover)] border border-transparent'
|
: 'hover:bg-[var(--overlay-hover)] border border-transparent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className="mt-0.5 text-xs text-[var(--text-muted)] font-mono w-4">
|
<span className="mt-0.5 text-xs text-[var(--text-muted)] font-mono w-4">
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className={`truncate font-medium ${
|
<div className={`truncate font-medium ${
|
||||||
topic.id === currentTopicId ? 'text-[var(--accent-cyan)]' : 'text-[var(--text-secondary)]'
|
topic.id === currentTopicId ? 'text-[var(--accent-cyan)]' : 'text-[var(--text-secondary)]'
|
||||||
}`}>
|
}`}>
|
||||||
{topic.description || topic.title}
|
{topic.description || topic.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 mt-1.5">
|
<div className="flex items-center gap-3 mt-1.5">
|
||||||
<span className="text-xs text-[var(--text-muted)] flex items-center gap-1">
|
<span className="text-xs text-[var(--text-muted)] flex items-center gap-1">
|
||||||
<Hash className="h-3 w-3" />
|
<Hash className="h-3 w-3" />
|
||||||
{topic.message_count} 条消息
|
{topic.message_count} 条消息
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-[var(--text-muted)] flex items-center gap-1">
|
<span className="text-xs text-[var(--text-muted)] flex items-center gap-1">
|
||||||
<Clock className="h-3 w-3" />
|
<Clock className="h-3 w-3" />
|
||||||
{formatTime(topic.updated_at)}
|
{formatTime(topic.updated_at)}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{topic.id === currentTopicId && (
|
||||||
|
<span className="inline-block h-2 w-2 rounded-full bg-[var(--accent-cyan)] shadow-lg shadow-[var(--shadow-glow-soft)] mt-1.5" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{topic.id === currentTopicId && (
|
</button>
|
||||||
<span className="inline-block h-2 w-2 rounded-full bg-[var(--accent-cyan)] shadow-lg shadow-[var(--shadow-glow-soft)] mt-1.5" />
|
|
||||||
|
{/* Delete button — visible on group hover */}
|
||||||
|
<div className="absolute top-2.5 right-2.5">
|
||||||
|
{confirmDeleteId === topic.id ? (
|
||||||
|
<span className="flex items-center gap-1.5 rounded-lg bg-[var(--bg-tertiary)] border border-[var(--border-color)] px-2 py-1 shadow-lg animate-scale-in">
|
||||||
|
<span className="text-xs text-red-400 whitespace-nowrap">确认删除?</span>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onDeleteTopic(topic.id)
|
||||||
|
setConfirmDeleteId(null)
|
||||||
|
}}
|
||||||
|
className="flex items-center justify-center h-5 w-5 rounded bg-emerald-500/20 text-emerald-400 hover:bg-emerald-500/30 transition-colors"
|
||||||
|
title="确认"
|
||||||
|
>
|
||||||
|
<Check className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setConfirmDeleteId(null)
|
||||||
|
}}
|
||||||
|
className="flex items-center justify-center h-5 w-5 rounded bg-zinc-500/20 text-zinc-400 hover:bg-zinc-500/30 transition-colors"
|
||||||
|
title="取消"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setConfirmDeleteId(topic.id)
|
||||||
|
}}
|
||||||
|
className="opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center h-6 w-6 rounded-md text-[var(--text-muted)] hover:text-red-400 hover:bg-red-500/10"
|
||||||
|
title="删除话题"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -63,6 +63,7 @@ interface UseChatReturn {
|
|||||||
selectTopic: (topicId: string) => void
|
selectTopic: (topicId: string) => void
|
||||||
createTopic: (title?: string) => Command
|
createTopic: (title?: string) => Command
|
||||||
switchTopic: (topicId: string) => Command
|
switchTopic: (topicId: string) => Command
|
||||||
|
deleteTopic: (topicId: string) => Command
|
||||||
|
|
||||||
// 初始化方法
|
// 初始化方法
|
||||||
requestSessionList: () => Command
|
requestSessionList: () => Command
|
||||||
@ -528,6 +529,7 @@ export function useChat(): UseChatReturn {
|
|||||||
case 'load_topic':
|
case 'load_topic':
|
||||||
case 'list_sessions':
|
case 'list_sessions':
|
||||||
case 'list_sessions_by_channel':
|
case 'list_sessions_by_channel':
|
||||||
|
case 'delete_topic':
|
||||||
case 'list_topics':
|
case 'list_topics':
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
break
|
break
|
||||||
@ -558,6 +560,13 @@ export function useChat(): UseChatReturn {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const deleteTopic = useCallback((topicId: string): Command => {
|
||||||
|
return {
|
||||||
|
type: 'delete_topic',
|
||||||
|
topic_id: topicId,
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// 初始化方法
|
// 初始化方法
|
||||||
const requestSessionList = useCallback((): Command => {
|
const requestSessionList = useCallback((): Command => {
|
||||||
return {
|
return {
|
||||||
@ -702,6 +711,7 @@ export function useChat(): UseChatReturn {
|
|||||||
selectTopic,
|
selectTopic,
|
||||||
createTopic,
|
createTopic,
|
||||||
switchTopic,
|
switchTopic,
|
||||||
|
deleteTopic,
|
||||||
requestSessionList,
|
requestSessionList,
|
||||||
requestTopicList,
|
requestTopicList,
|
||||||
requestChannelList,
|
requestChannelList,
|
||||||
|
|||||||
@ -303,6 +303,11 @@ export interface LoadChatMessagesCommand {
|
|||||||
chat_id: string
|
chat_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteTopicCommand {
|
||||||
|
type: 'delete_topic'
|
||||||
|
topic_id: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface StopExecutionCommand {
|
export interface StopExecutionCommand {
|
||||||
type: 'stop_execution'
|
type: 'stop_execution'
|
||||||
}
|
}
|
||||||
@ -322,6 +327,7 @@ export type Command =
|
|||||||
| LoadTaskMessagesCommand
|
| LoadTaskMessagesCommand
|
||||||
| ListSchedulerJobsCommand
|
| ListSchedulerJobsCommand
|
||||||
| LoadChatMessagesCommand
|
| LoadChatMessagesCommand
|
||||||
|
| DeleteTopicCommand
|
||||||
| StopExecutionCommand
|
| StopExecutionCommand
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user