feat:前端新增初版知识库管理页面

This commit is contained in:
PingChuan
2025-11-30 19:27:01 +08:00
parent 9614899171
commit c94cc00138
40 changed files with 3034 additions and 1024 deletions
+398
View File
@@ -0,0 +1,398 @@
import {
DeleteOutlined,
EditOutlined,
ExclamationCircleOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
MessageOutlined,
MoreOutlined,
PlusOutlined,
SearchOutlined,
} from '@ant-design/icons';
import { Button, Dropdown, Input, Layout, Menu, Modal, Tooltip, message, theme } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
import type { ConversationItem } from '~/api/dify-chat';
import { deleteConversation, renameConversation } from '~/api/dify-chat';
import '../../styles/components/chat-with-llm/sidebar.css';
const { Sider } = Layout;
interface ChatSidebarProps {
collapsed: boolean;
onToggle: () => void;
conversations: ConversationItem[];
currentConversationId: string;
onConversationSelect: (conversationId: string) => void;
onNewConversation: () => void;
onConversationDeleted?: (conversationId: string) => void;
onConversationRenamed?: (conversationId: string, newName: string) => void;
}
// 暴露给父组件的方法接口
export interface ChatSidebarRef {
autoRename: (conversationId: string) => Promise<void>;
}
/**
* 聊天侧边栏组件
*/
const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
collapsed,
onToggle,
conversations,
currentConversationId,
onConversationSelect,
onNewConversation,
onConversationDeleted,
onConversationRenamed,
}, ref) => {
const [searchValue, setSearchValue] = useState('');
const [renameModalVisible, setRenameModalVisible] = useState(false);
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const [renamingConversation, setRenamingConversation] = useState<ConversationItem | null>(null);
const [deletingConversation, setDeletingConversation] = useState<ConversationItem | null>(null);
const [newName, setNewName] = useState('');
const [renameLoading, setRenameLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
// 过滤会话列表
const filteredConversations = conversations.filter(conv =>
conv.name.toLowerCase().includes(searchValue.toLowerCase())
);
// 处理重命名
const handleRename = (conv: ConversationItem) => {
setRenamingConversation(conv);
setNewName(conv.name);
setRenameModalVisible(true);
};
// 处理删除会话 - 显示确认Modal
const handleDeleteClick = (conv: ConversationItem) => {
setDeletingConversation(conv);
setDeleteModalVisible(true);
};
// 确认删除会话
const handleDeleteConfirm = async () => {
if (!deletingConversation) return;
setDeleteLoading(true);
try {
// console.log('🗑️ 开始删除会话:', deletingConversation.id);
// 调用API删除服务器端的会话
const response = await deleteConversation(deletingConversation.id);
// console.log('✅ 服务器端会话删除响应:', response);
// 检查响应是否成功
if (response && (response as any).result === 'success') {
// console.log('✅ 服务器端会话删除成功');
message.success('会话删除成功');
setDeleteModalVisible(false);
// 通知父组件会话已删除
onConversationDeleted?.(deletingConversation.id);
// console.log('✅ 会话删除完成:', deletingConversation.id);
} else {
throw new Error((response as any)?.error || '删除会话失败');
}
} catch (error) {
console.error('❌ 删除会话失败:', error);
message.error(`删除会话失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setDeleteLoading(false);
}
};
// 取消删除
const handleDeleteCancel = () => {
setDeleteModalVisible(false);
setDeletingConversation(null);
setDeleteLoading(false);
};
// 确认重命名
const handleRenameConfirm = async () => {
if (!renamingConversation || !newName.trim()) {
message.error('请输入有效的会话名称');
return;
}
if (newName.trim() === renamingConversation.name) {
setRenameModalVisible(false);
return;
}
setRenameLoading(true);
try {
// console.log('✏️ 开始重命名会话:', { conversationId: renamingConversation.id, newName: newName.trim() });
// 调用API重命名服务器端的会话
const response = await renameConversation(renamingConversation.id, newName.trim(), false);
// console.log('✅ 服务器端会话重命名响应:', response);
// 检查响应是否成功
if (response && (response as any).name) {
// console.log('✅ 服务器端会话重命名成功');
message.success('重命名成功');
setRenameModalVisible(false);
// 通知父组件会话已重命名
onConversationRenamed?.(renamingConversation.id, (response as any).name);
// console.log('✅ 会话重命名完成:', renamingConversation.id, '->', (response as any).name);
} else {
throw new Error((response as any)?.error || '重命名会话失败');
}
} catch (error) {
console.error('❌ 重命名会话失败:', error);
message.error(`重命名失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setRenameLoading(false);
}
};
// 取消重命名
const handleRenameCancel = () => {
setRenameModalVisible(false);
setRenamingConversation(null);
setNewName('');
setRenameLoading(false);
};
// 生成菜单项
const menuItems = filteredConversations.map(conv => ({
key: conv.id,
icon: <MessageOutlined />,
label: (
<div className="flex items-center justify-between group">
<span className="truncate flex-1" title={conv.name}>
{conv.name}
</span>
{!collapsed && (
<Dropdown
menu={{
items: [
{
key: 'rename',
icon: <EditOutlined />,
label: '重命名',
onClick: () => handleRename(conv),
},
{
key: 'delete',
icon: <DeleteOutlined />,
label: '删除',
danger: true,
onClick: () => handleDeleteClick(conv),
},
],
}}
trigger={['click']}
placement="bottomRight"
>
<Button
type="text"
size="small"
icon={<MoreOutlined />}
className="opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => e.stopPropagation()}
// style={{ backgroundColor: '#00684A' }}
/>
</Dropdown>
)}
</div>
),
}));
useImperativeHandle(ref, () => ({
autoRename: async (conversationId: string) => {
try {
// console.log('🏷️ 开始自动重命名会话为"新对话":', conversationId);
// 调用API将会话重命名为固定的"新对话"
const response = await renameConversation(conversationId, '新对话', false);
// console.log('✅ 服务器端会话重命名响应:', response);
// 检查响应是否成功
if (response && (response as any).name) {
// console.log('✅ 服务器端会话重命名成功');
// 通知父组件会话已重命名
onConversationRenamed?.(conversationId, (response as any).name);
// console.log('✅ 会话重命名完成:', conversationId, '->', (response as any).name);
} else {
throw new Error((response as any)?.error || '重命名会话失败');
}
} catch (error) {
console.error('❌ 重命名会话失败:', error);
// 重命名失败时不显示错误消息,避免打扰用户
// console.warn('⚠️ 重命名失败,会话将保持默认名称');
}
},
}));
return (
<Sider
trigger={null}
collapsible
collapsed={collapsed}
width={280}
style={{
background: colorBgContainer,
borderRight: '1px solid #f0f0f0',
display: 'flex',
flexDirection: 'column',
height: '100%',
}}
>
{/* 侧边栏头部 - 固定在顶部 */}
<div className="p-4 border-b border-gray-100 flex-shrink-0">
<div className="flex items-center justify-between mb-3">
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={onToggle}
style={{
fontSize: '16px',
width: 32,
height: 32,
color: 'rgb(0, 104, 74)',
}}
/>
{!collapsed && (
<Tooltip title="新建对话">
<Button
type="primary"
icon={<PlusOutlined />}
onClick={onNewConversation}
size="small"
>
</Button>
</Tooltip>
)}
</div>
{/* 搜索框 */}
{!collapsed && (
<Input
placeholder="搜索对话..."
prefix={<SearchOutlined />}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
allowClear
/>
)}
</div>
{/* 会话列表 - 可滚动区域 */}
<div className="flex-1 overflow-hidden">
<div
className="h-full overflow-y-auto"
style={{
scrollbarWidth: 'thin',
scrollbarColor: '#c1c1c1 #f1f1f1',
}}
>
{!collapsed && filteredConversations.length === 0 && searchValue && (
<div className="p-4 text-center text-gray-500">
<MessageOutlined className="text-2xl mb-2" />
<p></p>
</div>
)}
{!collapsed && conversations.length === 0 && !searchValue && (
<div className="p-4 text-center text-gray-500">
<MessageOutlined className="text-2xl mb-2" />
<p></p>
<Button
type="link"
onClick={onNewConversation}
className="mt-2"
>
</Button>
</div>
)}
<Menu
mode="inline"
selectedKeys={[currentConversationId]}
items={menuItems}
onClick={({ key }) => onConversationSelect(key)}
style={{
border: 'none',
background: 'transparent',
}}
className="chat-sidebar-menu"
/>
</div>
</div>
{/* 侧边栏底部 - 固定在底部 */}
{!collapsed && conversations.length > 0 && (
<div className="sidebar-footer">
<div className="stats-text">
{conversations.length}
</div>
</div>
)}
{/* 重命名Modal */}
<Modal
title="重命名会话"
open={renameModalVisible}
onOk={handleRenameConfirm}
onCancel={handleRenameCancel}
confirmLoading={renameLoading}
okText="确定"
cancelText="取消"
destroyOnClose
>
<div className="py-4">
<Input
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder="请输入新的会话名称"
maxLength={10}
showCount
onPressEnter={handleRenameConfirm}
autoFocus
/>
</div>
</Modal>
{/* 删除确认Modal */}
<Modal
title={
<div className="flex items-center">
<ExclamationCircleOutlined className="text-red-500 mr-2" />
</div>
}
open={deleteModalVisible}
onOk={handleDeleteConfirm}
onCancel={handleDeleteCancel}
confirmLoading={deleteLoading}
okText="删除"
cancelText="取消"
okType="danger"
destroyOnClose
>
<div className="py-4">
<p> <strong>"{deletingConversation?.name}"</strong> </p>
<p className="text-gray-500 text-sm mt-2"></p>
</div>
</Modal>
</Sider>
);
});
export default ChatSidebar;