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 || "问师模块加载失败"); } } const MENTOR_AVATAR_TONES = { "xiaobai-perspective": "violet", "kobe92-perspective": "blue", "beijingchaojia-perspective": "green", "chaojiyangjia-perspective": "orange", "chenxiaoqun-perspective": "red", "longfeihu-perspective": "teal", "chuangshiji-perspective": "purple", "foshanwuyingjiao-perspective": "yellow", }; const MENTOR_AVATAR_TONE_CLASSES = [ "mentor-avatar-tone-violet", "mentor-avatar-tone-blue", "mentor-avatar-tone-green", "mentor-avatar-tone-orange", "mentor-avatar-tone-red", "mentor-avatar-tone-teal", "mentor-avatar-tone-purple", "mentor-avatar-tone-yellow", ]; function mentorAvatarTone(mentor) { return MENTOR_AVATAR_TONES[String(mentor?.id || "")] || "blue"; } function mentorAvatarToneClass(mentor) { return `mentor-avatar-tone-${mentorAvatarTone(mentor)}`; } function applyMentorAvatarTone(element, toneClass) { if (!(element instanceof Element)) return; element.classList.remove(...MENTOR_AVATAR_TONE_CLASSES); if (toneClass) element.classList.add(toneClass); } function renderMentorWorkspace() { const setup = state.mentorSetup; if (!setup) return; const selected = setup.mentors.find((item) => item.id === state.selectedMentorId) || null; setText("mentorPageSubtitle", `与不同交易思维模型持续对话 · 数据日期 ${displayCompactDate(setup.trade_date)}`); setText("currentPageSubtitle", `与不同交易思维模型持续对话 · 数据日期 ${displayCompactDate(setup.trade_date)}`); setText("activeMentorName", selected?.name || "--"); setText("activeMentorAvatar", mentorAvatarText(selected)); applyMentorAvatarTone(document.querySelector("#activeMentorAvatar"), mentorAvatarToneClass(selected)); const pinButton = document.querySelector("#mentorPinButton"); if (pinButton) { pinButton.classList.toggle("active", Boolean(selected?.pinned)); pinButton.setAttribute("aria-label", selected?.pinned ? "取消置顶当前思维模型" : "置顶当前思维模型"); pinButton.setAttribute("aria-pressed", String(Boolean(selected?.pinned))); } populateMentorDialogs(selected); renderMentorDirectory(); renderMentorMessages(); } function populateMentorDialogs(selected) { const noteInput = document.querySelector("#mentorNoteInput"); if (noteInput) { noteInput.value = loadMentorNote(selected); } setText("mentorProfileDialogName", selected?.name || "--"); setText("mentorProfileDialogTagline", selected?.tagline || selected?.description || "--"); setText("mentorProfileDialogSource", selected?.evidence?.label || "公开资料整理"); setText("mentorProfileDialogEvidence", selected?.evidence?.note || selected?.description || "--"); setText("mentorProfileDialogDataDate", `行情数据 ${displayCompactDate(state.mentorSetup?.trade_date || elements.tradeDate.value)}`); setText("mentorProfileDialogAvatar", mentorAvatarText(selected)); applyMentorAvatarTone(document.querySelector("#mentorProfileDialogAvatar"), mentorAvatarToneClass(selected)); document.querySelector("#mentorProfileDialogBadges").innerHTML = selected ? renderMentorBadges(selected) : ""; document.querySelector("#mentorProfileDialogFocus").innerHTML = (selected?.focus || []).slice(0, 4) .map((item) => `${escapeHtml(item)}`).join(""); } function mentorNoteStorageKey(selected) { const accountId = state.user?.id || state.user?.username || "anon"; return `xiaobai-mentor-note-${accountId}-${String(selected?.id || "")}`; } function loadMentorNote(selected) { if (!selected) return ""; try { return window.localStorage.getItem(mentorNoteStorageKey(selected)) || ""; } catch (_error) { return ""; } } function saveMentorNote() { const selected = selectedMentor(); if (!selected) return; const input = document.querySelector("#mentorNoteInput"); if (!input) return; try { window.localStorage.setItem(mentorNoteStorageKey(selected), input.value); } catch (_error) { showToast("备注保存失败"); } } 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.querySelector("#mentorFilterToggle").disabled = state.mentorSortMode; document.querySelectorAll("[data-mentor-grade]").forEach((button) => { button.disabled = state.mentorSortMode; }); const filterOptions = document.querySelector("#mentorFilterOptions"); if (state.mentorSortMode && filterOptions && !filterOptions.hidden) { filterOptions.hidden = true; document.querySelector("#mentorFilterToggle").setAttribute("aria-expanded", "false"); } 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 `
${state.mentorSortMode ? ` ` : ""}
`; }).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-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(); } function toggleMentorFilterMenu() { if (state.mentorSortMode || state.mentorLoading) return; const options = document.querySelector("#mentorFilterOptions"); const toggle = document.querySelector("#mentorFilterToggle"); if (!options || !toggle) return; const open = options.hidden; options.hidden = !open; toggle.setAttribute("aria-expanded", String(open)); if (open) { const active = options.querySelector("[data-mentor-grade].active"); (active || options.querySelector("[data-mentor-grade]"))?.focus({ preventScroll: true }); } } function closeMentorFilterMenu() { const options = document.querySelector("#mentorFilterOptions"); const toggle = document.querySelector("#mentorFilterToggle"); if (!options || options.hidden) return; options.hidden = true; toggle?.setAttribute("aria-expanded", "false"); } function selectMentorGrade(grade) { state.mentorGrade = grade || "all"; document.querySelectorAll("[data-mentor-grade]").forEach((button) => { button.classList.toggle("active", button.dataset.mentorGrade === state.mentorGrade); }); 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) { const badges = []; if (mentor.private) { badges.push('仅自己'); } if (mentor.pinned) { badges.push('置顶'); } const grade = mentor.evidence?.grade; if (grade) { badges.push(`${escapeHtml(grade)}级`); } return badges.join(""); } function mentorAvatarText(mentor) { return Array.from(String(mentor?.name || "师").trim())[0] || "师"; } function mentorMessageTime(message) { const parsed = new Date(String(message?.created_at || "")); if (Number.isNaN(parsed.getTime())) { return new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", hour12: false }); } return parsed.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", hour12: false }); } 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 = `
向「${escapeHtml(selected?.name || "问师")}」请教

${escapeHtml(selected?.tagline || selected?.description || "从一个具体问题开始对话")}

`; refreshIcons(); } else { container.innerHTML = state.mentorMessages.map((message) => `
${message.role === "user" ? escapeHtml(mentorMessageTime(message)) : `${escapeHtml(selected?.name || "问师")} · ${escapeHtml(mentorMessageTime(message))}`}
${message.role === "assistant" ? (message.content ? formatMentorAnswer(message.content) : '

正在读取复盘数据并推演...

') : escapeHtml(message.content)}
${message.streaming ? '' : ""} ${renderMentorFollowUps(message)}
`).join(""); } document.querySelector("#mentorQuickPrompts").hidden = false; 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 ? "正在生成回答..." : (selected?.tagline || selected?.description || "思维模型已就绪")); 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 `
继续追问 ${items.map((item) => ``).join("")}
`; } 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 = ""; syncMentorComposerHeight(); 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.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 || ""; syncMentorComposerHeight(); 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 blocks = []; let listType = ""; let listItems = []; const flushList = () => { if (!listItems.length) return; blocks.push(`<${listType} class="mentor-answer-list">${listItems.map((item) => `
  • ${item}
  • `).join("")}`); 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 = line.match(/^#{1,3}\s+(.+)$/); const bullet = line.match(/^[-*]\s+(.+)$/); const ordered = line.match(/^\d+[.、]\s*(.+)$/); if (heading) { flushList(); blocks.push(`${formatMentorInline(escapeHtml(heading[1]))}`); } else if (/^-{3,}$/.test(line)) { flushList(); blocks.push(''); } else if (line.startsWith("> ")) { flushList(); blocks.push(`${formatMentorInline(escapeHtml(line.slice(2)))}`); } 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(); blocks.push(`

    ${formatMentorInline(escapeHtml(line))}

    `); } }); flushList(); return blocks.join(""); } function formatMentorInline(content) { return content.replace(/\*\*(.+?)\*\*/g, "$1"); } 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("#mentorFilterToggle").addEventListener("click", (event) => { event.stopPropagation(); toggleMentorFilterMenu(); }); 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", () => { selectMentorGrade(button.dataset.mentorGrade); closeMentorFilterMenu(); }); }); document.addEventListener("click", (event) => { if (event.target.closest(".mentor-filter-menu")) return; closeMentorFilterMenu(); }); 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(); }); document.querySelector("#mentorQuestion").addEventListener("input", syncMentorComposerHeight); document.querySelector("#mentorNoteButton").addEventListener("click", openMentorNoteDialog); document.querySelector("#mentorProfileButton").addEventListener("click", openMentorProfileDialog); document.querySelector("#mentorPinButton").addEventListener("click", () => toggleMentorPin(state.selectedMentorId)); document.querySelector("#mentorNoteInput").addEventListener("input", saveMentorNote); document.querySelectorAll("[data-mentor-dialog-close]").forEach((button) => { button.addEventListener("click", () => closeMentorDialog(button.dataset.mentorDialogClose)); }); window.addEventListener("resize", centerOpenMentorDialogs); } function selectedMentor() { return (state.mentorSetup?.mentors || []).find((item) => item.id === state.selectedMentorId) || null; } function syncMentorComposerHeight() { const input = document.querySelector("#mentorQuestion"); if (!input) return; input.style.height = "0"; const nextHeight = Math.min(input.scrollHeight, 168); input.style.height = `${nextHeight}px`; input.style.overflowY = nextHeight >= 168 ? "auto" : "hidden"; } function openMentorDialog(dialogId) { const dialog = document.querySelector(`#${dialogId}`); if (!(dialog instanceof HTMLDialogElement)) return; if (!dialog.open) dialog.showModal(); positionMentorDialog(dialog); } function closeMentorDialog(dialogId) { const dialog = document.querySelector(`#${dialogId}`); if (dialog instanceof HTMLDialogElement && dialog.open) dialog.close(); } function openMentorNoteDialog() { const input = document.querySelector("#mentorNoteInput"); if (input) input.value = loadMentorNote(selectedMentor()); openMentorDialog("mentorNoteDialog"); const noteInput = document.querySelector("#mentorNoteInput"); if (noteInput) requestAnimationFrame(() => noteInput.focus()); } function openMentorProfileDialog() { populateMentorDialogs(selectedMentor()); openMentorDialog("mentorProfileDialog"); } function positionMentorDialog(dialog) { if (!(dialog instanceof HTMLDialogElement) || !dialog.open) return; const viewportPadding = 12; const rect = dialog.getBoundingClientRect(); const width = Math.min(400, window.innerWidth - viewportPadding * 2); const height = Math.min(rect.height, window.innerHeight - viewportPadding * 2); dialog.style.top = `${Math.max(viewportPadding, Math.round((window.innerHeight - height) / 2))}px`; dialog.style.left = `${Math.max(viewportPadding, Math.round((window.innerWidth - width) / 2))}px`; dialog.style.right = "auto"; dialog.style.bottom = "auto"; dialog.style.margin = "0"; } function centerOpenMentorDialogs() { document.querySelectorAll("#mentorNoteDialog, #mentorProfileDialog").forEach((dialog) => { if (dialog instanceof HTMLDialogElement && dialog.open) positionMentorDialog(dialog); }); }