diff --git a/ui/static/graph.html b/ui/static/graph.html index 14dbb69..29bda3a 100644 --- a/ui/static/graph.html +++ b/ui/static/graph.html @@ -453,6 +453,8 @@ + +
@@ -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 = `📄${file.name}${formatFileSize(file.size)}`; + const icon = getFileIcon(file.name); + chip.innerHTML = `${icon}${file.name}${formatFileSize(file.size)}`; 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 = [];