258 lines
11 KiB
TypeScript
258 lines
11 KiB
TypeScript
import { ApiOutlined, DatabaseOutlined, DeleteOutlined, EditOutlined, PlusOutlined, ToolOutlined } from '@ant-design/icons';
|
||
import { Button, Card, Collapse, Input, List, Popconfirm, Space, Tag } from 'antd';
|
||
import { Agent } from '../../../../api';
|
||
import { STATUS_TAG } from '../../constants';
|
||
|
||
interface KnowledgeSettingsPanelProps {
|
||
agent: Agent | null;
|
||
beforeUploadKnowledge: (file: any) => Promise<boolean>;
|
||
onDeleteKnowledge: (fileId: string) => Promise<void>;
|
||
onCreateSkill: () => void;
|
||
onEditSkill: (skillId: string) => void;
|
||
onDeleteSkill: (skillId: string) => Promise<void>;
|
||
onCreateExternalTool: () => void;
|
||
onEditExternalTool: (pluginId: string) => void;
|
||
onDeleteExternalTool: (pluginId: string) => Promise<void>;
|
||
}
|
||
|
||
export default function KnowledgeSettingsPanel({
|
||
agent,
|
||
beforeUploadKnowledge,
|
||
onDeleteKnowledge,
|
||
onCreateSkill,
|
||
onEditSkill,
|
||
onDeleteSkill,
|
||
onCreateExternalTool,
|
||
onEditExternalTool,
|
||
onDeleteExternalTool,
|
||
}: KnowledgeSettingsPanelProps) {
|
||
const skills = agent?.skills ?? [];
|
||
const plugins = agent?.plugins ?? [];
|
||
|
||
return (
|
||
<Collapse
|
||
ghost
|
||
expandIconPosition="end"
|
||
className="monica-collapse"
|
||
items={[
|
||
{
|
||
key: 'knowledge',
|
||
label: (
|
||
<div className="agent-editor-collapse-label">
|
||
<DatabaseOutlined className="agent-editor-label-icon" />
|
||
知识库 ({agent?.knowledge?.length ?? 0})
|
||
</div>
|
||
),
|
||
children: (
|
||
<div className="px-1">
|
||
<div className="flex justify-between items-center mb-4">
|
||
<span className="text-xs text-gray-500">上传文档以增强 AI 知识</span>
|
||
<input
|
||
type="file"
|
||
multiple
|
||
style={{ display: 'none' }}
|
||
id="knowledge-upload"
|
||
onChange={async (event) => {
|
||
const files = event.target.files;
|
||
if (!files) return;
|
||
for (let index = 0; index < files.length; index++) {
|
||
await beforeUploadKnowledge(files[index]);
|
||
}
|
||
event.target.value = '';
|
||
}}
|
||
/>
|
||
<Button
|
||
type="primary"
|
||
size="small"
|
||
ghost
|
||
icon={<DatabaseOutlined />}
|
||
className="agent-editor-small-action"
|
||
onClick={() => document.getElementById('knowledge-upload')?.click()}
|
||
>
|
||
添加
|
||
</Button>
|
||
</div>
|
||
<List
|
||
size="small"
|
||
dataSource={agent?.knowledge ?? []}
|
||
renderItem={(item) => (
|
||
<List.Item
|
||
className="bg-white mb-2 rounded-lg border border-gray-100 p-2"
|
||
actions={[
|
||
<Popconfirm key="del" title="确认删除?" onConfirm={() => onDeleteKnowledge(item.id)}>
|
||
<Button type="text" danger size="small" className="agent-editor-danger-action">
|
||
删除
|
||
</Button>
|
||
</Popconfirm>,
|
||
]}
|
||
>
|
||
<div className="flex flex-col gap-1 overflow-hidden">
|
||
<span className="text-sm font-medium truncate">{item.originalName}</span>
|
||
<span className="text-[10px] text-gray-400">
|
||
{item.size ? `${(item.size / 1024).toFixed(1)} KB` : ''} ·{' '}
|
||
{item.status === 'indexing' ? (
|
||
<Tag color="processing" className="m-0 text-[10px] px-1">
|
||
<span className="agent-editor-indexing-label">索引中…</span>
|
||
</Tag>
|
||
) : (
|
||
<Tag
|
||
color={STATUS_TAG[item.status as keyof typeof STATUS_TAG]?.color || 'default'}
|
||
className="m-0 text-[10px] px-1"
|
||
>
|
||
{STATUS_TAG[item.status as keyof typeof STATUS_TAG]?.text || item.status || '未知'}
|
||
</Tag>
|
||
)}
|
||
</span>
|
||
</div>
|
||
</List.Item>
|
||
)}
|
||
/>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
key: 'skills',
|
||
label: (
|
||
<div className="agent-editor-collapse-label">
|
||
<ToolOutlined className="agent-editor-label-icon" />
|
||
技能 & 工具 ({skills.length + plugins.length})
|
||
</div>
|
||
),
|
||
children: (
|
||
<div className="px-1">
|
||
<div className="agent-editor-skill-actions">
|
||
<span className="text-xs text-gray-500">Markdown 技能与外部 API 工具集分别管理</span>
|
||
<Space wrap>
|
||
<Button size="small" icon={<PlusOutlined />} onClick={onCreateSkill}>
|
||
新建 Skill
|
||
</Button>
|
||
<Button type="primary" ghost size="small" icon={<ApiOutlined />} onClick={onCreateExternalTool}>
|
||
添加外部工具
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
|
||
<div className="agent-editor-tool-section">
|
||
<div className="agent-editor-tool-section-title">
|
||
<span>Skills</span>
|
||
<Tag>{skills.length}</Tag>
|
||
</div>
|
||
<List
|
||
size="small"
|
||
locale={{ emptyText: '暂无 Markdown Skill' }}
|
||
dataSource={skills}
|
||
renderItem={(item) => (
|
||
<List.Item
|
||
className="agent-editor-skill-item"
|
||
actions={[
|
||
<Button key="edit" type="text" size="small" icon={<EditOutlined />} onClick={() => onEditSkill(item.id)}>
|
||
编辑
|
||
</Button>,
|
||
<Popconfirm key="delete" title="确认删除该 Skill?" onConfirm={() => onDeleteSkill(item.id)}>
|
||
<Button type="text" danger size="small" icon={<DeleteOutlined />}>
|
||
删除
|
||
</Button>
|
||
</Popconfirm>,
|
||
]}
|
||
>
|
||
<List.Item.Meta
|
||
title={<span className="agent-editor-skill-name">{item.filename || item.name}</span>}
|
||
description={
|
||
<div>
|
||
<div>{item.description || '暂无描述'}</div>
|
||
{item.filename && item.filename !== item.name && <div className="agent-editor-tool-help">{item.name}</div>}
|
||
</div>
|
||
}
|
||
/>
|
||
<Space size={6} wrap>
|
||
<Tag color="blue">{item.type}</Tag>
|
||
<Tag color={item.enabled ? 'success' : 'default'}>{item.enabled ? '已启用' : '未启用'}</Tag>
|
||
</Space>
|
||
</List.Item>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="agent-editor-tool-section">
|
||
<div className="agent-editor-tool-section-title">
|
||
<span>外部工具集</span>
|
||
<Tag>{plugins.length}</Tag>
|
||
</div>
|
||
{plugins.length === 0 ? (
|
||
<div className="agent-editor-tool-empty">暂无外部工具集</div>
|
||
) : (
|
||
<Space direction="vertical" size={10} className="agent-editor-tool-list">
|
||
{plugins.map((plugin) => (
|
||
<Card
|
||
key={plugin.id}
|
||
size="small"
|
||
className="agent-editor-plugin-card"
|
||
title={
|
||
<Space wrap>
|
||
<ApiOutlined className="agent-editor-label-icon" />
|
||
<span>{plugin.name}</span>
|
||
<Tag color={plugin.enabled ? 'success' : 'default'}>{plugin.enabled ? '已启用' : '未启用'}</Tag>
|
||
</Space>
|
||
}
|
||
extra={
|
||
<Space>
|
||
<Button type="text" size="small" icon={<EditOutlined />} onClick={() => onEditExternalTool(plugin.id)}>
|
||
编辑
|
||
</Button>
|
||
<Popconfirm title="确认删除该工具集及其全部 API?" onConfirm={() => onDeleteExternalTool(plugin.id)}>
|
||
<Button type="text" danger size="small" icon={<DeleteOutlined />}>
|
||
删除
|
||
</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
}
|
||
>
|
||
<div className="agent-editor-plugin-description">{plugin.description || '暂无描述'}</div>
|
||
<Space size={6} wrap className="agent-editor-plugin-meta">
|
||
<Tag>{plugin.authType || 'none'}</Tag>
|
||
<span>{plugin.baseUrl}</span>
|
||
<span>{plugin.apis?.length ?? 0} 个 API</span>
|
||
</Space>
|
||
<Collapse
|
||
ghost
|
||
size="small"
|
||
className="agent-editor-plugin-apis"
|
||
items={plugin.apis?.map((api) => ({
|
||
key: api.id || api.name,
|
||
label: (
|
||
<Space wrap>
|
||
<Tag color="geekblue">{api.method}</Tag>
|
||
<strong>{api.name}</strong>
|
||
<span className="agent-editor-tool-help">{api.path}</span>
|
||
</Space>
|
||
),
|
||
children: (
|
||
<div className="agent-editor-api-detail">
|
||
<div>{api.description || '暂无描述'}</div>
|
||
<div className="agent-editor-api-config-grid">
|
||
<div>
|
||
<strong>headers</strong>
|
||
<pre>{JSON.stringify(api.headers || {}, null, 2)}</pre>
|
||
</div>
|
||
<div>
|
||
<strong>parametersSchema</strong>
|
||
<pre>{JSON.stringify(api.parametersSchema || {}, null, 2)}</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
),
|
||
})) || []}
|
||
/>
|
||
</Card>
|
||
))}
|
||
</Space>
|
||
)}
|
||
</div>
|
||
</div>
|
||
),
|
||
},
|
||
]}
|
||
/>
|
||
);
|
||
}
|