286 lines
12 KiB
TypeScript
286 lines
12 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
||
import {
|
||
ArrowRightOutlined,
|
||
CompassOutlined,
|
||
DeleteOutlined,
|
||
EditOutlined,
|
||
MessageOutlined,
|
||
RobotOutlined
|
||
} from '@ant-design/icons';
|
||
import { Button, Col, Row, Empty, Popconfirm, App as AntApp, Tag, Space } from 'antd';
|
||
import { Link, useNavigate } from 'react-router-dom';
|
||
import dayjs from 'dayjs';
|
||
import { Agent, AgentAPI } from '../api';
|
||
|
||
export default function AgentList() {
|
||
const [list, setList] = useState<Agent[]>([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const navigate = useNavigate();
|
||
const { message } = AntApp.useApp();
|
||
|
||
const load = async () => {
|
||
setLoading(true);
|
||
try {
|
||
setList(await AgentAPI.list());
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
useEffect(() => {
|
||
load();
|
||
}, []);
|
||
|
||
const handleDelete = async (id: string) => {
|
||
await AgentAPI.remove(id);
|
||
message.success('已删除');
|
||
load();
|
||
};
|
||
|
||
const isImageUrl = (url: string) => url?.startsWith('http') || url?.startsWith('/');
|
||
const publicCount = useMemo(() => list.filter((a) => a.visibility === 'public').length, [list]);
|
||
const teamCount = useMemo(() => list.filter((a) => a.visibility === 'team').length, [list]);
|
||
|
||
return (
|
||
<div className="page-container">
|
||
<div
|
||
style={{
|
||
borderRadius: 24,
|
||
padding: '30px 30px 26px',
|
||
background:
|
||
'linear-gradient(135deg, rgba(255,255,255,0.98) 0%, rgba(236,253,245,0.92) 48%, rgba(239,246,255,0.96) 100%)',
|
||
border: '1px solid rgba(8, 145, 178, 0.12)',
|
||
boxShadow: '0 20px 48px rgba(15, 23, 42, 0.06)',
|
||
marginBottom: 24
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'flex-start',
|
||
gap: 20,
|
||
flexWrap: 'wrap',
|
||
marginBottom: 22
|
||
}}
|
||
>
|
||
<div style={{ maxWidth: 620 }}>
|
||
<div
|
||
style={{
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
gap: 8,
|
||
padding: '6px 12px',
|
||
borderRadius: 999,
|
||
background: 'rgba(255,255,255,0.78)',
|
||
border: '1px solid rgba(8, 145, 178, 0.10)',
|
||
color: 'var(--color-text-secondary)',
|
||
fontSize: 12,
|
||
fontWeight: 600,
|
||
marginBottom: 16
|
||
}}
|
||
>
|
||
<RobotOutlined style={{ color: 'var(--color-brand)' }} />
|
||
我的 Agent 资产
|
||
</div>
|
||
|
||
<h2 className="page-title" style={{ marginBottom: 10 }}>
|
||
我的智能体
|
||
</h2>
|
||
<div className="page-subtitle" style={{ marginTop: 0, fontSize: 15, lineHeight: 1.75 }}>
|
||
把你的 AI 助手沉淀成一组可管理、可协作、可持续进化的能力单元。创建入口统一在智能体广场,这里负责查看、进入和运营它们。
|
||
</div>
|
||
</div>
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<CompassOutlined />}
|
||
onClick={() => navigate('/marketplace')}
|
||
style={{ borderRadius: 14, height: 46, padding: '0 18px', fontWeight: 600 }}
|
||
>
|
||
前往智能体广场
|
||
</Button>
|
||
</div>
|
||
|
||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 14 }}>
|
||
{[
|
||
{ label: '已创建智能体', value: list.length, tone: 'rgba(8, 145, 178, 0.10)', color: 'var(--color-brand)' },
|
||
{ label: '公开可分享', value: publicCount, tone: 'rgba(34, 197, 94, 0.10)', color: 'var(--color-success)' },
|
||
{ label: '团队协作中', value: teamCount, tone: 'rgba(14, 165, 233, 0.10)', color: 'var(--color-info)' }
|
||
].map((item) => (
|
||
<div
|
||
key={item.label}
|
||
style={{
|
||
borderRadius: 18,
|
||
padding: '16px 18px',
|
||
background: 'rgba(255,255,255,0.72)',
|
||
border: '1px solid rgba(255,255,255,0.7)'
|
||
}}
|
||
>
|
||
<div style={{ fontSize: 12.5, color: 'var(--color-text-secondary)', marginBottom: 10 }}>{item.label}</div>
|
||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
|
||
<span style={{ fontSize: 28, fontWeight: 700, color: 'var(--color-text)' }}>{item.value}</span>
|
||
<span
|
||
style={{
|
||
borderRadius: 999,
|
||
padding: '4px 8px',
|
||
background: item.tone,
|
||
color: item.color,
|
||
fontSize: 12,
|
||
fontWeight: 600
|
||
}}
|
||
>
|
||
实时统计
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{!loading && list.length === 0 ? (
|
||
<div className="empty-state">
|
||
<Empty description="你还没有任何智能体">
|
||
<Button type="primary" onClick={() => navigate('/marketplace')} style={{ borderRadius: 10 }}>
|
||
前往广场创建
|
||
</Button>
|
||
</Empty>
|
||
</div>
|
||
) : (
|
||
<Row gutter={[18, 18]}>
|
||
{list.map((a) => (
|
||
<Col xs={24} sm={12} md={8} lg={6} key={a.id}>
|
||
<div
|
||
className="agent-card"
|
||
style={{
|
||
borderRadius: 20,
|
||
padding: 20,
|
||
minHeight: 292,
|
||
background: 'linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(252,252,253,1) 100%)',
|
||
boxShadow: '0 12px 28px rgba(15, 23, 42, 0.045)'
|
||
}}
|
||
>
|
||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
|
||
<div
|
||
className="avatar"
|
||
style={{ background: a.avatar || 'var(--gradient-brand)', borderRadius: '50%', overflow: 'hidden', width: 54, height: 54 }}
|
||
>
|
||
{isImageUrl(a.avatar) ? (
|
||
<img src={a.avatar} className="w-full h-full object-cover" alt="avatar" />
|
||
) : (
|
||
(a.name?.charAt(0) || '?').toUpperCase()
|
||
)}
|
||
</div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ fontWeight: 700, fontSize: 17, color: 'var(--color-text)', marginBottom: 4 }}>{a.name}</div>
|
||
<div style={{ fontSize: 12.5, color: 'var(--color-text-tertiary)' }}>
|
||
最近更新于 {dayjs(a.updated_at).format('YYYY-MM-DD')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
style={{
|
||
marginTop: 16,
|
||
padding: '16px 16px 18px',
|
||
borderRadius: 16,
|
||
background: 'linear-gradient(180deg, rgba(248,250,252,0.9) 0%, rgba(255,255,255,0.95) 100%)',
|
||
border: '1px solid rgba(148, 163, 184, 0.14)'
|
||
}}
|
||
>
|
||
<div className="desc" style={{ minHeight: 66, fontSize: 13.5, lineHeight: 1.7 }}>
|
||
{a.description || '还没有填写描述,可以补充这个智能体适合解决什么问题。'}
|
||
</div>
|
||
</div>
|
||
|
||
<Space size={6} wrap style={{ marginTop: 14 }}>
|
||
{a.visibility === 'public' && (
|
||
<Tag bordered={false} style={{ background: 'var(--color-success-soft)', color: 'var(--color-success)', borderRadius: 999, margin: 0 }}>
|
||
公开
|
||
</Tag>
|
||
)}
|
||
{a.visibility === 'team' && (
|
||
<Tag bordered={false} style={{ background: 'var(--color-info-soft)', color: 'var(--color-info)', borderRadius: 999, margin: 0 }}>
|
||
团队
|
||
</Tag>
|
||
)}
|
||
{a.visibility === 'private' && (
|
||
<Tag bordered={false} style={{ background: 'var(--color-surface-2)', color: 'var(--color-text-secondary)', borderRadius: 999, margin: 0 }}>
|
||
私有
|
||
</Tag>
|
||
)}
|
||
{a.model && (
|
||
<Tag bordered={false} style={{ background: 'var(--color-brand-soft)', color: 'var(--color-brand)', borderRadius: 999, margin: 0, maxWidth: '100%' }}>
|
||
<span style={{ display: 'inline-block', maxWidth: 190, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||
{a.model}
|
||
</span>
|
||
</Tag>
|
||
)}
|
||
<Tag bordered={false} style={{ background: 'var(--color-surface-2)', color: 'var(--color-text-secondary)', borderRadius: 999, margin: 0 }}>
|
||
T={a.temperature}
|
||
</Tag>
|
||
{(a.fork_count ?? 0) > 0 && (
|
||
<Tag bordered={false} style={{ background: 'var(--color-surface-2)', color: 'var(--color-text-secondary)', borderRadius: 999, margin: 0 }}>
|
||
Fork {a.fork_count}
|
||
</Tag>
|
||
)}
|
||
</Space>
|
||
|
||
<div style={{ display: 'flex', gap: 8, marginTop: 'auto', paddingTop: 16, borderTop: '1px solid var(--color-border)' }}>
|
||
<Link to={`/agents/${a.id}/chat`} style={{ flex: 1 }}>
|
||
<Button type="primary" block icon={<MessageOutlined />} style={{ borderRadius: 12, height: 40, fontWeight: 600 }}>
|
||
对话
|
||
</Button>
|
||
</Link>
|
||
<Link to={`/agents/${a.id}`} style={{ flex: 1 }}>
|
||
<Button block icon={<EditOutlined />} style={{ borderRadius: 12, height: 40 }}>
|
||
管理
|
||
</Button>
|
||
</Link>
|
||
<Popconfirm
|
||
title="确定删除该智能体?"
|
||
description="将删除其知识库与对话记录"
|
||
onConfirm={() => handleDelete(a.id)}
|
||
okText="删除"
|
||
cancelText="取消"
|
||
>
|
||
<Button danger icon={<DeleteOutlined />} style={{ borderRadius: 12, width: 40, height: 40 }} />
|
||
</Popconfirm>
|
||
</div>
|
||
</div>
|
||
</Col>
|
||
))}
|
||
</Row>
|
||
)}
|
||
|
||
{list.length > 0 && (
|
||
<div
|
||
style={{
|
||
marginTop: 24,
|
||
borderRadius: 20,
|
||
padding: '18px 20px',
|
||
background: 'var(--color-surface)',
|
||
border: '1px solid var(--color-border)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
gap: 16,
|
||
flexWrap: 'wrap'
|
||
}}
|
||
>
|
||
<div>
|
||
<div style={{ fontSize: 15, fontWeight: 600, color: 'var(--color-text)', marginBottom: 4 }}>
|
||
想创建新的智能体入口?
|
||
</div>
|
||
<div style={{ fontSize: 13, color: 'var(--color-text-secondary)' }}>
|
||
统一从智能体广场进入,保证创建流程和发现体验保持一致。
|
||
</div>
|
||
</div>
|
||
<Button type="text" icon={<ArrowRightOutlined />} onClick={() => navigate('/marketplace')} style={{ color: 'var(--color-brand)', fontWeight: 600 }}>
|
||
去广场继续发现
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|