309 lines
7.9 KiB
TypeScript
309 lines
7.9 KiB
TypeScript
|
|
import React, { useState, useEffect } from 'react';
|
|||
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|||
|
|
import {
|
|||
|
|
Box,
|
|||
|
|
Typography,
|
|||
|
|
Paper,
|
|||
|
|
CircularProgress,
|
|||
|
|
Alert,
|
|||
|
|
Button,
|
|||
|
|
Breadcrumbs,
|
|||
|
|
Link,
|
|||
|
|
Card,
|
|||
|
|
CardContent,
|
|||
|
|
CardMedia,
|
|||
|
|
} from '@mui/material';
|
|||
|
|
import {
|
|||
|
|
ArrowBack as ArrowBackIcon,
|
|||
|
|
Download as DownloadIcon,
|
|||
|
|
Visibility as VisibilityIcon,
|
|||
|
|
} from '@mui/icons-material';
|
|||
|
|
import knowledgeService from '@/services/knowledge_service';
|
|||
|
|
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
|||
|
|
|
|||
|
|
interface DocumentPreviewProps {}
|
|||
|
|
|
|||
|
|
function DocumentPreview(props: DocumentPreviewProps) {
|
|||
|
|
const { kb_id, doc_id } = useParams<{ kb_id: string; doc_id: string }>();
|
|||
|
|
const navigate = useNavigate();
|
|||
|
|
|
|||
|
|
const [kb, setKb] = useState<IKnowledge | null>(null);
|
|||
|
|
const [documentObj, setDocumentObj] = useState<IKnowledgeFile | null>(null);
|
|||
|
|
const [documentFile, setDocumentFile] = useState<Blob | null>(null);
|
|||
|
|
const [fileUrl, setFileUrl] = useState<string>('');
|
|||
|
|
const [loading, setLoading] = useState(true);
|
|||
|
|
const [error, setError] = useState<string | null>(null);
|
|||
|
|
const [fileLoading, setFileLoading] = useState(false);
|
|||
|
|
|
|||
|
|
// 获取知识库和文档信息
|
|||
|
|
useEffect(() => {
|
|||
|
|
const fetchData = async () => {
|
|||
|
|
if (!kb_id || !doc_id) {
|
|||
|
|
setError('缺少必要的参数');
|
|||
|
|
setLoading(false);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
setLoading(true);
|
|||
|
|
|
|||
|
|
// 并行获取知识库信息和文档详情
|
|||
|
|
const [kbResponse, docResponse] = await Promise.all([
|
|||
|
|
knowledgeService.getKnowledgeDetail({ kb_id }),
|
|||
|
|
knowledgeService.getDocumentInfos({ doc_ids: [doc_id] })
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
if (kbResponse.data.data) {
|
|||
|
|
setKb(kbResponse.data.data);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (docResponse.data.data?.length > 0) {
|
|||
|
|
setDocumentObj(docResponse.data.data[0]);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('获取数据失败:', err);
|
|||
|
|
setError('获取数据失败,请稍后重试');
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fetchData();
|
|||
|
|
}, [kb_id, doc_id]);
|
|||
|
|
|
|||
|
|
// 异步加载文档文件
|
|||
|
|
const loadDocumentFile = async () => {
|
|||
|
|
if (!doc_id || fileLoading) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
setFileLoading(true);
|
|||
|
|
setError(null);
|
|||
|
|
|
|||
|
|
const fileResponse = await knowledgeService.getDocumentFile({ doc_id });
|
|||
|
|
|
|||
|
|
if (fileResponse.data instanceof Blob) {
|
|||
|
|
setDocumentFile(fileResponse.data);
|
|||
|
|
const url = URL.createObjectURL(fileResponse.data);
|
|||
|
|
setFileUrl(url);
|
|||
|
|
} else {
|
|||
|
|
setError('文件格式不支持预览');
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('获取文档文件失败:', err);
|
|||
|
|
setError('获取文档文件失败,请稍后重试');
|
|||
|
|
} finally {
|
|||
|
|
setFileLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 清理文件URL
|
|||
|
|
useEffect(() => {
|
|||
|
|
return () => {
|
|||
|
|
if (fileUrl) {
|
|||
|
|
URL.revokeObjectURL(fileUrl);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}, [fileUrl]);
|
|||
|
|
|
|||
|
|
// 下载文件
|
|||
|
|
const handleDownload = () => {
|
|||
|
|
if (fileUrl && window.document) {
|
|||
|
|
const link = window.document.createElement('a');
|
|||
|
|
link.href = fileUrl;
|
|||
|
|
link.download = documentObj?.name || 'document';
|
|||
|
|
window.document.body.appendChild(link);
|
|||
|
|
link.click();
|
|||
|
|
window.document.body.removeChild(link);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 返回上一页
|
|||
|
|
const handleGoBack = () => {
|
|||
|
|
navigate(-1);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 渲染文件预览
|
|||
|
|
const renderFilePreview = () => {
|
|||
|
|
if (!documentFile || !fileUrl) return null;
|
|||
|
|
|
|||
|
|
const fileType = documentFile.type;
|
|||
|
|
|
|||
|
|
if (fileType.startsWith('image/')) {
|
|||
|
|
return (
|
|||
|
|
<CardMedia
|
|||
|
|
component="img"
|
|||
|
|
image={fileUrl}
|
|||
|
|
alt={documentObj?.name || '文档预览'}
|
|||
|
|
sx={{
|
|||
|
|
maxWidth: '100%',
|
|||
|
|
maxHeight: '80vh',
|
|||
|
|
objectFit: 'contain',
|
|||
|
|
borderRadius: 1,
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
} else if (fileType === 'application/pdf') {
|
|||
|
|
return (
|
|||
|
|
<Box
|
|||
|
|
component="iframe"
|
|||
|
|
src={fileUrl}
|
|||
|
|
sx={{
|
|||
|
|
width: '100%',
|
|||
|
|
height: '80vh',
|
|||
|
|
border: 'none',
|
|||
|
|
borderRadius: 1,
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
} else if (fileType.startsWith('text/')) {
|
|||
|
|
return (
|
|||
|
|
<Box
|
|||
|
|
component="iframe"
|
|||
|
|
src={fileUrl}
|
|||
|
|
sx={{
|
|||
|
|
width: '100%',
|
|||
|
|
height: '80vh',
|
|||
|
|
border: '1px solid',
|
|||
|
|
borderColor: 'grey.300',
|
|||
|
|
borderRadius: 1,
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
return (
|
|||
|
|
<Alert severity="info" sx={{ mt: 2 }}>
|
|||
|
|
此文件类型不支持在线预览,请下载后查看。
|
|||
|
|
</Alert>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (loading) {
|
|||
|
|
return (
|
|||
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '400px' }}>
|
|||
|
|
<CircularProgress />
|
|||
|
|
</Box>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (error && !documentObj) {
|
|||
|
|
return (
|
|||
|
|
<Box sx={{ p: 3 }}>
|
|||
|
|
<Alert severity="error">{error}</Alert>
|
|||
|
|
<Button
|
|||
|
|
startIcon={<ArrowBackIcon />}
|
|||
|
|
onClick={handleGoBack}
|
|||
|
|
sx={{ mt: 2 }}
|
|||
|
|
>
|
|||
|
|
返回
|
|||
|
|
</Button>
|
|||
|
|
</Box>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Box sx={{ p: 3 }}>
|
|||
|
|
{/* 面包屑导航 */}
|
|||
|
|
<Breadcrumbs sx={{ mb: 2 }}>
|
|||
|
|
<Link
|
|||
|
|
component="button"
|
|||
|
|
variant="body2"
|
|||
|
|
onClick={() => navigate('/knowledge')}
|
|||
|
|
sx={{ textDecoration: 'none' }}
|
|||
|
|
>
|
|||
|
|
知识库
|
|||
|
|
</Link>
|
|||
|
|
<Link
|
|||
|
|
component="button"
|
|||
|
|
variant="body2"
|
|||
|
|
onClick={() => navigate(`/knowledge/${kb_id}`)}
|
|||
|
|
sx={{ textDecoration: 'none' }}
|
|||
|
|
>
|
|||
|
|
{kb?.name || '知识库详情'}
|
|||
|
|
</Link>
|
|||
|
|
<Link
|
|||
|
|
component="button"
|
|||
|
|
variant="body2"
|
|||
|
|
onClick={() => navigate(`/knowledge/${kb_id}/document/${doc_id}/chunks`)}
|
|||
|
|
sx={{ textDecoration: 'none' }}
|
|||
|
|
>
|
|||
|
|
文档分块
|
|||
|
|
</Link>
|
|||
|
|
<Typography color="text.primary">文件预览</Typography>
|
|||
|
|
</Breadcrumbs>
|
|||
|
|
|
|||
|
|
{/* 页面标题和操作按钮 */}
|
|||
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
|||
|
|
<Box>
|
|||
|
|
<Typography variant="h4" gutterBottom>
|
|||
|
|
文件预览
|
|||
|
|
</Typography>
|
|||
|
|
{documentObj && (
|
|||
|
|
<Typography variant="subtitle1" color="text.secondary">
|
|||
|
|
{documentObj.name}
|
|||
|
|
</Typography>
|
|||
|
|
)}
|
|||
|
|
</Box>
|
|||
|
|
|
|||
|
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
|||
|
|
<Button
|
|||
|
|
startIcon={<ArrowBackIcon />}
|
|||
|
|
onClick={handleGoBack}
|
|||
|
|
variant="outlined"
|
|||
|
|
>
|
|||
|
|
返回
|
|||
|
|
</Button>
|
|||
|
|
|
|||
|
|
{!fileUrl && (
|
|||
|
|
<Button
|
|||
|
|
startIcon={<VisibilityIcon />}
|
|||
|
|
onClick={loadDocumentFile}
|
|||
|
|
variant="contained"
|
|||
|
|
disabled={fileLoading}
|
|||
|
|
>
|
|||
|
|
{fileLoading ? '加载中...' : '预览文件'}
|
|||
|
|
</Button>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{fileUrl && (
|
|||
|
|
<Button
|
|||
|
|
startIcon={<DownloadIcon />}
|
|||
|
|
onClick={handleDownload}
|
|||
|
|
variant="contained"
|
|||
|
|
>
|
|||
|
|
下载文件
|
|||
|
|
</Button>
|
|||
|
|
)}
|
|||
|
|
</Box>
|
|||
|
|
</Box>
|
|||
|
|
|
|||
|
|
{/* 错误提示 */}
|
|||
|
|
{error && (
|
|||
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|||
|
|
{error}
|
|||
|
|
</Alert>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 文件加载状态 */}
|
|||
|
|
{fileLoading && (
|
|||
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }}>
|
|||
|
|
<CircularProgress />
|
|||
|
|
<Typography sx={{ ml: 2 }}>正在加载文件...</Typography>
|
|||
|
|
</Box>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 文件预览区域 */}
|
|||
|
|
{fileUrl && (
|
|||
|
|
<Paper elevation={1} sx={{ p: 2 }}>
|
|||
|
|
<Card>
|
|||
|
|
<CardContent>
|
|||
|
|
{renderFilePreview()}
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</Paper>
|
|||
|
|
)}
|
|||
|
|
</Box>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default DocumentPreview;
|