From ea967fc22094c8fa0ba0e9887fcfc930f2525879 Mon Sep 17 00:00:00 2001 From: zimk Date: Mon, 16 Mar 2026 21:53:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=EF=BC=8C=E6=8C=89=20OpenAI=20=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=8E=A5=E5=8F=A3=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=B1=BB=E5=9E=8B=E9=99=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/app.js | 131 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/public/app.js b/public/app.js index 05c289b..18ed712 100644 --- a/public/app.js +++ b/public/app.js @@ -452,31 +452,57 @@ function buildUserMessage(message, attachments) { return { role: 'user', content: message.content || '' }; } - const attachmentText = attachments - .map((file) => `- ${file.name} (${file.type || 'application/octet-stream'}, ${formatBytes(file.size)})`) - .join('\n'); - const promptText = message.content || '请读取并处理这些附件。'; - const content = [ - { - type: 'text', - text: `${promptText}\n\n附件列表:\n${attachmentText}`, - }, - ]; + const content = []; + + // 先放主文本 + if (promptText) { + content.push({ type: 'text', text: promptText }); + } + + const nonInlineable = []; attachments.forEach((file) => { - content.push({ - type: 'image_url', - image_url: { - url: file.dataUrl, - }, - }); + if (file.kind === 'image') { + // 图片:用 image_url + base64 data URL(OpenAI 标准) + content.push({ + type: 'image_url', + image_url: { url: file.dataUrl }, + }); + } else if (file.kind === 'text') { + // 文本类文件:解码 base64,内联为 text block + let decoded = ''; + try { + decoded = atob(file.base64); + // 尝试 UTF-8 解码(处理多字节字符) + decoded = new TextDecoder('utf-8').decode( + Uint8Array.from(atob(file.base64), (c) => c.charCodeAt(0)) + ); + } catch { + decoded = file.base64; + } + content.push({ + type: 'text', + text: `文件名:${file.name}\n内容:\n\`\`\`\n${decoded}\n\`\`\``, + }); + } else { + // PDF / 其他二进制:记录下来,后面统一追加描述 + nonInlineable.push(file); + } }); - return { - role: 'user', - content, - }; + // 不可内联的文件:追加描述性文本 + if (nonInlineable.length) { + const desc = nonInlineable + .map((f) => `- ${f.name} (${f.type || 'application/octet-stream'}, ${formatBytes(f.size)})`) + .join('\n'); + content.push({ + type: 'text', + text: `以下附件无法直接内联,仅供参考:\n${desc}`, + }); + } + + return { role: 'user', content }; } function extractAssistantText(data) { @@ -732,39 +758,52 @@ function renderMessageContent(container, message) { container.appendChild(details); } - // 主文本 - const images = extractImageDataUrls(mainText); - const textOnly = removeImageDataUrls(mainText).trim(); - if (textOnly) { + // 主文本(marked.js 会自动处理图片) + if (mainText) { const div = document.createElement('div'); - div.innerHTML = renderMarkdown(textOnly); - container.appendChild(div); - } - if (images.length) { - const gallery = document.createElement('div'); - gallery.className = 'message-image-gallery'; - images.forEach((src) => { - const img = document.createElement('img'); - img.src = src; + div.innerHTML = renderMarkdown(mainText); + // 给 marked 渲染出来的 img 加上样式类 + div.querySelectorAll('img').forEach((img) => { img.className = 'message-inline-image'; - gallery.appendChild(img); }); - container.appendChild(gallery); + container.appendChild(div); } } function renderMarkdown(input) { - return marked.parse(String(input || ''), { - renderer: (() => { - const r = new marked.Renderer(); - r.code = ({ text, lang }) => { - const escaped = text.replace(/&/g,'&').replace(//g,'>'); - return `
${escaped}
`; - }; - return r; - })(), - breaks: true, - }); + const renderer = new marked.Renderer(); + + renderer.code = ({ text, lang }) => { + const escaped = text.replace(/&/g,'&').replace(//g,'>'); + return `
${escaped}
`; + }; + + renderer.image = ({ href, title, text }) => { + if (!href) return ''; + const isVideo = /\.(mp4|webm|ogg|mov)([?#]|$)/i.test(href) || /video/i.test(href); + if (isVideo) { + return ``; + } + const alt = text || title || ''; + return `${alt}`; + }; + + renderer.link = ({ href, title, text }) => { + if (!href) return text; + const isVideo = /\.(mp4|webm|ogg|mov)([?#]|$)/i.test(href) || /video/i.test(href); + if (isVideo) { + return ``; + } + return `${text}`; + }; + + return marked.parse(String(input || ''), { renderer, breaks: true }); } function extractImageDataUrls(text) {