diff --git a/src/previewsetup.ts b/src/previewsetup.ts index b236bc3..294bb05 100644 --- a/src/previewsetup.ts +++ b/src/previewsetup.ts @@ -2,6 +2,7 @@ // 全部 dynamic import——对应格式首次预览时才下载 chunk,主 bundle 不含解析器。 // 链接预览 fetcher 走服务端 /api/url-preview(SSRF 防护在服务端执行端)。 import { configureFilePreviewLoaders, configureLinkPreviewFetcher } from '@ui' +import { marked } from 'marked' import { auth } from './auth' import type { LinkPreviewData } from '@ui' @@ -11,9 +12,35 @@ const fetchBuf = async (url: string | undefined): Promise => { return r.arrayBuffer() } -const docxLoader = async (el: HTMLElement, source: { contentUrl?: string }) => { +// docx/OOXML 是 ZIP 容器,真文件必以 PK 魔数开头(PK\x03\x04 常规,PK\x05\x06 空包)。 +// 智能体生成侧可能写出"内容是文本的伪 docx",先嗅探再喂 mammoth: +// 非 ZIP 直接按文本/markdown 回退渲染,不再抛 JSZip 底层错误。 +const isZipBytes = (buf: ArrayBuffer): boolean => { + if (buf.byteLength < 4) return false + const h = new Uint8Array(buf, 0, 4) + return h[0] === 0x50 && h[1] === 0x4b && + ((h[2] === 0x03 && h[3] === 0x04) || (h[2] === 0x05 && h[3] === 0x06)) +} + +const renderTextFallback = (el: HTMLElement, buf: ArrayBuffer, name?: string) => { + const note = document.createElement('div') + note.className = 'ui-fp-note' + note.textContent = `「${name ?? '该文件'}」不是有效的 docx 文件(缺少 ZIP 结构),已按文本内容展示;需要正式 Word 格式请让智能体以 docx 格式重新生成。` + const body = document.createElement('div') + body.className = 'doc-viewer' + body.innerHTML = marked.parse(new TextDecoder().decode(buf)) as string + el.innerHTML = '' + el.append(note, body) +} + +const docxLoader = async (el: HTMLElement, source: { contentUrl?: string; name?: string }) => { + const buf = await fetchBuf(source.contentUrl) + if (!isZipBytes(buf)) { + renderTextFallback(el, buf, source.name) + return + } const mammoth = await import('mammoth') - const { value } = await mammoth.convertToHtml({ arrayBuffer: await fetchBuf(source.contentUrl) }) + const { value } = await mammoth.convertToHtml({ arrayBuffer: buf }) el.innerHTML = value }