feat: 前端增加PDF(.docx)文档解析,文件类型图标
This commit is contained in:
@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user