137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
import { Button, Spin, Empty, Tabs } from 'antd';
|
|
import { PlusOutlined } from '@ant-design/icons';
|
|
import { useEffect, useState, useMemo } from 'react';
|
|
import type { Agent } from '../../../api';
|
|
import './AgentSidebar.css';
|
|
|
|
const isImageUrl = (url: string) => url?.startsWith('http') || url?.startsWith('/');
|
|
|
|
export default function AgentSidebar(props: {
|
|
agentList: Agent[];
|
|
activeAgentId?: string;
|
|
onCreate: () => void;
|
|
onSelect: (agentId: string) => void;
|
|
isSidebar?: boolean;
|
|
}) {
|
|
const { agentList, activeAgentId, onCreate, onSelect, isSidebar = true } = props;
|
|
const [loadingTimeout, setLoadingTimeout] = useState(false);
|
|
const [activeTab, setActiveTab] = useState('mine');
|
|
|
|
useEffect(() => {
|
|
if (agentList.length > 0) return;
|
|
|
|
const timer = setTimeout(() => {
|
|
setLoadingTimeout(true);
|
|
}, 5000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [agentList.length]);
|
|
|
|
const filteredList = useMemo(() => {
|
|
if (activeTab === 'public') return agentList.filter(a => a.visibility === 'public');
|
|
if (activeTab === 'private') return agentList.filter(a => a.visibility === 'private');
|
|
return agentList; // 'mine' tab shows all for now or filter by owner_id if available
|
|
}, [agentList, activeTab]);
|
|
|
|
// 模拟分组逻辑
|
|
const groupedAgents = useMemo(() => {
|
|
const groups: Record<string, Agent[]> = {
|
|
'我的智能体': [],
|
|
'推荐': []
|
|
};
|
|
|
|
filteredList.forEach(a => {
|
|
if (a.visibility === 'public') {
|
|
groups['推荐'].push(a);
|
|
} else {
|
|
groups['我的智能体'].push(a);
|
|
}
|
|
});
|
|
|
|
return Object.entries(groups).filter(([_, list]) => list.length > 0);
|
|
}, [filteredList]);
|
|
|
|
return (
|
|
<aside className={`chat-side${isSidebar ? '' : ' is-full'}`}>
|
|
<div className="chat-agent-sidebar-header">
|
|
<Button
|
|
block
|
|
type="primary"
|
|
icon={<PlusOutlined />}
|
|
onClick={onCreate}
|
|
className="chat-agent-create-btn"
|
|
>
|
|
创建智能体
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="chat-agent-tabs">
|
|
<Tabs
|
|
activeKey={activeTab}
|
|
onChange={setActiveTab}
|
|
items={[
|
|
{ key: 'mine', label: '我的' },
|
|
{ key: 'public', label: '公开' },
|
|
{ key: 'private', label: '私有' }
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
<div className="chat-agent-sidebar-list">
|
|
{agentList.length > 0 ? (
|
|
groupedAgents.map(([groupName, list]) => (
|
|
<div key={groupName} className="chat-agent-group">
|
|
<div className="chat-agent-group-label">
|
|
<span>{groupName}</span>
|
|
<span className="chat-agent-group-count">{list.length}</span>
|
|
</div>
|
|
{list.map((a) => {
|
|
const isActive = a.id === activeAgentId;
|
|
return (
|
|
<div
|
|
key={a.id}
|
|
onClick={() => onSelect(a.id)}
|
|
className={`chat-agent-item ${isActive ? 'active' : ''}`}
|
|
>
|
|
<div
|
|
className="chat-agent-avatar-wrap"
|
|
style={{ background: a.avatar || 'var(--gradient-brand)' }}
|
|
>
|
|
{isImageUrl(a.avatar) ? (
|
|
<img src={a.avatar} className="chat-agent-avatar-img" alt="avatar" />
|
|
) : (
|
|
(a.name?.charAt(0) || '?').toUpperCase()
|
|
)}
|
|
</div>
|
|
<div className="chat-agent-info">
|
|
<div className="chat-agent-name">{a.name}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
))
|
|
) : !loadingTimeout ? (
|
|
<div className="chat-agent-sidebar-status">
|
|
<Spin />
|
|
<div className="chat-agent-loading-text">正在加载智能体...</div>
|
|
</div>
|
|
) : (
|
|
<div className="chat-agent-sidebar-status">
|
|
<Empty
|
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
description={
|
|
<span className="chat-agent-empty-text">
|
|
暂无智能体<br />
|
|
点击上方按钮开始创建
|
|
</span>
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|
|
|