405 lines
17 KiB
TypeScript
405 lines
17 KiB
TypeScript
// ============================================
|
|
// AgentList.tsx - 智能体列表页面组件
|
|
// ============================================
|
|
// 功能描述:展示系统中所有智能体的列表,支持搜索、筛选、分页等功能
|
|
// 主要特性:
|
|
// - 智能体卡片展示(名称、描述、模型类型、状态等)
|
|
// - 支持按类型和状态筛选
|
|
// - 支持搜索智能体名称
|
|
// - 分页功能
|
|
// - 暗黑/亮色主题切换
|
|
// - 查看、编辑、测试智能体操作
|
|
// ============================================
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
// 导入图标组件,用于界面中的各种图标显示
|
|
import { Plus, Search, Eye, Edit, Play, Pause, Trash2, Bot, ChevronRight, GitBranch } from 'lucide-react';
|
|
// 导入主题上下文,用于获取当前主题(暗黑/亮色)
|
|
import { useTheme } from '../contexts/ThemeContext';
|
|
// 导入页面类型定义,用于页面导航
|
|
import { PageType } from '../components/Sidebar';
|
|
// 导入请求服务,用于与后端 API 交互
|
|
import { requestCrudService } from '@/service/request-axios-service';
|
|
// 导入 TDesign 组件库的加载和分页组件
|
|
import { Loading, Pagination } from 'tdesign-react';
|
|
|
|
/**
|
|
* AgentItem - 智能体数据项接口定义
|
|
* 描述单个智能体的数据结构
|
|
*/
|
|
interface AgentItem {
|
|
avatarUrl: string; // 智能体头像 URL
|
|
createdAt: string; // 创建时间
|
|
description: string; // 智能体描述
|
|
id: string; // 智能体唯一标识符
|
|
modelName: string; // 使用的模型名称
|
|
name: string; // 智能体名称
|
|
systemPrompt: string; // 系统提示词
|
|
userId: string; // 创建者用户 ID
|
|
isDraft: boolean; // 是否为草稿状态
|
|
}
|
|
|
|
/**
|
|
* AgentListProps - AgentList 组件属性接口
|
|
* 定义组件接收的外部属性
|
|
*/
|
|
interface AgentListProps {
|
|
onViewDetail: (id: string) => void; // 查看智能体详情的回调函数
|
|
onNavigate: (page: PageType) => void; // 页面导航的回调函数
|
|
}
|
|
|
|
// 定义 API 基础 URL,从环境变量中获取,如果不存在则为空字符串
|
|
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
|
// 创建请求服务实例,用于后续 API 调用
|
|
const service = requestCrudService;
|
|
|
|
/**
|
|
* AgentList - 智能体列表组件
|
|
* 主组件函数,负责渲染智能体列表页面
|
|
* @param onViewDetail - 查看详情的回调函数
|
|
* @param onNavigate - 页面导航的回调函数
|
|
*/
|
|
const AgentList: React.FC<AgentListProps> = ({ onViewDetail, onNavigate }) => {
|
|
// 获取当前主题配置
|
|
const { theme } = useTheme();
|
|
// 判断是否为暗黑模式
|
|
const isDark = theme === 'dark';
|
|
|
|
// 智能体列表数据状态,存储所有智能体信息
|
|
const [agents, setAgents] = useState<AgentItem[]>([]);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
// 加载状态,用于显示加载动画
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
// 当前页码,默认为第 1 页
|
|
const [pageNum, setPageNum] = useState<number>(1);
|
|
// 每页显示数量,默认为 10 条
|
|
const [pageSize, setPageSize] = useState<number>(10); // 每页显示 10 条
|
|
// 数据总数,用于分页组件显示
|
|
const [total, setTotal] = useState<number>(0);
|
|
// 使用 ref 存储分页状态,避免组件重新渲染时丢失
|
|
const state = useRef({
|
|
pageNum: 1,
|
|
pageSize: 10
|
|
});
|
|
|
|
// 回车处理
|
|
const handleKeyDown = async (e) => {
|
|
if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") {
|
|
e.preventDefault();
|
|
} else if (e.keyCode === 13) {
|
|
e.preventDefault();
|
|
await agentListSearch();
|
|
} else if (e.key === "/") {
|
|
e.preventDefault();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 根据智能体类型返回对应的标签颜色样式
|
|
* 根据当前主题(暗黑/亮色)返回不同的颜色配置
|
|
* @param category - 智能体类型(如 GLM-5、Qwen3.5-27B、叙事生成等)
|
|
* @returns 返回对应的 CSS 类名,用于标签背景色和文字颜色
|
|
*/
|
|
const getCategoryColor = (category: string) => {
|
|
// 暗黑模式下的颜色配置
|
|
if (isDark) {
|
|
switch (category) {
|
|
case 'GLM-5':
|
|
return 'bg-blue-500/20 text-blue-400'; // 蓝色背景 + 蓝色文字
|
|
case 'Qwen3.5-27B':
|
|
return 'bg-cyan-500/20 text-cyan-400'; // 青色背景 + 青色文字
|
|
case 'Qwen3-32B':
|
|
return 'bg-purple-500/20 text-purple-400'; // 紫色背景 + 紫色文字
|
|
case '叙事生成':
|
|
return 'bg-pink-500/20 text-pink-400'; // 粉色背景 + 粉色文字
|
|
case '逻辑链构建':
|
|
return 'bg-amber-500/20 text-amber-400'; // 琥珀色背景 + 琥珀色文字
|
|
case '风险评估':
|
|
return 'bg-red-500/20 text-red-400'; // 红色背景 + 红色文字
|
|
case '方案生成':
|
|
return 'bg-green-500/20 text-green-400'; // 绿色背景 + 绿色文字
|
|
default:
|
|
return 'bg-slate-500/20 text-slate-400'; // 默认灰色
|
|
}
|
|
} else {
|
|
// 亮色模式下的颜色配置,统一使用红色系
|
|
switch (category) {
|
|
case 'GLM-5':
|
|
return 'bg-red-100 text-red-600';
|
|
case 'Qwen3.5-27B':
|
|
return 'bg-red-100 text-red-600';
|
|
case 'Qwen3-32B':
|
|
return 'bg-red-100 text-red-600';
|
|
case '叙事生成':
|
|
return 'bg-red-100 text-red-600';
|
|
case '逻辑链构建':
|
|
return 'bg-red-100 text-red-600';
|
|
case '风险评估':
|
|
return 'bg-red-100 text-red-600';
|
|
case '方案生成':
|
|
return 'bg-red-100 text-red-600';
|
|
default:
|
|
return 'bg-gray-100 text-gray-600'; // 默认灰色
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 根据智能体类型返回对应的图标颜色样式
|
|
* 用于智能体卡片左侧图标的背景色和图标颜色
|
|
* @param type - 智能体类型
|
|
* @returns 返回包含背景色(bg)和图标颜色(icon)的对象
|
|
*/
|
|
const getTypeIconColor = (type: string) => {
|
|
// 暗黑模式下的图标颜色配置
|
|
if (isDark) {
|
|
switch (type) {
|
|
case 'GLM-5':
|
|
return { bg: 'bg-blue-500/20', icon: 'text-blue-400' }; // 蓝色系
|
|
case 'Qwen3.5-27B':
|
|
return { bg: 'bg-cyan-500/20', icon: 'text-cyan-400' }; // 青色系
|
|
case 'Qwen3-32B':
|
|
return { bg: 'bg-pink-500/20', icon: 'text-pink-400' }; // 粉色系
|
|
case '校验类':
|
|
return { bg: 'bg-amber-500/20', icon: 'text-amber-400' }; // 琥珀色系
|
|
case '成稿类':
|
|
return { bg: 'bg-green-500/20', icon: 'text-green-400' }; // 绿色系
|
|
default:
|
|
return { bg: 'bg-slate-500/20', icon: 'text-slate-400' }; // 默认灰色
|
|
}
|
|
} else {
|
|
// 亮色模式下统一使用红色系
|
|
return { bg: 'bg-red-100', icon: 'text-red-500' };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取智能体列表数据
|
|
* 调用后端 API 获取智能体列表,并更新组件状态
|
|
* 请求参数包括页码、每页数量和搜索名称
|
|
*/
|
|
const agentListSearch = async () => {
|
|
// 构建请求参数对象
|
|
const parmy = {
|
|
pageNum: state.current.pageNum, // 当前页码
|
|
pageSize: state.current.pageSize, // 每页显示数量
|
|
name: searchQuery // 搜索名称(当前为空,可扩展)
|
|
}
|
|
// 开始加载,显示加载动画
|
|
setLoading(true);
|
|
// 调用后端 API 获取智能体列表数据
|
|
const res = await requestCrudService.post(`${zncmUrl}/agent/expertAgents/list`, parmy);
|
|
// 加载完成,隐藏加载动画
|
|
setLoading(false);
|
|
// 打印响应数据到控制台,用于调试
|
|
console.log('智能体列表', res);
|
|
// 解析响应数据
|
|
const result = res.data;
|
|
// 更新数据总数,用于分页
|
|
setTotal(result.total);
|
|
// 更新智能体列表数据,如果无数据则设置为空数组
|
|
setAgents(result.records || []);
|
|
|
|
}
|
|
|
|
/**
|
|
* 页码变化时的回调函数
|
|
* 当用户切换页码时触发,更新页码并重新获取数据
|
|
* @param value - 新的页码值
|
|
*/
|
|
const pageIndexChange = async (value: number) => {
|
|
// 更新 ref 中存储的页码值
|
|
state.current.pageNum = value;
|
|
// 更新页码状态,触发组件重新渲染
|
|
setPageNum(value);
|
|
// 重新获取智能体列表数据
|
|
await agentListSearch();
|
|
}
|
|
|
|
/**
|
|
* 每页显示数量变化时的回调函数
|
|
* 当用户切换每页显示条数时触发,更新每页数量并重新获取数据
|
|
* @param value - 新的每页显示数量
|
|
*/
|
|
const pageSizeChange = async (value: number) => {
|
|
// 更新 ref 中存储的每页显示数量
|
|
state.current.pageSize = value;
|
|
// 更新每页显示数量状态,触发组件重新渲染
|
|
setPageSize(value);
|
|
// 重新获取智能体列表数据
|
|
await agentListSearch();
|
|
}
|
|
|
|
/**
|
|
* 组件挂载时的副作用
|
|
* 组件首次渲染时自动调用一次获取智能体列表
|
|
* 依赖数组为空,表示只在组件挂载时执行一次
|
|
*/
|
|
useEffect(() => {
|
|
agentListSearch();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* 页面标题区 */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>智能体配置管理</h1>
|
|
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>统一管理系统中的智能体配置</p>
|
|
</div>
|
|
<button className={`flex items-center gap-2 px-4 py-2.5 text-white rounded-lg transition-colors shadow-md ${isDark
|
|
? 'bg-blue-600 hover:bg-blue-500'
|
|
: 'bg-red-600 hover:bg-red-500'
|
|
}`}>
|
|
<Plus className="w-4 h-4" />
|
|
<span className="text-sm font-medium">新建智能体</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* 搜索和筛选区 */}
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative flex-1 max-w-md">
|
|
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${isDark ? 'text-slate-500' : 'text-red-400'}`} />
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onKeyDown={(e) => handleKeyDown(e)}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="搜索智能体名称..."
|
|
className={`w-full pl-10 pr-4 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${isDark
|
|
? 'bg-slate-800/50 border-blue-800/30 text-white placeholder-slate-500'
|
|
: 'bg-white border-red-200 text-gray-900 placeholder-red-300'
|
|
}`}
|
|
/>
|
|
</div>
|
|
<button className={`px-4 py-2.5 border rounded-lg text-sm transition-colors ${isDark
|
|
? 'bg-slate-800/50 border-blue-800/30 text-slate-300 hover:bg-slate-700/50'
|
|
: 'bg-white border-red-200 text-gray-700 hover:bg-red-50'
|
|
}`}>
|
|
全部类型
|
|
</button>
|
|
<button className={`px-4 py-2.5 border rounded-lg text-sm transition-colors ${isDark
|
|
? 'bg-slate-800/50 border-blue-800/30 text-slate-300 hover:bg-slate-700/50'
|
|
: 'bg-white border-red-200 text-gray-700 hover:bg-red-50'
|
|
}`}>
|
|
全部状态
|
|
</button>
|
|
</div>
|
|
|
|
{/* 分类标签 */}
|
|
<div className="flex items-center gap-3">
|
|
<span className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>按类型筛选:</span>
|
|
{['编排类', '分析类', '叙事类', '校验类', '成稿类'].map((type) => (
|
|
<button
|
|
key={type}
|
|
className={`px-3 py-1.5 border rounded-full text-sm transition-colors ${isDark
|
|
? 'bg-slate-800/50 border-blue-800/30 text-slate-300 hover:bg-blue-600/20 hover:border-blue-500/50 hover:text-blue-400'
|
|
: 'bg-white border-red-200 text-gray-700 hover:bg-red-50 hover:border-red-300 hover:text-red-600'
|
|
}`}
|
|
>
|
|
{type}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* 智能体列表 */}
|
|
<Loading loading={loading}>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
{agents.map((agent) => {
|
|
const typeColors = getTypeIconColor(agent.modelName);
|
|
return (
|
|
<div
|
|
key={agent.id}
|
|
className={`rounded-xl p-5 transition-all duration-300 group cursor-pointer border theme-card ${isDark
|
|
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30 hover:border-blue-500/50'
|
|
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
|
|
}`}
|
|
onClick={() => {
|
|
onViewDetail(agent.id);
|
|
onNavigate('agent-detail');
|
|
}}
|
|
>
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${typeColors.bg}`}>
|
|
<Bot className={`w-6 h-6 ${typeColors.icon}`} />
|
|
</div>
|
|
<div>
|
|
<h3 className={`font-semibold text-lg group-hover:text-red-500 transition-colors ${isDark ? 'text-white' : 'text-gray-900'}`}>{agent.name}</h3>
|
|
<p className={`text-sm ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{agent.description}</p>
|
|
</div>
|
|
</div>
|
|
<span
|
|
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${agent.isDraft
|
|
? isDark
|
|
? 'bg-green-500/20 text-green-400'
|
|
: 'bg-green-100 text-green-600'
|
|
: isDark
|
|
? 'bg-yellow-500/20 text-yellow-400'
|
|
: 'bg-yellow-100 text-yellow-600'
|
|
}`}
|
|
>
|
|
<span className={`w-1.5 h-1.5 rounded-full ${agent.isDraft
|
|
? isDark ? 'bg-green-400' : 'bg-green-500'
|
|
: isDark ? 'bg-yellow-400' : 'bg-yellow-500'
|
|
}`} />
|
|
{agent.isDraft ? '启用' : '暂停'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4 mb-4">
|
|
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${getCategoryColor(agent.modelName)}`}>
|
|
{agent.modelName}
|
|
</span>
|
|
{/* <span className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{agent.description}</span> */}
|
|
</div>
|
|
|
|
<div className={`flex items-center justify-between pt-4 border-t ${isDark ? 'border-blue-800/30' : 'border-red-100'
|
|
}`}>
|
|
<div className="flex items-center gap-6">
|
|
<div className="overflow-hidden text-ellipsis line-clamp-3">
|
|
{/* <p className={`text-xs mb-1 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>输入字段</p> */}
|
|
<p className={`font-medium ${isDark ? 'text-cyan-400' : 'text-red-600'}`}>{agent.systemPrompt} 个</p>
|
|
</div>
|
|
{/* <div>
|
|
<p className={`text-xs mb-1 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>输出字段</p>
|
|
<p className={`font-medium ${isDark ? 'text-green-400' : 'text-red-600'}`}>{agent.outputs} 个</p>
|
|
</div> */}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button className={`p-2 rounded-lg transition-colors group/btn ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
|
|
}`} title="查看">
|
|
<Eye className={`w-4 h-4 ${isDark ? 'text-slate-400 group-hover/btn:text-white' : 'text-red-400 group-hover/btn:text-red-600'}`} />
|
|
</button>
|
|
<button className={`p-2 rounded-lg transition-colors group/btn ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
|
|
}`} title="编辑">
|
|
<Edit className={`w-4 h-4 ${isDark ? 'text-slate-400 group-hover/btn:text-white' : 'text-red-400 group-hover/btn:text-red-600'}`} />
|
|
</button>
|
|
<button className={`p-2 rounded-lg transition-colors group/btn ${isDark ? 'hover:bg-green-500/20' : 'hover:bg-green-50'
|
|
}`} title="测试">
|
|
<Play className={`w-4 h-4 ${isDark ? 'text-slate-400 group-hover/btn:text-green-400' : 'text-red-400 group-hover/btn:text-green-600'}`} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</Loading>
|
|
|
|
{/* 分页 */}
|
|
<div className={`flex items-center justify-between pt-4 ${isDark ? '' : 'border-t border-red-100'}`}>
|
|
<Pagination
|
|
total={total}
|
|
current={pageNum}
|
|
pageSize={pageSize}
|
|
onCurrentChange={(page) => pageIndexChange(Number(page))}
|
|
onPageSizeChange={(size) => pageSizeChange(Number(size))} // 每页数量变化时的回调
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AgentList;
|