diff --git a/js/apiclient.js b/js/apiclient.js index 5446aee..a6b7e5c 100644 --- a/js/apiclient.js +++ b/js/apiclient.js @@ -133,7 +133,7 @@ class APIClient { { role: 'user', content: userRequest } ]; - return await this.sendChatMessage(messages, { maxTokens: 3000 }); + return await this.sendChatMessage(messages, { maxTokens: 18000 }); } // 生成SWOT分析的专用方法 @@ -144,7 +144,7 @@ class APIClient { { role: 'user', content: userRequest } ]; - return await this.sendChatMessage(messages, { maxTokens: 3000 }); + return await this.sendChatMessage(messages, { maxTokens: 18000 }); } // 流式生成产品画布 @@ -155,7 +155,7 @@ class APIClient { { role: 'user', content: userRequest } ]; - return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete); + return await this.sendChatMessageStream(messages, { maxTokens: 13000 }, onChunk, onComplete); } // 流式生成SWOT分析 @@ -166,7 +166,7 @@ class APIClient { { role: 'user', content: userRequest } ]; - return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete); + return await this.sendChatMessageStream(messages, { maxTokens: 13000 }, onChunk, onComplete); } // 流式发送聊天请求 diff --git a/js/app.js b/js/app.js index 569ece4..1d4366b 100644 --- a/js/app.js +++ b/js/app.js @@ -320,14 +320,14 @@ class ProductCanvasApp { } }; - const onComplete = () => { - // 流式接收完成,处理完整消息 - this.finalizeStreamingMessage(messageId, fullContent, svgId, beforeText); - - this.isProcessing = false; - this.sendButton.disabled = false; - this.sendButton.innerHTML = ''; - }; + const onComplete = () => { + // 流式接收完成,处理完整消息 + this.finalizeStreamingMessage(messageId, fullContent, svgId); + + this.isProcessing = false; + this.sendButton.disabled = false; + this.sendButton.innerHTML = ''; + }; // 调用流式API if (this.currentMode === 'canvas') { @@ -394,11 +394,11 @@ 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); + // 更新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 = `
@@ -410,12 +410,36 @@ class ProductCanvasApp {
${parsedAfterText}
- `; - Utils.scrollToBottom(this.chatHistory); - } - - // 完成流式消息 - finalizeStreamingMessage(messageId, fullContent, svgId = null, beforeText = '') { + `; + Utils.scrollToBottom(this.chatHistory); + } + + // 组装标准化的SVG消息字符串 + buildSVGMessageContent(beforeText = '', svgBody = '', afterText = '') { + const segments = []; + const trimmedBefore = (beforeText || '').trim(); + const trimmedAfter = (afterText || '').trim(); + const trimmedSvg = (svgBody || '').trim(); + + if (trimmedBefore) { + segments.push(trimmedBefore); + } + + if (trimmedSvg) { + segments.push('```svg'); + segments.push(trimmedSvg); + segments.push('```'); + } + + if (trimmedAfter) { + segments.push(trimmedAfter); + } + + return segments.join('\n\n').trim(); + } + + // 完成流式消息 + finalizeStreamingMessage(messageId, fullContent, svgId = null) { let container = document.querySelector(`.chat-bubble-ai[data-message-id="${messageId}"]`); if (!container) { const fallback = document.querySelector(`[data-message-id="${messageId}"]`); @@ -426,121 +450,97 @@ class ProductCanvasApp { } } if (!container) return; - - const message = { - id: messageId, - type: 'ai', - content: fullContent, - timestamp: new Date().toISOString() - }; - - this.conversationHistory[this.currentMode].push(message); - - // 如果已经有SVG ID(从流式处理中获得),直接使用 - if (svgId && this.svgStorage[this.currentMode][svgId]) { - // 提取SVG后的文本 - let afterText = ''; - if (fullContent.includes('')) { - const svgEndIndex = fullContent.indexOf('') + 6; - 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 = ` -
- ${parsedBeforeText} -
- 📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG -
- ${parsedAfterText} -
- -
- - -
- `; - } else { - // 使用原有的解析方法作为后备 - const parsed = Utils.parseSVGResponse(fullContent); - - // 如果包含SVG,存储SVG内容 - if (parsed.svgContent) { - const newSvgId = Utils.generateId('svg'); - this.svgStorage[this.currentMode][newSvgId] = { - content: parsed.svgContent, - messageId: messageId, - mode: this.currentMode, - timestamp: new Date().toISOString() - }; - - 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 = ` -
- ${parsedBeforeText} -
- 📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG -
- ${parsedAfterText} -
- -
- - -
- `; - } else { - // 使用Markdown解析内容 - const parsedContent = typeof marked !== 'undefined' ? marked.parse(fullContent) : Utils.escapeHtml(fullContent); - - // 更新容器内容为普通消息 - container.innerHTML = ` -
- ${parsedContent} -
- -
- - -
- `; - } - } - - // 保存数据 - Utils.storage.set('canvasHistory', this.conversationHistory.canvas); - Utils.storage.set('swotHistory', this.conversationHistory.swot); - Utils.storage.set('canvasSVGs', this.svgStorage.canvas); - Utils.storage.set('swotSVGs', this.svgStorage.swot); - } + + const parsed = Utils.parseSVGResponse(fullContent); + const timestamp = new Date().toISOString(); + + const message = { + id: messageId, + type: 'ai', + content: '', + timestamp + }; + + container.classList.remove('streaming-text'); + container.setAttribute('data-message-id', messageId); + + const actionFooter = ` +
+ + +
+ `; + + if (parsed.svgContent && parsed.svgContent.includes('') + ? parsed.svgContent.trim() + : `${parsed.svgContent.trim()}\n`; + + let targetSvgId = svgId || null; + if (!targetSvgId || !this.svgStorage[this.currentMode][targetSvgId]) { + targetSvgId = targetSvgId || Utils.generateId('svg'); + } + + this.svgStorage[this.currentMode][targetSvgId] = { + content: svgBody, + messageId, + mode: this.currentMode, + timestamp + }; + + const beforeHtml = parsed.beforeText + ? (typeof marked !== 'undefined' ? marked.parse(parsed.beforeText) : Utils.escapeHtml(parsed.beforeText)) + : ''; + const afterHtml = parsed.afterText + ? (typeof marked !== 'undefined' ? marked.parse(parsed.afterText) : Utils.escapeHtml(parsed.afterText)) + : ''; + + container.innerHTML = ` +
+ ${beforeHtml} +
+ 📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG +
+ ${afterHtml} +
+ ${actionFooter} + `; + + this.currentSvgId = targetSvgId; + this.svgViewer.innerHTML = svgBody; + + message.content = this.buildSVGMessageContent(parsed.beforeText, svgBody, parsed.afterText); + } else { + const sanitizedText = fullContent.replace(/^\s*```/, '').replace(/```$/, '').trim(); + const parsedContent = sanitizedText + ? (typeof marked !== 'undefined' ? marked.parse(sanitizedText) : Utils.escapeHtml(sanitizedText)) + : ''; + + container.innerHTML = ` +
+ ${parsedContent} +
+ ${actionFooter} + `; + + message.content = sanitizedText; + } + + this.conversationHistory[this.currentMode].push(message); + + // 保存数据 + Utils.storage.set('canvasHistory', this.conversationHistory.canvas); + Utils.storage.set('swotHistory', this.conversationHistory.swot); + Utils.storage.set('canvasSVGs', this.svgStorage.canvas); + Utils.storage.set('swotSVGs', this.svgStorage.swot); + Utils.scrollToBottom(this.chatHistory); + } // 清空当前对话 clearCurrentConversation() { diff --git a/js/utils.js b/js/utils.js index e2f15de..02b2b2b 100644 --- a/js/utils.js +++ b/js/utils.js @@ -21,29 +21,63 @@ function generateId(prefix = 'id') { return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } -// 解析SVG响应,提取SVG内容和前后文本 -function parseSVGResponse(response) { - const svgRegex = /```svg\s*([\s\S]*?)```/i; - const match = response.match(svgRegex); - - if (match) { - const svgContent = match[1].trim(); - const beforeText = response.substring(0, match.index).trim(); - const afterText = response.substring(match.index + match[0].length).trim(); - - return { - svgContent, - beforeText, - afterText - }; - } - - return { - svgContent: null, - beforeText: response, - afterText: '' - }; -} +// 解析SVG响应,提取SVG内容和前后文本,容错缺失的结束反引号 +function parseSVGResponse(response = '') { + const content = typeof response === 'string' ? response : String(response || ''); + const svgFenceRegex = /```(?:svg)?\s*([\s\S]*?)```/i; + const fenceMatch = content.match(svgFenceRegex); + + if (fenceMatch) { + const svgBody = fenceMatch[1].trim(); + const beforeText = content.substring(0, fenceMatch.index).trim(); + let afterText = content.substring(fenceMatch.index + fenceMatch[0].length).trim(); + afterText = afterText.replace(/^\s*```/, '').trim(); + + return { + svgContent: svgBody, + beforeText, + afterText + }; + } + + // 兼容缺失结束反引号的情况 + const svgStartRegex = /```(?:svg)?\s*'); + if (svgEndIndex !== -1) { + afterText = svgSection.substring(svgEndIndex + 6).replace(/```/, '').trim(); + svgSection = svgSection.substring(0, svgEndIndex + 6).trim(); + } + + // 补齐缺失的结束标签 + if (svgSection && !svgSection.endsWith('')) { + svgSection += '\n'; + } + + return { + svgContent: svgSection || null, + beforeText, + afterText + }; + } + + return { + svgContent: null, + beforeText: content.trim(), + afterText: '' + }; +} // 下载文件 function downloadFile(content, filename, mimeType = 'text/plain') { @@ -286,4 +320,4 @@ window.Utils = { autoResizeTextarea, StreamProcessor, createStreamRequest -}; \ No newline at end of file +};