feat: 前端增加PDF(.docx)文档解析,文件类型图标

This commit is contained in:
root
2026-04-29 10:20:41 +08:00
parent 64b31bbf4f
commit 1b5a3adf3e

View File

@ -453,6 +453,8 @@
</style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
</head>
<body>
<div id="app-container">
@ -1853,6 +1855,18 @@ function smoothReposition() {
let attachedFiles = [];
function getFileIcon(name) {
const lower = name.toLowerCase();
if (lower.endsWith('.pdf')) return '📕';
if (lower.endsWith('.docx') || lower.endsWith('.doc')) return '📘';
if (lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.csv')) return '📊';
if (lower.endsWith('.pptx') || lower.endsWith('.ppt')) return '📙';
if (lower.endsWith('.jpg') || lower.endsWith('.jpeg') || lower.endsWith('.png') || lower.endsWith('.gif') || lower.endsWith('.webp')) return '🖼️';
if (lower.endsWith('.json') || lower.endsWith('.xml') || lower.endsWith('.yaml') || lower.endsWith('.yml') || lower.endsWith('.toml')) return '⚙️';
if (lower.endsWith('.py') || lower.endsWith('.js') || lower.endsWith('.ts') || lower.endsWith('.java') || lower.endsWith('.c') || lower.endsWith('.cpp') || lower.endsWith('.h') || lower.endsWith('.sh') || lower.endsWith('.bat') || lower.endsWith('.ps1')) return '💻';
return '📄';
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + 'B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
@ -1881,7 +1895,8 @@ function smoothReposition() {
attachedFiles.forEach(file => {
const chip = document.createElement('div');
chip.className = 'chat-file-chip';
chip.innerHTML = `<span class="file-icon">📄</span><span class="file-name" title="${file.name}">${file.name}</span><span class="file-size">${formatFileSize(file.size)}</span><span class="file-remove" title="删除">✕</span>`;
const icon = getFileIcon(file.name);
chip.innerHTML = `<span class="file-icon">${icon}</span><span class="file-name" title="${file.name}">${file.name}</span><span class="file-size">${formatFileSize(file.size)}</span><span class="file-remove" title="删除">✕</span>`;
chip.querySelector('.file-remove').onclick = () => removeFileChip(file);
chatFilesContainer.appendChild(chip);
});
@ -1969,6 +1984,42 @@ function smoothReposition() {
}
}
async function readFileContent(file) {
const name = file.name.toLowerCase();
// PDF 文件:用 pdf.js 解析
if (name.endsWith('.pdf')) {
if (typeof pdfjsLib === 'undefined') throw new Error('PDF.js 未加载');
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
const pages = [];
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
const text = content.items.map(item => item.str).join(' ');
pages.push(text);
}
return pages.join('\n\n');
}
// Word 文件:用 mammoth.js 解析
if (name.endsWith('.docx')) {
if (typeof mammoth === 'undefined') throw new Error('mammoth.js 未加载');
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
return result.value || '(Word 文档内容为空)';
}
// 其他文件:当作纯文本读取
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(new Error('文件读取失败'));
reader.readAsText(file);
});
}
function buildFullMessage(text, files) {
if (!files || files.length === 0) return text;
let parts = [];
@ -1990,17 +2041,14 @@ function smoothReposition() {
// 读取文件内容
let fullMsg = msg;
if (files.length > 0) {
const fileReads = files.map(file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => { file.content = reader.result; resolve(); };
reader.onerror = reject;
reader.readAsText(file);
}));
try {
await Promise.all(fileReads);
} catch(e) {
console.error('文件读取失败:', e);
}
const fileReads = files.map(async (file) => {
try {
file.content = await readFileContent(file);
} catch(e) {
file.content = `[文件读取失败: ${file.name}] ${e.message}`;
}
});
await Promise.all(fileReads);
fullMsg = buildFullMessage(msg, files);
// 清除文件芯片
attachedFiles = [];