Files
AIclinicalresearch/frontend/src/components/knowledge/KnowledgeBaseList.tsx
AI Clinical Dev Team 239c7ea85e feat: Day 21-22 - knowledge base frontend completed, fix CORS and file upload issues
- Complete knowledge base list and detail pages
- Complete document upload component
- Fix CORS config (add PUT/DELETE method support)
- Fix file upload issues (disabled state and beforeUpload return value)
- Add detailed debug logs (cleaned up)
- Create Day 21-22 completion summary document
2025-10-11 15:40:12 +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;