182 lines
4.5 KiB
TypeScript
182 lines
4.5 KiB
TypeScript
/**
|
|
* ConversationList - 会话列表组件
|
|
*
|
|
* 现代感设计的会话列表,支持:
|
|
* - 分组显示(今天、昨天、更早)
|
|
* - 新建会话
|
|
* - 会话切换
|
|
* - 会话删除
|
|
* - 智能体图标
|
|
*/
|
|
|
|
import React, { useMemo } from 'react';
|
|
import { Button, Typography, Dropdown, Empty } from 'antd';
|
|
import {
|
|
PlusOutlined,
|
|
MessageOutlined,
|
|
DeleteOutlined,
|
|
MoreOutlined,
|
|
RobotOutlined,
|
|
} from '@ant-design/icons';
|
|
import type { Conversation, ConversationGroup } from './hooks/useConversations';
|
|
import './styles/conversation-list.css';
|
|
|
|
const { Text } = Typography;
|
|
|
|
/**
|
|
* ConversationList Props
|
|
*/
|
|
export interface ConversationListProps {
|
|
/** 分组会话列表 */
|
|
groups: ConversationGroup[];
|
|
/** 当前会话 ID */
|
|
currentId: string | null;
|
|
/** 会话点击回调 */
|
|
onSelect: (id: string) => void;
|
|
/** 新建会话回调 */
|
|
onNew: () => void;
|
|
/** 删除会话回调 */
|
|
onDelete?: (id: string) => void;
|
|
/** 标题 */
|
|
title?: string;
|
|
/** Logo */
|
|
logo?: React.ReactNode;
|
|
/** 自定义类名 */
|
|
className?: string;
|
|
/** 是否加载中 */
|
|
loading?: boolean;
|
|
}
|
|
|
|
/**
|
|
* 会话列表组件
|
|
*/
|
|
export const ConversationList: React.FC<ConversationListProps> = ({
|
|
groups,
|
|
currentId,
|
|
onSelect,
|
|
onNew,
|
|
onDelete,
|
|
title = 'AI 助手',
|
|
logo,
|
|
className = '',
|
|
loading = false,
|
|
}) => {
|
|
// 会话项菜单
|
|
const getMenuItems = (conv: Conversation) => [
|
|
{
|
|
key: 'delete',
|
|
icon: <DeleteOutlined />,
|
|
label: '删除对话',
|
|
danger: true,
|
|
onClick: () => onDelete?.(conv.id),
|
|
},
|
|
];
|
|
|
|
// 是否为空
|
|
const isEmpty = useMemo(() => {
|
|
return groups.every(g => g.conversations.length === 0);
|
|
}, [groups]);
|
|
|
|
return (
|
|
<div className={`conversation-list ${className}`}>
|
|
{/* 头部 */}
|
|
<div className="conversation-list-header">
|
|
<div className="conversation-list-logo">
|
|
{logo || <RobotOutlined className="default-logo" />}
|
|
<span className="conversation-list-title">{title}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 新建会话按钮 */}
|
|
<div className="conversation-list-new">
|
|
<Button
|
|
type="default"
|
|
icon={<PlusOutlined />}
|
|
onClick={onNew}
|
|
block
|
|
className="new-conversation-btn"
|
|
>
|
|
新对话
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 会话列表 */}
|
|
<div className="conversation-list-content">
|
|
{isEmpty ? (
|
|
<Empty
|
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
description="暂无对话"
|
|
className="conversation-empty"
|
|
/>
|
|
) : (
|
|
groups.map(group => (
|
|
<div key={group.key} className="conversation-group">
|
|
<div className="conversation-group-label">
|
|
<Text type="secondary">{group.label}</Text>
|
|
</div>
|
|
|
|
{group.conversations.map(conv => (
|
|
<div
|
|
key={conv.id}
|
|
className={`conversation-item ${currentId === conv.id ? 'active' : ''}`}
|
|
onClick={() => onSelect(conv.id)}
|
|
>
|
|
<div className="conversation-item-icon">
|
|
{conv.agentIcon ? (
|
|
<span className="agent-icon">{conv.agentIcon}</span>
|
|
) : (
|
|
<MessageOutlined />
|
|
)}
|
|
</div>
|
|
|
|
<div className="conversation-item-content">
|
|
<div className="conversation-item-title">
|
|
{conv.title || '新对话'}
|
|
</div>
|
|
{conv.lastMessage && (
|
|
<div className="conversation-item-preview">
|
|
{conv.lastMessage}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{onDelete && (
|
|
<Dropdown
|
|
menu={{ items: getMenuItems(conv) }}
|
|
trigger={['click']}
|
|
placement="bottomRight"
|
|
>
|
|
<Button
|
|
type="text"
|
|
size="small"
|
|
icon={<MoreOutlined />}
|
|
className="conversation-item-more"
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
</Dropdown>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ConversationList;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|