test1 / app /agents /create /KnowledgeBase.tsx
daios007's picture
init
9eb1c55
'use client';
import { useState } from 'react';
interface KnowledgeBaseProps {
data: any;
onChange: (data: any) => void;
}
export default function KnowledgeBase({ data, onChange }: KnowledgeBaseProps) {
const [uploadedFiles, setUploadedFiles] = useState([
{ id: 1, name: '产品手册.pdf', size: '2.4 MB', status: 'uploaded', type: 'pdf', chunks: 45, lastModified: '2024-01-15' },
{ id: 2, name: '常见问题FAQ.docx', size: '856 KB', status: 'uploaded', type: 'docx', chunks: 23, lastModified: '2024-01-14' },
{ id: 3, name: '技术文档.md', size: '1.2 MB', status: 'uploaded', type: 'md', chunks: 67, lastModified: '2024-01-13' },
]);
const [webSources, setWebSources] = useState([
{ id: 1, url: 'https://docs.example.com', title: '官方文档', status: 'crawled', pages: 15, lastUpdate: '2024-01-15' },
{ id: 2, url: 'https://help.example.com', title: '帮助中心', status: 'crawling', pages: 8, lastUpdate: '2024-01-14' }
]);
const [manualContent, setManualContent] = useState('');
const [newUrl, setNewUrl] = useState('');
const [activeTab, setActiveTab] = useState('files');
const [processingSettings, setProcessingSettings] = useState({
autoChunk: true,
smartDedup: true,
generateSummary: false,
chunkSize: 1000,
overlap: 200
});
const knowledgeBases = [
{ id: 'kb-001', name: '通用知识库', description: '包含基础常识和通用信息', items: 1240, isDefault: true },
{ id: 'kb-002', name: '技术文档库', description: '技术相关的文档和资料', items: 567, isDefault: false },
{ id: 'kb-003', name: '产品说明库', description: '产品相关的说明和手册', items: 892, isDefault: false },
{ id: 'kb-004', name: '客服问答库', description: '客服常见问题和解答', items: 345, isDefault: false }
];
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState(['kb-001']);
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
const newFiles = files.map((file, index) => ({
id: uploadedFiles.length + index + 1,
name: file.name,
size: `${(file.size / 1024 / 1024).toFixed(1)} MB`,
status: 'uploading',
type: file.name.split('.').pop() || 'unknown',
chunks: 0,
lastModified: new Date().toISOString().split('T')[0]
}));
setUploadedFiles([...uploadedFiles, ...newFiles]);
setTimeout(() => {
setUploadedFiles(prev => prev.map(file =>
newFiles.some(nf => nf.id === file.id)
? { ...file, status: 'uploaded', chunks: Math.floor(Math.random() * 50) + 10 }
: file
));
}, 2000);
};
const removeFile = (fileId: number) => {
setUploadedFiles(prev => prev.filter(file => file.id !== fileId));
};
const addWebSource = () => {
if (newUrl) {
const newSource = {
id: webSources.length + 1,
url: newUrl,
title: '正在获取标题...',
status: 'crawling',
pages: 0,
lastUpdate: new Date().toISOString().split('T')[0]
};
setWebSources([...webSources, newSource]);
setNewUrl('');
setTimeout(() => {
setWebSources(prev => prev.map(source =>
source.id === newSource.id
? { ...source, status: 'crawled', title: `${newUrl.split('/')[2]} - 文档`, pages: Math.floor(Math.random() * 20) + 5 }
: source
));
}, 3000);
}
};
const removeWebSource = (sourceId: number) => {
setWebSources(prev => prev.filter(source => source.id !== sourceId));
};
const toggleKnowledgeBase = (kbId: string) => {
setSelectedKnowledgeBases(prev =>
prev.includes(kbId)
? prev.filter(id => id !== kbId)
: [...prev, kbId]
);
};
const addManualContent = () => {
if (manualContent.trim()) {
const newFile = {
id: uploadedFiles.length + 1,
name: `手动内容_${new Date().toLocaleTimeString()}`,
size: `${Math.ceil(manualContent.length / 1024)} KB`,
status: 'uploaded',
type: 'text',
chunks: Math.ceil(manualContent.length / processingSettings.chunkSize),
lastModified: new Date().toISOString().split('T')[0]
};
setUploadedFiles([...uploadedFiles, newFile]);
setManualContent('');
}
};
return (
<div className="space-y-8">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">知识库配置</h3>
<p className="text-gray-600 mb-6">为Agent配置知识来源,提升回答的准确性和专业性</p>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 space-y-6">
{/* 选择现有知识库 */}
<div className="bg-white border border-gray-200 rounded-lg p-6">
<h4 className="font-medium text-gray-900 mb-4">选择现有知识库</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{knowledgeBases.map((kb) => (
<div
key={kb.id}
onClick={() => toggleKnowledgeBase(kb.id)}
className={`p-4 border rounded-lg cursor-pointer transition-all ${
selectedKnowledgeBases.includes(kb.id)
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2">
<h5 className="font-medium text-gray-900">{kb.name}</h5>
{kb.isDefault && (
<span className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">默认</span>
)}
</div>
<p className="text-sm text-gray-600 mt-1">{kb.description}</p>
<p className="text-xs text-gray-500 mt-2">{kb.items.toLocaleString()} 条记录</p>
</div>
<div className={`w-5 h-5 rounded border-2 flex items-center justify-center ${
selectedKnowledgeBases.includes(kb.id)
? 'border-blue-500 bg-blue-500'
: 'border-gray-300'
}`}>
{selectedKnowledgeBases.includes(kb.id) && (
<i className="ri-check-line text-white text-xs"></i>
)}
</div>
</div>
</div>
))}
</div>
</div>
{/* 添加新内容 */}
<div className="bg-white border border-gray-200 rounded-lg">
<div className="border-b border-gray-200">
<div className="flex space-x-0">
{[
{ key: 'files', label: '文件上传', icon: 'ri-file-line' },
{ key: 'web', label: '网页抓取', icon: 'ri-global-line' },
{ key: 'manual', label: '手动输入', icon: 'ri-edit-line' }
].map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`px-6 py-4 text-sm font-medium border-b-2 transition-colors ${
activeTab === tab.key
? 'border-blue-500 text-blue-600 bg-blue-50'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<div className="w-4 h-4 flex items-center justify-center inline-block mr-2">
<i className={tab.icon}></i>
</div>
{tab.label}
</button>
))}
</div>
</div>
<div className="p-6">
{activeTab === 'files' && (
<div className="space-y-4">
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
<div className="w-12 h-12 bg-gray-100 rounded-lg flex items-center justify-center mx-auto mb-4">
<i className="ri-upload-cloud-line text-xl text-gray-400"></i>
</div>
<p className="text-gray-600 mb-2">拖拽文件到此处,或者</p>
<label className="inline-block px-4 py-2 bg-blue-600 text-white rounded-lg cursor-pointer hover:bg-blue-700 transition-colors text-sm">
选择文件
<input
type="file"
multiple
accept=".pdf,.doc,.docx,.txt,.md,.csv,.xlsx"
onChange={handleFileUpload}
className="hidden"
/>
</label>
<p className="text-xs text-gray-500 mt-2">支持 PDF, DOC, DOCX, TXT, MD, CSV, XLSX 格式,最大 50MB</p>
</div>
</div>
)}
{activeTab === 'web' && (
<div className="space-y-4">
<div className="flex space-x-2">
<input
type="url"
placeholder="输入要抓取的网址(如:https://docs.example.com)"
value={newUrl}
onChange={(e) => setNewUrl(e.target.value)}
className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
/>
<button
onClick={addWebSource}
className="px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors cursor-pointer whitespace-nowrap text-sm"
>
开始抓取
</button>
</div>
<div className="text-xs text-gray-500">
支持抓取文档站点、博客文章、产品页面等,自动提取文本内容
</div>
</div>
)}
{activeTab === 'manual' && (
<div className="space-y-4">
<textarea
placeholder="直接输入要添加到知识库的内容...&#10;&#10;例如:&#10;• 产品使用说明&#10;• 常见问题解答&#10;• 操作步骤指南&#10;• 专业知识条目"
rows={8}
maxLength={500}
value={manualContent}
onChange={(e) => setManualContent(e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none"
/>
<div className="flex items-center justify-between">
<div className="text-xs text-gray-500">{manualContent.length}/500 字符</div>
<button
onClick={addManualContent}
disabled={!manualContent.trim()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 transition-colors cursor-pointer whitespace-nowrap text-sm"
>
添加内容
</button>
</div>
</div>
)}
</div>
</div>
</div>
<div className="space-y-6">
{/* 已添加内容统计 */}
<div className="bg-white border border-gray-200 rounded-lg p-4">
<h4 className="font-medium text-gray-900 mb-3">内容统计</h4>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">文档数量</span>
<span className="font-medium text-gray-900">{uploadedFiles.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">网页来源</span>
<span className="font-medium text-gray-900">{webSources.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">总分块数</span>
<span className="font-medium text-gray-900">{uploadedFiles.reduce((sum, file) => sum + file.chunks, 0)}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">知识库</span>
<span className="font-medium text-gray-900">{selectedKnowledgeBases.length} 个</span>
</div>
</div>
</div>
{/* 处理设置 */}
<div className="bg-white border border-gray-200 rounded-lg p-4">
<h4 className="font-medium text-gray-900 mb-3">处理设置</h4>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-700">自动分块处理</span>
<div
onClick={() => setProcessingSettings(prev => ({...prev, autoChunk: !prev.autoChunk}))}
className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
processingSettings.autoChunk ? 'bg-blue-600' : 'bg-gray-300'
}`}
>
<div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
processingSettings.autoChunk ? 'right-1' : 'left-1'
}`}></div>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-700">智能去重</span>
<div
onClick={() => setProcessingSettings(prev => ({...prev, smartDedup: !prev.smartDedup}))}
className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
processingSettings.smartDedup ? 'bg-blue-600' : 'bg-gray-300'
}`}
>
<div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
processingSettings.smartDedup ? 'right-1' : 'left-1'
}`}></div>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-700">生成摘要</span>
<div
onClick={() => setProcessingSettings(prev => ({...prev, generateSummary: !prev.generateSummary}))}
className={`w-10 h-6 rounded-full relative cursor-pointer transition-colors ${
processingSettings.generateSummary ? 'bg-blue-600' : 'bg-gray-300'
}`}
>
<div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
processingSettings.generateSummary ? 'right-1' : 'left-1'
}`}></div>
</div>
</div>
<div className="pt-2 border-t border-gray-200">
<div className="mb-3">
<label className="block text-sm text-gray-700 mb-1">
分块大小: {processingSettings.chunkSize} 字符
</label>
<input
type="range"
min="500"
max="2000"
step="100"
value={processingSettings.chunkSize}
onChange={(e) => setProcessingSettings(prev => ({...prev, chunkSize: parseInt(e.target.value)}))}
className="w-full h-1 bg-gray-200 rounded-lg appearance-none cursor-pointer"
/>
</div>
<div>
<label className="block text-sm text-gray-700 mb-1">
重叠字符: {processingSettings.overlap}
</label>
<input
type="range"
min="0"
max="500"
step="50"
value={processingSettings.overlap}
onChange={(e) => setProcessingSettings(prev => ({...prev, overlap: parseInt(e.target.value)}))}
className="w-full h-1 bg-gray-200 rounded-lg appearance-none cursor-pointer"
/>
</div>
</div>
</div>
</div>
{/* 使用提示 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-start space-x-3">
<div className="w-5 h-5 flex items-center justify-center mt-0.5">
<i className="ri-lightbulb-line text-blue-600"></i>
</div>
<div>
<h4 className="font-medium text-blue-900 mb-2">优化建议</h4>
<ul className="text-sm text-blue-800 space-y-1">
<li>• 上传结构化的文档获得更好效果</li>
<li>• 定期更新知识库内容</li>
<li>• 避免重复和冗余信息</li>
<li>• 使用标准的文档格式</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{/* 已上传文件列表 */}
{uploadedFiles.length > 0 && (
<div className="mt-8">
<h4 className="font-medium text-gray-900 mb-4">已添加的文件</h4>
<div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
<div className="divide-y divide-gray-200">
{uploadedFiles.map((file) => (
<div key={file.id} className="p-4 hover:bg-gray-50 transition-colors">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<i className={`${
file.type === 'pdf' ? 'ri-file-pdf-line text-red-600' :
file.type === 'docx' ? 'ri-file-word-line text-blue-600' :
file.type === 'md' ? 'ri-markdown-line text-purple-600' :
'ri-file-text-line text-gray-600'
}`}></i>
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<p className="font-medium text-gray-900">{file.name}</p>
{file.status === 'uploading' ? (
<span className="px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full">处理中</span>
) : (
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">已完成</span>
)}
</div>
<div className="flex items-center space-x-4 mt-1 text-sm text-gray-500">
<span>{file.size}</span>
<span>{file.chunks} 个分块</span>
<span>修改于 {file.lastModified}</span>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
{file.status === 'uploading' ? (
<div className="w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
) : (
<button className="p-1 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
<div className="w-4 h-4 flex items-center justify-center">
<i className="ri-eye-line"></i>
</div>
</button>
)}
<button
onClick={() => removeFile(file.id)}
className="p-1 text-gray-400 hover:text-red-600 transition-colors cursor-pointer"
>
<div className="w-4 h-4 flex items-center justify-center">
<i className="ri-delete-bin-line"></i>
</div>
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
{/* 网页来源列表 */}
{webSources.length > 0 && (
<div className="mt-8">
<h4 className="font-medium text-gray-900 mb-4">网页来源</h4>
<div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
<div className="divide-y divide-gray-200">
{webSources.map((source) => (
<div key={source.id} className="p-4 hover:bg-gray-50 transition-colors">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
<i className="ri-global-line text-green-600"></i>
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<p className="font-medium text-gray-900">{source.title}</p>
{source.status === 'crawling' ? (
<span className="px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full">抓取中</span>
) : (
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">已完成</span>
)}
</div>
<div className="flex items-center space-x-4 mt-1 text-sm text-gray-500">
<span>{source.url}</span>
<span>{source.pages} 个页面</span>
<span>更新于 {source.lastUpdate}</span>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
{source.status === 'crawling' ? (
<div className="w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
) : (
<button className="p-1 text-gray-400 hover:text-blue-600 transition-colors cursor-pointer">
<div className="w-4 h-4 flex items-center justify-center">
<i className="ri-refresh-line"></i>
</div>
</button>
)}
<button
onClick={() => removeWebSource(source.id)}
className="p-1 text-gray-400 hover:text-red-600 transition-colors cursor-pointer"
>
<div className="w-4 h-4 flex items-center justify-center">
<i className="ri-delete-bin-line"></i>
</div>
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
</div>
);
}