From 6fe5f4175da99e37c2850cd12648bd297c483cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E6=82=A6?= Date: Fri, 24 Oct 2025 18:20:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86SVG=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E7=BB=98=E5=88=B6=E9=97=AE=E9=A2=98=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用更精确的正则表达式检测SVG代码块开始 正确处理Markdown格式的SVG代码块(svg或产品画布 / SWOT分析 + diff --git a/js/app.js b/js/app.js index 4bd8e40..06f151a 100644 --- a/js/app.js +++ b/js/app.js @@ -2,6 +2,17 @@ * 应用核心逻辑 */ +// 配置Markdown解析器 +if (typeof marked !== 'undefined') { + marked.setOptions({ + breaks: true, // 支持换行 + gfm: true, // 支持GitHub风格的Markdown + sanitize: false, // 允许HTML(因为我们自己处理SVG) + smartLists: true, // 智能列表 + smartypants: true // 智能标点 + }); +} + class ProductCanvasApp { constructor() { this.currentMode = 'canvas'; // 'canvas' 或 'swot' @@ -217,29 +228,30 @@ class ProductCanvasApp { fullContent += content; // 检测SVG开始标记 - if (!svgStarted && (fullContent.includes('```svg') || fullContent.includes('``` -
- -

正在绘制${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'}...

+ if (!svgStarted) { + // 使用正则表达式更准确地检测SVG代码块开始 + const svgStartMatch = fullContent.match(/```(?:svg)?\s*/i); + if (svgStartMatch) { + svgStarted = true; + svgId = Utils.generateId('svg'); + + // 提取SVG开始前的文本 + const svgStartIndex = svgStartMatch.index; + beforeText = fullContent.substring(0, svgStartIndex); + + // 显示绘制中占位符 + this.updateStreamingMessageWithPlaceholder(messageContainer, beforeText, svgId); + + // 初始化SVG显示区域 + this.svgViewer.innerHTML = ` +
+
+ +

正在绘制${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'}...

+
-
- `; + `; + } } // 如果SVG已经开始,收集SVG内容 @@ -249,57 +261,57 @@ class ProductCanvasApp { const svgEndIndex = fullContent.indexOf('') + 6; // +6 是 '' 的长度 // 提取完整的SVG内容 - const svgStartIndex = Math.max( - fullContent.indexOf('```svg'), - fullContent.indexOf('``` ')) { - svgContent += ''; + const svgStartMatch = fullContent.match(/```(?:svg)?\s*/i); + if (svgStartMatch) { + const svgStartIndex = svgStartMatch.index; + let svgWithMarkers = fullContent.substring(svgStartIndex, svgEndIndex); + + // 移除代码块标记 + svgContent = svgWithMarkers.replace(/```(?:svg)?\s*/, '').replace(/```$/, '').trim(); + + // 补全SVG结束标签(如果没有的话) + if (!svgContent.endsWith('')) { + svgContent += ''; + } + + // 实时显示SVG + this.svgViewer.innerHTML = svgContent; + + // 存储SVG内容 + this.svgStorage[this.currentMode][svgId] = { + content: svgContent, + messageId: messageId, + mode: this.currentMode, + timestamp: new Date().toISOString() + }; + + // 更新占位符为可点击状态 + this.updatePlaceholderToClickable(messageContainer, svgId); + + // 重置SVG状态,继续接收剩余文本 + svgStarted = false; + const afterText = fullContent.substring(svgEndIndex); + this.updateStreamingMessageAfterSVG(messageContainer, beforeText, svgId, afterText); } - - // 实时显示SVG - this.svgViewer.innerHTML = svgContent; - - // 存储SVG内容 - this.svgStorage[this.currentMode][svgId] = { - content: svgContent, - messageId: messageId, - mode: this.currentMode, - timestamp: new Date().toISOString() - }; - - // 更新占位符为可点击状态 - this.updatePlaceholderToClickable(messageContainer, svgId); - - // 重置SVG状态,继续接收剩余文本 - svgStarted = false; - const afterText = fullContent.substring(svgEndIndex); - this.updateStreamingMessageAfterSVG(messageContainer, beforeText, svgId, afterText); - } else if (svgContent) { + } else { // SVG还在继续,更新内容 - const svgStartIndex = Math.max( - fullContent.indexOf('```svg'), - fullContent.indexOf('``` ')) { - tempSvgContent += ''; + const svgStartMatch = fullContent.match(/```(?:svg)?\s*/i); + if (svgStartMatch) { + const svgStartIndex = svgStartMatch.index; + let svgWithMarkers = fullContent.substring(svgStartIndex); + + // 移除代码块标记 + svgContent = svgWithMarkers.replace(/```(?:svg)?\s*/, '').replace(/```$/, '').trim(); + + // 补全SVG结束标签以便实时显示 + let tempSvgContent = svgContent; + if (!tempSvgContent.endsWith('')) { + tempSvgContent += ''; + } + + // 实时更新SVG显示 + this.svgViewer.innerHTML = tempSvgContent; } - - // 实时更新SVG显示 - this.svgViewer.innerHTML = tempSvgContent; } } else { // 普通文本更新 @@ -341,17 +353,25 @@ class ProductCanvasApp { updateStreamingMessage(container, content) { const contentDiv = container.querySelector('.typing-cursor'); if (contentDiv) { - contentDiv.textContent = content; + // 使用Markdown解析内容 + if (typeof marked !== 'undefined') { + contentDiv.innerHTML = marked.parse(content); + } else { + contentDiv.textContent = content; + } Utils.scrollToBottom(this.chatHistory); } } // 更新流式消息内容并显示SVG占位符 updateStreamingMessageWithPlaceholder(container, beforeText, svgId) { + // 使用Markdown解析beforeText + const parsedBeforeText = typeof marked !== 'undefined' ? marked.parse(beforeText) : Utils.escapeHtml(beforeText); + container.innerHTML = `
- ${Utils.escapeHtml(beforeText)} + ${parsedBeforeText}
🎨 正在绘制${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'}...
@@ -375,14 +395,18 @@ class ProductCanvasApp { // 更新SVG后的消息内容 updateStreamingMessageAfterSVG(container, beforeText, svgId, afterText) { + // 使用Markdown解析文本 + const parsedBeforeText = typeof marked !== 'undefined' ? marked.parse(beforeText) : Utils.escapeHtml(beforeText); + const parsedAfterText = typeof marked !== 'undefined' ? marked.parse(afterText) : Utils.escapeHtml(afterText); + container.innerHTML = `
- ${Utils.escapeHtml(beforeText)} + ${parsedBeforeText}
📊 点击查看${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
-
${Utils.escapeHtml(afterText)}
+
${parsedAfterText}
`; @@ -412,14 +436,18 @@ class ProductCanvasApp { afterText = fullContent.substring(svgEndIndex); } + // 使用Markdown解析文本 + const parsedBeforeText = typeof marked !== 'undefined' ? marked.parse(beforeText) : Utils.escapeHtml(beforeText); + const parsedAfterText = typeof marked !== 'undefined' ? marked.parse(afterText) : Utils.escapeHtml(afterText); + // 更新容器内容为包含SVG的消息 container.innerHTML = `
- ${Utils.escapeHtml(beforeText)} + ${parsedBeforeText}
📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
- ${Utils.escapeHtml(afterText)} + ${parsedAfterText}
@@ -449,14 +477,18 @@ class ProductCanvasApp { this.viewSVG(newSvgId); + // 使用Markdown解析文本 + const parsedBeforeText = typeof marked !== 'undefined' ? marked.parse(parsed.beforeText) : Utils.escapeHtml(parsed.beforeText); + const parsedAfterText = typeof marked !== 'undefined' ? marked.parse(parsed.afterText) : Utils.escapeHtml(parsed.afterText); + // 更新容器内容为包含SVG的消息 container.innerHTML = `
- ${Utils.escapeHtml(parsed.beforeText)} + ${parsedBeforeText}
📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
- ${Utils.escapeHtml(parsed.afterText)} + ${parsedAfterText}
@@ -471,10 +503,13 @@ class ProductCanvasApp {
`; } else { + // 使用Markdown解析内容 + const parsedContent = typeof marked !== 'undefined' ? marked.parse(fullContent) : Utils.escapeHtml(fullContent); + // 更新容器内容为普通消息 container.innerHTML = `
- ${Utils.escapeHtml(fullContent)} + ${parsedContent}
@@ -629,7 +664,7 @@ class ProductCanvasApp { messageDiv.innerHTML = `
- ${Utils.escapeHtml(message.content)} + ${typeof marked !== 'undefined' ? marked.parse(message.content) : Utils.escapeHtml(message.content)}
@@ -656,11 +691,11 @@ class ProductCanvasApp { messageDiv.innerHTML = `
- ${Utils.escapeHtml(parsed.beforeText)} + ${typeof marked !== 'undefined' ? marked.parse(parsed.beforeText) : Utils.escapeHtml(parsed.beforeText)}
📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
- ${Utils.escapeHtml(parsed.afterText)} + ${typeof marked !== 'undefined' ? marked.parse(parsed.afterText) : Utils.escapeHtml(parsed.afterText)}