新增结尾补渲逻辑 ensureFinalMermaidRender 并在 finalizeAssistantMessage 中调用(js/core/app-

shell.js:916-1355),即便流式阶段已渲染过,也会在响应完成后使用最终完整代码再渲染一次,同时更新已保
    存的 artifact,解决末尾缺失的问题。
This commit is contained in:
史悦
2025-10-28 10:12:31 +08:00
parent 71cfa133a6
commit e1ffd14bc1

View File

@@ -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');