@huyooo/ai-chat-shared 0.2.12 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +42 -12
- package/dist/index.js +335 -36
- package/dist/index.js.map +1 -1
- package/dist/styles.css +274 -22
- package/package.json +7 -3
- package/src/index.ts +15 -2
- package/src/markdown.ts +262 -6
- package/src/parser.ts +60 -30
- package/src/styles.css +274 -22
- package/src/types.ts +0 -13
- package/src/visual.ts +68 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parser.ts","../src/highlighter.ts","../src/markdown.ts"],"sourcesContent":["/**\n * 内容解析器\n * 将 AI 输出的原始文本解析为结构化的内容块\n */\n\nimport type { ContentBlock, TextBlock, CodeBlock } from './types'\n\n/** 生成唯一 ID */\nfunction generateId(): string {\n return Math.random().toString(36).slice(2, 11)\n}\n\n/** 代码块正则:匹配 ```language\\ncode\\n``` 或 ```\\ncode\\n``` */\nconst CODE_BLOCK_REGEX = /```(\\w*)\\n([\\s\\S]*?)```/g\n\n/**\n * 解析原始内容为内容块列表\n * @param raw 原始文本内容\n * @returns 内容块列表\n */\nexport function parseContent(raw: string): ContentBlock[] {\n if (!raw) return []\n\n const blocks: ContentBlock[] = []\n let lastIndex = 0\n\n // 使用正则匹配所有代码块\n let match: RegExpExecArray | null\n const regex = new RegExp(CODE_BLOCK_REGEX.source, 'g')\n\n while ((match = regex.exec(raw)) !== null) {\n // 添加代码块前的文本\n if (match.index > lastIndex) {\n const textContent = raw.slice(lastIndex, match.index)\n if (textContent.trim()) {\n blocks.push(createTextBlock(textContent))\n }\n }\n\n // 添加代码块\n const language = match[1] || undefined\n const code = match[2]\n blocks.push(createCodeBlock(code, language))\n\n lastIndex = match.index + match[0].length\n }\n\n // 添加最后一段文本\n if (lastIndex < raw.length) {\n const textContent = raw.slice(lastIndex)\n if (textContent.trim()) {\n blocks.push(createTextBlock(textContent))\n }\n }\n\n return blocks\n}\n\n/** 创建文本块 */\nfunction createTextBlock(content: string): TextBlock {\n return {\n id: generateId(),\n type: 'text',\n content: content.trim(),\n }\n}\n\n/** 创建代码块 */\nfunction createCodeBlock(content: string, language?: string, filename?: string): CodeBlock {\n return {\n id: generateId(),\n type: 'code',\n content,\n language,\n filename,\n }\n}\n\n// ============ 流式解析支持 ============\n\n/** 流式解析状态 */\nexport interface StreamParseState {\n /** 未完成的缓冲区 */\n buffer: string\n /** 已解析的块 */\n blocks: ContentBlock[]\n /** 是否在代码块中 */\n inCodeBlock: boolean\n /** 当前代码块语言 */\n codeLanguage?: string\n /** 当前代码块内容 */\n codeContent: string\n}\n\n/** 创建初始流式解析状态 */\nexport function createStreamParseState(): StreamParseState {\n return {\n buffer: '',\n blocks: [],\n inCodeBlock: false,\n codeLanguage: undefined,\n codeContent: '',\n }\n}\n\n/**\n * 流式解析(增量更新)\n * @param chunk 新增的文本块\n * @param state 当前解析状态\n * @returns 更新后的状态\n */\nexport function parseContentStream(chunk: string, state: StreamParseState): StreamParseState {\n const newState = { ...state }\n newState.buffer += chunk\n\n // 处理缓冲区\n while (true) {\n if (newState.inCodeBlock) {\n // 在代码块中,查找结束标记\n const endIndex = newState.buffer.indexOf('```')\n if (endIndex !== -1) {\n // 找到结束标记\n newState.codeContent += newState.buffer.slice(0, endIndex)\n newState.blocks.push(createCodeBlock(\n newState.codeContent.trim(),\n newState.codeLanguage\n ))\n newState.buffer = newState.buffer.slice(endIndex + 3)\n newState.inCodeBlock = false\n newState.codeLanguage = undefined\n newState.codeContent = ''\n } else {\n // 没找到结束标记,继续累积\n newState.codeContent += newState.buffer\n newState.buffer = ''\n break\n }\n } else {\n // 不在代码块中,查找开始标记\n const startIndex = newState.buffer.indexOf('```')\n if (startIndex !== -1) {\n // 找到开始标记\n const beforeCode = newState.buffer.slice(0, startIndex)\n if (beforeCode.trim()) {\n newState.blocks.push(createTextBlock(beforeCode))\n }\n\n // 解析语言标识\n const afterStart = newState.buffer.slice(startIndex + 3)\n const newlineIndex = afterStart.indexOf('\\n')\n if (newlineIndex !== -1) {\n newState.codeLanguage = afterStart.slice(0, newlineIndex).trim() || undefined\n newState.buffer = afterStart.slice(newlineIndex + 1)\n newState.inCodeBlock = true\n newState.codeContent = ''\n } else {\n // 语言行还没完整,等待更多数据\n break\n }\n } else {\n // 没有代码块标记,检查是否有不完整的 ``` 开头\n const lastBackticks = newState.buffer.lastIndexOf('`')\n if (lastBackticks !== -1 && newState.buffer.length - lastBackticks < 3) {\n // 可能是不完整的 ```,保留\n const safeText = newState.buffer.slice(0, lastBackticks)\n if (safeText.trim()) {\n newState.blocks.push(createTextBlock(safeText))\n }\n newState.buffer = newState.buffer.slice(lastBackticks)\n } else {\n // 安全的纯文本\n if (newState.buffer.trim()) {\n newState.blocks.push(createTextBlock(newState.buffer))\n }\n newState.buffer = ''\n }\n break\n }\n }\n }\n\n return newState\n}\n\n/**\n * 完成流式解析(处理剩余缓冲区)\n * @param state 当前解析状态\n * @returns 最终的内容块列表\n */\nexport function finishStreamParse(state: StreamParseState): ContentBlock[] {\n const blocks = [...state.blocks]\n\n if (state.inCodeBlock) {\n // 未闭合的代码块,作为代码块处理\n blocks.push(createCodeBlock(\n (state.codeContent + state.buffer).trim(),\n state.codeLanguage\n ))\n } else if (state.buffer.trim()) {\n // 剩余文本\n blocks.push(createTextBlock(state.buffer))\n }\n\n return blocks\n}\n","/**\n * 代码高亮工具\n * 使用 highlight.js 提供代码语法高亮\n */\n\nimport hljs from 'highlight.js'\n\n/** 支持的语言别名映射 */\nconst LANGUAGE_ALIASES: Record<string, string> = {\n js: 'javascript',\n ts: 'typescript',\n tsx: 'typescript',\n jsx: 'javascript',\n py: 'python',\n rb: 'ruby',\n sh: 'bash',\n shell: 'bash',\n yml: 'yaml',\n md: 'markdown',\n}\n\n/**\n * 高亮代码\n * @param code 代码内容\n * @param language 语言标识\n * @returns 高亮后的 HTML\n */\nexport function highlightCode(code: string, language?: string): string {\n if (!language) {\n // 无语言指定,尝试自动检测\n try {\n const result = hljs.highlightAuto(code)\n return result.value\n } catch {\n return escapeHtml(code)\n }\n }\n\n // 处理语言别名\n const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase()\n\n try {\n if (hljs.getLanguage(normalizedLang)) {\n return hljs.highlight(code, { language: normalizedLang }).value\n }\n // 语言不支持,返回转义后的原文\n return escapeHtml(code)\n } catch {\n return escapeHtml(code)\n }\n}\n\n/** HTML 转义 */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * 获取语言显示名称\n * @param language 语言标识\n * @returns 显示名称\n */\nexport function getLanguageDisplayName(language?: string): string {\n if (!language) return 'Plain Text'\n \n const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase()\n const langDef = hljs.getLanguage(normalizedLang)\n \n if (langDef) {\n // 首字母大写\n return normalizedLang.charAt(0).toUpperCase() + normalizedLang.slice(1)\n }\n \n return language\n}\n","/**\n * Markdown 渲染工具\n * 提供统一的 Markdown 渲染函数,供 React 和 Vue 版本使用\n */\n\nimport { marked, Renderer } from 'marked'\nimport DOMPurify from 'dompurify'\nimport { highlightCode } from './highlighter'\n\n/**\n * 渲染 Markdown 为 HTML\n * @param markdown Markdown 文本\n * @returns 安全的 HTML 字符串\n */\nexport function renderMarkdown(markdown: string): string {\n if (!markdown) return ''\n\n // 创建自定义渲染器(继承默认渲染器)\n const renderer = new Renderer()\n\n // 自定义代码块渲染\n renderer.code = (code: string, language?: string) => {\n const highlighted = highlightCode(code, language)\n const lang = language || 'plaintext'\n return `<pre class=\"markdown-code-block\"><code class=\"language-${lang}\">${highlighted}</code></pre>`\n }\n\n // 自定义表格渲染(添加样式类和滚动容器)\n renderer.table = (header: string, body: string) => {\n return `<div class=\"markdown-table-wrapper\"><table class=\"markdown-table\"><thead>${header}</thead><tbody>${body}</tbody></table></div>`\n }\n\n // 自定义链接渲染(添加 target=\"_blank\" 和 rel=\"noopener noreferrer\")\n renderer.link = (href: string, title: string | null, text: string) => {\n const titleAttr = title ? ` title=\"${title}\"` : ''\n return `<a href=\"${href}\"${titleAttr} target=\"_blank\" rel=\"noopener noreferrer\" class=\"markdown-link\">${text}</a>`\n }\n\n // 自定义列表项渲染,将 checkbox 替换为文本 [x] 或 [ ]\n renderer.listitem = (text: string, task: boolean, checked: boolean) => {\n if (task) {\n // 任务列表:将 checkbox 替换为文本\n const checkboxText = checked ? '[x]' : '[ ]'\n // 移除 marked 生成的 checkbox input,替换为文本\n const textWithoutCheckbox = text.replace(/<input[^>]*>/g, '').trim()\n return `<li class=\"markdown-task-item\"><span class=\"markdown-task-checkbox\">${checkboxText}</span> ${textWithoutCheckbox}</li>`\n }\n // 普通列表项\n return `<li>${text}</li>`\n }\n\n // 配置 marked(marked 12.x 使用新的配置方式)\n marked.setOptions({\n breaks: true, // 支持换行\n gfm: true, // GitHub Flavored Markdown\n })\n\n // 渲染 Markdown(marked 12.x 返回字符串,不是 Promise)\n let html = marked(markdown, { renderer }) as string\n\n // 使用 DOMPurify 清理 HTML,防止 XSS\n html = DOMPurify.sanitize(html, {\n ALLOWED_TAGS: [\n 'p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre',\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'ul', 'ol', 'li', 'blockquote',\n 'table', 'thead', 'tbody', 'tr', 'th', 'td',\n 'a', 'img', 'hr',\n 'div', 'span',\n ],\n ALLOWED_ATTR: [\n 'href', 'title', 'target', 'rel', 'class',\n 'src', 'alt', 'width', 'height',\n ],\n })\n\n return html\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/parser.ts","../src/highlighter.ts","../src/markdown.ts","../src/visual.ts"],"sourcesContent":["/**\n * 内容解析器\n * 将 AI 输出的原始文本解析为结构化的内容块\n */\n\nimport type { ContentBlock, TextBlock, CodeBlock } from './types'\n\n/** 生成唯一 ID */\nfunction generateId(): string {\n return Math.random().toString(36).slice(2, 11)\n}\n\n/** 代码块正则:匹配 ```language\\ncode\\n``` 或 ```\\ncode\\n``` */\nconst CODE_BLOCK_REGEX = /```(\\w*)\\n([\\s\\S]*?)```/g\n\n/**\n * 解析原始内容为内容块列表\n * @param raw 原始文本内容\n * @returns 内容块列表\n */\nexport function parseContent(raw: string): ContentBlock[] {\n if (!raw) return []\n\n const blocks: ContentBlock[] = []\n let lastIndex = 0\n\n // 使用正则匹配所有代码块\n let match: RegExpExecArray | null\n const regex = new RegExp(CODE_BLOCK_REGEX.source, 'g')\n\n while ((match = regex.exec(raw)) !== null) {\n // 添加代码块前的文本\n if (match.index > lastIndex) {\n const textContent = raw.slice(lastIndex, match.index)\n if (textContent.trim()) {\n blocks.push(createTextBlock(textContent, undefined, true))\n }\n }\n\n // 添加代码块\n const language = match[1] || undefined\n const code = match[2]\n blocks.push(createCodeBlock(code, language))\n\n lastIndex = match.index + match[0].length\n }\n\n // 添加最后一段文本\n if (lastIndex < raw.length) {\n const textContent = raw.slice(lastIndex)\n if (textContent.trim()) {\n blocks.push(createTextBlock(textContent, undefined, true))\n }\n }\n\n return blocks\n}\n\n/** 创建文本块 */\nfunction createTextBlock(content: string, id?: string, trimContent = true): TextBlock {\n return {\n id: id || generateId(),\n type: 'text',\n content: trimContent ? content.trim() : content,\n }\n}\n\n/** 创建代码块 */\nfunction createCodeBlock(content: string, language?: string, filename?: string, id?: string): CodeBlock {\n return {\n id: id || generateId(),\n type: 'code',\n content,\n language,\n filename,\n }\n}\n\n// ============ 流式解析支持 ============\n\n/** 流式解析状态 */\nexport interface StreamParseState {\n /** 未完成的缓冲区 */\n buffer: string\n /** 已解析的块 */\n blocks: ContentBlock[]\n /** 是否在代码块中 */\n inCodeBlock: boolean\n /** 当前代码块语言 */\n codeLanguage?: string\n /** 当前代码块内容 */\n codeContent: string\n /** 当前代码块 ID(用于稳定 key,避免流式时每次 render 都生成新 id) */\n codeBlockId: string | null\n /** 当前文本块内容(流式追加,避免把一句话拆成很多 text block) */\n textContent: string\n /** 当前文本块 ID(稳定 key) */\n textBlockId: string | null\n}\n\n/** 创建初始流式解析状态 */\nexport function createStreamParseState(): StreamParseState {\n return {\n buffer: '',\n blocks: [],\n inCodeBlock: false,\n codeLanguage: undefined,\n codeContent: '',\n codeBlockId: null,\n textContent: '',\n textBlockId: null,\n }\n}\n\nfunction appendText(state: StreamParseState, text: string): void {\n if (!text) return\n if (state.textBlockId === null) state.textBlockId = generateId()\n state.textContent += text\n}\n\nfunction flushTextBlock(state: StreamParseState): void {\n if (!state.textContent.trim()) return\n state.blocks.push(createTextBlock(state.textContent, state.textBlockId || undefined, false))\n state.textContent = ''\n state.textBlockId = null\n}\n\n/**\n * 流式解析(增量更新)\n * @param chunk 新增的文本块\n * @param state 当前解析状态\n * @returns 更新后的状态\n */\nexport function parseContentStream(chunk: string, state: StreamParseState): StreamParseState {\n const newState: StreamParseState = { ...state, blocks: [...state.blocks] }\n newState.buffer += chunk\n\n // 处理缓冲区\n while (true) {\n if (newState.inCodeBlock) {\n // 在代码块中,查找结束标记\n const endIndex = newState.buffer.indexOf('```')\n if (endIndex !== -1) {\n // 找到结束标记\n newState.codeContent += newState.buffer.slice(0, endIndex)\n newState.blocks.push(\n createCodeBlock(\n newState.codeContent.trim(),\n newState.codeLanguage,\n undefined,\n newState.codeBlockId || undefined\n )\n )\n newState.buffer = newState.buffer.slice(endIndex + 3)\n newState.inCodeBlock = false\n newState.codeLanguage = undefined\n newState.codeContent = ''\n newState.codeBlockId = null\n } else {\n // 没找到结束标记,继续累积\n newState.codeContent += newState.buffer\n newState.buffer = ''\n break\n }\n } else {\n // 不在代码块中,查找开始标记\n const startIndex = newState.buffer.indexOf('```')\n if (startIndex !== -1) {\n // 找到开始标记\n const beforeCode = newState.buffer.slice(0, startIndex)\n appendText(newState, beforeCode)\n flushTextBlock(newState)\n\n // 解析语言标识\n const afterStart = newState.buffer.slice(startIndex + 3)\n const newlineIndex = afterStart.indexOf('\\n')\n if (newlineIndex !== -1) {\n newState.codeLanguage = afterStart.slice(0, newlineIndex).trim() || undefined\n newState.buffer = afterStart.slice(newlineIndex + 1)\n newState.inCodeBlock = true\n newState.codeContent = ''\n newState.codeBlockId = generateId()\n } else {\n // 语言行还没完整,等待更多数据\n break\n }\n } else {\n // 没有代码块标记:追加到“当前文本块”里(不 flush),避免碎片化\n // 同时检查末尾是否可能是不完整的 ```,如果是就保留在 buffer 等待后续补全\n const lastBackticks = newState.buffer.lastIndexOf('`')\n if (lastBackticks !== -1 && newState.buffer.length - lastBackticks < 3) {\n // 可能是不完整的 ```,保留\n const safeText = newState.buffer.slice(0, lastBackticks)\n appendText(newState, safeText)\n newState.buffer = newState.buffer.slice(lastBackticks)\n } else {\n appendText(newState, newState.buffer)\n newState.buffer = ''\n }\n break\n }\n }\n }\n\n return newState\n}\n\n/**\n * 完成流式解析(处理剩余缓冲区)\n * @param state 当前解析状态\n * @returns 最终的内容块列表\n */\nexport function finishStreamParse(state: StreamParseState): ContentBlock[] {\n const blocks = [...state.blocks]\n\n if (state.inCodeBlock) {\n // 未闭合的代码块,作为代码块处理\n blocks.push(\n createCodeBlock(\n (state.codeContent + state.buffer).trim(),\n state.codeLanguage,\n undefined,\n state.codeBlockId || undefined\n )\n )\n } else {\n // 剩余文本 + 累积文本:合并成一个 text block(稳定 id),避免碎片化\n const merged = state.textContent + state.buffer\n if (merged.trim()) {\n blocks.push(createTextBlock(merged, state.textBlockId || undefined, false))\n }\n }\n\n return blocks\n}\n","/**\n * 代码高亮工具\n * 使用 highlight.js 提供代码语法高亮\n */\n\nimport hljs from 'highlight.js'\n\n/** 支持的语言别名映射 */\nconst LANGUAGE_ALIASES: Record<string, string> = {\n js: 'javascript',\n ts: 'typescript',\n tsx: 'typescript',\n jsx: 'javascript',\n py: 'python',\n rb: 'ruby',\n sh: 'bash',\n shell: 'bash',\n yml: 'yaml',\n md: 'markdown',\n}\n\n/**\n * 高亮代码\n * @param code 代码内容\n * @param language 语言标识\n * @returns 高亮后的 HTML\n */\nexport function highlightCode(code: string, language?: string): string {\n if (!language) {\n // 无语言指定,尝试自动检测\n try {\n const result = hljs.highlightAuto(code)\n return result.value\n } catch {\n return escapeHtml(code)\n }\n }\n\n // 处理语言别名\n const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase()\n\n try {\n if (hljs.getLanguage(normalizedLang)) {\n return hljs.highlight(code, { language: normalizedLang }).value\n }\n // 语言不支持,返回转义后的原文\n return escapeHtml(code)\n } catch {\n return escapeHtml(code)\n }\n}\n\n/** HTML 转义 */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * 获取语言显示名称\n * @param language 语言标识\n * @returns 显示名称\n */\nexport function getLanguageDisplayName(language?: string): string {\n if (!language) return 'Plain Text'\n \n const normalizedLang = LANGUAGE_ALIASES[language.toLowerCase()] || language.toLowerCase()\n const langDef = hljs.getLanguage(normalizedLang)\n \n if (langDef) {\n // 首字母大写\n return normalizedLang.charAt(0).toUpperCase() + normalizedLang.slice(1)\n }\n \n return language\n}\n","/**\n * Markdown 渲染工具\n * 提供统一的 Markdown 渲染函数,供 React 和 Vue 版本使用\n * 支持 LaTeX 数学公式渲染和 Mermaid 图表\n */\n\nimport { marked, Renderer } from 'marked'\nimport DOMPurify from 'dompurify'\nimport katex from 'katex'\nimport mermaid from 'mermaid'\nimport { highlightCode } from './highlighter'\n\n// Mermaid 初始化标记\nlet mermaidInitialized = false\n\n/**\n * base64url 编码(UTF-8)\n * 不能直接把源码放进 attribute(换行会被规范化),所以使用 base64 传递。\n * 同时避免在某些链路里 '+' 被当成空格的问题,这里使用 base64url(- _,无 padding)。\n */\nfunction encodeBase64Utf8(text: string): string {\n const bytes = new TextEncoder().encode(text)\n let binary = ''\n for (const b of bytes) binary += String.fromCharCode(b)\n const b64 = btoa(binary)\n // base64url: +/ -> -_,去掉末尾 padding =\n return b64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '')\n}\n\n/**\n * Mermaid 源码编码成可安全放入 attribute 的 base64url(UTF-8)\n * 给前端组件(非 markdown 渲染路径)复用,避免重复实现。\n */\nexport function encodeMermaidCodeToBase64Url(code: string): string {\n return encodeBase64Utf8(code)\n}\n\n/**\n * base64url 解码(UTF-8)\n */\nfunction decodeBase64Utf8(b64: string): string {\n // 兼容:某些情况下 '+' 可能被变成空格\n const normalized = b64.replace(/\\s/g, '+')\n // base64url -> base64\n let base64 = normalized.replace(/-/g, '+').replace(/_/g, '/')\n // 补齐 padding\n const pad = base64.length % 4\n if (pad === 2) base64 += '=='\n else if (pad === 3) base64 += '='\n else if (pad !== 0) {\n // 非法长度,交给 atob 抛错\n }\n\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return new TextDecoder().decode(bytes)\n}\n\n/**\n * 初始化 Mermaid(仅需调用一次)\n */\nexport function initMermaid(): void {\n if (mermaidInitialized) return\n \n mermaid.initialize({\n startOnLoad: false,\n theme: 'dark',\n securityLevel: 'loose',\n fontFamily: 'inherit',\n flowchart: {\n useMaxWidth: true,\n htmlLabels: true,\n curve: 'basis',\n },\n sequence: {\n useMaxWidth: true,\n },\n gantt: {\n useMaxWidth: true,\n },\n })\n \n mermaidInitialized = true\n}\n\n/**\n * 渲染页面中的 Mermaid 图表\n * 用户点击\"显示图表\"按钮后调用\n * @param container 容器元素(可选,默认为 document)\n */\nexport async function renderMermaidDiagrams(container?: Element): Promise<void> {\n if (!mermaidInitialized) {\n initMermaid()\n }\n \n const root = container || document\n // 只选择未渲染的占位符\n const elements = root.querySelectorAll('.mermaid-placeholder:not(.mermaid-rendered):not(.mermaid-error-container)')\n \n for (const el of elements) {\n const b64 = el.getAttribute('data-mermaid-code-b64')\n if (!b64) continue\n\n let code = ''\n try {\n code = decodeBase64Utf8(b64)\n } catch (e) {\n console.warn('Mermaid 解码失败:', e)\n el.innerHTML = `<div class=\"mermaid-error\">图表解码失败</div>`\n el.classList.remove('mermaid-placeholder')\n el.classList.add('mermaid-error-container')\n continue\n }\n\n // 显示加载中\n el.innerHTML = `<div class=\"mermaid-loading\">渲染中...</div>`\n \n try {\n const id = `mermaid-${Math.random().toString(36).slice(2, 11)}`\n const { svg } = await mermaid.render(id, code)\n el.innerHTML = svg\n el.classList.remove('mermaid-placeholder')\n el.classList.add('mermaid-rendered')\n el.removeAttribute('data-mermaid-code-b64')\n } catch (error) {\n console.warn('Mermaid 渲染失败:', error)\n // 直接显示错误和源码\n el.innerHTML = `<div class=\"mermaid-error\">图表渲染失败</div><pre class=\"mermaid-source\">${code.replace(/</g, '<').replace(/>/g, '>')}</pre>`\n el.classList.remove('mermaid-placeholder')\n el.classList.add('mermaid-error-container')\n }\n }\n}\n\n/**\n * 渲染 LaTeX 公式\n * @param latex LaTeX 字符串\n * @param displayMode 是否为块级公式\n * @returns 渲染后的 HTML\n */\nfunction renderLatex(latex: string, displayMode: boolean): string {\n try {\n return katex.renderToString(latex, {\n displayMode,\n throwOnError: false,\n strict: false,\n trust: true,\n output: 'html',\n })\n } catch (error) {\n // 渲染失败时返回原文\n console.warn('LaTeX 渲染失败:', error)\n return displayMode \n ? `<div class=\"latex-error\">$$${latex}$$</div>`\n : `<span class=\"latex-error\">$${latex}$</span>`\n }\n}\n\n/**\n * 渲染 fenced 的 LaTeX 代码块(```latex / ```katex),用于“先代码、后渲染”的视图切换。\n * 注意:这里只返回 HTML 字符串,容器样式由 `.latex-block` 控制。\n */\nexport function renderLatexBlockToHtml(code: string): string {\n // 滚动条样式与系统对齐:直接复用全局 `.chat-scrollbar`\n return `<div class=\"latex-block chat-scrollbar\">${renderLatex(code.trim(), true)}</div>`\n}\n\n/**\n * 预处理文本中的 LaTeX 公式\n * 将 LaTeX 公式替换为占位符,避免被 markdown 解析器处理\n * @param text 原始文本\n * @returns { processed: 处理后文本, placeholders: 占位符映射 }\n */\nfunction preprocessLatex(text: string): { processed: string; placeholders: Map<string, string> } {\n const placeholders = new Map<string, string>()\n let counter = 0\n \n // 生成唯一占位符(使用 HTML 注释格式,避免被 markdown 解析)\n const createPlaceholder = () => {\n const placeholder = `<!--LATEX:${counter++}-->`\n return placeholder\n }\n\n let processed = text\n\n // 处理块级公式 $$...$$ (优先处理,避免和行内公式冲突)\n processed = processed.replace(/\\$\\$([\\s\\S]*?)\\$\\$/g, (_, latex) => {\n const placeholder = createPlaceholder()\n const rendered = renderLatex(latex.trim(), true)\n placeholders.set(placeholder, `<div class=\"latex-block chat-scrollbar\">${rendered}</div>`)\n return placeholder\n })\n\n // 处理块级公式 \\[...\\]\n processed = processed.replace(/\\\\\\[([\\s\\S]*?)\\\\\\]/g, (_, latex) => {\n const placeholder = createPlaceholder()\n const rendered = renderLatex(latex.trim(), true)\n placeholders.set(placeholder, `<div class=\"latex-block chat-scrollbar\">${rendered}</div>`)\n return placeholder\n })\n\n // 处理行内公式 $...$ (单个 $,不能跨行)\n // 注意:避免匹配 $$ 和转义的 \\$\n processed = processed.replace(/(?<!\\$)\\$(?!\\$)((?:\\\\.|[^$\\n])+?)\\$(?!\\$)/g, (_, latex) => {\n const placeholder = createPlaceholder()\n const rendered = renderLatex(latex.trim(), false)\n placeholders.set(placeholder, `<span class=\"latex-inline\">${rendered}</span>`)\n return placeholder\n })\n\n // 处理行内公式 \\(...\\)\n processed = processed.replace(/\\\\\\(([\\s\\S]*?)\\\\\\)/g, (_, latex) => {\n const placeholder = createPlaceholder()\n const rendered = renderLatex(latex.trim(), false)\n placeholders.set(placeholder, `<span class=\"latex-inline\">${rendered}</span>`)\n return placeholder\n })\n\n return { processed, placeholders }\n}\n\n/**\n * 后处理:将占位符替换回渲染后的 LaTeX\n */\nfunction postprocessLatex(html: string, placeholders: Map<string, string>): string {\n let result = html\n placeholders.forEach((rendered, placeholder) => {\n result = result.replace(placeholder, rendered)\n })\n return result\n}\n\n/**\n * 渲染 Markdown 为 HTML\n * @param markdown Markdown 文本\n * @returns 安全的 HTML 字符串\n */\nexport function renderMarkdown(markdown: string): string {\n if (!markdown) return ''\n\n // 预处理 LaTeX 公式\n const { processed, placeholders } = preprocessLatex(markdown)\n\n // 创建自定义渲染器(继承默认渲染器)\n const renderer = new Renderer()\n\n // 自定义代码块渲染(支持 Mermaid)\n renderer.code = (code: string, language?: string) => {\n const lang = (language || 'plaintext').toLowerCase()\n \n // Mermaid 图表:生成占位符,延迟渲染\n if (lang === 'mermaid') {\n // 注意:HTML attribute 会把换行规范化成空格,直接写入会破坏 mermaid 语法\n // 所以这里用 base64(UTF-8) 存储源码,渲染时再解码\n const b64 = encodeBase64Utf8(code)\n return `<div class=\"mermaid-placeholder\" data-mermaid-code-b64=\"${b64}\"><div class=\"mermaid-loading\">加载图表中...</div></div>`\n }\n \n const highlighted = highlightCode(code, language)\n // 滚动条样式与系统对齐:直接复用全局 `.chat-scrollbar`\n return `<pre class=\"markdown-code-block chat-scrollbar\"><code class=\"language-${lang}\">${highlighted}</code></pre>`\n }\n\n // 自定义表格渲染(添加样式类和滚动容器)\n renderer.table = (header: string, body: string) => {\n // 滚动条样式与系统对齐:直接复用全局 `.chat-scrollbar`\n return `<div class=\"markdown-table-wrapper chat-scrollbar\"><table class=\"markdown-table\"><thead>${header}</thead><tbody>${body}</tbody></table></div>`\n }\n\n // 自定义链接渲染(添加 target=\"_blank\" 和 rel=\"noopener noreferrer\")\n renderer.link = (href: string, title: string | null, text: string) => {\n const titleAttr = title ? ` title=\"${title}\"` : ''\n return `<a href=\"${href}\"${titleAttr} target=\"_blank\" rel=\"noopener noreferrer\" class=\"markdown-link\">${text}</a>`\n }\n\n // 自定义列表项渲染,将 checkbox 替换为文本 [x] 或 [ ]\n renderer.listitem = (text: string, task: boolean, checked: boolean) => {\n if (task) {\n // 任务列表:将 checkbox 替换为文本\n const checkboxText = checked ? '[x]' : '[ ]'\n // 移除 marked 生成的 checkbox input,替换为文本\n const textWithoutCheckbox = text.replace(/<input[^>]*>/g, '').trim()\n return `<li class=\"markdown-task-item\"><span class=\"markdown-task-checkbox\">${checkboxText}</span> ${textWithoutCheckbox}</li>`\n }\n // 普通列表项\n return `<li>${text}</li>`\n }\n\n // 配置 marked(marked 12.x 使用新的配置方式)\n marked.setOptions({\n breaks: true, // 支持换行\n gfm: true, // GitHub Flavored Markdown\n })\n\n // 渲染 Markdown(marked 12.x 返回字符串,不是 Promise)\n let html = marked(processed, { renderer }) as string\n\n // 后处理:将占位符替换回渲染后的 LaTeX\n html = postprocessLatex(html, placeholders)\n\n // 使用 DOMPurify 清理 HTML,防止 XSS\n // 允许 KaTeX 生成的 SVG 和相关元素\n html = DOMPurify.sanitize(html, {\n ALLOWED_TAGS: [\n 'p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre',\n 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'ul', 'ol', 'li', 'blockquote',\n 'table', 'thead', 'tbody', 'tr', 'th', 'td',\n 'a', 'img', 'hr',\n 'div', 'span',\n // KaTeX 相关标签\n 'math', 'semantics', 'mrow', 'mi', 'mn', 'mo', 'msup', 'msub',\n 'mfrac', 'mroot', 'msqrt', 'mtext', 'mspace', 'mtable', 'mtr', 'mtd',\n 'annotation', 'mover', 'munder', 'munderover', 'menclose', 'mpadded',\n 'svg', 'line', 'path', 'rect', 'circle', 'g', 'use', 'defs', 'symbol',\n ],\n ALLOWED_ATTR: [\n 'href', 'title', 'target', 'rel', 'class',\n 'src', 'alt', 'width', 'height',\n // Mermaid 相关属性\n 'data-mermaid-code-b64',\n // KaTeX 相关属性\n 'style', 'xmlns', 'viewBox', 'preserveAspectRatio',\n 'd', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'r', 'cx', 'cy',\n 'fill', 'stroke', 'stroke-width', 'transform',\n 'xlink:href', 'aria-hidden', 'focusable', 'role',\n 'mathvariant', 'encoding', 'stretchy', 'fence', 'separator',\n 'lspace', 'rspace', 'minsize', 'maxsize', 'accent', 'accentunder',\n ],\n ADD_ATTR: ['xmlns:xlink'],\n })\n\n return html\n}\n","import { renderMarkdown } from './markdown'\n\nfunction normalizeLanguage(lang?: string): string {\n return (lang || '').trim().toLowerCase()\n}\n\nexport function isMermaidLanguage(lang?: string): boolean {\n return normalizeLanguage(lang) === 'mermaid'\n}\n\nexport function isLatexLanguage(lang?: string): boolean {\n const l = normalizeLanguage(lang)\n return l === 'latex' || l === 'katex' || l === 'tex'\n}\n\nexport function isLatexDocument(code: string): boolean {\n const t = (code || '').trim()\n return /\\\\documentclass\\b|\\\\usepackage\\b|\\\\begin\\{document\\}|\\\\end\\{document\\}/.test(t)\n}\n\nexport function canVisualizeLatex(code: string): boolean {\n const t = (code || '').trim()\n if (!t) return false\n if (isLatexDocument(t)) return false\n return true\n}\n\nexport function hasLatexDelimiters(code: string): boolean {\n const t = (code || '').trim()\n if (!t) return false\n return (\n /\\$\\$[\\s\\S]*\\$\\$/.test(t) ||\n /\\\\\\[[\\s\\S]*\\\\\\]/.test(t) ||\n /\\\\\\([\\s\\S]*\\\\\\)/.test(t) ||\n /(?<!\\$)\\$(?!\\$)[\\s\\S]*?\\$(?!\\$)/.test(t)\n )\n}\n\nexport function shouldShowVisualToggle(lang?: string, code?: string): boolean {\n if (isMermaidLanguage(lang)) return true\n if (isLatexLanguage(lang)) return canVisualizeLatex(code || '')\n return false\n}\n\n/**\n * 将 fenced 的 latex/katex/tex 代码块渲染成 HTML(复用 shared 的 renderMarkdown + latex 预处理)。\n * - 如果是完整 LaTeX 文档:返回提示 + code block(避免 KaTeX 报错)\n * - 如果已经带分隔符:直接渲染(避免二次包裹)\n * - 否则:自动包一层 $$...$$ 作为 display 公式渲染\n */\nexport function renderFencedLatexToHtml(code: string): string {\n const t = (code || '').trim()\n if (!t) return ''\n\n if (!canVisualizeLatex(t)) {\n return renderMarkdown(\n `> 不支持将完整 LaTeX 文档作为公式渲染,请切换到代码视图查看。\\n\\n\\`\\`\\`latex\\n${t}\\n\\`\\`\\``\n )\n }\n\n if (hasLatexDelimiters(t)) {\n return renderMarkdown(t)\n }\n\n return renderMarkdown(`$$\\n${t}\\n$$`)\n}\n\n\n"],"mappings":"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","names":[]}
|
package/dist/styles.css
CHANGED
|
@@ -295,6 +295,8 @@
|
|
|
295
295
|
overflow-x: auto;
|
|
296
296
|
margin: 1em 0;
|
|
297
297
|
border-radius: 8px;
|
|
298
|
+
/* 确保滚动条始终可见(内容超出时) */
|
|
299
|
+
scrollbar-gutter: stable;
|
|
298
300
|
}
|
|
299
301
|
|
|
300
302
|
/* 统一滚动条样式 */
|
|
@@ -316,9 +318,10 @@
|
|
|
316
318
|
background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
|
|
317
319
|
}
|
|
318
320
|
|
|
319
|
-
/*
|
|
320
|
-
.markdown-table {
|
|
321
|
-
width: 100%;
|
|
321
|
+
/* 表格(仅在 markdown 容器内生效,避免影响业务里其它同名 class) */
|
|
322
|
+
.markdown-content .markdown-table {
|
|
323
|
+
min-width: 100%;
|
|
324
|
+
width: max-content;
|
|
322
325
|
border-collapse: separate;
|
|
323
326
|
border-spacing: 0;
|
|
324
327
|
margin: 0;
|
|
@@ -329,11 +332,11 @@
|
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
/* 表头样式 */
|
|
332
|
-
.markdown-table thead {
|
|
335
|
+
.markdown-content .markdown-table thead {
|
|
333
336
|
display: table-header-group;
|
|
334
337
|
}
|
|
335
338
|
|
|
336
|
-
.markdown-table th {
|
|
339
|
+
.markdown-content .markdown-table th {
|
|
337
340
|
padding: 0.875em 1em;
|
|
338
341
|
text-align: left;
|
|
339
342
|
font-weight: 600;
|
|
@@ -344,16 +347,16 @@
|
|
|
344
347
|
background: transparent;
|
|
345
348
|
}
|
|
346
349
|
|
|
347
|
-
.markdown-table th:last-child {
|
|
350
|
+
.markdown-content .markdown-table th:last-child {
|
|
348
351
|
border-right: none;
|
|
349
352
|
}
|
|
350
353
|
|
|
351
354
|
/* 表格内容样式 */
|
|
352
|
-
.markdown-table tbody {
|
|
355
|
+
.markdown-content .markdown-table tbody {
|
|
353
356
|
display: table-row-group;
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
.markdown-table td {
|
|
359
|
+
.markdown-content .markdown-table td {
|
|
357
360
|
padding: 0.875em 1em;
|
|
358
361
|
text-align: left;
|
|
359
362
|
color: var(--chat-text, #ccc);
|
|
@@ -363,53 +366,53 @@
|
|
|
363
366
|
vertical-align: top;
|
|
364
367
|
}
|
|
365
368
|
|
|
366
|
-
.markdown-table td:last-child {
|
|
369
|
+
.markdown-content .markdown-table td:last-child {
|
|
367
370
|
border-right: none;
|
|
368
371
|
}
|
|
369
372
|
|
|
370
|
-
.markdown-table tbody tr:last-child td {
|
|
373
|
+
.markdown-content .markdown-table tbody tr:last-child td {
|
|
371
374
|
border-bottom: none;
|
|
372
375
|
}
|
|
373
376
|
|
|
374
377
|
/* 表格行悬停效果(可选,保持简洁) */
|
|
375
|
-
.markdown-table tbody tr {
|
|
378
|
+
.markdown-content .markdown-table tbody tr {
|
|
376
379
|
transition: opacity 0.15s ease;
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
/* 表格中的图标和特殊内容 */
|
|
380
|
-
.markdown-table td img,
|
|
381
|
-
.markdown-table td svg,
|
|
382
|
-
.markdown-table th img,
|
|
383
|
-
.markdown-table th svg {
|
|
383
|
+
.markdown-content .markdown-table td img,
|
|
384
|
+
.markdown-content .markdown-table td svg,
|
|
385
|
+
.markdown-content .markdown-table th img,
|
|
386
|
+
.markdown-content .markdown-table th svg {
|
|
384
387
|
vertical-align: middle;
|
|
385
388
|
margin-right: 0.5em;
|
|
386
389
|
display: inline-block;
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
/* 表格中的进度条、星级等 */
|
|
390
|
-
.markdown-table td .progress,
|
|
391
|
-
.markdown-table td .stars {
|
|
393
|
+
.markdown-content .markdown-table td .progress,
|
|
394
|
+
.markdown-content .markdown-table td .stars {
|
|
392
395
|
display: inline-flex;
|
|
393
396
|
align-items: center;
|
|
394
397
|
gap: 0.25em;
|
|
395
398
|
}
|
|
396
399
|
|
|
397
400
|
/* 表格中的代码块 */
|
|
398
|
-
.markdown-table td code,
|
|
399
|
-
.markdown-table th code {
|
|
401
|
+
.markdown-content .markdown-table td code,
|
|
402
|
+
.markdown-content .markdown-table th code {
|
|
400
403
|
font-size: 0.85em;
|
|
401
404
|
padding: 0.15em 0.35em;
|
|
402
405
|
}
|
|
403
406
|
|
|
404
407
|
/* 表格响应式 */
|
|
405
408
|
@media (max-width: 768px) {
|
|
406
|
-
.markdown-table {
|
|
409
|
+
.markdown-content .markdown-table {
|
|
407
410
|
font-size: 13px;
|
|
408
411
|
border-radius: 6px;
|
|
409
412
|
}
|
|
410
413
|
|
|
411
|
-
.markdown-table th,
|
|
412
|
-
.markdown-table td {
|
|
414
|
+
.markdown-content .markdown-table th,
|
|
415
|
+
.markdown-content .markdown-table td {
|
|
413
416
|
padding: 0.6em 0.8em;
|
|
414
417
|
}
|
|
415
418
|
}
|
|
@@ -503,3 +506,252 @@
|
|
|
503
506
|
user-select: none;
|
|
504
507
|
opacity: 0.5;
|
|
505
508
|
}
|
|
509
|
+
|
|
510
|
+
/* ============================================
|
|
511
|
+
LaTeX 数学公式样式
|
|
512
|
+
============================================ */
|
|
513
|
+
|
|
514
|
+
/* 块级公式 */
|
|
515
|
+
.latex-block {
|
|
516
|
+
display: block;
|
|
517
|
+
margin: 1em 0;
|
|
518
|
+
padding: 0.75em 1em;
|
|
519
|
+
overflow-x: auto;
|
|
520
|
+
background: var(--chat-latex-bg, rgba(255, 255, 255, 0.02));
|
|
521
|
+
border-radius: 8px;
|
|
522
|
+
border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* LaTeX fenced 代码块渲染:嵌在代码块容器内时由外层负责卡片与 padding,避免套娃 */
|
|
526
|
+
.code-block-wrapper .latex-rendered {
|
|
527
|
+
margin: 0;
|
|
528
|
+
padding: 14px 18px;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.code-block-wrapper .latex-rendered .latex-block {
|
|
532
|
+
margin: 0;
|
|
533
|
+
padding: 0;
|
|
534
|
+
background: transparent;
|
|
535
|
+
border: none;
|
|
536
|
+
border-radius: 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.latex-block .katex-display {
|
|
540
|
+
margin: 0;
|
|
541
|
+
overflow-x: auto;
|
|
542
|
+
overflow-y: hidden;
|
|
543
|
+
text-align: left;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.latex-block .katex {
|
|
547
|
+
text-align: left;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* 行内公式 */
|
|
551
|
+
.latex-inline {
|
|
552
|
+
display: inline;
|
|
553
|
+
padding: 0 0.15em;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/* 公式错误提示 */
|
|
557
|
+
.latex-error {
|
|
558
|
+
color: var(--chat-error, #ef4444);
|
|
559
|
+
background: rgba(239, 68, 68, 0.1);
|
|
560
|
+
padding: 0.2em 0.4em;
|
|
561
|
+
border-radius: 4px;
|
|
562
|
+
font-family: monospace;
|
|
563
|
+
font-size: 0.9em;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* KaTeX 样式覆盖 - 适配暗色主题 */
|
|
567
|
+
.katex {
|
|
568
|
+
font-size: 1.1em;
|
|
569
|
+
color: var(--chat-text, #e5e7eb);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.katex .mord,
|
|
573
|
+
.katex .mop,
|
|
574
|
+
.katex .mrel,
|
|
575
|
+
.katex .mbin,
|
|
576
|
+
.katex .mpunct,
|
|
577
|
+
.katex .mopen,
|
|
578
|
+
.katex .mclose,
|
|
579
|
+
.katex .minner {
|
|
580
|
+
color: inherit;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* KaTeX 分数线颜色 */
|
|
584
|
+
.katex .frac-line {
|
|
585
|
+
background: var(--chat-text, #e5e7eb);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* KaTeX 根号 */
|
|
589
|
+
.katex .sqrt > .sqrt-sign {
|
|
590
|
+
color: var(--chat-text, #e5e7eb);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/* KaTeX 矩阵边框 */
|
|
594
|
+
.katex .mord.mtight,
|
|
595
|
+
.katex .delimsizing {
|
|
596
|
+
color: var(--chat-text, #e5e7eb);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/* ==================== Markdown 滚动条样式(与系统 chat-scrollbar 对齐) ==================== */
|
|
600
|
+
/* 说明:
|
|
601
|
+
* - table/pre/code/latex/mermaid 等在 markdown 中都会产生横向/纵向滚动
|
|
602
|
+
* - 这里统一使用系统变量 --chat-scrollbar / --chat-scrollbar-hover
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
.markdown-content pre::-webkit-scrollbar,
|
|
606
|
+
.text-block pre::-webkit-scrollbar,
|
|
607
|
+
.markdown-code-block::-webkit-scrollbar,
|
|
608
|
+
.markdown-content .markdown-table-wrapper::-webkit-scrollbar,
|
|
609
|
+
.text-block .markdown-table-wrapper::-webkit-scrollbar,
|
|
610
|
+
.latex-block::-webkit-scrollbar,
|
|
611
|
+
.mermaid-rendered::-webkit-scrollbar,
|
|
612
|
+
.mermaid-source::-webkit-scrollbar {
|
|
613
|
+
width: 6px;
|
|
614
|
+
height: 6px;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.markdown-content pre::-webkit-scrollbar-track,
|
|
618
|
+
.text-block pre::-webkit-scrollbar-track,
|
|
619
|
+
.markdown-code-block::-webkit-scrollbar-track,
|
|
620
|
+
.markdown-content .markdown-table-wrapper::-webkit-scrollbar-track,
|
|
621
|
+
.text-block .markdown-table-wrapper::-webkit-scrollbar-track,
|
|
622
|
+
.latex-block::-webkit-scrollbar-track,
|
|
623
|
+
.mermaid-rendered::-webkit-scrollbar-track,
|
|
624
|
+
.mermaid-source::-webkit-scrollbar-track {
|
|
625
|
+
background: transparent;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.markdown-content pre::-webkit-scrollbar-thumb,
|
|
629
|
+
.text-block pre::-webkit-scrollbar-thumb,
|
|
630
|
+
.markdown-code-block::-webkit-scrollbar-thumb,
|
|
631
|
+
.markdown-content .markdown-table-wrapper::-webkit-scrollbar-thumb,
|
|
632
|
+
.text-block .markdown-table-wrapper::-webkit-scrollbar-thumb,
|
|
633
|
+
.latex-block::-webkit-scrollbar-thumb,
|
|
634
|
+
.mermaid-rendered::-webkit-scrollbar-thumb,
|
|
635
|
+
.mermaid-source::-webkit-scrollbar-thumb {
|
|
636
|
+
background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
|
|
637
|
+
border-radius: 3px;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.markdown-content pre::-webkit-scrollbar-thumb:hover,
|
|
641
|
+
.text-block pre::-webkit-scrollbar-thumb:hover,
|
|
642
|
+
.markdown-code-block::-webkit-scrollbar-thumb:hover,
|
|
643
|
+
.markdown-content .markdown-table-wrapper::-webkit-scrollbar-thumb:hover,
|
|
644
|
+
.text-block .markdown-table-wrapper::-webkit-scrollbar-thumb:hover,
|
|
645
|
+
.latex-block::-webkit-scrollbar-thumb:hover,
|
|
646
|
+
.mermaid-rendered::-webkit-scrollbar-thumb:hover,
|
|
647
|
+
.mermaid-source::-webkit-scrollbar-thumb:hover {
|
|
648
|
+
background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* ==================== Mermaid 图表样式 ==================== */
|
|
652
|
+
|
|
653
|
+
/* Mermaid 占位符(加载中) */
|
|
654
|
+
.mermaid-placeholder {
|
|
655
|
+
margin: 1em 0;
|
|
656
|
+
padding: 2em;
|
|
657
|
+
background: var(--chat-muted, #2a2a2a);
|
|
658
|
+
border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.1));
|
|
659
|
+
border-radius: 8px;
|
|
660
|
+
text-align: center;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.mermaid-loading {
|
|
664
|
+
color: var(--chat-text-muted, #888);
|
|
665
|
+
font-size: 0.9em;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* Mermaid 嵌在代码块容器内时:外层已有边框/圆角,这里只做内容区对齐 */
|
|
669
|
+
.code-block-wrapper .mermaid-placeholder,
|
|
670
|
+
.code-block-wrapper .mermaid-rendered,
|
|
671
|
+
.code-block-wrapper .mermaid-error-container {
|
|
672
|
+
margin: 0;
|
|
673
|
+
border: none;
|
|
674
|
+
border-radius: 0;
|
|
675
|
+
background: transparent;
|
|
676
|
+
padding: 14px 18px;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/* 错误态仍然需要明显提示 */
|
|
680
|
+
.code-block-wrapper .mermaid-error-container {
|
|
681
|
+
background: rgba(239, 68, 68, 0.1);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/* Mermaid 渲染后容器 */
|
|
685
|
+
.mermaid-rendered {
|
|
686
|
+
/* Mermaid 通常嵌在消息的代码块容器里,外层已有卡片/边框。
|
|
687
|
+
这里保持最小必要样式:只负责滚动与尺寸适配,避免“套娃边框/多余背景”。 */
|
|
688
|
+
margin: 0.75em 0;
|
|
689
|
+
padding: 0;
|
|
690
|
+
background: transparent;
|
|
691
|
+
border: none;
|
|
692
|
+
border-radius: 0;
|
|
693
|
+
overflow-x: auto;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.mermaid-rendered svg {
|
|
697
|
+
max-width: 100%;
|
|
698
|
+
height: auto;
|
|
699
|
+
display: block;
|
|
700
|
+
margin: 0 auto;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/* Mermaid 错误状态 */
|
|
704
|
+
.mermaid-error-container {
|
|
705
|
+
margin: 1em 0;
|
|
706
|
+
padding: 1em;
|
|
707
|
+
background: rgba(239, 68, 68, 0.1);
|
|
708
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
709
|
+
border-radius: 8px;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.mermaid-error {
|
|
713
|
+
color: #ef4444;
|
|
714
|
+
font-size: 0.9em;
|
|
715
|
+
margin-bottom: 0.5em;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.mermaid-source {
|
|
719
|
+
margin: 0;
|
|
720
|
+
padding: 0.75em;
|
|
721
|
+
background: var(--chat-muted, #1a1a1a);
|
|
722
|
+
border-radius: 4px;
|
|
723
|
+
font-size: 0.8em;
|
|
724
|
+
color: var(--chat-text-muted, #888);
|
|
725
|
+
white-space: pre-wrap;
|
|
726
|
+
word-break: break-all;
|
|
727
|
+
max-height: 200px;
|
|
728
|
+
overflow: auto;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/* Mermaid 深色主题覆盖 */
|
|
732
|
+
.mermaid-rendered .node rect,
|
|
733
|
+
.mermaid-rendered .node circle,
|
|
734
|
+
.mermaid-rendered .node ellipse,
|
|
735
|
+
.mermaid-rendered .node polygon,
|
|
736
|
+
.mermaid-rendered .node path {
|
|
737
|
+
fill: var(--chat-muted, #3a3a3a);
|
|
738
|
+
stroke: var(--chat-border, #555);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.mermaid-rendered .node .label {
|
|
742
|
+
color: var(--chat-text, #e4e4e7);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.mermaid-rendered .edgeLabel {
|
|
746
|
+
background-color: var(--chat-muted, #2a2a2a);
|
|
747
|
+
color: var(--chat-text, #e4e4e7);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.mermaid-rendered .edgePath path {
|
|
751
|
+
stroke: var(--chat-text-muted, #888);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.mermaid-rendered .marker {
|
|
755
|
+
fill: var(--chat-text-muted, #888);
|
|
756
|
+
stroke: var(--chat-text-muted, #888);
|
|
757
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huyooo/ai-chat-shared",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "AI Chat 共享模块 - 内容解析器、类型定义",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,13 +25,17 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"highlight.js": "^11.11.1",
|
|
28
|
+
"katex": "^0.16.11",
|
|
28
29
|
"marked": "^12.0.0",
|
|
29
|
-
"dompurify": "^3.2.0"
|
|
30
|
+
"dompurify": "^3.2.0",
|
|
31
|
+
"mermaid": "^11.4.1"
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
32
34
|
"tsup": "^8.0.1",
|
|
33
35
|
"typescript": "^5.3.3",
|
|
34
|
-
"@types/dompurify": "^3.0.5"
|
|
36
|
+
"@types/dompurify": "^3.0.5",
|
|
37
|
+
"@types/katex": "^0.16.7",
|
|
38
|
+
"@types/mermaid": "^9.2.0"
|
|
35
39
|
},
|
|
36
40
|
"peerDependencies": {},
|
|
37
41
|
"publishConfig": {
|
package/src/index.ts
CHANGED
|
@@ -15,8 +15,6 @@ export type {
|
|
|
15
15
|
TextBlock,
|
|
16
16
|
CodeBlock,
|
|
17
17
|
ContentBlock,
|
|
18
|
-
// 工具渲染器类型
|
|
19
|
-
ToolRendererProps,
|
|
20
18
|
// 内置数据类型
|
|
21
19
|
WeatherData,
|
|
22
20
|
WeatherForecastItem,
|
|
@@ -43,4 +41,19 @@ export {
|
|
|
43
41
|
// Markdown 渲染工具导出
|
|
44
42
|
export {
|
|
45
43
|
renderMarkdown,
|
|
44
|
+
initMermaid,
|
|
45
|
+
renderMermaidDiagrams,
|
|
46
|
+
encodeMermaidCodeToBase64Url,
|
|
47
|
+
renderLatexBlockToHtml,
|
|
46
48
|
} from './markdown'
|
|
49
|
+
|
|
50
|
+
// Mermaid / LaTeX 可视化渲染工具
|
|
51
|
+
export {
|
|
52
|
+
isMermaidLanguage,
|
|
53
|
+
isLatexLanguage,
|
|
54
|
+
isLatexDocument,
|
|
55
|
+
canVisualizeLatex,
|
|
56
|
+
hasLatexDelimiters,
|
|
57
|
+
shouldShowVisualToggle,
|
|
58
|
+
renderFencedLatexToHtml,
|
|
59
|
+
} from './visual'
|