let watchlistSearchTimer = null;
let assistantRenderFrame = 0;
window.XiaobaiPageModules.register("review", ["reviewWorkspaceView"], {
bind: bindReviewEvents,
enter: ["loadReview"],
});
async function loadReviewWorkspace() {
try {
const [watchlistPayload, notesPayload, tradesPayload] = await Promise.all([
apiRequest(`/api/watchlist?trade_date=${encodeURIComponent(elements.tradeDate.value)}`),
apiRequest("/api/notes?scope=daily"),
apiRequest("/api/trades"),
]);
state.watchlist = watchlistPayload.items || [];
state.notes = notesPayload.items || [];
state.tradeEntries = tradesPayload.items || [];
state.tradeSummary = tradesPayload.summary || {};
setText("reviewDataDate", displayCompactDate(elements.tradeDate.value));
renderWatchlist();
renderNotesHistory(state.notes, document.querySelector("#notesHistory"), false);
setText("notesCount", `${state.notes.length} 条`);
renderTradeLog();
populateJournalForm();
} catch (error) {
showToast(error.message || "我的复盘加载失败");
}
}
function renderWatchlist() {
setText("watchlistCount", `${state.watchlist.length} 只`);
const body = document.querySelector("#watchlistTableBody");
body.innerHTML = state.watchlist.map((item) => `
| ★ |
${escapeHtml(item.name)}${escapeHtml(item.code)} |
${escapeHtml(item.sector || "其他")} |
${formatWatchMetric(item.change)} |
${formatWatchMetric(item.return_5d)} |
${item.attention_score == null ? "" : formatNumber(item.attention_score, 1)} |
|
|
`).join("");
document.querySelector("#watchlistEmpty").hidden = state.watchlist.length > 0;
body.querySelectorAll("[data-watch-remark]").forEach((button) => {
button.addEventListener("click", () => {
const item = state.watchlist.find((row) => row.code === button.dataset.watchRemark);
openWatchlistDialog(item);
});
});
body.querySelectorAll("[data-watch-delete]").forEach((button) => {
button.addEventListener("click", () => removeWatchlist(button.dataset.watchDelete));
});
bindStockRows(body);
}
function formatWatchMetric(value) {
if (value == null || !Number.isFinite(Number(value))) return "";
return signed(value);
}
function openWatchlistDialog(item = null) {
clearTimeout(watchlistSearchTimer);
state.watchlistSelection = item ? {
code: item.code,
name: item.name,
sector: item.sector || "其他",
color: item.color || "red",
} : null;
state.watchlistSearchResults = [];
setText("watchlistDialogTitle", item ? "编辑跟踪备注" : "添加自选");
document.querySelector("#watchlistRemark").value = item?.remark || "";
document.querySelector("#watchlistSearchInput").value = "";
document.querySelector("#watchlistSearchResults").innerHTML = "";
syncWatchlistSelection(Boolean(item));
openModalDialog(elements.watchlistDialog);
requestAnimationFrame(() => (item ? document.querySelector("#watchlistRemark") : document.querySelector("#watchlistSearchInput")).focus());
}
function closeWatchlistDialog() {
clearTimeout(watchlistSearchTimer);
if (elements.watchlistDialog.open) elements.watchlistDialog.close();
}
function clearWatchlistSelection() {
state.watchlistSelection = null;
syncWatchlistSelection(false);
document.querySelector("#watchlistSearchInput").focus();
}
function syncWatchlistSelection(editing = false) {
const item = state.watchlistSelection;
document.querySelector("#watchlistSearchField").hidden = Boolean(item);
document.querySelector("#watchlistSelection").hidden = !item;
document.querySelector("#changeWatchlistSelection").hidden = editing;
document.querySelector("#saveWatchlist").disabled = !item;
if (!item) return;
setText("watchlistSelectionName", item.name || "--");
setText("watchlistSelectionCode", item.code || "--");
setText("watchlistSelectionSector", item.sector || "其他");
refreshIcons();
}
function scheduleWatchlistSearch() {
clearTimeout(watchlistSearchTimer);
const query = document.querySelector("#watchlistSearchInput").value.trim();
if (!query) {
document.querySelector("#watchlistSearchResults").innerHTML = "";
return;
}
document.querySelector("#watchlistSearchResults").innerHTML = '正在查找股票
';
watchlistSearchTimer = setTimeout(() => runWatchlistSearch(query), 160);
}
async function runWatchlistSearch(query) {
const sequence = ++state.watchlistSearchRequestSequence;
try {
const params = new URLSearchParams({ q: query, trade_date: elements.tradeDate.value });
const payload = await apiRequest(`/api/search?${params}`);
if (sequence !== state.watchlistSearchRequestSequence) return;
state.watchlistSearchResults = payload.groups?.stocks || [];
document.querySelector("#watchlistSearchResults").innerHTML = state.watchlistSearchResults.map((item, index) => `
`).join("") || '没有找到匹配的股票
';
} catch (error) {
document.querySelector("#watchlistSearchResults").innerHTML = `${escapeHtml(error.message || "搜索失败")}
`;
}
}
function handleWatchlistSearchResult(event) {
const button = event.target.closest("[data-watchlist-result]");
if (!button) return;
const item = state.watchlistSearchResults[number(button.dataset.watchlistResult)];
if (!item) return;
state.watchlistSelection = {
code: item.code,
name: item.name,
sector: item.industry || "其他",
color: "red",
};
syncWatchlistSelection(false);
}
async function saveWatchlistFromDialog(event) {
event.preventDefault();
const item = state.watchlistSelection;
if (!item) return;
const button = document.querySelector("#saveWatchlist");
button.disabled = true;
try {
await apiRequest("/api/watchlist", "POST", {
code: item.code,
name: item.name,
sector: item.sector || "其他",
color: item.color || "red",
remark: document.querySelector("#watchlistRemark").value.trim(),
});
closeWatchlistDialog();
await loadReviewWorkspace();
showToast(state.watchlist.some((row) => row.code === item.code) ? "自选跟踪已保存" : "已加入自选");
} catch (error) {
showToast(error.message || "自选保存失败");
button.disabled = false;
}
}
async function toggleActiveWatchlist() {
const stock = state.activeStock;
if (!stock?.code) return;
const isWatched = Boolean(state.stockDetail?.stock?.watchlist || state.watchlist.some((item) => item.code === stock.code));
try {
if (isWatched) {
await apiRequest(`/api/watchlist/${stock.code}`, "DELETE");
state.watchlist = state.watchlist.filter((item) => item.code !== stock.code);
if (state.stockDetail?.stock) state.stockDetail.stock.watchlist = null;
showToast("已移出自选");
} else {
const payload = await apiRequest("/api/watchlist", "POST", {
code: stock.code,
name: stock.name || "--",
sector: stock.sector || "其他",
color: "red",
});
state.watchlist = payload.items || state.watchlist;
if (state.stockDetail?.stock) state.stockDetail.stock.watchlist = state.watchlist.find((item) => item.code === stock.code);
showToast("已加入自选");
}
updateWatchButton();
renderWatchlist();
} catch (error) {
showToast(error.message);
}
}
function updateWatchButton() {
const code = state.activeStock?.code;
const watched = Boolean(state.stockDetail?.stock?.watchlist || state.watchlist.some((item) => item.code === code));
setText("watchStockButton", watched ? "移出自选" : "加入自选");
}
async function removeWatchlist(code) {
try {
await apiRequest(`/api/watchlist/${code}`, "DELETE");
state.watchlist = state.watchlist.filter((item) => item.code !== code);
renderWatchlist();
showToast("已移出自选");
} catch (error) {
showToast(error.message);
}
}
async function saveJournal(event) {
event.preventDefault();
try {
await apiRequest("/api/notes", "POST", {
trade_date: document.querySelector("#journalDate").value,
id: state.editingDailyNoteId || undefined,
summary: document.querySelector("#journalSummary").value,
content: document.querySelector("#journalContent").value,
plan: document.querySelector("#journalPlan").value,
});
await loadReviewWorkspace();
showToast("每日复盘已保存");
} catch (error) {
showToast(error.message);
}
}
function populateJournalForm() {
const selectedDate = document.querySelector("#journalDate").value.replaceAll("-", "");
const note = state.notes.find((item) => String(item.trade_date).replaceAll("-", "") === selectedDate);
state.editingDailyNoteId = number(note?.id);
document.querySelector("#journalSummary").value = note?.summary || "";
document.querySelector("#journalContent").value = note?.content || "";
document.querySelector("#journalPlan").value = note?.plan || "";
}
function openTradeLogDialog() {
resetTradeLogForm();
openModalDialog(elements.tradeLogDialog);
requestAnimationFrame(() => document.querySelector("#tradeLogCode").focus());
}
function closeTradeLogDialog() {
if (elements.tradeLogDialog.open) elements.tradeLogDialog.close();
else resetTradeLogForm();
}
async function saveTradeLog(event) {
event.preventDefault();
const button = document.querySelector("#saveTradeLog");
button.disabled = true;
try {
const payload = await apiRequest("/api/trades", "POST", {
id: state.editingTradeId || undefined,
trade_date: document.querySelector("#tradeLogDate").value,
code: document.querySelector("#tradeLogCode").value.trim(),
name: document.querySelector("#tradeLogName").value.trim(),
action: document.querySelector("#tradeLogAction").value,
price: document.querySelector("#tradeLogPrice").value,
quantity: document.querySelector("#tradeLogQuantity").value,
position_pct: document.querySelector("#tradeLogPosition").value,
pnl_amount: document.querySelector("#tradeLogPnlAmount").value,
pnl_pct: document.querySelector("#tradeLogPnlPct").value,
emotion: document.querySelector("#tradeLogEmotion").value,
tags: document.querySelector("#tradeLogTags").value,
thesis: document.querySelector("#tradeLogThesis").value,
execution: document.querySelector("#tradeLogExecution").value,
});
state.tradeEntries = payload.items || [];
state.tradeSummary = payload.summary || {};
renderTradeLog();
closeTradeLogDialog();
showToast("交易记录已保存");
} catch (error) {
showToast(error.message || "交易记录保存失败");
} finally {
button.disabled = false;
}
}
function resetTradeLogForm() {
state.editingTradeId = 0;
document.querySelector("#tradeLogForm").reset();
document.querySelector("#tradeLogDate").value = elements.tradeDate.value || todayString();
document.querySelector("#tradeLogQuantity").value = "0";
document.querySelector("#tradeLogPosition").value = "0";
setText("tradeLogDialogTitle", "交易日志");
setText("saveTradeLog", "保存交易");
}
function editTradeLog(id) {
const item = state.tradeEntries.find((entry) => number(entry.id) === id);
if (!item) return;
state.editingTradeId = id;
document.querySelector("#tradeLogDate").value = displayCompactDate(item.trade_date);
document.querySelector("#tradeLogCode").value = item.code;
document.querySelector("#tradeLogName").value = item.name;
document.querySelector("#tradeLogAction").value = item.action;
document.querySelector("#tradeLogPrice").value = item.price;
document.querySelector("#tradeLogQuantity").value = item.quantity;
document.querySelector("#tradeLogPosition").value = item.position_pct;
document.querySelector("#tradeLogPnlAmount").value = item.pnl_amount ?? "";
document.querySelector("#tradeLogPnlPct").value = item.pnl_pct ?? "";
document.querySelector("#tradeLogEmotion").value = item.emotion;
document.querySelector("#tradeLogTags").value = (item.tags || []).join(", ");
document.querySelector("#tradeLogThesis").value = item.thesis || "";
document.querySelector("#tradeLogExecution").value = item.execution || "";
setText("tradeLogDialogTitle", "编辑交易日志");
setText("saveTradeLog", "保存修改");
openModalDialog(elements.tradeLogDialog);
requestAnimationFrame(() => document.querySelector("#tradeLogCode").focus());
}
async function handleTradeLogAction(event) {
const button = event.target.closest("[data-trade-action]");
if (!button) return;
const id = number(button.dataset.tradeId);
if (button.dataset.tradeAction === "edit") {
editTradeLog(id);
return;
}
if (!window.confirm("确定删除这条交易记录吗?")) return;
try {
const payload = await apiRequest(`/api/trades/${id}`, "DELETE");
state.tradeEntries = payload.items || [];
state.tradeSummary = payload.summary || {};
if (state.editingTradeId === id) resetTradeLogForm();
renderTradeLog();
showToast("交易记录已删除");
} catch (error) {
showToast(error.message || "交易记录删除失败");
}
}
function renderTradeLog() {
const summary = state.tradeSummary || {};
setText("tradeLogCount", `${state.tradeEntries.length} 条`);
document.querySelector("#tradeLogSummary").innerHTML = [
["记录", `${number(summary.total)} 条`],
["已实现", `${number(summary.realized)} 条`],
["胜率", summary.win_rate == null ? "--" : `${formatNumber(summary.win_rate, 1)}%`],
["累计盈亏", summary.pnl_amount == null ? "--" : `${number(summary.pnl_amount) > 0 ? "+" : ""}${formatNumber(summary.pnl_amount, 2)}`],
["平均仓位", summary.average_position == null ? "--" : `${formatNumber(summary.average_position, 1)}%`],
].map(([label, value]) => `${label}${value}
`).join("");
document.querySelector("#tradeLogEmpty").hidden = state.tradeEntries.length > 0;
document.querySelector("#tradeLogTableBody").innerHTML = state.tradeEntries.map((item) => `
| ${displayCompactDate(item.trade_date)} |
${escapeHtml(item.name)}${escapeHtml(item.code)} |
${escapeHtml(item.action_label)} |
${item.position_pct == null ? "" : formatNumber(item.position_pct, 1)} |
${item.pnl_pct == null ? "" : signed(item.pnl_pct)} |
${item.pnl_amount == null ? "" : signed(item.pnl_amount)} |
${escapeHtml(item.emotion_label)} ${(item.tags || []).map((tag) => `${escapeHtml(tag)}`).join("")} |
${escapeHtml(item.thesis || "")}${escapeHtml(item.execution || "尚未填写执行复核")} |
|
`).join("");
bindStockRows(document.querySelector("#tradeLogTableBody"));
}
async function saveStockNote(event) {
event.preventDefault();
if (!state.activeStock?.code) return;
try {
await apiRequest("/api/notes", "POST", {
code: state.activeStock.code,
stock_name: state.activeStock.name || "--",
trade_date: elements.tradeDate.value,
content: document.querySelector("#stockNoteContent").value,
plan: document.querySelector("#stockNotePlan").value,
});
document.querySelector("#stockNoteContent").value = "";
document.querySelector("#stockNotePlan").value = "";
const payload = await apiRequest(`/api/notes?scope=stock&code=${encodeURIComponent(state.activeStock.code)}`);
state.stockDetail.notes = payload.items || [];
renderStockNotes(state.stockDetail.notes);
showToast("个股笔记已保存");
} catch (error) {
showToast(error.message);
}
}
async function saveReasonOverride(event) {
event.preventDefault();
if (!state.activeStock?.code) return;
const reason = document.querySelector("#reasonInput").value.trim();
try {
await apiRequest("/api/reasons", "POST", {
trade_date: elements.tradeDate.value,
code: state.activeStock.code,
reason,
});
state.activeStock.reason = reason;
for (const key of ["limits", "broken", "down_limits"]) {
const row = state.dashboard?.[key]?.find((item) => item.code === state.activeStock.code);
if (row) row.reason = reason;
}
setText("detailReason", reason);
renderDashboard();
showToast("事件逻辑已修订");
} catch (error) {
showToast(error.message);
}
}
function renderMoneyflow(flow) {
for (const [id, value] of [["flowNet", flow.net_million], ["flowLarge", flow.large_million], ["flowMedium", flow.medium_million], ["flowSmall", flow.small_million]]) {
const element = document.getElementById(id);
element.textContent = formatMoneyMillion(value);
element.className = changeClass(value);
}
}
function renderStockNotes(notes) {
renderNotesHistory(notes, document.querySelector("#stockNotes"), true);
}
function renderNotesHistory(notes, container, compact) {
container.innerHTML = notes.map((note) => `
${note.stock_name ? `${escapeHtml(note.stock_name)}` : ""}
${!compact ? `盘面${escapeHtml(note.summary || "--")}
` : ""}
复盘${escapeHtml(note.content || "--")}
计划${escapeHtml(note.plan || "--")}
`).join("") || emptyStateHtml("暂无复盘记录");
container.querySelectorAll("[data-note-delete]").forEach((button) => {
button.addEventListener("click", () => deleteNote(number(button.dataset.noteDelete), compact));
});
}
async function deleteNote(noteId, compact) {
try {
await apiRequest(`/api/notes/${noteId}`, "DELETE");
if (compact && state.activeStock) {
state.stockDetail.notes = state.stockDetail.notes.filter((note) => number(note.id) !== noteId);
renderStockNotes(state.stockDetail.notes);
} else {
await loadReviewWorkspace();
}
showToast("笔记已删除");
} catch (error) {
showToast(error.message);
}
}
async function loadAlerts(openDialog = false) {
try {
const query = new URLSearchParams({ status: state.alertFilter, as_of: todayString() });
const payload = await apiRequest(`/api/alerts?${query}`);
state.alerts = payload.items || [];
state.alertUnreadCount = number(payload.unread_count);
renderAlerts();
if (openDialog) openModalDialog(elements.alertsDialog);
} catch (error) {
if (openDialog) showToast(error.message || "提醒加载失败");
}
}
function openAlerts() {
toggleHeaderCommandMenu(false);
toggleAccountDropdown(false);
document.querySelector("#alertDate").value ||= todayString();
openModalDialog(elements.alertsDialog);
loadAlerts();
}
function openStockReminder() {
const stock = state.activeStock || {};
document.querySelector("#alertTitle").value = `${stock.name || stock.code || "个股"}观察提醒`;
document.querySelector("#alertCode").value = stock.code || "";
document.querySelector("#alertDate").value = todayString();
if (elements.stockDialog.open) elements.stockDialog.close();
openAlerts();
document.querySelector("#alertContent").focus();
}
function selectAlertFilter(filter) {
state.alertFilter = filter === "unread" ? "unread" : "all";
document.querySelectorAll("[data-alert-filter]").forEach((button) => {
button.classList.toggle("active", button.dataset.alertFilter === state.alertFilter);
});
loadAlerts();
}
async function saveAlert(event) {
event.preventDefault();
const button = event.currentTarget.querySelector("button[type='submit']");
button.disabled = true;
try {
const payload = await apiRequest("/api/alerts", "POST", {
title: document.querySelector("#alertTitle").value.trim(),
remind_date: document.querySelector("#alertDate").value,
code: document.querySelector("#alertCode").value.trim(),
content: document.querySelector("#alertContent").value.trim(),
});
event.currentTarget.reset();
document.querySelector("#alertDate").value = todayString();
state.alertFilter = "all";
state.alerts = payload.items || [];
state.alertUnreadCount = number(payload.unread_count);
renderAlerts();
showToast("提醒已保存");
} catch (error) {
showToast(error.message || "提醒保存失败");
} finally {
button.disabled = false;
}
}
async function markAllAlertsRead() {
try {
await apiRequest("/api/alerts/read-all", "POST", { as_of: todayString() });
await loadAlerts();
} catch (error) {
showToast(error.message || "提醒状态更新失败");
}
}
async function handleAlertAction(event) {
const button = event.target.closest("[data-alert-action]");
if (!button) return;
const id = number(button.dataset.alertId);
if (!id) return;
try {
if (button.dataset.alertAction === "delete") {
await apiRequest(`/api/alerts/${id}`, "DELETE");
} else {
await apiRequest(`/api/alerts/${id}/read`, "POST", {});
}
await loadAlerts();
} catch (error) {
showToast(error.message || "提醒操作失败");
}
}
function renderAlerts() {
const badge = document.querySelector("#alertBadge");
badge.hidden = state.alertUnreadCount <= 0;
badge.textContent = state.alertUnreadCount > 99 ? "99+" : String(state.alertUnreadCount);
document.querySelector("#alertButton").classList.toggle("has-alerts", state.alertUnreadCount > 0);
setText("alertListCount", `${state.alerts.length} 条`);
document.querySelectorAll("[data-alert-filter]").forEach((button) => {
button.classList.toggle("active", button.dataset.alertFilter === state.alertFilter);
});
document.querySelector("#markAllAlertsRead").disabled = state.alertUnreadCount <= 0;
const container = document.querySelector("#alertList");
container.innerHTML = state.alerts.map((item) => {
const upcoming = !item.due;
const kindLabel = item.kind === "manual" ? "自定提醒" : item.kind === "strategy_t5" ? "跟踪完成" : "策略反馈";
return `
${escapeHtml(kindLabel)}
${escapeHtml(item.title)}
${item.content ? `
${escapeHtml(item.content)}
` : ""}
${item.code ? `
` : ""}
${!item.is_read && !upcoming ? `` : ""}
`;
}).join("") || emptyStateHtml("暂无提醒");
bindStockRows(container);
refreshIcons();
}
async function openReviewAssistant() {
toggleHeaderCommandMenu(false);
toggleAccountDropdown(false);
openModalDialog(elements.assistantDialog);
updateAssistantControls();
if (!hasMemberAccess()) {
document.querySelector("#closeAssistantDialog").focus();
return;
}
try {
const payload = await apiRequest("/api/assistant/messages");
state.assistantMessages = payload.items || [];
renderAssistantMessages();
} catch (error) {
showToast(error.message || "对话记录加载失败");
}
document.querySelector("#assistantQuestion").focus();
}
function useAssistantPrompt(prompt) {
const input = document.querySelector("#assistantQuestion");
input.value = prompt;
input.focus();
}
async function sendAssistantQuestion(event) {
event.preventDefault();
if (state.assistantLoading) return;
const input = document.querySelector("#assistantQuestion");
const question = input.value.trim();
if (!question) return;
input.value = "";
state.assistantMessages.push({ role: "user", content: question, context_date: elements.tradeDate.value.replaceAll("-", "") });
state.assistantMessages.push({ role: "assistant", content: "", streaming: true, context_date: elements.tradeDate.value.replaceAll("-", "") });
state.assistantLoading = true;
state.assistantController = new AbortController();
updateAssistantControls();
renderAssistantMessages();
try {
await streamAssistantRequest(question, state.assistantController.signal, (chunk) => {
const message = state.assistantMessages.at(-1);
if (message?.role === "assistant") message.content += chunk;
scheduleAssistantRender();
});
const message = state.assistantMessages.at(-1);
if (message) message.streaming = false;
setStatus("复盘助手回答完成");
} catch (error) {
const message = state.assistantMessages.at(-1);
if (message?.role === "assistant") {
message.streaming = false;
message.error = true;
if (!message.content) message.content = error.name === "AbortError" ? "已停止生成。" : error.message || "回答失败,请稍后重试。";
}
if (error.name !== "AbortError") showToast(error.message || "复盘助手回答失败");
} finally {
state.assistantLoading = false;
state.assistantController = null;
updateAssistantControls();
renderAssistantMessages();
input.focus();
}
}
async function streamAssistantRequest(question, signal, onDelta) {
await window.XiaobaiAPI.streamNdjson("/api/assistant/chat", {
method: "POST",
body: { question, trade_date: elements.tradeDate.value },
signal,
errorMessage: "复盘助手暂不可用",
onEvent: (event) => {
if (event.type === "delta") onDelta(String(event.content || ""));
},
});
}
function stopAssistantResponse() {
state.assistantController?.abort();
}
async function clearAssistantConversation() {
if (state.assistantLoading || !state.assistantMessages.length) return;
if (!window.confirm("确定清空复盘助手的对话记录吗?")) return;
try {
await apiRequest("/api/assistant/messages", "DELETE");
state.assistantMessages = [];
renderAssistantMessages();
} catch (error) {
showToast(error.message || "对话记录清空失败");
}
}
function scheduleAssistantRender() {
if (assistantRenderFrame) return;
assistantRenderFrame = requestAnimationFrame(() => {
assistantRenderFrame = 0;
renderAssistantMessages();
});
}
function renderAssistantMessages() {
const container = document.querySelector("#assistantMessages");
container.innerHTML = state.assistantMessages.map((message) => `
${message.role === "user" ? "我" : "复盘助手"}${message.context_date ? `` : ""}
${message.role === "assistant" ? (message.content ? formatMentorAnswer(message.content) : '正在整理复盘数据') : escapeHtml(message.content)}
${message.streaming ? '' : ""}
`).join("") || emptyStateHtml("可以从市场、策略或自己的交易记录开始复盘");
updateAssistantControls();
requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; });
}
function updateAssistantControls() {
const unlocked = hasMemberAccess();
elements.assistantDialog.classList.toggle("member-locked", !unlocked);
document.querySelector("#assistantMemberGate").hidden = unlocked;
document.querySelector("#assistantMemberContent").setAttribute("aria-disabled", String(!unlocked));
document.querySelector("#assistantQuestion").disabled = !unlocked || state.assistantLoading;
document.querySelector("#sendAssistant").disabled = !unlocked || state.assistantLoading;
document.querySelector("#stopAssistant").hidden = !unlocked || !state.assistantLoading;
document.querySelector("#clearAssistantMessages").disabled = !unlocked || state.assistantLoading || !state.assistantMessages.length;
document.querySelectorAll("[data-assistant-prompt]").forEach((button) => {
button.disabled = !unlocked || state.assistantLoading;
});
}
function bindReviewEvents() {
document.querySelector("#alertButton").addEventListener("click", openAlerts);
document.querySelector("#assistantButton").addEventListener("click", openReviewAssistant);
document.querySelector("#closeAssistantDialog").addEventListener("click", () => elements.assistantDialog.close());
document.querySelector("#assistantForm").addEventListener("submit", sendAssistantQuestion);
document.querySelector("#stopAssistant").addEventListener("click", stopAssistantResponse);
document.querySelector("#clearAssistantMessages").addEventListener("click", clearAssistantConversation);
document.querySelectorAll("[data-assistant-prompt]").forEach((button) => {
button.addEventListener("click", () => useAssistantPrompt(button.dataset.assistantPrompt));
});
document.querySelector("#closeAlertsDialog").addEventListener("click", () => elements.alertsDialog.close());
document.querySelector("#alertForm").addEventListener("submit", saveAlert);
document.querySelector("#markAllAlertsRead").addEventListener("click", markAllAlertsRead);
document.querySelector("#alertList").addEventListener("click", handleAlertAction);
document.querySelectorAll("[data-alert-filter]").forEach((button) => {
button.addEventListener("click", () => selectAlertFilter(button.dataset.alertFilter));
});
document.querySelector("#journalForm").addEventListener("submit", saveJournal);
document.querySelector("#journalDate").addEventListener("change", populateJournalForm);
document.querySelector("#openWatchlistDialog").addEventListener("click", () => openWatchlistDialog());
document.querySelector("#closeWatchlistDialog").addEventListener("click", closeWatchlistDialog);
document.querySelector("#cancelWatchlistEdit").addEventListener("click", closeWatchlistDialog);
document.querySelector("#changeWatchlistSelection").addEventListener("click", clearWatchlistSelection);
document.querySelector("#watchlistSearchInput").addEventListener("input", scheduleWatchlistSearch);
document.querySelector("#watchlistForm").addEventListener("submit", saveWatchlistFromDialog);
document.querySelector("#watchlistSearchResults").addEventListener("click", handleWatchlistSearchResult);
document.querySelector("#reviewHistoryToggle").addEventListener("click", (event) => {
const panel = document.querySelector("#reviewHistoryPanel");
const expanded = event.currentTarget.getAttribute("aria-expanded") === "true";
event.currentTarget.setAttribute("aria-expanded", String(!expanded));
event.currentTarget.querySelector("span").textContent = expanded ? "历史复盘" : "收起历史";
panel.hidden = expanded;
if (!expanded) panel.scrollIntoView({ behavior: "smooth", block: "nearest" });
});
document.querySelector("#openTradeLogDialog").addEventListener("click", openTradeLogDialog);
document.querySelector("#closeTradeLogDialog").addEventListener("click", closeTradeLogDialog);
document.querySelector("#tradeLogForm").addEventListener("submit", saveTradeLog);
document.querySelector("#cancelTradeEdit").addEventListener("click", closeTradeLogDialog);
elements.tradeLogDialog.addEventListener("close", resetTradeLogForm);
document.querySelector("#tradeLogTableBody").addEventListener("click", handleTradeLogAction);
document.querySelector("#stockNoteForm").addEventListener("submit", saveStockNote);
document.querySelector("#watchStockButton").addEventListener("click", toggleActiveWatchlist);
document.querySelector("#stockHeavenButton").addEventListener("click", openActiveStockInHeaven);
document.querySelector("#stockReminderButton").addEventListener("click", openStockReminder);
}