577 lines
25 KiB
JavaScript
577 lines
25 KiB
JavaScript
window.XiaobaiPageModules.register("mentor", ["mentorView"], {
|
||
bind: bindMentorEvents,
|
||
enter: ["loadMentor"],
|
||
});
|
||
|
||
async function loadMentorSetup(force = false) {
|
||
const requestedDate = elements.tradeDate.value.replaceAll("-", "");
|
||
if (!force && state.mentorSetup?.requestedDate === requestedDate) {
|
||
renderMentorWorkspace();
|
||
return;
|
||
}
|
||
try {
|
||
const query = new URLSearchParams({ trade_date: elements.tradeDate.value });
|
||
const payload = await apiRequest(`/api/mentors/setup?${query}`);
|
||
payload.requestedDate = requestedDate;
|
||
if (!payload.preferences_configured) {
|
||
payload.mentors.sort((first, second) => {
|
||
if (Boolean(first.private) !== Boolean(second.private)) return first.private ? -1 : 1;
|
||
return String(first.name || "").localeCompare(String(second.name || ""), "zh-CN");
|
||
});
|
||
payload.mentors.forEach((mentor, index) => { mentor.sort_order = index; });
|
||
}
|
||
state.mentorSetup = payload;
|
||
const selectedExists = payload.mentors.some((item) => item.id === state.selectedMentorId);
|
||
state.selectedMentorId = selectedExists ? state.selectedMentorId : payload.mentors[0]?.id || "";
|
||
state.mentorMessages = await loadMentorMessages();
|
||
renderMentorWorkspace();
|
||
} catch (error) {
|
||
showMentorNotice(error.message || "问师模块加载失败");
|
||
showToast(error.message || "问师模块加载失败");
|
||
}
|
||
}
|
||
|
||
function renderMentorWorkspace() {
|
||
const setup = state.mentorSetup;
|
||
if (!setup) return;
|
||
const selected = setup.mentors.find((item) => item.id === state.selectedMentorId) || null;
|
||
setText("mentorDataDate", `数据日期 ${displayCompactDate(setup.trade_date)}`);
|
||
setText("mentorProfileName", selected?.name || "--");
|
||
setText("mentorProfileTagline", selected?.tagline || selected?.description || "--");
|
||
setText("mentorProfileSource", selected?.evidence?.label || "公开资料整理");
|
||
setText("mentorProfileDataDate", `行情数据 ${displayCompactDate(setup.trade_date)}`);
|
||
setText("mentorProfileAvatar", mentorAvatarText(selected));
|
||
document.querySelector("#mentorProfileAvatar").dataset.grade = String(selected?.evidence?.grade || "").toLowerCase();
|
||
document.querySelector("#mentorProfileBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
||
setText("activeMentorEvidence", selected?.evidence?.note || selected?.description || "--");
|
||
document.querySelector("#activeMentorFocus").innerHTML = (selected?.focus || []).slice(0, 4)
|
||
.map((item) => `<span>${escapeHtml(item)}</span>`).join("");
|
||
renderMentorDirectory();
|
||
renderMentorMessages();
|
||
}
|
||
|
||
function renderMentorDirectory() {
|
||
const mentors = state.mentorSetup?.mentors || [];
|
||
const query = state.mentorQuery;
|
||
const filtered = mentors.filter((mentor) => {
|
||
if (state.mentorSortMode) return true;
|
||
if (state.mentorGrade !== "all" && mentor.evidence?.grade !== state.mentorGrade) return false;
|
||
if (!query) return true;
|
||
const haystack = [
|
||
mentor.name,
|
||
mentor.description,
|
||
mentor.tagline,
|
||
mentor.evidence?.label,
|
||
mentor.evidence?.note,
|
||
...(mentor.focus || []),
|
||
].filter(Boolean).join(" ").toLocaleLowerCase("zh-CN");
|
||
return haystack.includes(query);
|
||
});
|
||
setText("mentorCount", filtered.length === mentors.length ? `${mentors.length} 位` : `${filtered.length} / ${mentors.length} 位`);
|
||
const sortToggle = document.querySelector("#mentorSortToggle");
|
||
sortToggle.classList.toggle("active", state.mentorSortMode);
|
||
sortToggle.setAttribute("aria-pressed", String(state.mentorSortMode));
|
||
sortToggle.querySelector("span").textContent = state.mentorSortMode ? "完成" : "整理";
|
||
document.querySelector("#mentorSortHint").hidden = !state.mentorSortMode;
|
||
document.querySelector("#mentorSearchInput").disabled = state.mentorSortMode;
|
||
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
||
button.disabled = state.mentorSortMode;
|
||
});
|
||
const container = document.querySelector("#mentorList");
|
||
container.classList.toggle("is-sorting", state.mentorSortMode);
|
||
container.innerHTML = filtered.map((mentor) => {
|
||
const group = mentors.filter((item) => Boolean(item.pinned) === Boolean(mentor.pinned));
|
||
const groupIndex = group.findIndex((item) => item.id === mentor.id);
|
||
return `
|
||
<article class="mentor-option ${mentor.id === state.selectedMentorId ? "active" : ""} ${mentor.pinned ? "is-pinned" : ""}"
|
||
data-mentor-card="${escapeHtml(mentor.id)}" draggable="${state.mentorSortMode && !state.mentorSavingPreferences}">
|
||
<button type="button" class="mentor-option-main" data-mentor-id="${escapeHtml(mentor.id)}" aria-pressed="${mentor.id === state.selectedMentorId}" ${state.mentorLoading ? "disabled" : ""}>
|
||
<span class="mentor-avatar" data-grade="${escapeHtml(String(mentor.evidence?.grade || "").toLowerCase())}" aria-hidden="true">${escapeHtml(mentorAvatarText(mentor))}</span>
|
||
<span class="mentor-option-copy">
|
||
<span class="mentor-option-heading">
|
||
<strong>${escapeHtml(mentor.name)}</strong>
|
||
<span class="mentor-option-badges">${renderMentorBadges(mentor)}</span>
|
||
</span>
|
||
<em title="${escapeHtml(mentor.description || "")}">${escapeHtml(mentor.description || mentor.tagline || "思维模型")}</em>
|
||
<span class="mentor-option-meta">${escapeHtml((mentor.focus || [])[0] || mentor.evidence?.label || "公开资料模型")}</span>
|
||
</span>
|
||
</button>
|
||
<span class="mentor-option-tools">
|
||
<button type="button" class="mentor-pin-button ${mentor.pinned ? "active" : ""}" data-mentor-pin="${escapeHtml(mentor.id)}"
|
||
aria-label="${mentor.pinned ? "取消置顶" : "置顶"}${escapeHtml(mentor.name)}" title="${mentor.pinned ? "取消置顶" : "置顶"}" ${state.mentorSavingPreferences ? "disabled" : ""}>
|
||
<i data-lucide="pin"></i>
|
||
</button>
|
||
${state.mentorSortMode ? `
|
||
<button type="button" class="mentor-order-button" data-mentor-move="up" data-mentor-target="${escapeHtml(mentor.id)}" aria-label="上移${escapeHtml(mentor.name)}" title="上移" ${groupIndex <= 0 || state.mentorSavingPreferences ? "disabled" : ""}><i data-lucide="chevron-up"></i></button>
|
||
<button type="button" class="mentor-order-button" data-mentor-move="down" data-mentor-target="${escapeHtml(mentor.id)}" aria-label="下移${escapeHtml(mentor.name)}" title="下移" ${groupIndex >= group.length - 1 || state.mentorSavingPreferences ? "disabled" : ""}><i data-lucide="chevron-down"></i></button>
|
||
` : ""}
|
||
</span>
|
||
</article>
|
||
`;
|
||
}).join("");
|
||
document.querySelector("#mentorListEmpty").hidden = filtered.length > 0;
|
||
document.querySelectorAll("[data-mentor-id]").forEach((button) => {
|
||
button.addEventListener("click", () => selectMentor(button.dataset.mentorId));
|
||
});
|
||
document.querySelectorAll("[data-mentor-pin]").forEach((button) => {
|
||
button.addEventListener("click", () => toggleMentorPin(button.dataset.mentorPin));
|
||
});
|
||
document.querySelectorAll("[data-mentor-move]").forEach((button) => {
|
||
button.addEventListener("click", () => moveMentor(button.dataset.mentorTarget, button.dataset.mentorMove));
|
||
});
|
||
document.querySelectorAll("[data-mentor-card]").forEach((card) => {
|
||
card.addEventListener("dragstart", handleMentorDragStart);
|
||
card.addEventListener("dragover", handleMentorDragOver);
|
||
card.addEventListener("drop", handleMentorDrop);
|
||
card.addEventListener("dragend", clearMentorDragState);
|
||
});
|
||
refreshIcons();
|
||
}
|
||
|
||
function toggleMentorSortMode() {
|
||
state.mentorSortMode = !state.mentorSortMode;
|
||
if (state.mentorSortMode) {
|
||
state.mentorQuery = "";
|
||
state.mentorGrade = "all";
|
||
document.querySelector("#mentorSearchInput").value = "";
|
||
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
||
button.classList.toggle("active", button.dataset.mentorGrade === "all");
|
||
});
|
||
}
|
||
renderMentorDirectory();
|
||
}
|
||
|
||
async function toggleMentorPin(mentorId) {
|
||
if (state.mentorSavingPreferences) return;
|
||
const mentors = state.mentorSetup?.mentors || [];
|
||
const index = mentors.findIndex((item) => item.id === mentorId);
|
||
if (index < 0) return;
|
||
const [mentor] = mentors.splice(index, 1);
|
||
mentor.pinned = !mentor.pinned;
|
||
if (mentor.pinned) {
|
||
mentors.unshift(mentor);
|
||
} else {
|
||
const firstUnpinned = mentors.findIndex((item) => !item.pinned);
|
||
mentors.splice(firstUnpinned < 0 ? mentors.length : firstUnpinned, 0, mentor);
|
||
}
|
||
normalizeMentorOrder();
|
||
renderMentorWorkspace();
|
||
await persistMentorPreferences();
|
||
}
|
||
|
||
async function moveMentor(mentorId, direction) {
|
||
if (state.mentorSavingPreferences) return;
|
||
const mentors = state.mentorSetup?.mentors || [];
|
||
const index = mentors.findIndex((item) => item.id === mentorId);
|
||
if (index < 0) return;
|
||
const step = direction === "up" ? -1 : 1;
|
||
const targetIndex = index + step;
|
||
if (targetIndex < 0 || targetIndex >= mentors.length) return;
|
||
if (Boolean(mentors[index].pinned) !== Boolean(mentors[targetIndex].pinned)) return;
|
||
[mentors[index], mentors[targetIndex]] = [mentors[targetIndex], mentors[index]];
|
||
normalizeMentorOrder();
|
||
renderMentorDirectory();
|
||
await persistMentorPreferences();
|
||
}
|
||
|
||
function handleMentorDragStart(event) {
|
||
if (!state.mentorSortMode || state.mentorSavingPreferences) {
|
||
event.preventDefault();
|
||
return;
|
||
}
|
||
state.mentorDragId = event.currentTarget.dataset.mentorCard || "";
|
||
event.dataTransfer.effectAllowed = "move";
|
||
event.dataTransfer.setData("text/plain", state.mentorDragId);
|
||
event.currentTarget.classList.add("is-dragging");
|
||
}
|
||
|
||
function handleMentorDragOver(event) {
|
||
const source = state.mentorSetup?.mentors.find((item) => item.id === state.mentorDragId);
|
||
const target = state.mentorSetup?.mentors.find((item) => item.id === event.currentTarget.dataset.mentorCard);
|
||
if (!source || !target || Boolean(source.pinned) !== Boolean(target.pinned)) return;
|
||
event.preventDefault();
|
||
event.dataTransfer.dropEffect = "move";
|
||
event.currentTarget.classList.add("is-drag-over");
|
||
}
|
||
|
||
async function handleMentorDrop(event) {
|
||
event.preventDefault();
|
||
const sourceId = state.mentorDragId || event.dataTransfer.getData("text/plain");
|
||
const targetId = event.currentTarget.dataset.mentorCard || "";
|
||
clearMentorDragState();
|
||
if (!sourceId || !targetId || sourceId === targetId) return;
|
||
const mentors = state.mentorSetup?.mentors || [];
|
||
const sourceIndex = mentors.findIndex((item) => item.id === sourceId);
|
||
const targetIndex = mentors.findIndex((item) => item.id === targetId);
|
||
if (sourceIndex < 0 || targetIndex < 0) return;
|
||
if (Boolean(mentors[sourceIndex].pinned) !== Boolean(mentors[targetIndex].pinned)) return;
|
||
const [mentor] = mentors.splice(sourceIndex, 1);
|
||
const insertionIndex = mentors.findIndex((item) => item.id === targetId);
|
||
mentors.splice(insertionIndex, 0, mentor);
|
||
normalizeMentorOrder();
|
||
renderMentorDirectory();
|
||
await persistMentorPreferences();
|
||
}
|
||
|
||
function clearMentorDragState() {
|
||
state.mentorDragId = "";
|
||
document.querySelectorAll(".mentor-option.is-dragging, .mentor-option.is-drag-over").forEach((item) => {
|
||
item.classList.remove("is-dragging", "is-drag-over");
|
||
});
|
||
}
|
||
|
||
function normalizeMentorOrder() {
|
||
(state.mentorSetup?.mentors || []).forEach((mentor, index) => {
|
||
mentor.sort_order = index;
|
||
});
|
||
}
|
||
|
||
async function persistMentorPreferences() {
|
||
const mentors = state.mentorSetup?.mentors || [];
|
||
state.mentorSavingPreferences = true;
|
||
renderMentorDirectory();
|
||
try {
|
||
await apiRequest("/api/mentors/preferences", "POST", {
|
||
order: mentors.map((item) => item.id),
|
||
pinned: mentors.filter((item) => item.pinned).map((item) => item.id),
|
||
});
|
||
} catch (error) {
|
||
showToast(error.message || "问师顺序保存失败");
|
||
await loadMentorSetup(true);
|
||
} finally {
|
||
state.mentorSavingPreferences = false;
|
||
renderMentorDirectory();
|
||
}
|
||
}
|
||
|
||
function renderMentorBadges(mentor, expanded = false) {
|
||
const badges = [];
|
||
if (mentor.private) {
|
||
badges.push('<span class="mentor-badge private" title="仅管理员本人可见"><i data-lucide="lock-keyhole"></i>仅自己</span>');
|
||
}
|
||
const grade = mentor.evidence?.grade;
|
||
if (grade) {
|
||
badges.push(`<span class="mentor-badge evidence grade-${escapeHtml(grade.toLowerCase())}" title="${escapeHtml(mentor.evidence?.note || "素材等级")}">${escapeHtml(grade)}</span>`);
|
||
}
|
||
return badges.join("");
|
||
}
|
||
|
||
function mentorAvatarText(mentor) {
|
||
return Array.from(String(mentor?.name || "师").trim())[0] || "师";
|
||
}
|
||
|
||
async function selectMentor(mentorId) {
|
||
if (mentorId === state.selectedMentorId) return;
|
||
state.selectedMentorId = mentorId;
|
||
state.mentorMessages = [];
|
||
hideMentorNotice();
|
||
renderMentorWorkspace();
|
||
state.mentorMessages = await loadMentorMessages();
|
||
renderMentorMessages();
|
||
}
|
||
|
||
function renderMentorMessages() {
|
||
const container = document.querySelector("#mentorMessages");
|
||
const selected = state.mentorSetup?.mentors.find((item) => item.id === state.selectedMentorId);
|
||
if (!state.mentorMessages.length && !state.mentorLoading) {
|
||
container.innerHTML = `
|
||
<div class="mentor-empty-state">
|
||
<span class="mentor-empty-mark" aria-hidden="true"><i data-lucide="messages-square"></i></span>
|
||
<strong>向「${escapeHtml(selected?.name || "问师")}」请教</strong>
|
||
<p>${escapeHtml(selected?.tagline || selected?.description || "从一个具体问题开始对话")}</p>
|
||
</div>
|
||
`;
|
||
refreshIcons();
|
||
} else {
|
||
container.innerHTML = state.mentorMessages.map((message) => `
|
||
<article class="mentor-message ${message.role} ${message.error ? "is-error" : ""}">
|
||
<span class="mentor-message-avatar" data-grade="${message.role === "assistant" ? escapeHtml(String(selected?.evidence?.grade || "").toLowerCase()) : ""}" aria-hidden="true">${message.role === "user" ? "我" : escapeHtml(mentorAvatarText(selected))}</span>
|
||
<div class="mentor-message-body">
|
||
<div class="mentor-message-label">${message.role === "user" ? "我" : escapeHtml(selected?.name || "问师")}</div>
|
||
${message.role === "assistant"
|
||
? `<div class="mentor-message-stack">${message.content
|
||
? formatMentorAnswer(message.content)
|
||
: '<div class="mentor-message-content"><p class="mentor-loading-copy">正在读取复盘数据并推演...</p></div>'}</div>`
|
||
: `<div class="mentor-message-content">${escapeHtml(message.content)}</div>`}
|
||
${message.streaming ? '<span class="assistant-stream-caret" aria-hidden="true"></span>' : ""}
|
||
${renderMentorFollowUps(message)}
|
||
${message.meta && !message.streaming ? `<small>${escapeHtml(message.meta)}</small>` : ""}
|
||
</div>
|
||
</article>
|
||
`).join("");
|
||
}
|
||
document.querySelector("#mentorQuickPrompts").hidden = state.mentorMessages.length > 0 || state.mentorLoading;
|
||
document.querySelector("#clearMentorChatButton").disabled = !state.mentorMessages.length || state.mentorLoading;
|
||
document.querySelector("#mentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
|
||
document.querySelector("#sendMentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
|
||
document.querySelector("#sendMentorQuestion").hidden = state.mentorLoading;
|
||
document.querySelector("#stopMentorQuestion").hidden = !state.mentorLoading;
|
||
document.querySelector("#mentorSortToggle").disabled = state.mentorLoading;
|
||
setText("activeMentorStatus", state.mentorLoading ? "正在生成回答..." : "思维模型已就绪");
|
||
container.querySelectorAll("[data-mentor-follow-up]").forEach((button) => {
|
||
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorFollowUp));
|
||
});
|
||
refreshIcons();
|
||
requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; });
|
||
}
|
||
|
||
function renderMentorFollowUps(message) {
|
||
if (message.role !== "assistant" || message.streaming || message.error || !Array.isArray(message.followUps)) return "";
|
||
const items = message.followUps.filter(Boolean).slice(0, 3);
|
||
if (items.length < 2) return "";
|
||
return `
|
||
<div class="mentor-follow-ups" aria-label="继续追问">
|
||
<span>继续追问</span>
|
||
${items.map((item) => `<button type="button" data-mentor-follow-up="${escapeHtml(item)}"><i data-lucide="corner-down-right"></i><span>${escapeHtml(item)}</span></button>`).join("")}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
async function sendMentorQuestion(event) {
|
||
event.preventDefault();
|
||
if (state.mentorLoading || !state.selectedMentorId) return;
|
||
const input = document.querySelector("#mentorQuestion");
|
||
const question = input.value.trim();
|
||
if (!question) return;
|
||
const history = state.mentorMessages.slice(-6).map((item) => ({
|
||
role: item.role,
|
||
content: item.content.slice(0, 3500),
|
||
}));
|
||
state.mentorMessages.forEach((message) => { delete message.followUps; });
|
||
state.mentorMessages.push({ role: "user", content: question });
|
||
const responseMessage = { role: "assistant", content: "", streaming: true, meta: "", followUps: [] };
|
||
state.mentorMessages.push(responseMessage);
|
||
input.value = "";
|
||
state.mentorLoading = true;
|
||
state.mentorController = new AbortController();
|
||
hideMentorNotice();
|
||
renderMentorMessages();
|
||
renderMentorDirectory();
|
||
setStatus("问师正在读取复盘数据");
|
||
try {
|
||
await streamMentorRequest(
|
||
{
|
||
mentor_id: state.selectedMentorId,
|
||
trade_date: elements.tradeDate.value,
|
||
question,
|
||
history,
|
||
},
|
||
state.mentorController.signal,
|
||
(chunk) => {
|
||
responseMessage.content += chunk;
|
||
scheduleMentorRender();
|
||
},
|
||
(meta) => {
|
||
responseMessage.meta = `${displayCompactDate(meta.data_trade_date || elements.tradeDate.value)} · 回答完成`;
|
||
responseMessage.followUps = Array.isArray(meta.follow_ups)
|
||
? meta.follow_ups.filter((item) => typeof item === "string" && item.trim()).slice(0, 3)
|
||
: [];
|
||
if (meta.notice) showMentorNotice(meta.notice);
|
||
},
|
||
);
|
||
responseMessage.streaming = false;
|
||
setStatus("问师回答完成");
|
||
} catch (error) {
|
||
responseMessage.streaming = false;
|
||
responseMessage.followUps = [];
|
||
if (state.mentorController?.signal.aborted) {
|
||
if (responseMessage.content) {
|
||
responseMessage.meta = "生成已停止";
|
||
} else {
|
||
state.mentorMessages = state.mentorMessages.filter((item) => item !== responseMessage);
|
||
}
|
||
setStatus("已停止问师回答");
|
||
} else {
|
||
responseMessage.error = true;
|
||
if (!responseMessage.content) {
|
||
state.mentorMessages = state.mentorMessages.filter((item) => item !== responseMessage);
|
||
}
|
||
showMentorNotice(error.message || "问师回答失败");
|
||
showToast(error.message || "问师回答失败");
|
||
setStatus("问师回答失败");
|
||
}
|
||
} finally {
|
||
state.mentorLoading = false;
|
||
state.mentorController = null;
|
||
renderMentorMessages();
|
||
renderMentorDirectory();
|
||
input.focus();
|
||
}
|
||
}
|
||
|
||
function stopMentorGeneration() {
|
||
if (!state.mentorLoading || !state.mentorController) return;
|
||
state.mentorController.abort();
|
||
setText("activeMentorStatus", "正在停止...");
|
||
}
|
||
|
||
let mentorRenderFrame = 0;
|
||
|
||
function scheduleMentorRender() {
|
||
if (mentorRenderFrame) return;
|
||
mentorRenderFrame = requestAnimationFrame(() => {
|
||
mentorRenderFrame = 0;
|
||
renderMentorMessages();
|
||
});
|
||
}
|
||
|
||
async function streamMentorRequest(body, signal, onDelta, onMeta) {
|
||
await window.XiaobaiAPI.streamNdjson("/api/mentors/chat", {
|
||
method: "POST",
|
||
body,
|
||
signal,
|
||
errorMessage: "问师暂不可用",
|
||
onEvent: (event) => {
|
||
if (event.type === "delta") onDelta(String(event.content || ""));
|
||
if (event.type === "meta") onMeta(event);
|
||
},
|
||
});
|
||
}
|
||
|
||
function useMentorQuickPrompt(prompt) {
|
||
const input = document.querySelector("#mentorQuestion");
|
||
input.value = prompt || "";
|
||
input.focus();
|
||
}
|
||
|
||
async function clearMentorConversation() {
|
||
if (!state.mentorMessages.length || !window.confirm("确定清空当前老师的对话记录吗?")) return;
|
||
try {
|
||
const query = new URLSearchParams({
|
||
mentor_id: state.selectedMentorId,
|
||
trade_date: state.mentorSetup?.trade_date || elements.tradeDate.value,
|
||
});
|
||
await apiRequest(`/api/mentors/messages?${query}`, "DELETE");
|
||
state.mentorMessages = [];
|
||
hideMentorNotice();
|
||
renderMentorMessages();
|
||
} catch (error) {
|
||
showToast(error.message || "对话记录清空失败");
|
||
}
|
||
}
|
||
|
||
async function loadMentorMessages() {
|
||
if (!state.selectedMentorId) return [];
|
||
try {
|
||
const query = new URLSearchParams({
|
||
mentor_id: state.selectedMentorId,
|
||
trade_date: state.mentorSetup?.trade_date || elements.tradeDate.value,
|
||
});
|
||
const payload = await apiRequest(`/api/mentors/messages?${query}`);
|
||
return (payload.items || []).filter(
|
||
(item) => ["user", "assistant"].includes(item?.role) && typeof item.content === "string",
|
||
).slice(-100);
|
||
} catch (error) {
|
||
showMentorNotice(error.message || "对话记录加载失败");
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function showMentorNotice(message) {
|
||
const notice = document.querySelector("#mentorNotice");
|
||
notice.textContent = message;
|
||
notice.hidden = false;
|
||
}
|
||
|
||
function hideMentorNotice() {
|
||
document.querySelector("#mentorNotice").hidden = true;
|
||
}
|
||
|
||
function formatMentorAnswer(content) {
|
||
const sections = [[]];
|
||
let currentSection = sections[0];
|
||
let headingCount = 0;
|
||
let listType = "";
|
||
let listItems = [];
|
||
const flushList = () => {
|
||
if (!listItems.length) return;
|
||
currentSection.push(`<${listType} class="mentor-answer-list">${listItems.map((item) => `<li>${item}</li>`).join("")}</${listType}>`);
|
||
listItems = [];
|
||
listType = "";
|
||
};
|
||
String(content || "").replace(/\r\n?/g, "\n").replace(/\n{3,}/g, "\n\n").split("\n").forEach((rawLine) => {
|
||
const line = rawLine.trim();
|
||
if (!line) {
|
||
flushList();
|
||
return;
|
||
}
|
||
const heading = mentorAnswerHeading(line);
|
||
const bullet = line.match(/^[-*]\s+(.+)$/);
|
||
const ordered = line.match(/^\d+[.、]\s*(.+)$/);
|
||
if (heading) {
|
||
flushList();
|
||
if (currentSection.length) {
|
||
currentSection = [];
|
||
sections.push(currentSection);
|
||
}
|
||
headingCount += 1;
|
||
currentSection.push(`<strong class="mentor-answer-heading">${formatMentorInline(escapeHtml(heading))}</strong>`);
|
||
} else if (/^-{3,}$/.test(line)) {
|
||
flushList();
|
||
currentSection.push('<span class="mentor-answer-rule"></span>');
|
||
} else if (line.startsWith("> ")) {
|
||
flushList();
|
||
currentSection.push(`<span class="mentor-answer-quote">${formatMentorInline(escapeHtml(line.slice(2)))}</span>`);
|
||
} else if (bullet || ordered) {
|
||
const nextType = bullet ? "ul" : "ol";
|
||
if (listType && listType !== nextType) flushList();
|
||
listType = nextType;
|
||
listItems.push(formatMentorInline(escapeHtml((bullet || ordered)[1])));
|
||
} else {
|
||
flushList();
|
||
currentSection.push(`<p class="mentor-answer-paragraph">${formatMentorInline(escapeHtml(line))}</p>`);
|
||
}
|
||
});
|
||
flushList();
|
||
const populatedSections = sections.filter((section) => section.length);
|
||
if (headingCount < 2) {
|
||
return `<div class="mentor-message-content">${populatedSections.flat().join("")}</div>`;
|
||
}
|
||
return populatedSections.map((section) => (
|
||
`<section class="mentor-message-content mentor-answer-bubble">${section.join("")}</section>`
|
||
)).join("");
|
||
}
|
||
|
||
function mentorAnswerHeading(line) {
|
||
const markdownHeading = line.match(/^#{1,3}\s+(.+)$/);
|
||
if (markdownHeading) return markdownHeading[1].trim();
|
||
const boldHeading = line.match(/^\*\*([^*]+)\*\*$/);
|
||
if (!boldHeading) return "";
|
||
const title = boldHeading[1].trim();
|
||
if (!title || title.length > 32 || /[。!?!?;;::]$/.test(title)) return "";
|
||
return title;
|
||
}
|
||
|
||
function formatMentorInline(content) {
|
||
return content.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
||
}
|
||
|
||
|
||
function bindMentorEvents() {
|
||
document.querySelector("#mentorChatForm").addEventListener("submit", sendMentorQuestion);
|
||
document.querySelector("#stopMentorQuestion").addEventListener("click", stopMentorGeneration);
|
||
document.querySelector("#clearMentorChatButton").addEventListener("click", clearMentorConversation);
|
||
document.querySelector("#mentorSortToggle").addEventListener("click", toggleMentorSortMode);
|
||
document.querySelector("#mentorSearchInput").addEventListener("input", (event) => {
|
||
state.mentorQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
|
||
renderMentorDirectory();
|
||
});
|
||
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
state.mentorGrade = button.dataset.mentorGrade || "all";
|
||
document.querySelectorAll("[data-mentor-grade]").forEach((item) => {
|
||
item.classList.toggle("active", item === button);
|
||
});
|
||
renderMentorDirectory();
|
||
});
|
||
});
|
||
document.querySelectorAll("[data-mentor-prompt]").forEach((button) => {
|
||
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorPrompt));
|
||
});
|
||
document.querySelector("#mentorQuestion").addEventListener("keydown", (event) => {
|
||
if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
|
||
event.preventDefault();
|
||
document.querySelector("#mentorChatForm").requestSubmit();
|
||
});
|
||
}
|