feat: expand and secure mentor library
This commit is contained in:
+103
-12
@@ -62,6 +62,9 @@ const state = {
|
||||
selectedMentorId: "",
|
||||
mentorMessages: [],
|
||||
mentorLoading: false,
|
||||
mentorQuery: "",
|
||||
mentorGrade: "all",
|
||||
mentorDirectoryOpen: false,
|
||||
heavenSetup: null,
|
||||
heavenManualData: null,
|
||||
personalField: null,
|
||||
@@ -436,11 +439,13 @@ function bindEvents() {
|
||||
if (event.key === "Escape") {
|
||||
toggleHeaderCommandMenu(false);
|
||||
toggleAccountDropdown(false, true);
|
||||
toggleMentorDirectory(false);
|
||||
}
|
||||
handleAccountMenuKeydown(event);
|
||||
});
|
||||
window.addEventListener("resize", () => {
|
||||
if (window.innerWidth > 720) toggleHeaderCommandMenu(false);
|
||||
if (window.innerWidth > 720) toggleMentorDirectory(false);
|
||||
if (!elements.stockPreview.hidden) closeStockPreview();
|
||||
updateSidebarControl();
|
||||
if (state.activeView === "dragonView") layoutDragonCards();
|
||||
@@ -544,6 +549,24 @@ function bindEvents() {
|
||||
document.querySelector("#runBacktestToggle").addEventListener("change", updateBacktestTaskStatus);
|
||||
document.querySelector("#mentorChatForm").addEventListener("submit", sendMentorQuestion);
|
||||
document.querySelector("#clearMentorChatButton").addEventListener("click", clearMentorConversation);
|
||||
document.querySelector("#mentorDirectoryToggle").addEventListener("click", () => {
|
||||
toggleMentorDirectory(!state.mentorDirectoryOpen);
|
||||
});
|
||||
document.querySelector("#closeMentorDirectory").addEventListener("click", () => toggleMentorDirectory(false));
|
||||
document.querySelector("#mentorDirectoryBackdrop").addEventListener("click", () => toggleMentorDirectory(false));
|
||||
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));
|
||||
});
|
||||
@@ -2037,28 +2060,96 @@ function renderMentorWorkspace() {
|
||||
const setup = state.mentorSetup;
|
||||
if (!setup) return;
|
||||
const selected = setup.mentors.find((item) => item.id === state.selectedMentorId) || null;
|
||||
setText("mentorCount", `${setup.mentors.length} 位`);
|
||||
setText("mentorDataDate", `数据日期 ${displayCompactDate(setup.trade_date)}`);
|
||||
setText("activeMentorName", selected?.name || "--");
|
||||
document.querySelector("#mentorList").innerHTML = setup.mentors.map((mentor) => `
|
||||
<button type="button" class="mentor-option ${mentor.id === state.selectedMentorId ? "active" : ""}" data-mentor-id="${escapeHtml(mentor.id)}">
|
||||
<strong>${escapeHtml(mentor.name)}</strong>
|
||||
<span title="${escapeHtml(mentor.description)}">${escapeHtml(mentor.description || "--")}</span>
|
||||
<small>${mentor.focus.slice(0, 3).map((item) => `<i>${escapeHtml(item)}</i>`).join("")}</small>
|
||||
</button>
|
||||
`).join("");
|
||||
document.querySelectorAll("[data-mentor-id]").forEach((button) => {
|
||||
button.addEventListener("click", () => selectMentor(button.dataset.mentorId));
|
||||
});
|
||||
setText("mobileActiveMentorName", selected?.name || "选择思维模型");
|
||||
document.querySelector("#activeMentorBadges").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.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);
|
||||
}).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");
|
||||
});
|
||||
setText("mentorCount", filtered.length === mentors.length ? `${mentors.length} 位` : `${filtered.length} / ${mentors.length} 位`);
|
||||
const container = document.querySelector("#mentorList");
|
||||
container.innerHTML = filtered.map((mentor) => `
|
||||
<button type="button" class="mentor-option ${mentor.id === state.selectedMentorId ? "active" : ""}" data-mentor-id="${escapeHtml(mentor.id)}" aria-pressed="${mentor.id === state.selectedMentorId}">
|
||||
<span class="mentor-option-copy">
|
||||
<strong>${escapeHtml(mentor.name)}</strong>
|
||||
<em title="${escapeHtml(mentor.description || "")}">${escapeHtml(mentor.description || mentor.tagline || "思维模型")}</em>
|
||||
</span>
|
||||
<span class="mentor-option-badges">${renderMentorBadges(mentor)}</span>
|
||||
</button>
|
||||
`).join("");
|
||||
document.querySelector("#mentorListEmpty").hidden = filtered.length > 0;
|
||||
document.querySelectorAll("[data-mentor-id]").forEach((button) => {
|
||||
button.addEventListener("click", () => selectMentor(button.dataset.mentorId));
|
||||
});
|
||||
refreshIcons();
|
||||
}
|
||||
|
||||
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) {
|
||||
const label = expanded && mentor.evidence?.label ? `${grade} · ${mentor.evidence.label}` : grade;
|
||||
badges.push(`<span class="mentor-badge evidence grade-${escapeHtml(grade.toLowerCase())}" title="${escapeHtml(mentor.evidence?.note || "素材等级")}">${escapeHtml(label)}</span>`);
|
||||
}
|
||||
const score = mentor.quality?.score;
|
||||
const total = mentor.quality?.total;
|
||||
if (Number.isInteger(score) && Number.isInteger(total)) {
|
||||
const conditional = mentor.quality?.status === "conditional";
|
||||
badges.push(`<span class="mentor-badge quality ${conditional ? "conditional" : ""}" title="结构质检${conditional ? ",有条件通过" : ""}">${score}/${total}</span>`);
|
||||
}
|
||||
return badges.join("");
|
||||
}
|
||||
|
||||
function toggleMentorDirectory(open) {
|
||||
const mobileOpen = Boolean(open) && window.innerWidth <= 720;
|
||||
state.mentorDirectoryOpen = mobileOpen;
|
||||
const sidebar = document.querySelector("#mentorView .mentor-sidebar");
|
||||
const backdrop = document.querySelector("#mentorDirectoryBackdrop");
|
||||
const toggle = document.querySelector("#mentorDirectoryToggle");
|
||||
sidebar.classList.toggle("is-open", mobileOpen);
|
||||
backdrop.hidden = !mobileOpen;
|
||||
toggle.setAttribute("aria-expanded", String(mobileOpen));
|
||||
document.body.classList.toggle("mentor-directory-open", mobileOpen);
|
||||
if (mobileOpen) requestAnimationFrame(() => document.querySelector("#mentorSearchInput").focus());
|
||||
}
|
||||
|
||||
async function selectMentor(mentorId) {
|
||||
if (mentorId === state.selectedMentorId) return;
|
||||
if (mentorId === state.selectedMentorId) {
|
||||
toggleMentorDirectory(false);
|
||||
return;
|
||||
}
|
||||
state.selectedMentorId = mentorId;
|
||||
state.mentorMessages = [];
|
||||
hideMentorNotice();
|
||||
renderMentorWorkspace();
|
||||
toggleMentorDirectory(false);
|
||||
state.mentorMessages = await loadMentorMessages();
|
||||
renderMentorMessages();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user