fix: stabilize AI coach chat against truncation and empty replies
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4327,6 +4327,10 @@ body.hub-page-ai #page-ai {
|
||||
font-style: italic;
|
||||
animation: ai-think-pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
.ai-bubble-error {
|
||||
border-color: color-mix(in srgb, var(--red) 55%, var(--border-soft));
|
||||
color: var(--red);
|
||||
}
|
||||
@keyframes ai-think-pulse {
|
||||
0%,
|
||||
100% {
|
||||
|
||||
@@ -990,10 +990,7 @@
|
||||
btn.classList.toggle("is-active", on);
|
||||
btn.setAttribute("aria-selected", on ? "true" : "false");
|
||||
});
|
||||
if (mobile && active === "chat") {
|
||||
const box = document.getElementById("ai-chat-messages");
|
||||
if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; });
|
||||
}
|
||||
if (mobile && active === "chat") scrollAiChatToEnd();
|
||||
}
|
||||
|
||||
function initAiMobileTabs() {
|
||||
@@ -3271,12 +3268,34 @@
|
||||
].join("");
|
||||
}
|
||||
|
||||
function scrollAiChatToEnd() {
|
||||
const box = document.getElementById("ai-chat-messages");
|
||||
if (!box) return;
|
||||
const run = () => {
|
||||
box.scrollTop = box.scrollHeight;
|
||||
const rows = box.querySelectorAll(".ai-msg-row");
|
||||
const last = rows[rows.length - 1];
|
||||
if (last && last.scrollIntoView) {
|
||||
try {
|
||||
last.scrollIntoView({ block: "end", behavior: "auto" });
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
};
|
||||
requestAnimationFrame(() => requestAnimationFrame(run));
|
||||
}
|
||||
|
||||
function renderAiChatRow(role, content, extraClass, attachments) {
|
||||
const isUser = role === "user";
|
||||
const label = isUser ? "主人" : "AI教练";
|
||||
const rowCls = isUser ? "ai-msg-row-user" : "ai-msg-row-coach";
|
||||
const bubbleCls = isUser ? "ai-bubble-user" : "ai-bubble-assistant";
|
||||
const isThinking = extraClass && String(extraClass).includes("ai-bubble-thinking");
|
||||
const isError =
|
||||
!isUser &&
|
||||
!isThinking &&
|
||||
/^(AI 调用失败|AI 生成失败)/.test(String(content || "").trim());
|
||||
const bubbleInner = isUser || isThinking ? esc(content || "") : renderHubMarkdown(content || "");
|
||||
const mdCls = !isUser && !isThinking ? " ai-result-md" : "";
|
||||
const attList = Array.isArray(attachments) ? attachments : [];
|
||||
@@ -3289,7 +3308,7 @@
|
||||
`<div class="ai-msg-row ${rowCls}">` +
|
||||
`<span class="ai-msg-role">${label}</span>` +
|
||||
`${attHtml}` +
|
||||
`<div class="ai-bubble ${bubbleCls}${mdCls}${extraClass ? " " + extraClass : ""}">${bubbleInner}</div>` +
|
||||
`<div class="ai-bubble ${bubbleCls}${mdCls}${isError ? " ai-bubble-error" : ""}${extraClass ? " " + extraClass : ""}">${bubbleInner}</div>` +
|
||||
`</div>`
|
||||
);
|
||||
}
|
||||
@@ -3327,7 +3346,7 @@
|
||||
html += renderAiChatRow("assistant", "正在思考…", "ai-bubble-thinking");
|
||||
}
|
||||
box.innerHTML = html;
|
||||
box.scrollTop = box.scrollHeight;
|
||||
scrollAiChatToEnd();
|
||||
}
|
||||
|
||||
function setAiChatBusy(busy) {
|
||||
@@ -3452,7 +3471,11 @@
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(String(e), true);
|
||||
renderAiChatMessages(aiChatSessionCache);
|
||||
try {
|
||||
await loadAiChatSession();
|
||||
} catch (_) {
|
||||
renderAiChatMessages(aiChatSessionCache);
|
||||
}
|
||||
} finally {
|
||||
setAiChatBusy(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user