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 = [];