fix: stabilize AI coach chat against truncation and empty replies

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-11 06:27:06 +08:00
parent 7f1015f852
commit 401ee2f130
7 changed files with 111 additions and 32 deletions
+4
View File
@@ -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% {
+30 -7
View File
@@ -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);
}