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
+30 -21
View File
@@ -3914,25 +3914,32 @@ body.hub-page-ai #page-ai {
}
body.hub-page-ai .ai-mobile-tabs {
display: flex;
gap: 8px;
margin-bottom: 8px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 6px;
margin-bottom: 6px;
flex-shrink: 0;
width: 100%;
}
body.hub-page-ai .ai-mobile-tab {
flex: 1;
min-height: 40px;
padding: 8px 12px;
min-height: 38px;
padding: 6px 4px;
border-radius: 8px;
border: 1px solid var(--border-soft);
background: var(--inset-surface);
color: var(--muted);
font-family: var(--font);
font-size: 0.82rem;
font-size: 0.7rem;
font-weight: 600;
cursor: pointer;
line-height: 1.2;
text-align: center;
}
body.hub-page-ai .ai-mobile-tab-action {
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 35%, var(--border-soft));
}
body.hub-page-ai .ai-mobile-tab.is-active {
@@ -3942,7 +3949,7 @@ body.hub-page-ai #page-ai {
box-shadow: none;
}
body.hub-page-ai #page-ai .page-desc {
body.hub-page-ai #page-ai .page-head {
display: none;
}
@@ -3957,7 +3964,8 @@ body.hub-page-ai #page-ai {
overflow: hidden;
}
body.hub-page-ai .ai-layout[data-ai-mobile-tab="chat"] .ai-chat-panel,
body.hub-page-ai .ai-layout[data-ai-mobile-tab="trading"] .ai-chat-panel,
body.hub-page-ai .ai-layout[data-ai-mobile-tab="general"] .ai-chat-panel,
body.hub-page-ai .ai-layout[data-ai-mobile-tab="history"] .ai-chat-panel {
display: flex;
flex: 1 1 auto;
@@ -3967,10 +3975,19 @@ body.hub-page-ai #page-ai {
min-width: 0;
}
body.hub-page-ai .ai-layout[data-ai-mobile-tab="chat"] .ai-chat-history-panel {
body.hub-page-ai .ai-layout[data-ai-mobile-tab="trading"] .ai-chat-history-panel,
body.hub-page-ai .ai-layout[data-ai-mobile-tab="general"] .ai-chat-history-panel {
display: none !important;
}
body.hub-page-ai .ai-layout[data-ai-mobile-tab="trading"] .ai-chat-main,
body.hub-page-ai .ai-layout[data-ai-mobile-tab="general"] .ai-chat-main {
display: flex;
flex: 1 1 auto;
min-height: 0;
flex-direction: column;
}
body.hub-page-ai .ai-layout[data-ai-mobile-tab="history"] .ai-chat-main,
body.hub-page-ai .ai-layout[data-ai-mobile-tab="history"] .ai-chat-topbar {
display: none !important;
@@ -4003,7 +4020,7 @@ body.hub-page-ai #page-ai {
}
body.hub-page-ai .ai-chat-topbar {
gap: 6px;
display: none;
}
body.hub-page-ai .ai-bot-tab {
@@ -4039,15 +4056,7 @@ body.hub-page-ai #page-ai {
}
body.hub-page-ai .ai-chat-session-head {
margin: 0;
padding: 0 2px 4px;
}
body.hub-page-ai .ai-chat-session-head h2 {
font-size: 0.78rem;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.02em;
display: none;
}
body.hub-page-ai .ai-chat-messages {
@@ -4470,7 +4479,7 @@ body.hub-page-ai #page-ai {
flex: 1 1 auto;
min-height: 0;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(200px, 260px);
grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
gap: 0;
overflow: hidden;
border: 1px solid var(--border-soft);
+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);
+31
View File
@@ -317,6 +317,37 @@ body.hub-page-dashboard .page#page-dashboard {
color: var(--dash-muted);
line-height: 1.4;
word-break: break-word;
display: flex;
flex-direction: column;
gap: 3px;
}
.dash-ac-remark-line {
margin: 0;
padding: 3px 0;
border-top: 1px solid color-mix(in srgb, var(--dash-card-border) 65%, transparent);
}
.dash-ac-remark-line:first-child {
border-top: none;
padding-top: 0;
}
.dash-ac-remark-mon {
color: var(--dash-muted);
}
.dash-ac-remark-pos {
color: var(--dash-text);
}
.dash-ac-remark-pos .pos,
.dash-ac-remark-pos .neg {
font-weight: 600;
}
.dash-ac-remark-issue {
color: var(--dash-warn);
}
.dash-table-wrap {
+30 -1
View File
@@ -90,6 +90,35 @@
</div>`;
}
function renderRemarkLines(ac) {
const lines = Array.isArray(ac && ac.remark_lines) ? ac.remark_lines : [];
if (!lines.length) {
const fallback = esc((ac && ac.remark) || "—");
return `<div class="dash-ac-remark"><div class="dash-ac-remark-line">${fallback}</div></div>`;
}
return `<div class="dash-ac-remark">${lines
.map((ln) => {
const kind = ln && ln.kind;
const text = esc((ln && ln.text) || "");
if (kind === "position" && ln.pnl != null && Number.isFinite(Number(ln.pnl))) {
const pnl = Number(ln.pnl);
return (
`<div class="dash-ac-remark-line dash-ac-remark-pos">` +
`${text} 浮<span class="${pnlClass(pnl)}">${pnlSigned(pnl, 2)}</span>` +
`</div>`
);
}
const cls =
kind === "monitor"
? "dash-ac-remark-line dash-ac-remark-mon"
: kind === "issue"
? "dash-ac-remark-line dash-ac-remark-issue"
: "dash-ac-remark-line";
return `<div class="${cls}">${text}</div>`;
})
.join("")}</div>`;
}
function renderAccounts(accounts, threshold) {
if (!elAccounts) return;
const rows = Array.isArray(accounts) ? accounts : [];
@@ -129,7 +158,7 @@
<div class="dash-ac-metric"><span>浮盈亏</span><strong class="${pnlClass(floatPnl)}">${pnlSigned(floatPnl, 2)}</strong></div>
</div>
${lossBar}
<div class="dash-ac-remark">${esc(ac.remark || "—")}</div>
${renderRemarkLines(ac)}
</article>`;
})
.join("");
+7 -5
View File
@@ -15,8 +15,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
<link rel="stylesheet" href="/assets/app.css?v=20260611-hub-ai-mobile" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260611-hub-dash-sse" />
<link rel="stylesheet" href="/assets/app.css?v=20260611-hub-ai-tabs" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260611-dash-remark-lines" />
</head>
<body>
<div class="app-bg" aria-hidden="true"></div>
@@ -444,8 +444,10 @@
<p class="page-desc">交易教练 / 普通聊天 · 右侧可回看历史会话</p>
</div>
<div class="ai-mobile-tabs" role="tablist" aria-label="AI 教练视图">
<button type="button" class="ai-mobile-tab is-active" data-ai-tab="chat" role="tab" aria-selected="true">聊天</button>
<button type="button" class="ai-mobile-tab is-active" data-ai-tab="trading" role="tab" aria-selected="true">交易教练</button>
<button type="button" class="ai-mobile-tab" data-ai-tab="general" role="tab" aria-selected="false">普通聊天</button>
<button type="button" class="ai-mobile-tab" data-ai-tab="history" role="tab" aria-selected="false">历史</button>
<button type="button" class="ai-mobile-tab ai-mobile-tab-action" data-ai-tab="new" role="tab" aria-selected="false" title="新开对话">新开</button>
</div>
<div class="ai-layout" data-ai-mobile-tab="chat">
<section class="ai-panel ai-chat-panel" data-ai-panel="chat">
@@ -553,8 +555,8 @@
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
<script src="/assets/archive.js?v=20260608-hub-archive-history"></script>
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
<script src="/assets/dashboard.js?v=20260611-hub-dash-sse"></script>
<script src="/assets/dashboard.js?v=20260611-dash-remark-lines"></script>
<script src="/assets/ai_review_render.js?v=2"></script>
<script src="/assets/app.js?v=20260611-hub-ai-mobile"></script>
<script src="/assets/app.js?v=20260611-hub-ai-tabs"></script>
</body>
</html>