Files
AIclinicalresearch/frontend-v2/src/modules/pkb/components/KnowledgeBaseList.tsx
HaHafeng 5a17d096a7 feat(pkb): Complete PKB module frontend migration with V3 design
Summary:
- Implement PKB Dashboard and Workspace pages based on V3 prototype
- Add single-layer header with integrated Tab navigation
- Implement 3 work modes: Full Text, Deep Read, Batch Processing
- Integrate Ant Design X Chat component for AI conversations
- Create BatchModeComplete with template selection and document processing
- Add compact work mode selector with dropdown design

Backend:
- Migrate PKB controllers and services to /modules/pkb structure
- Register v2 API routes at /api/v2/pkb/knowledge
- Maintain dual API routes for backward compatibility

Technical details:
- Use Zustand for state management
- Handle SSE streaming responses for AI chat
- Support document selection for Deep Read mode
- Implement batch processing with progress tracking

Known issues:
- Batch processing API integration pending
- Knowledge assets page navigation needs optimization

Status: Frontend functional, pending refinement
2026-01-06 22:15:42 +08:00

205 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { Card, Button, Empty, Tag, Popconfirm, Space, Typography } from 'antd';
import {
PlusOutlined,
FolderOutlined,
FileTextOutlined,
EditOutlined,
DeleteOutlined
} from '@ant-design/icons';
import type { KnowledgeBase } from '../../api/knowledgeBaseApi';
const { Title, Text, Paragraph } = Typography;
interface KnowledgeBaseListProps {
knowledgeBases: KnowledgeBase[];
loading: boolean;
onCreateClick: () => void;
onEditClick: (kb: KnowledgeBase) => void;
onDeleteClick: (kb: KnowledgeBase) => void;
onSelectClick: (kb: KnowledgeBase) => void;
}
const KnowledgeBaseList: React.FC<KnowledgeBaseListProps> = ({
knowledgeBases,
loading,
onCreateClick,
onEditClick,
onDeleteClick,
onSelectClick,
}) => {
const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const canCreateMore = knowledgeBases.length < 3;
return (
<div>
{/* 标题和创建按钮 */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 24
}}>
<div>
<Title level={4} style={{ margin: 0 }}></Title>
<Text type="secondary">
使 {knowledgeBases.length}/3
</Text>
</div>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={onCreateClick}
disabled={!canCreateMore || loading}
>
</Button>
</div>
{/* 配额提示 */}
{!canCreateMore && (
<div style={{ marginBottom: 16 }}>
<Tag color="warning">
3
</Tag>
</div>
)}
{/* 知识库列表 */}
{knowledgeBases.length === 0 ? (
<Card>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="还没有知识库,创建第一个吧!"
>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={onCreateClick}
>
</Button>
</Empty>
</Card>
) : (
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: 16
}}>
{knowledgeBases.map((kb) => (
<Card
key={kb.id}
hoverable
onClick={() => onSelectClick(kb)}
style={{ cursor: 'pointer' }}
actions={[
<Button
key="edit"
type="text"
icon={<EditOutlined />}
onClick={(e) => {
e.stopPropagation();
onEditClick(kb);
}}
>
</Button>,
<Popconfirm
key="delete"
title="确认删除?"
description={`删除知识库 "${kb.name}" 将同时删除其中的所有文档,此操作不可恢复。`}
onConfirm={(e) => {
e?.stopPropagation();
onDeleteClick(kb);
}}
okText="确认"
cancelText="取消"
okButtonProps={{ danger: true }}
>
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={(e) => e.stopPropagation()}
>
</Button>
</Popconfirm>,
]}
>
<Card.Meta
avatar={
<div style={{
width: 48,
height: 48,
background: '#1890ff20',
borderRadius: 8,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<FolderOutlined style={{ fontSize: 24, color: '#1890ff' }} />
</div>
}
title={
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<Text strong ellipsis style={{ flex: 1 }}>
{kb.name}
</Text>
</div>
}
description={
<div>
<Paragraph
ellipsis={{ rows: 2 }}
type="secondary"
style={{ marginBottom: 12, minHeight: 44 }}
>
{kb.description || '暂无描述'}
</Paragraph>
<Space direction="vertical" size={4} style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<FileTextOutlined style={{ marginRight: 8, color: '#8c8c8c' }} />
<Text type="secondary" style={{ fontSize: 13 }}>
{kb._count?.documents || kb.fileCount || 0}
</Text>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Text type="secondary" style={{ fontSize: 13 }}>
: {formatFileSize(kb.totalSizeBytes)}
</Text>
</div>
<div style={{ marginTop: 4 }}>
<Text type="secondary" style={{ fontSize: 12 }}>
{new Date(kb.createdAt).toLocaleDateString()}
</Text>
</div>
</Space>
</div>
}
/>
</Card>
))}
</div>
)}
</div>
);
};
export default KnowledgeBaseList;