feat(hub): mobile AI tabs and dashboard position lines

Mobile AI coach uses four top tabs (trading, general, history, new) with single-panel view and wider desktop history. Dashboard account cards show key levels and positions one per line with colored float PnL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-11 11:16:28 +08:00
parent c59a17f9ac
commit 1042fdeef3
7 changed files with 185 additions and 39 deletions
+48 -12
View File
@@ -1038,22 +1038,43 @@
}
const AI_MOBILE_TAB_KEY = "hub_ai_mobile_tab";
const AI_MOBILE_CHAT_TABS = new Set(["trading", "general"]);
function normalizeAiMobileTab(tab) {
const raw = (tab || "").trim().toLowerCase();
if (raw === "chat") return "trading";
if (AI_MOBILE_CHAT_TABS.has(raw) || raw === "history") return raw;
return "trading";
}
function applyAiMobileTab(tab) {
const layout = document.querySelector(".ai-layout");
const tabs = document.querySelectorAll(".ai-mobile-tab");
if (!layout) return;
const mobile = isMobileLayout();
const active = mobile ? tab || localStorage.getItem(AI_MOBILE_TAB_KEY) || "chat" : "both";
if (mobile) layout.dataset.aiMobileTab = active;
else delete layout.dataset.aiMobileTab;
if (!mobile) {
delete layout.dataset.aiMobileTab;
tabs.forEach((btn) => {
btn.classList.remove("is-active");
btn.setAttribute("aria-selected", "false");
});
return;
}
const active = normalizeAiMobileTab(
tab || localStorage.getItem(AI_MOBILE_TAB_KEY) || "trading"
);
layout.dataset.aiMobileTab = active;
tabs.forEach((btn) => {
const on = mobile && btn.dataset.aiTab === active;
const t = btn.dataset.aiTab || "";
const on = t === active;
btn.classList.toggle("is-active", on);
btn.setAttribute("aria-selected", on ? "true" : "false");
});
if (mobile && active === "chat") scrollAiChatToEnd();
if (mobile && active === "history") {
if (AI_MOBILE_CHAT_TABS.has(active)) {
updateAiBotTabs(active);
scrollAiChatToEnd();
}
if (active === "history") {
const hist = document.getElementById("ai-chat-history-list");
if (hist) hist.scrollTop = 0;
}
@@ -1064,10 +1085,16 @@
if (!tabs.length) return;
tabs.forEach((btn) => {
btn.addEventListener("click", () => {
const tab = btn.dataset.aiTab || "chat";
const tab = btn.dataset.aiTab || "trading";
if (tab === "new") {
const prev = normalizeAiMobileTab(localStorage.getItem(AI_MOBILE_TAB_KEY) || "trading");
const botMode = prev === "general" ? "general" : "trading";
void newAiChat(botMode);
return;
}
localStorage.setItem(AI_MOBILE_TAB_KEY, tab);
applyAiMobileTab(tab);
if (tab === "chat") {
if (AI_MOBILE_CHAT_TABS.has(tab)) {
const input = document.getElementById("ai-chat-input");
if (input && isMobileLayout()) input.focus();
}
@@ -3388,8 +3415,13 @@
aiChatSessionsCache = j.sessions || [];
renderAiChatMessages(aiChatSessionCache);
renderAiChatHistory(aiChatSessionsCache);
updateAiBotTabs((aiChatSessionCache && aiChatSessionCache.bot_mode) || "trading");
applyAiMobileTab("chat");
const mode =
(aiChatSessionCache && aiChatSessionCache.bot_mode) === "general" ? "general" : "trading";
updateAiBotTabs(mode);
if (isMobileLayout()) {
localStorage.setItem(AI_MOBILE_TAB_KEY, mode);
applyAiMobileTab(mode);
}
scrollAiChatToEnd();
} catch (e) {
showToast(String(e), true);
@@ -3421,7 +3453,8 @@
async function loadAiPage() {
applyAiMobileTab();
await loadAiChatSession();
if (isMobileLayout() && (localStorage.getItem(AI_MOBILE_TAB_KEY) || "chat") === "chat") {
const mobTab = normalizeAiMobileTab(localStorage.getItem(AI_MOBILE_TAB_KEY) || "trading");
if (isMobileLayout() && AI_MOBILE_CHAT_TABS.has(mobTab)) {
const input = document.getElementById("ai-chat-input");
if (input && !aiChatLoading) {
setTimeout(() => input.focus(), 80);
@@ -3443,7 +3476,10 @@
renderAiChatMessages(aiChatSessionCache);
renderAiChatHistory(aiChatSessionsCache);
updateAiBotTabs(mode);
applyAiMobileTab("chat");
if (isMobileLayout()) {
localStorage.setItem(AI_MOBILE_TAB_KEY, mode);
applyAiMobileTab(mode);
}
showToast(mode === "general" ? "已开始普通聊天" : "已开始交易教练对话");
} catch (e) {
showToast(String(e), true);