fix(hub): render AI coach summary and chat as Markdown

Reuse shared ai_review_render.js so summaries and coach replies display formatted lists and bold text instead of raw syntax.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-07 00:23:44 +08:00
parent 821e260912
commit 8417784dd8
4 changed files with 110 additions and 14 deletions
+31 -13
View File
@@ -2958,17 +2958,32 @@
return `${n > 0 ? "+" : "-"}${abs}U`;
}
function renderAiMarkdown(text) {
const escLocal = (s) =>
String(s || "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return escLocal(text)
function renderHubMarkdown(text) {
const raw = String(text || "");
if (typeof window !== "undefined" && window.AiReviewRender && window.AiReviewRender.renderMarkdown) {
return window.AiReviewRender.renderMarkdown(raw);
}
return esc(raw)
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
.replace(/\n/g, "<br>");
}
function renderAiMarkdown(text) {
return renderHubMarkdown(text);
}
function setAiSummaryMarkdown(body, contentMd) {
if (!body) return;
body.classList.add("ai-result-md");
body.innerHTML = renderHubMarkdown(contentMd);
}
function setAiSummaryPlaceholder(body, html) {
if (!body) return;
body.classList.remove("ai-result-md");
body.innerHTML = html;
}
function renderAiSummaryStats(snapshot) {
const el = document.getElementById("ai-summary-stats");
if (!el) return;
@@ -2994,10 +3009,13 @@
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 bubbleInner = isUser || isThinking ? esc(content || "") : renderHubMarkdown(content || "");
const mdCls = !isUser && !isThinking ? " ai-result-md" : "";
return (
`<div class="ai-msg-row ${rowCls}">` +
`<span class="ai-msg-role">${label}</span>` +
`<div class="ai-bubble ${bubbleCls}${extraClass ? " " + extraClass : ""}">${esc(content || "")}</div>` +
`<div class="ai-bubble ${bubbleCls}${mdCls}${extraClass ? " " + extraClass : ""}">${bubbleInner}</div>` +
`</div>`
);
}
@@ -3057,7 +3075,7 @@
const j = await r.json();
const latest = j.latest;
if (latest && latest.content_md) {
if (body) body.innerHTML = renderAiMarkdown(latest.content_md);
if (body) setAiSummaryMarkdown(body, latest.content_md);
renderAiSummaryStats(latest.stats_snapshot);
const sm = document.getElementById("ai-summary-meta");
if (sm && latest.generated_at) {
@@ -3065,7 +3083,7 @@
}
}
} catch (e) {
if (body) body.innerHTML = `<p class="ai-placeholder">${esc(String(e))}</p>`;
if (body) setAiSummaryPlaceholder(body, `<p class="ai-placeholder">${esc(String(e))}</p>`);
}
}
@@ -3087,7 +3105,7 @@
const btn = document.getElementById("btn-ai-summary");
const body = document.getElementById("ai-summary-body");
if (btn) btn.disabled = true;
if (body) body.innerHTML = '<p class="ai-placeholder">正在聚合四户数据并生成总结…</p>';
if (body) setAiSummaryPlaceholder(body, '<p class="ai-placeholder">正在聚合四户数据并生成总结…</p>');
try {
const r = await apiFetch("/api/ai/summary/generate", {
method: "POST",
@@ -3099,14 +3117,14 @@
if (!j.ok && j.detail) throw new Error(j.detail);
const sum = j.summary;
if (sum && sum.content_md && body) {
body.innerHTML = renderAiMarkdown(sum.content_md);
setAiSummaryMarkdown(body, sum.content_md);
renderAiSummaryStats(sum.stats_snapshot);
}
showToast(j.cached ? "已是最新上下文,返回缓存总结" : "今日总结已生成");
await loadAiSummary();
} catch (e) {
showToast(String(e), true);
if (body) body.innerHTML = `<p class="ai-placeholder">${esc(String(e))}</p>`;
if (body) setAiSummaryPlaceholder(body, `<p class="ai-placeholder">${esc(String(e))}</p>`);
} finally {
aiSummaryLoading = false;
if (btn) btn.disabled = false;