From e1ffd14bc1ca9366ac150597d746d59c1ce41c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E6=82=A6?= Date: Tue, 28 Oct 2025 10:12:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BB=93=E5=B0=BE=E8=A1=A5?= =?UTF-8?q?=E6=B8=B2=E9=80=BB=E8=BE=91=20ensureFinalMermaidRender=20?= =?UTF-8?q?=E5=B9=B6=E5=9C=A8=20finalizeAssistantMessage=20=E4=B8=AD?= =?UTF-8?q?=E8=B0=83=E7=94=A8=EF=BC=88js/core/app-=20=20=20=20=20shell.js:?= =?UTF-8?q?916-1355=EF=BC=89=EF=BC=8C=E5=8D=B3=E4=BE=BF=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E9=98=B6=E6=AE=B5=E5=B7=B2=E6=B8=B2=E6=9F=93=E8=BF=87=EF=BC=8C?= =?UTF-8?q?=E4=B9=9F=E4=BC=9A=E5=9C=A8=E5=93=8D=E5=BA=94=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=90=8E=E4=BD=BF=E7=94=A8=E6=9C=80=E7=BB=88=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=86=8D=E6=B8=B2=E6=9F=93=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=EF=BC=8C=E5=90=8C=E6=97=B6=E6=9B=B4=E6=96=B0=E5=B7=B2=E4=BF=9D?= =?UTF-8?q?=20=20=20=20=20=E5=AD=98=E7=9A=84=20artifact=EF=BC=8C=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E6=9C=AB=E5=B0=BE=E7=BC=BA=E5=A4=B1=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/core/app-shell.js | 129 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 117 insertions(+), 12 deletions(-) diff --git a/js/core/app-shell.js b/js/core/app-shell.js index b74282c..2091fc6 100644 --- a/js/core/app-shell.js +++ b/js/core/app-shell.js @@ -28,6 +28,7 @@ this.isProcessing = false; this.activeStreamHandle = null; this.pendingCancel = false; + this.manualAbortRequested = false; this.streamState = null; this.echartsInstance = null; this.mermaidPanZoom = null; @@ -374,19 +375,21 @@ return; } - let lastAiMessageId = null; + let lastAssistantLikeId = null; for (let i = history.length - 1; i >= 0; i -= 1) { - if (history[i].type === 'ai') { - lastAiMessageId = history[i].id; + if (history[i].type === 'ai' || history[i].type === 'error') { + lastAssistantLikeId = history[i].id; break; } } history.forEach((message) => { + const isAiMessage = message.type === 'ai'; + const isAssistantLike = isAiMessage || message.type === 'error'; const bubble = this.buildMessageBubble(message, { - allowRollback: message.type === 'ai', + allowRollback: isAiMessage, allowRegenerate: - message.type === 'ai' && message.id === lastAiMessageId, + isAssistantLike && message.id === lastAssistantLikeId, allowDelete: true }); this.el.chatHistory.appendChild(bubble); @@ -591,16 +594,17 @@ const history = this.conversationService.getHistory(manifest); const index = history.findIndex((msg) => msg.id === messageId); if (index === -1) { - alert('未找到指定的AI消息。'); + alert('未找到指定的回复。'); return; } const target = history[index]; - if (target.type !== 'ai') { - alert('只能对AI回复执行重新生成。'); + const isAssistantLike = target.type === 'ai' || target.type === 'error'; + if (!isAssistantLike) { + alert('只能对AI回复或错误提示执行重新生成。'); return; } if (index !== history.length - 1) { - alert('请先使用退回功能,确保该AI回复位于对话末尾后再重新生成。'); + alert('请先使用退回功能,确保该回复位于对话末尾后再重新生成。'); return; } @@ -724,6 +728,7 @@ } beginStreaming(manifest, payload) { + this.manualAbortRequested = false; const messageId = Utils.generateId('msg'); const container = this.createStreamingContainer(messageId); this.el.chatHistory.appendChild(container); @@ -745,10 +750,18 @@ this.el.sendButton.disabled = false; this.activeStreamHandle = null; this.pendingCancel = false; + const wasManualAbort = this.manualAbortRequested; + this.manualAbortRequested = false; this.streamState = null; if (aborted) { container.remove(); + if (wasManualAbort) { + this.handleManualStreamAbort(manifest, messageId, fullContent); + } else { + this.ensureActiveArtifact(manifest); + this.renderActiveArtifact(); + } return; } @@ -916,11 +929,40 @@ if (artifactId && artifactPayload) { this.runtime.saveArtifact(manifest.id, artifactId, artifactPayload); this.renderArtifact(artifactId); + if ( + manifest.artifact?.type === 'mermaid' && + parsedResult?.code + ) { + this.ensureFinalMermaidRender( + manifest, + artifactId, + messageId, + parsedResult.code, + streamContext?.mermaid || null + ); + } } this.renderConversationHistory(); } + handleManualStreamAbort(manifest, messageId, fullContent) { + const content = (fullContent || '').trim(); + const messageRecord = { + id: messageId, + type: 'ai', + content: + content || '生成已被手动终止,您可以点击重新生成继续。', + timestamp: new Date().toISOString(), + artifactId: null, + interrupted: true + }; + this.conversationService.appendMessage(manifest, messageRecord); + this.ensureActiveArtifact(manifest); + this.renderConversationHistory(); + this.renderActiveArtifact(); + } + parseMarkdownContent(text) { if (!text) return ''; if (typeof marked !== 'undefined') { @@ -1174,7 +1216,8 @@ pendingCode: null, renderLoopPromise: null, codeStartIndex: null, - completed: false + completed: false, + finalRendered: false }; } const ctx = streamState.mermaid; @@ -1186,6 +1229,7 @@ ctx.artifactId = ctx.artifactId || Utils.generateId('mermaid'); ctx.beforeText = fullContent.substring(0, match.index); ctx.codeStartIndex = match.index + match[0].length; + ctx.finalRendered = false; this.updateMermaidPlaceholder(streamState.container, manifest, ctx); this.showViewerStreaming(manifest); } @@ -1206,6 +1250,7 @@ if (!code || code === ctx.renderedCode) { return; } + ctx.finalRendered = false; this.scheduleMermaidStreamRender(manifest, streamState, code); } @@ -1250,6 +1295,65 @@ }); } + ensureFinalMermaidRender( + manifest, + artifactId, + messageId, + finalCode, + mermaidState + ) { + const state = mermaidState || {}; + if (state.finalizing) { + return; + } + const alreadyFinal = + state.finalRendered && + state.renderedCode === finalCode && + !!state.svgContent; + if (alreadyFinal) { + return; + } + state.finalizing = true; + (async () => { + try { + await this.ensureMermaidReady(); + window.mermaid.parse(finalCode); + const renderId = `mermaidSvg`; + const { svg } = await window.mermaid.render(renderId, finalCode); + state.renderedCode = finalCode; + state.svgContent = svg; + state.completed = true; + state.finalRendered = true; + const artifacts = + this.runtime.getArtifacts(manifest.id) || {}; + const existing = artifacts[artifactId] || { + id: artifactId, + type: 'mermaid', + messageId + }; + const updatedArtifact = { + ...existing, + code: finalCode, + svgContent: svg + }; + this.runtime.saveArtifact(manifest.id, artifactId, updatedArtifact); + const currentId = + this.runtime.getState(manifest.id)?.currentArtifactId; + if (currentId === artifactId) { + this.destroyMermaidPanZoom(); + this.renderSvgMarkup(svg, manifest.id, { + applyTransform: false, + wrapperClasses: ['svg-content-wrapper--mermaid'] + }); + } + } catch (error) { + console.warn('Mermaid 最终渲染失败:', error); + } finally { + state.finalizing = false; + } + })(); + } + updateMermaidPlaceholder(container, manifest, ctx) { if (!container) return; const beforeHtml = this.parseMarkdownContent(ctx.beforeText || ''); @@ -1326,7 +1430,6 @@ return artifact.svgContent; } await this.ensureMermaidReady(); - //const renderId = `mermaid-${artifact.id || Utils.generateId('mermaid')}-${Date.now()}`; const code = artifact.code || artifact.content || ''; if (!code.trim()) { throw new Error('缺少 Mermaid 代码,无法渲染'); @@ -1340,7 +1443,8 @@ error.isMermaidSyntaxError = true; throw error; } - const { svg } = await window.mermaid.render("mermaidSvg", code); + const renderId = `mermaidSvg`; + const { svg } = await window.mermaid.render(renderId, code); const updatedArtifact = { ...artifact, svgContent: svg @@ -1647,6 +1751,7 @@ cancelActiveStream() { + this.manualAbortRequested = true; if (!this.activeStreamHandle || typeof this.activeStreamHandle.cancel !== 'function') { this.pendingCancel = true; this.setSendButtonState('terminating');