Compare commits
4 Commits
dd930805e0
...
e1ffd14bc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1ffd14bc1 | ||
|
|
71cfa133a6 | ||
|
|
7bcfadde59 | ||
|
|
298e421f7d |
@@ -459,3 +459,5 @@ iconify-icon {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#dmermaidSvg{ height: 0px;}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
this.activeStreamHandle = null;
|
this.activeStreamHandle = null;
|
||||||
this.pendingCancel = false;
|
this.pendingCancel = false;
|
||||||
|
this.manualAbortRequested = false;
|
||||||
this.streamState = null;
|
this.streamState = null;
|
||||||
this.echartsInstance = null;
|
this.echartsInstance = null;
|
||||||
this.mermaidPanZoom = null;
|
this.mermaidPanZoom = null;
|
||||||
@@ -374,19 +375,21 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastAiMessageId = null;
|
let lastAssistantLikeId = null;
|
||||||
for (let i = history.length - 1; i >= 0; i -= 1) {
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
||||||
if (history[i].type === 'ai') {
|
if (history[i].type === 'ai' || history[i].type === 'error') {
|
||||||
lastAiMessageId = history[i].id;
|
lastAssistantLikeId = history[i].id;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
history.forEach((message) => {
|
history.forEach((message) => {
|
||||||
|
const isAiMessage = message.type === 'ai';
|
||||||
|
const isAssistantLike = isAiMessage || message.type === 'error';
|
||||||
const bubble = this.buildMessageBubble(message, {
|
const bubble = this.buildMessageBubble(message, {
|
||||||
allowRollback: message.type === 'ai',
|
allowRollback: isAiMessage,
|
||||||
allowRegenerate:
|
allowRegenerate:
|
||||||
message.type === 'ai' && message.id === lastAiMessageId,
|
isAssistantLike && message.id === lastAssistantLikeId,
|
||||||
allowDelete: true
|
allowDelete: true
|
||||||
});
|
});
|
||||||
this.el.chatHistory.appendChild(bubble);
|
this.el.chatHistory.appendChild(bubble);
|
||||||
@@ -591,16 +594,17 @@
|
|||||||
const history = this.conversationService.getHistory(manifest);
|
const history = this.conversationService.getHistory(manifest);
|
||||||
const index = history.findIndex((msg) => msg.id === messageId);
|
const index = history.findIndex((msg) => msg.id === messageId);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
alert('未找到指定的AI消息。');
|
alert('未找到指定的回复。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = history[index];
|
const target = history[index];
|
||||||
if (target.type !== 'ai') {
|
const isAssistantLike = target.type === 'ai' || target.type === 'error';
|
||||||
alert('只能对AI回复执行重新生成。');
|
if (!isAssistantLike) {
|
||||||
|
alert('只能对AI回复或错误提示执行重新生成。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (index !== history.length - 1) {
|
if (index !== history.length - 1) {
|
||||||
alert('请先使用退回功能,确保该AI回复位于对话末尾后再重新生成。');
|
alert('请先使用退回功能,确保该回复位于对话末尾后再重新生成。');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,6 +728,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
beginStreaming(manifest, payload) {
|
beginStreaming(manifest, payload) {
|
||||||
|
this.manualAbortRequested = false;
|
||||||
const messageId = Utils.generateId('msg');
|
const messageId = Utils.generateId('msg');
|
||||||
const container = this.createStreamingContainer(messageId);
|
const container = this.createStreamingContainer(messageId);
|
||||||
this.el.chatHistory.appendChild(container);
|
this.el.chatHistory.appendChild(container);
|
||||||
@@ -745,10 +750,18 @@
|
|||||||
this.el.sendButton.disabled = false;
|
this.el.sendButton.disabled = false;
|
||||||
this.activeStreamHandle = null;
|
this.activeStreamHandle = null;
|
||||||
this.pendingCancel = false;
|
this.pendingCancel = false;
|
||||||
|
const wasManualAbort = this.manualAbortRequested;
|
||||||
|
this.manualAbortRequested = false;
|
||||||
this.streamState = null;
|
this.streamState = null;
|
||||||
|
|
||||||
if (aborted) {
|
if (aborted) {
|
||||||
container.remove();
|
container.remove();
|
||||||
|
if (wasManualAbort) {
|
||||||
|
this.handleManualStreamAbort(manifest, messageId, fullContent);
|
||||||
|
} else {
|
||||||
|
this.ensureActiveArtifact(manifest);
|
||||||
|
this.renderActiveArtifact();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,11 +929,40 @@
|
|||||||
if (artifactId && artifactPayload) {
|
if (artifactId && artifactPayload) {
|
||||||
this.runtime.saveArtifact(manifest.id, artifactId, artifactPayload);
|
this.runtime.saveArtifact(manifest.id, artifactId, artifactPayload);
|
||||||
this.renderArtifact(artifactId);
|
this.renderArtifact(artifactId);
|
||||||
|
if (
|
||||||
|
manifest.artifact?.type === 'mermaid' &&
|
||||||
|
parsedResult?.code
|
||||||
|
) {
|
||||||
|
this.ensureFinalMermaidRender(
|
||||||
|
manifest,
|
||||||
|
artifactId,
|
||||||
|
messageId,
|
||||||
|
parsedResult.code,
|
||||||
|
streamContext?.mermaid || null
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderConversationHistory();
|
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) {
|
parseMarkdownContent(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
if (typeof marked !== 'undefined') {
|
if (typeof marked !== 'undefined') {
|
||||||
@@ -1169,7 +1211,13 @@
|
|||||||
streamState.mermaid = {
|
streamState.mermaid = {
|
||||||
started: false,
|
started: false,
|
||||||
artifactId: null,
|
artifactId: null,
|
||||||
beforeText: ''
|
beforeText: '',
|
||||||
|
renderedCode: null,
|
||||||
|
pendingCode: null,
|
||||||
|
renderLoopPromise: null,
|
||||||
|
codeStartIndex: null,
|
||||||
|
completed: false,
|
||||||
|
finalRendered: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const ctx = streamState.mermaid;
|
const ctx = streamState.mermaid;
|
||||||
@@ -1180,10 +1228,130 @@
|
|||||||
ctx.started = true;
|
ctx.started = true;
|
||||||
ctx.artifactId = ctx.artifactId || Utils.generateId('mermaid');
|
ctx.artifactId = ctx.artifactId || Utils.generateId('mermaid');
|
||||||
ctx.beforeText = fullContent.substring(0, match.index);
|
ctx.beforeText = fullContent.substring(0, match.index);
|
||||||
|
ctx.codeStartIndex = match.index + match[0].length;
|
||||||
|
ctx.finalRendered = false;
|
||||||
this.updateMermaidPlaceholder(streamState.container, manifest, ctx);
|
this.updateMermaidPlaceholder(streamState.container, manifest, ctx);
|
||||||
this.showViewerStreaming(manifest);
|
this.showViewerStreaming(manifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!ctx.started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof ctx.codeStartIndex !== 'number' || ctx.codeStartIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let codeSection = fullContent.substring(ctx.codeStartIndex);
|
||||||
|
const closingFenceIndex = codeSection.indexOf('```');
|
||||||
|
if (closingFenceIndex !== -1) {
|
||||||
|
ctx.completed = true;
|
||||||
|
codeSection = codeSection.substring(0, closingFenceIndex);
|
||||||
|
}
|
||||||
|
const code = codeSection.trim();
|
||||||
|
if (!code || code === ctx.renderedCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.finalRendered = false;
|
||||||
|
this.scheduleMermaidStreamRender(manifest, streamState, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleMermaidStreamRender(manifest, streamState, code) {
|
||||||
|
if (!streamState || !streamState.mermaid) return;
|
||||||
|
const ctx = streamState.mermaid;
|
||||||
|
ctx.pendingCode = code;
|
||||||
|
if (ctx.renderLoopPromise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const renderLoop = async () => {
|
||||||
|
while (ctx.pendingCode && ctx.pendingCode !== ctx.renderedCode) {
|
||||||
|
const nextCode = ctx.pendingCode;
|
||||||
|
ctx.pendingCode = null;
|
||||||
|
try {
|
||||||
|
await this.ensureMermaidReady();
|
||||||
|
window.mermaid.parse(nextCode);
|
||||||
|
const renderId = `mermaidSvg`;
|
||||||
|
const { svg } = await window.mermaid.render(renderId, nextCode);
|
||||||
|
ctx.renderedCode = nextCode;
|
||||||
|
ctx.svgContent = svg;
|
||||||
|
streamState.mermaid.svgContent = svg;
|
||||||
|
streamState.mermaid.code = nextCode;
|
||||||
|
this.destroyMermaidPanZoom();
|
||||||
|
this.renderSvgMarkup(svg, manifest.id, {
|
||||||
|
applyTransform: false,
|
||||||
|
wrapperClasses: ['svg-content-wrapper--mermaid']
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
ctx.lastError = error;
|
||||||
|
console.warn('Mermaid 流式渲染失败,等待更多内容补全:', error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.renderLoopPromise = renderLoop()
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('Mermaid 流式渲染循环异常:', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
ctx.renderLoopPromise = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
updateMermaidPlaceholder(container, manifest, ctx) {
|
||||||
@@ -1238,11 +1406,22 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.destroyMermaidPanZoom();
|
this.destroyMermaidPanZoom();
|
||||||
console.error('Mermaid 渲染失败:', error);
|
console.error('Mermaid 渲染失败:', error);
|
||||||
|
const errorMessage = error.message || '未知错误';
|
||||||
this.el.viewer.innerHTML = `
|
this.el.viewer.innerHTML = `
|
||||||
<div class="p-4 text-center text-red-500 font-bold">
|
<div class="p-4 text-center text-red-500 font-bold">
|
||||||
Mermaid 渲染失败:${Utils.escapeHtml(error.message || '未知错误')}
|
Mermaid 渲染失败:${Utils.escapeHtml(errorMessage)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
if (this.el.chatInput) {
|
||||||
|
const existingValue = this.el.chatInput.value || '';
|
||||||
|
const appendedValue = existingValue.includes(errorMessage)
|
||||||
|
? existingValue
|
||||||
|
: existingValue
|
||||||
|
? `${existingValue}\n${errorMessage}`
|
||||||
|
: errorMessage;
|
||||||
|
this.el.chatInput.value = appendedValue;
|
||||||
|
Utils.autoResizeTextarea(this.el.chatInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1251,12 +1430,21 @@
|
|||||||
return artifact.svgContent;
|
return artifact.svgContent;
|
||||||
}
|
}
|
||||||
await this.ensureMermaidReady();
|
await this.ensureMermaidReady();
|
||||||
const renderId = `mermaid-${artifact.id || Utils.generateId('mermaid')}-${Date.now()}`;
|
|
||||||
const code = artifact.code || artifact.content || '';
|
const code = artifact.code || artifact.content || '';
|
||||||
if (!code.trim()) {
|
if (!code.trim()) {
|
||||||
throw new Error('缺少 Mermaid 代码,无法渲染');
|
throw new Error('缺少 Mermaid 代码,无法渲染');
|
||||||
}
|
}
|
||||||
const { svg } = await window.mermaid.render("mermaidSvg", code);
|
try {
|
||||||
|
window.mermaid.parse(code);
|
||||||
|
} catch (parseError) {
|
||||||
|
const syntaxMessage =
|
||||||
|
parseError?.str || parseError?.message || '未知错误';
|
||||||
|
const error = new Error(`Mermaid 语法错误:${syntaxMessage}`);
|
||||||
|
error.isMermaidSyntaxError = true;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const renderId = `mermaidSvg`;
|
||||||
|
const { svg } = await window.mermaid.render(renderId, code);
|
||||||
const updatedArtifact = {
|
const updatedArtifact = {
|
||||||
...artifact,
|
...artifact,
|
||||||
svgContent: svg
|
svgContent: svg
|
||||||
@@ -1563,6 +1751,7 @@
|
|||||||
|
|
||||||
|
|
||||||
cancelActiveStream() {
|
cancelActiveStream() {
|
||||||
|
this.manualAbortRequested = true;
|
||||||
if (!this.activeStreamHandle || typeof this.activeStreamHandle.cancel !== 'function') {
|
if (!this.activeStreamHandle || typeof this.activeStreamHandle.cancel !== 'function') {
|
||||||
this.pendingCancel = true;
|
this.pendingCancel = true;
|
||||||
this.setSendButtonState('terminating');
|
this.setSendButtonState('terminating');
|
||||||
|
|||||||
@@ -36,3 +36,70 @@
|
|||||||
(技术特长 . '(Mermaid语法 图表设计 可视化 代码优化))
|
(技术特长 . '(Mermaid语法 图表设计 可视化 代码优化))
|
||||||
(工作原则 . '(准确理解 智能选择 规范输出 易于理解))
|
(工作原则 . '(准确理解 智能选择 规范输出 易于理解))
|
||||||
(输出标准 . '(语法正确 结构清晰 美观实用 可直接使用))
|
(输出标准 . '(语法正确 结构清晰 美观实用 可直接使用))
|
||||||
|
|
||||||
|
<失败案例>
|
||||||
|
请避免失败案例
|
||||||
|
```
|
||||||
|
flowchart LR
|
||||||
|
A[需求分析] --> B[系统设计]
|
||||||
|
B --> C[编码实现]
|
||||||
|
C --> D[单元测试]
|
||||||
|
D --> E[集成测试]
|
||||||
|
E --> F[用户验收测试 (UAT)]
|
||||||
|
F --> G[部署与上线]
|
||||||
|
G --> H[系统维护与优化]
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Mermaid 渲染失败:Parse error on line 6: ... E --> F[用户验收测试 (UAT)] F --> G[部 ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
|
||||||
|
```
|
||||||
|
> **正确写法:** `F[用户验收测试 (UAT)]` 应该改为 `F["用户验收测试 (UAT)"]`
|
||||||
|
</失败案例>
|
||||||
|
<失败案例>
|
||||||
|
```
|
||||||
|
classDiagram
|
||||||
|
%% 使用中文作为类名和属性名称,注意使用双引号包裹中文
|
||||||
|
class "用户" {
|
||||||
|
+字符串 "用户名"
|
||||||
|
+字符串 "密码"
|
||||||
|
+登录()
|
||||||
|
+注销()
|
||||||
|
}
|
||||||
|
class "订单" {
|
||||||
|
+整数 "订单编号"
|
||||||
|
+日期 "订单日期"
|
||||||
|
+计算总价()
|
||||||
|
}
|
||||||
|
class "商品" {
|
||||||
|
+字符串 "商品名称"
|
||||||
|
+浮点数 "价格"
|
||||||
|
+库存数量
|
||||||
|
+更新库存()
|
||||||
|
}
|
||||||
|
"用户" --> "订单" : "下单"
|
||||||
|
"订单" *-- "商品" : "包含"
|
||||||
|
```
|
||||||
|
**正确写法:**
|
||||||
|
```
|
||||||
|
classDiagram
|
||||||
|
%% 使用中文作为类名和属性名称,注意使用双引号包裹中文
|
||||||
|
class 用户 {
|
||||||
|
+字符串 "用户名"
|
||||||
|
+字符串 "密码"
|
||||||
|
+登录()
|
||||||
|
+注销()
|
||||||
|
}
|
||||||
|
class 订单 {
|
||||||
|
+整数 "订单编号"
|
||||||
|
+日期 "订单日期"
|
||||||
|
+计算总价()
|
||||||
|
}
|
||||||
|
class 商品 {
|
||||||
|
+字符串 "商品名称"
|
||||||
|
+浮点数 "价格"
|
||||||
|
+库存数量
|
||||||
|
+更新库存()
|
||||||
|
}
|
||||||
|
用户 --> 订单 : "下单"
|
||||||
|
订单 *-- 商品 : "包含"
|
||||||
|
```
|
||||||
|
</失败案例>
|
||||||
Reference in New Issue
Block a user