Files
TERES_web_frontend/src/pages/chunk/document-preview.tsx

309 lines
7.9 KiB
TypeScript
Raw Normal View History

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;