feat: restore mentor page to final day/night spec
Rebuild the mentor workspace per the approved final design (day.png/night.png): - mentorView gets a dedicated full-width immersive shell (independent 56px top bar with page title slot + theme mode text, hidden module-nav/market tape/overview/status bar) scoped to body[data-active-view=mentorView]. - Assistant messages become borderless body text with name/time above; only user messages use a blue bubble; keep loading/error/streaming caret states. - 4 quick topics stay visible with history; composer restored to a framed ~94px card with bottom-left shortcut hint and bottom-right send button. - Directory tools merged into one row (search + filter menu + sort); the filter menu still offers all/A/B/C; list selected state is an inset rounded fill; contact rows are borderless 75px items. - Chat header always shows pin/note/profile/clear; pin reuses /api/mentors/ preferences; theme toggle stays the single #themeToggle. - Night tokens match the spec including the two distinct blues (#316FEF link, #5B8DEF quote/selected icon). Mobile (<768) stacks panels with no horizontal overflow; 1024+ follows the desktop spec. - Update stale test baselines (300px sidebar, 94px composer, hint presence, night bubble color, centered disclaimer) and regenerate the architecture inventory. Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
multica-agent
parent
33f9db43b1
commit
8ac3adbb5d
@@ -370,9 +370,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"css_layers": [
|
"css_layers": [
|
||||||
"/shared/tokens.css?v=20260806-2",
|
"/shared/tokens.css?v=20260818-1",
|
||||||
"/shared/base.css?v=20260806-1",
|
"/shared/base.css?v=20260806-1",
|
||||||
"/shared/shell.css?v=20260806-2",
|
"/shared/shell.css?v=20260818-1",
|
||||||
"/shared/auth.css?v=20260806-1",
|
"/shared/auth.css?v=20260806-1",
|
||||||
"/shared/components/controls.css?v=20260806-1",
|
"/shared/components/controls.css?v=20260806-1",
|
||||||
"/shared/components/navigation.css?v=20260806-1",
|
"/shared/components/navigation.css?v=20260806-1",
|
||||||
@@ -390,7 +390,7 @@
|
|||||||
"/pages/popularity/foundation.css?v=20260806-1",
|
"/pages/popularity/foundation.css?v=20260806-1",
|
||||||
"/pages/dragon-tiger/foundation.css?v=20260806-1",
|
"/pages/dragon-tiger/foundation.css?v=20260806-1",
|
||||||
"/pages/screener/foundation.css?v=20260806-2",
|
"/pages/screener/foundation.css?v=20260806-2",
|
||||||
"/pages/mentor/foundation.css?v=20260806-2",
|
"/pages/mentor/foundation.css?v=20260818-1",
|
||||||
"/pages/heaven/foundation.css?v=20260806-2",
|
"/pages/heaven/foundation.css?v=20260806-2",
|
||||||
"/pages/review/foundation.css?v=20260806-1"
|
"/pages/review/foundation.css?v=20260806-1"
|
||||||
],
|
],
|
||||||
@@ -451,8 +451,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "frontend/shared/shell.css",
|
"path": "frontend/shared/shell.css",
|
||||||
"bytes": 51978,
|
"bytes": 54738,
|
||||||
"lines": 3224
|
"lines": 3348
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/heaven/engine.py",
|
"path": "backend/features/heaven/engine.py",
|
||||||
@@ -461,8 +461,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "frontend/index.html",
|
"path": "frontend/index.html",
|
||||||
"bytes": 45846,
|
"bytes": 46345,
|
||||||
"lines": 643
|
"lines": 649
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/screener/catalog.py",
|
"path": "backend/features/screener/catalog.py",
|
||||||
@@ -541,8 +541,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "frontend/shared/admin.js",
|
"path": "frontend/shared/admin.js",
|
||||||
"bytes": 13975,
|
"bytes": 14386,
|
||||||
"lines": 256
|
"lines": 265
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/heaven/market_context.py",
|
"path": "backend/features/heaven/market_context.py",
|
||||||
@@ -561,13 +561,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/system/service.py",
|
"path": "backend/features/system/service.py",
|
||||||
"bytes": 12392,
|
"bytes": 12812,
|
||||||
"lines": 254
|
"lines": 262
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/market/insights_auction.py",
|
"path": "backend/features/market/insights_auction.py",
|
||||||
"bytes": 10717,
|
"bytes": 11618,
|
||||||
"lines": 221
|
"lines": 243
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/heaven/readings.py",
|
"path": "backend/features/heaven/readings.py",
|
||||||
@@ -605,14 +605,19 @@
|
|||||||
"lines": 194
|
"lines": 194
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/screener/formula.py",
|
"path": "frontend/pages/mentor/page.html",
|
||||||
"bytes": 6983,
|
"bytes": 8199,
|
||||||
"lines": 146
|
"lines": 110
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/data/providers/tushare_daily.py",
|
"path": "backend/data/providers/tushare_daily.py",
|
||||||
"bytes": 6837,
|
"bytes": 7121,
|
||||||
"lines": 160
|
"lines": 167
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "backend/features/screener/formula.py",
|
||||||
|
"bytes": 6983,
|
||||||
|
"lines": 146
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/application.py",
|
"path": "backend/application.py",
|
||||||
@@ -644,11 +649,6 @@
|
|||||||
"bytes": 6202,
|
"bytes": 6202,
|
||||||
"lines": 141
|
"lines": 141
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "frontend/pages/mentor/page.html",
|
|
||||||
"bytes": 6190,
|
|
||||||
"lines": 89
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "backend/features/screener/selection.py",
|
"path": "backend/features/screener/selection.py",
|
||||||
"bytes": 6092,
|
"bytes": 6092,
|
||||||
@@ -704,6 +704,11 @@
|
|||||||
"bytes": 4276,
|
"bytes": 4276,
|
||||||
"lines": 91
|
"lines": 91
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "frontend/shared/theme.js",
|
||||||
|
"bytes": 4258,
|
||||||
|
"lines": 117
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/screener/engine.py",
|
"path": "backend/features/screener/engine.py",
|
||||||
"bytes": 4242,
|
"bytes": 4242,
|
||||||
@@ -714,11 +719,6 @@
|
|||||||
"bytes": 4118,
|
"bytes": 4118,
|
||||||
"lines": 115
|
"lines": 115
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "frontend/shared/theme.js",
|
|
||||||
"bytes": 4118,
|
|
||||||
"lines": 115
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "frontend/shared/table.js",
|
"path": "frontend/shared/table.js",
|
||||||
"bytes": 3790,
|
"bytes": 3790,
|
||||||
|
|||||||
+10
-4
@@ -17,9 +17,9 @@
|
|||||||
document.documentElement.style.colorScheme = theme;
|
document.documentElement.style.colorScheme = theme;
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/shared/tokens.css?v=20260806-2">
|
<link rel="stylesheet" href="/shared/tokens.css?v=20260818-1">
|
||||||
<link rel="stylesheet" href="/shared/base.css?v=20260806-1">
|
<link rel="stylesheet" href="/shared/base.css?v=20260806-1">
|
||||||
<link rel="stylesheet" href="/shared/shell.css?v=20260806-2">
|
<link rel="stylesheet" href="/shared/shell.css?v=20260818-1">
|
||||||
<link rel="stylesheet" href="/shared/auth.css?v=20260806-1">
|
<link rel="stylesheet" href="/shared/auth.css?v=20260806-1">
|
||||||
<link rel="stylesheet" href="/shared/components/controls.css?v=20260806-1">
|
<link rel="stylesheet" href="/shared/components/controls.css?v=20260806-1">
|
||||||
<link rel="stylesheet" href="/shared/components/navigation.css?v=20260806-1">
|
<link rel="stylesheet" href="/shared/components/navigation.css?v=20260806-1">
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<link rel="stylesheet" href="/pages/popularity/foundation.css?v=20260806-1">
|
<link rel="stylesheet" href="/pages/popularity/foundation.css?v=20260806-1">
|
||||||
<link rel="stylesheet" href="/pages/dragon-tiger/foundation.css?v=20260806-1">
|
<link rel="stylesheet" href="/pages/dragon-tiger/foundation.css?v=20260806-1">
|
||||||
<link rel="stylesheet" href="/pages/screener/foundation.css?v=20260806-2">
|
<link rel="stylesheet" href="/pages/screener/foundation.css?v=20260806-2">
|
||||||
<link rel="stylesheet" href="/pages/mentor/foundation.css?v=20260806-2">
|
<link rel="stylesheet" href="/pages/mentor/foundation.css?v=20260818-1">
|
||||||
<link rel="stylesheet" href="/pages/heaven/foundation.css?v=20260806-2">
|
<link rel="stylesheet" href="/pages/heaven/foundation.css?v=20260806-2">
|
||||||
<link rel="stylesheet" href="/pages/review/foundation.css?v=20260806-1">
|
<link rel="stylesheet" href="/pages/review/foundation.css?v=20260806-1">
|
||||||
</head>
|
</head>
|
||||||
@@ -68,6 +68,10 @@
|
|||||||
<span id="mobilePageGroup">行情</span>
|
<span id="mobilePageGroup">行情</span>
|
||||||
<strong id="mobilePageTitle">情绪周期</strong>
|
<strong id="mobilePageTitle">情绪周期</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="app-page-context" aria-live="polite">
|
||||||
|
<strong id="currentPageTitle">小白复盘</strong>
|
||||||
|
<span id="currentPageSubtitle"></span>
|
||||||
|
</div>
|
||||||
<div class="market-tape" aria-label="市场概况">
|
<div class="market-tape" aria-label="市场概况">
|
||||||
<span class="market-item up">上涨 <strong id="tapeUp">--</strong></span>
|
<span class="market-item up">上涨 <strong id="tapeUp">--</strong></span>
|
||||||
<span class="market-item down">下跌 <strong id="tapeDown">--</strong></span>
|
<span class="market-item down">下跌 <strong id="tapeDown">--</strong></span>
|
||||||
@@ -82,7 +86,7 @@
|
|||||||
<button id="nextDate" class="icon-button" type="button" title="后一个交易日" aria-label="后一个交易日"><i data-lucide="chevron-right"></i></button>
|
<button id="nextDate" class="icon-button" type="button" title="后一个交易日" aria-label="后一个交易日"><i data-lucide="chevron-right"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<button id="globalSearchButton" class="icon-button global-search-button" type="button" title="全局搜索(Ctrl+K)" aria-label="全局搜索"><i data-lucide="search"></i></button>
|
<button id="globalSearchButton" class="icon-button global-search-button" type="button" title="全局搜索(Ctrl+K)" aria-label="全局搜索"><i data-lucide="search"></i></button>
|
||||||
<button id="themeToggle" class="icon-button theme-toggle" type="button" title="切换到夜间模式" aria-label="切换到夜间模式" aria-pressed="false"><i data-lucide="moon"></i></button>
|
<button id="themeToggle" class="icon-button theme-toggle" type="button" title="切换到夜间模式" aria-label="切换到夜间模式" aria-pressed="false"><i data-lucide="moon"></i><span id="themeModeText">日间模式</span></button>
|
||||||
<button id="alertButton" class="icon-button alert-button" type="button" title="提醒中心" aria-label="提醒中心"><i data-lucide="bell"></i><span id="alertBadge" class="alert-badge" hidden>0</span></button>
|
<button id="alertButton" class="icon-button alert-button" type="button" title="提醒中心" aria-label="提醒中心"><i data-lucide="bell"></i><span id="alertBadge" class="alert-badge" hidden>0</span></button>
|
||||||
<button id="assistantButton" class="icon-button assistant-button" type="button" title="复盘助手" aria-label="复盘助手"><i data-lucide="message-circle-more"></i></button>
|
<button id="assistantButton" class="icon-button assistant-button" type="button" title="复盘助手" aria-label="复盘助手"><i data-lucide="message-circle-more"></i></button>
|
||||||
<button id="headerMenuButton" class="icon-button header-menu-button" type="button" title="打开命令菜单" aria-label="打开命令菜单" aria-expanded="false" aria-controls="headerCommandGroup"><i data-lucide="ellipsis"></i></button>
|
<button id="headerMenuButton" class="icon-button header-menu-button" type="button" title="打开命令菜单" aria-label="打开命令菜单" aria-expanded="false" aria-controls="headerCommandGroup"><i data-lucide="ellipsis"></i></button>
|
||||||
@@ -589,6 +593,8 @@
|
|||||||
<div class="settings-section-heading"><h3>公共行情</h3><span id="systemDataStatus">待检查</span></div>
|
<div class="settings-section-heading"><h3>公共行情</h3><span id="systemDataStatus">待检查</span></div>
|
||||||
<label class="form-field"><span>Tushare Token</span><input id="systemTokenInput" type="password" autocomplete="off" minlength="20" placeholder="留空保留现有 Token"></label>
|
<label class="form-field"><span>Tushare Token</span><input id="systemTokenInput" type="password" autocomplete="off" minlength="20" placeholder="留空保留现有 Token"></label>
|
||||||
<label class="form-field"><span>iFinD Refresh Token</span><input id="systemIfindTokenInput" type="password" autocomplete="off" maxlength="2048" placeholder="留空保留现有 Token"></label>
|
<label class="form-field"><span>iFinD Refresh Token</span><input id="systemIfindTokenInput" type="password" autocomplete="off" maxlength="2048" placeholder="留空保留现有 Token"></label>
|
||||||
|
<label class="switch-control"><input id="systemIfindEnabled" type="checkbox"><span>启用 iFinD 数据源</span></label>
|
||||||
|
<p class="form-hint">默认停用。停用时仅保留凭据,不会发出任何 iFinD 请求;续费后开启此开关并保存即可恢复。</p>
|
||||||
<label class="switch-control"><input id="systemBackgroundRefresh" type="checkbox"><span>启用交易时段后台刷新</span></label>
|
<label class="switch-control"><input id="systemBackgroundRefresh" type="checkbox"><span>启用交易时段后台刷新</span></label>
|
||||||
<p class="form-hint">所有用户读取同一份后台快照,页面不会随后台任务自动重绘。</p>
|
<p class="form-hint">所有用户读取同一份后台快照,页面不会随后台任务自动重绘。</p>
|
||||||
<div class="dialog-actions admin-inline-actions"><button id="adminRefreshButton" class="button" type="button"><i data-lucide="refresh-cw"></i>立即后台刷新</button><button class="button primary" type="submit">保存行情配置</button></div>
|
<div class="dialog-actions admin-inline-actions"><button id="adminRefreshButton" class="button" type="button"><i data-lucide="refresh-cw"></i>立即后台刷新</button><button class="button primary" type="submit">保存行情配置</button></div>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
["heaven", "/pages/heaven/page.html?v=20260803-1", ["heavenView"]],
|
["heaven", "/pages/heaven/page.html?v=20260803-1", ["heavenView"]],
|
||||||
["ladder", "/pages/ladder/page.html?v=20260803-1", ["ladderView"]],
|
["ladder", "/pages/ladder/page.html?v=20260803-1", ["ladderView"]],
|
||||||
["screener", "/pages/screener/page.html?v=20260804-1", ["screenerView", "screenerTrackingView"]],
|
["screener", "/pages/screener/page.html?v=20260804-1", ["screenerView", "screenerTrackingView"]],
|
||||||
["mentor", "/pages/mentor/page.html?v=20260806-2", ["mentorView"]],
|
["mentor", "/pages/mentor/page.html?v=20260818-1", ["mentorView"]],
|
||||||
["rotation", "/pages/rotation/page.html?v=20260803-1", ["rotationView"]],
|
["rotation", "/pages/rotation/page.html?v=20260803-1", ["rotationView"]],
|
||||||
["auction", "/pages/auction/page.html?v=20260803-1", ["auctionView"]],
|
["auction", "/pages/auction/page.html?v=20260803-1", ["auctionView"]],
|
||||||
["themes", "/pages/themes/page.html?v=20260803-1", ["themeLibraryView"]],
|
["themes", "/pages/themes/page.html?v=20260803-1", ["themeLibraryView"]],
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"/pages/popularity/page.js?v=20260729-1",
|
"/pages/popularity/page.js?v=20260729-1",
|
||||||
"/pages/dragon-tiger/page.js?v=20260806-1",
|
"/pages/dragon-tiger/page.js?v=20260806-1",
|
||||||
"/pages/screener/page.js?v=20260806-1",
|
"/pages/screener/page.js?v=20260806-1",
|
||||||
"/pages/mentor/page.js?v=20260806-2",
|
"/pages/mentor/page.js?v=20260818-1",
|
||||||
"/pages/heaven/page.js?v=20260729-1",
|
"/pages/heaven/page.js?v=20260729-1",
|
||||||
"/pages/review/page.js?v=20260729-1",
|
"/pages/review/page.js?v=20260729-1",
|
||||||
"/shared/state.js?v=20260729-1",
|
"/shared/state.js?v=20260729-1",
|
||||||
|
|||||||
+602
-346
File diff suppressed because it is too large
Load Diff
@@ -1,35 +1,28 @@
|
|||||||
<section id="mentorView" class="workspace-view page member-feature-view redesigned-mentor-view">
|
<section id="mentorView" class="workspace-view page member-feature-view redesigned-mentor-view mentor-page-header">
|
||||||
<div class="member-gate" hidden><div class="member-gate-icon"><i data-lucide="lock-keyhole"></i></div><div><strong>问师仅对会员开放</strong><span>开通会员后可使用思维模型进行对话。会员状态可从顶部账号标识进入。</span></div></div>
|
<div class="member-gate" hidden><div class="member-gate-icon"><i data-lucide="lock-keyhole"></i></div><div><strong>问师仅对会员开放</strong><span>开通会员后可使用思维模型进行对话。会员状态可从顶部账号标识进入。</span></div></div>
|
||||||
<header class="section-toolbar lad-head mentor-page-header">
|
|
||||||
<div class="section-title-group mentor-page-title">
|
|
||||||
<h2>问师</h2>
|
|
||||||
<span class="section-subtitle">与不同交易思维模型持续对话 · <span id="mentorDataDate">--</span></span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div id="mentorNotice" class="inline-notice" hidden></div>
|
<div id="mentorNotice" class="inline-notice" hidden></div>
|
||||||
<div class="mentor-layout">
|
<div class="mentor-layout">
|
||||||
<aside class="mentor-sidebar" aria-label="思维模型目录">
|
<aside class="mentor-sidebar" aria-label="思维模型目录">
|
||||||
<div class="mentor-directory-heading">
|
|
||||||
<div class="mentor-directory-title"><strong>联系人</strong><span id="mentorCount">0 位</span></div>
|
|
||||||
<button id="mentorSortToggle" class="mentor-sort-toggle" type="button" aria-pressed="false" title="整理联系人顺序"><i data-lucide="list-ordered"></i><span>整理</span></button>
|
|
||||||
</div>
|
|
||||||
<div class="mentor-directory-tools">
|
<div class="mentor-directory-tools">
|
||||||
<label class="mentor-search-field">
|
<label class="mentor-search-field">
|
||||||
<span class="visually-hidden">搜索思维模型</span>
|
<span class="visually-hidden">搜索思维模型</span>
|
||||||
<i data-lucide="search"></i>
|
<i data-lucide="search"></i>
|
||||||
<input id="mentorSearchInput" type="search" maxlength="50" placeholder="搜索联系人或标签" autocomplete="off">
|
<input id="mentorSearchInput" type="search" maxlength="50" placeholder="搜索联系人或标签" autocomplete="off">
|
||||||
</label>
|
</label>
|
||||||
<div class="mentor-evidence-filters" role="group" aria-label="按素材等级筛选">
|
<div class="mentor-filter-menu">
|
||||||
<button class="active" type="button" data-mentor-grade="all">全部</button>
|
<button id="mentorFilterToggle" class="mentor-filter-toggle" type="button" aria-haspopup="menu" aria-expanded="false" title="筛选思维模型"><span id="mentorFilterLabel">全部</span><i data-lucide="chevron-down"></i></button>
|
||||||
<button type="button" data-mentor-grade="A">A级</button>
|
<div id="mentorFilterOptions" class="mentor-filter-options" role="menu" hidden>
|
||||||
<button type="button" data-mentor-grade="B">B级</button>
|
<button type="button" class="active" data-mentor-grade="all" role="menuitem">全部<span id="mentorCount">0 位</span></button>
|
||||||
<button type="button" data-mentor-grade="C">C级</button>
|
<button type="button" data-mentor-grade="A" role="menuitem">A级</button>
|
||||||
|
<button type="button" data-mentor-grade="B" role="menuitem">B级</button>
|
||||||
|
<button type="button" data-mentor-grade="C" role="menuitem">C级</button>
|
||||||
</div>
|
</div>
|
||||||
<p id="mentorSortHint" class="mentor-sort-hint" hidden>拖动联系人,或使用箭头调整顺序</p>
|
</div>
|
||||||
|
<button id="mentorSortToggle" class="mentor-sort-toggle" type="button" aria-pressed="false" title="整理联系人顺序"><i data-lucide="list-ordered"></i><span>整理</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="mentorList" class="mentor-list"></div>
|
<div id="mentorList" class="mentor-list"></div>
|
||||||
<div id="mentorListEmpty" class="mentor-list-empty" hidden>没有符合条件的思维模型</div>
|
<div id="mentorListEmpty" class="mentor-list-empty" hidden>没有符合条件的思维模型</div>
|
||||||
<p class="mentor-evidence-legend">等级仅表示公开素材完整度</p>
|
<p id="mentorSortHint" class="mentor-sort-hint" hidden>拖动联系人,或使用箭头调整顺序</p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<section class="mentor-chat-panel" aria-label="问师对话">
|
<section class="mentor-chat-panel" aria-label="问师对话">
|
||||||
@@ -41,7 +34,12 @@
|
|||||||
<p id="activeMentorStatus">思维模型已就绪</p>
|
<p id="activeMentorStatus">思维模型已就绪</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mentor-chat-actions">
|
||||||
|
<button id="mentorPinButton" class="icon-button mentor-header-action" type="button" aria-label="置顶当前思维模型" title="置顶"><i data-lucide="pin"></i></button>
|
||||||
|
<button id="mentorNoteButton" class="icon-button mentor-header-action" type="button" aria-label="备注标签" title="备注标签"><i data-lucide="tag"></i></button>
|
||||||
|
<button id="mentorProfileButton" class="icon-button mentor-header-action" type="button" aria-label="游资档案" title="游资档案"><i data-lucide="id-card"></i></button>
|
||||||
<button id="clearMentorChatButton" class="icon-button mentor-clear-button" type="button" disabled aria-label="清空当前对话" title="清空对话"><i data-lucide="trash-2"></i></button>
|
<button id="clearMentorChatButton" class="icon-button mentor-clear-button" type="button" disabled aria-label="清空当前对话" title="清空对话"><i data-lucide="trash-2"></i></button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div id="mentorMessages" class="mentor-messages" aria-live="polite"></div>
|
<div id="mentorMessages" class="mentor-messages" aria-live="polite"></div>
|
||||||
<div id="mentorQuickPrompts" class="mentor-quick-prompts">
|
<div id="mentorQuickPrompts" class="mentor-quick-prompts">
|
||||||
@@ -52,38 +50,61 @@
|
|||||||
<button type="button" data-mentor-prompt="现在最需要防范的风险是什么?"><i data-lucide="shield-alert"></i>风险检查</button>
|
<button type="button" data-mentor-prompt="现在最需要防范的风险是什么?"><i data-lucide="shield-alert"></i>风险检查</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="mentorChatForm" class="mentor-chat-form">
|
<form id="mentorChatForm" class="mentor-chat-form">
|
||||||
|
<div class="mentor-composer-main">
|
||||||
<label class="visually-hidden" for="mentorQuestion">向当前思维模型提问</label>
|
<label class="visually-hidden" for="mentorQuestion">向当前思维模型提问</label>
|
||||||
<textarea id="mentorQuestion" maxlength="2000" placeholder="输入市场、板块、个股代码或交易问题"></textarea>
|
<textarea id="mentorQuestion" maxlength="2000" placeholder="输入市场、板块、个股代码或交易问题"></textarea>
|
||||||
|
<span class="mentor-composer-hint">Enter 发送 · Shift + Enter 换行</span>
|
||||||
|
</div>
|
||||||
<div class="mentor-composer-actions">
|
<div class="mentor-composer-actions">
|
||||||
<span>Enter 发送 · Shift + Enter 换行</span>
|
|
||||||
<button id="stopMentorQuestion" class="button ghost mentor-stop-button" type="button" hidden><i data-lucide="square"></i><span>停止</span></button>
|
<button id="stopMentorQuestion" class="button ghost mentor-stop-button" type="button" hidden><i data-lucide="square"></i><span>停止</span></button>
|
||||||
<button id="sendMentorQuestion" class="button primary mentor-send-button" type="submit"><i data-lucide="send-horizontal"></i><span>发送</span></button>
|
<button id="sendMentorQuestion" class="button primary mentor-send-button" type="submit"><i data-lucide="send-horizontal"></i><span>发送</span></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="mentor-disclaimer">基于公开资料提炼的思维模型模拟,不代表本人观点,不构成投资建议。</p>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<aside class="mentor-profile-panel" aria-label="当前思维模型资料">
|
</div>
|
||||||
|
<footer class="mentor-workspace-footer">
|
||||||
|
<span class="mentor-evidence-legend">等级仅表示公开素材完整度</span>
|
||||||
|
<p class="mentor-disclaimer">基于公开资料提炼的思维模型模拟,不代表本人观点,不构成投资建议。</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<dialog id="mentorNoteDialog" class="mentor-floating-dialog mentor-note-dialog" aria-labelledby="mentorNoteDialogTitle">
|
||||||
|
<div class="mentor-dialog-head">
|
||||||
|
<div><span class="dialog-eyebrow">问师</span><h3 id="mentorNoteDialogTitle">备注标签</h3></div>
|
||||||
|
<button type="button" class="icon-button mentor-dialog-close" data-mentor-dialog-close="mentorNoteDialog" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="mentor-dialog-body">
|
||||||
|
<label class="visually-hidden" for="mentorNoteInput">备注标签</label>
|
||||||
|
<textarea id="mentorNoteInput" maxlength="2000" placeholder="为该思维模型记录备注或标签"></textarea>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="mentorProfileDialog" class="mentor-floating-dialog mentor-profile-dialog" aria-labelledby="mentorProfileDialogTitle">
|
||||||
|
<div class="mentor-dialog-head">
|
||||||
|
<div><span class="dialog-eyebrow">问师</span><h3 id="mentorProfileDialogTitle">游资档案</h3></div>
|
||||||
|
<button type="button" class="icon-button mentor-dialog-close" data-mentor-dialog-close="mentorProfileDialog" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="mentor-profile-dialog-body">
|
||||||
<div class="mentor-profile-hero">
|
<div class="mentor-profile-hero">
|
||||||
<span id="mentorProfileAvatar" class="mentor-profile-avatar" aria-hidden="true">师</span>
|
<span id="mentorProfileDialogAvatar" class="mentor-profile-avatar" aria-hidden="true">师</span>
|
||||||
<h3 id="mentorProfileName">--</h3>
|
<h3 id="mentorProfileDialogName">--</h3>
|
||||||
<p id="mentorProfileTagline">--</p>
|
<p id="mentorProfileDialogTagline">--</p>
|
||||||
<div id="mentorProfileBadges" class="mentor-profile-badges"></div>
|
<div id="mentorProfileDialogBadges" class="mentor-profile-badges"></div>
|
||||||
</div>
|
</div>
|
||||||
<section class="mentor-profile-section">
|
<section class="mentor-profile-section">
|
||||||
<h4>关注维度</h4>
|
<h4>关注维度</h4>
|
||||||
<div id="activeMentorFocus" class="mentor-active-focus"></div>
|
<div id="mentorProfileDialogFocus" class="mentor-active-focus"></div>
|
||||||
</section>
|
</section>
|
||||||
<section class="mentor-profile-section">
|
<section class="mentor-profile-section">
|
||||||
<h4>资料依据</h4>
|
<h4>资料依据</h4>
|
||||||
<strong id="mentorProfileSource">--</strong>
|
<strong id="mentorProfileDialogSource">--</strong>
|
||||||
<p id="activeMentorEvidence">--</p>
|
<p id="mentorProfileDialogEvidence">--</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="mentor-profile-section mentor-profile-boundary">
|
<section class="mentor-profile-section mentor-profile-boundary">
|
||||||
<h4>数据边界</h4>
|
<h4>数据边界</h4>
|
||||||
<p><i data-lucide="calendar-days"></i><span id="mentorProfileDataDate">--</span></p>
|
<p><i data-lucide="calendar-days"></i><span id="mentorProfileDialogDataDate">--</span></p>
|
||||||
<p><i data-lucide="database"></i><span>仅使用网页已提供的市场数据</span></p>
|
<p><i data-lucide="database"></i><span>仅使用网页已提供的市场数据</span></p>
|
||||||
</section>
|
</section>
|
||||||
</aside>
|
|
||||||
</div>
|
</div>
|
||||||
|
</dialog>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -35,23 +35,63 @@ function renderMentorWorkspace() {
|
|||||||
const setup = state.mentorSetup;
|
const setup = state.mentorSetup;
|
||||||
if (!setup) return;
|
if (!setup) return;
|
||||||
const selected = setup.mentors.find((item) => item.id === state.selectedMentorId) || null;
|
const selected = setup.mentors.find((item) => item.id === state.selectedMentorId) || null;
|
||||||
setText("mentorDataDate", `数据日期 ${displayCompactDate(setup.trade_date)}`);
|
setText("currentPageSubtitle", `与不同交易思维模型持续对话 · 数据日期 ${displayCompactDate(setup.trade_date)}`);
|
||||||
setText("activeMentorName", selected?.name || "--");
|
setText("activeMentorName", selected?.name || "--");
|
||||||
setText("mentorProfileName", selected?.name || "--");
|
|
||||||
setText("mentorProfileTagline", selected?.tagline || selected?.description || "--");
|
|
||||||
setText("mentorProfileSource", selected?.evidence?.label || "公开资料整理");
|
|
||||||
setText("mentorProfileDataDate", `行情数据 ${displayCompactDate(setup.trade_date)}`);
|
|
||||||
setText("activeMentorAvatar", mentorAvatarText(selected));
|
setText("activeMentorAvatar", mentorAvatarText(selected));
|
||||||
setText("mentorProfileAvatar", mentorAvatarText(selected));
|
|
||||||
document.querySelector("#activeMentorBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
document.querySelector("#activeMentorBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
||||||
document.querySelector("#mentorProfileBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
const pinButton = document.querySelector("#mentorPinButton");
|
||||||
setText("activeMentorEvidence", selected?.evidence?.note || selected?.description || "--");
|
if (pinButton) {
|
||||||
document.querySelector("#activeMentorFocus").innerHTML = (selected?.focus || []).slice(0, 4)
|
pinButton.classList.toggle("active", Boolean(selected?.pinned));
|
||||||
.map((item) => `<span>${escapeHtml(item)}</span>`).join("");
|
pinButton.setAttribute("aria-label", selected?.pinned ? "取消置顶当前思维模型" : "置顶当前思维模型");
|
||||||
|
pinButton.setAttribute("aria-pressed", String(Boolean(selected?.pinned)));
|
||||||
|
}
|
||||||
|
populateMentorDialogs(selected);
|
||||||
renderMentorDirectory();
|
renderMentorDirectory();
|
||||||
renderMentorMessages();
|
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));
|
||||||
|
document.querySelector("#mentorProfileDialogBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
||||||
|
document.querySelector("#mentorProfileDialogFocus").innerHTML = (selected?.focus || []).slice(0, 4)
|
||||||
|
.map((item) => `<span>${escapeHtml(item)}</span>`).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() {
|
function renderMentorDirectory() {
|
||||||
const mentors = state.mentorSetup?.mentors || [];
|
const mentors = state.mentorSetup?.mentors || [];
|
||||||
const query = state.mentorQuery;
|
const query = state.mentorQuery;
|
||||||
@@ -70,15 +110,22 @@ function renderMentorDirectory() {
|
|||||||
return haystack.includes(query);
|
return haystack.includes(query);
|
||||||
});
|
});
|
||||||
setText("mentorCount", filtered.length === mentors.length ? `${mentors.length} 位` : `${filtered.length} / ${mentors.length} 位`);
|
setText("mentorCount", filtered.length === mentors.length ? `${mentors.length} 位` : `${filtered.length} / ${mentors.length} 位`);
|
||||||
|
setText("mentorFilterLabel", state.mentorGrade === "all" ? "全部" : `${state.mentorGrade}级`);
|
||||||
const sortToggle = document.querySelector("#mentorSortToggle");
|
const sortToggle = document.querySelector("#mentorSortToggle");
|
||||||
sortToggle.classList.toggle("active", state.mentorSortMode);
|
sortToggle.classList.toggle("active", state.mentorSortMode);
|
||||||
sortToggle.setAttribute("aria-pressed", String(state.mentorSortMode));
|
sortToggle.setAttribute("aria-pressed", String(state.mentorSortMode));
|
||||||
sortToggle.querySelector("span").textContent = state.mentorSortMode ? "完成" : "整理";
|
sortToggle.querySelector("span").textContent = state.mentorSortMode ? "完成" : "整理";
|
||||||
document.querySelector("#mentorSortHint").hidden = !state.mentorSortMode;
|
document.querySelector("#mentorSortHint").hidden = !state.mentorSortMode;
|
||||||
document.querySelector("#mentorSearchInput").disabled = state.mentorSortMode;
|
document.querySelector("#mentorSearchInput").disabled = state.mentorSortMode;
|
||||||
|
document.querySelector("#mentorFilterToggle").disabled = state.mentorSortMode;
|
||||||
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
||||||
button.disabled = state.mentorSortMode;
|
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");
|
const container = document.querySelector("#mentorList");
|
||||||
container.classList.toggle("is-sorting", state.mentorSortMode);
|
container.classList.toggle("is-sorting", state.mentorSortMode);
|
||||||
container.innerHTML = filtered.map((mentor) => {
|
container.innerHTML = filtered.map((mentor) => {
|
||||||
@@ -143,6 +190,36 @@ function toggleMentorSortMode() {
|
|||||||
renderMentorDirectory();
|
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) {
|
async function toggleMentorPin(mentorId) {
|
||||||
if (state.mentorSavingPreferences) return;
|
if (state.mentorSavingPreferences) return;
|
||||||
const mentors = state.mentorSetup?.mentors || [];
|
const mentors = state.mentorSetup?.mentors || [];
|
||||||
@@ -262,6 +339,14 @@ function mentorAvatarText(mentor) {
|
|||||||
return Array.from(String(mentor?.name || "师").trim())[0] || "师";
|
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) {
|
async function selectMentor(mentorId) {
|
||||||
if (mentorId === state.selectedMentorId) return;
|
if (mentorId === state.selectedMentorId) return;
|
||||||
state.selectedMentorId = mentorId;
|
state.selectedMentorId = mentorId;
|
||||||
@@ -289,7 +374,7 @@ function renderMentorMessages() {
|
|||||||
<article class="mentor-message ${message.role} ${message.error ? "is-error" : ""}">
|
<article class="mentor-message ${message.role} ${message.error ? "is-error" : ""}">
|
||||||
<span class="mentor-message-avatar" aria-hidden="true">${message.role === "user" ? "我" : escapeHtml(mentorAvatarText(selected))}</span>
|
<span class="mentor-message-avatar" aria-hidden="true">${message.role === "user" ? "我" : escapeHtml(mentorAvatarText(selected))}</span>
|
||||||
<div class="mentor-message-body">
|
<div class="mentor-message-body">
|
||||||
<div class="mentor-message-label">${message.role === "user" ? "我" : escapeHtml(selected?.name || "问师")}</div>
|
<div class="mentor-message-label">${message.role === "user" ? "我" : escapeHtml(selected?.name || "问师")} · ${escapeHtml(mentorMessageTime(message))}</div>
|
||||||
<div class="mentor-message-content">${message.role === "assistant"
|
<div class="mentor-message-content">${message.role === "assistant"
|
||||||
? (message.content ? formatMentorAnswer(message.content) : '<p class="mentor-loading-copy">正在读取复盘数据并推演...</p>')
|
? (message.content ? formatMentorAnswer(message.content) : '<p class="mentor-loading-copy">正在读取复盘数据并推演...</p>')
|
||||||
: escapeHtml(message.content)}</div>
|
: escapeHtml(message.content)}</div>
|
||||||
@@ -300,7 +385,7 @@ function renderMentorMessages() {
|
|||||||
</article>
|
</article>
|
||||||
`).join("");
|
`).join("");
|
||||||
}
|
}
|
||||||
document.querySelector("#mentorQuickPrompts").hidden = state.mentorMessages.length > 0 || state.mentorLoading;
|
document.querySelector("#mentorQuickPrompts").hidden = false;
|
||||||
document.querySelector("#clearMentorChatButton").disabled = !state.mentorMessages.length || state.mentorLoading;
|
document.querySelector("#clearMentorChatButton").disabled = !state.mentorMessages.length || state.mentorLoading;
|
||||||
document.querySelector("#mentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
|
document.querySelector("#mentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
|
||||||
document.querySelector("#sendMentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
|
document.querySelector("#sendMentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
|
||||||
@@ -342,6 +427,7 @@ async function sendMentorQuestion(event) {
|
|||||||
const responseMessage = { role: "assistant", content: "", streaming: true, meta: "", followUps: [] };
|
const responseMessage = { role: "assistant", content: "", streaming: true, meta: "", followUps: [] };
|
||||||
state.mentorMessages.push(responseMessage);
|
state.mentorMessages.push(responseMessage);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
syncMentorComposerHeight();
|
||||||
state.mentorLoading = true;
|
state.mentorLoading = true;
|
||||||
state.mentorController = new AbortController();
|
state.mentorController = new AbortController();
|
||||||
hideMentorNotice();
|
hideMentorNotice();
|
||||||
@@ -431,6 +517,7 @@ async function streamMentorRequest(body, signal, onDelta, onMeta) {
|
|||||||
function useMentorQuickPrompt(prompt) {
|
function useMentorQuickPrompt(prompt) {
|
||||||
const input = document.querySelector("#mentorQuestion");
|
const input = document.querySelector("#mentorQuestion");
|
||||||
input.value = prompt || "";
|
input.value = prompt || "";
|
||||||
|
syncMentorComposerHeight();
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,18 +616,20 @@ function bindMentorEvents() {
|
|||||||
document.querySelector("#stopMentorQuestion").addEventListener("click", stopMentorGeneration);
|
document.querySelector("#stopMentorQuestion").addEventListener("click", stopMentorGeneration);
|
||||||
document.querySelector("#clearMentorChatButton").addEventListener("click", clearMentorConversation);
|
document.querySelector("#clearMentorChatButton").addEventListener("click", clearMentorConversation);
|
||||||
document.querySelector("#mentorSortToggle").addEventListener("click", toggleMentorSortMode);
|
document.querySelector("#mentorSortToggle").addEventListener("click", toggleMentorSortMode);
|
||||||
|
document.querySelector("#mentorFilterToggle").addEventListener("click", toggleMentorFilterMenu);
|
||||||
document.querySelector("#mentorSearchInput").addEventListener("input", (event) => {
|
document.querySelector("#mentorSearchInput").addEventListener("input", (event) => {
|
||||||
state.mentorQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
|
state.mentorQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
|
||||||
renderMentorDirectory();
|
renderMentorDirectory();
|
||||||
});
|
});
|
||||||
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
state.mentorGrade = button.dataset.mentorGrade || "all";
|
selectMentorGrade(button.dataset.mentorGrade);
|
||||||
document.querySelectorAll("[data-mentor-grade]").forEach((item) => {
|
closeMentorFilterMenu();
|
||||||
item.classList.toggle("active", item === button);
|
|
||||||
});
|
});
|
||||||
renderMentorDirectory();
|
|
||||||
});
|
});
|
||||||
|
document.addEventListener("click", (event) => {
|
||||||
|
if (event.target.closest(".mentor-filter-menu")) return;
|
||||||
|
closeMentorFilterMenu();
|
||||||
});
|
});
|
||||||
document.querySelectorAll("[data-mentor-prompt]").forEach((button) => {
|
document.querySelectorAll("[data-mentor-prompt]").forEach((button) => {
|
||||||
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorPrompt));
|
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorPrompt));
|
||||||
@@ -550,4 +639,70 @@ function bindMentorEvents() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
document.querySelector("#mentorChatForm").requestSubmit();
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3222,3 +3222,127 @@ body.sidebar-collapsed .status-bar {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dedicated mentor workspace (final day/night spec). Only the active mentor
|
||||||
|
view enables this immersive state; other pages keep the canonical shell. */
|
||||||
|
.app-page-context {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#themeModeText {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-page-context strong {
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
line-height: 1.3;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-page-context span {
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body[data-active-view="mentorView"] .main {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
height: 100dvh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .module-nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .status-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .market-tape,
|
||||||
|
body[data-active-view="mentorView"] .mobile-page-context {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .overview-strip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .app-header {
|
||||||
|
flex: 0 0 56px;
|
||||||
|
height: 56px;
|
||||||
|
min-height: 56px;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 0 20px;
|
||||||
|
background: #F5F6F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] body[data-active-view="mentorView"] .app-header {
|
||||||
|
background: #141519;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .app-page-context {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .header-actions > :is(.global-search-button, .alert-button, .assistant-button, .header-menu-button) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .theme-toggle {
|
||||||
|
width: auto;
|
||||||
|
height: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-label);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .theme-toggle .lucide {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] #themeModeText {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .app-main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: calc(100dvh - 56px);
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-active-view="mentorView"] .workspace-view.active-view {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
const page = registry.get(viewId);
|
const page = registry.get(viewId);
|
||||||
const navigationId = page?.navigation_alias || viewId;
|
const navigationId = page?.navigation_alias || viewId;
|
||||||
const marketView = page?.group === "market";
|
const marketView = page?.group === "market";
|
||||||
|
const previousActiveView = document.body.dataset.activeView;
|
||||||
document.body.dataset.activeView = viewId;
|
document.body.dataset.activeView = viewId;
|
||||||
const mobileGroup = document.querySelector("#mobilePageGroup");
|
const mobileGroup = document.querySelector("#mobilePageGroup");
|
||||||
const mobileTitle = document.querySelector("#mobilePageTitle");
|
const mobileTitle = document.querySelector("#mobilePageTitle");
|
||||||
@@ -69,6 +70,10 @@
|
|||||||
: page?.group === "personal" ? "复盘" : "工具";
|
: page?.group === "personal" ? "复盘" : "工具";
|
||||||
}
|
}
|
||||||
if (mobileTitle) mobileTitle.textContent = page?.title || "小白复盘";
|
if (mobileTitle) mobileTitle.textContent = page?.title || "小白复盘";
|
||||||
|
const pageTitle = document.querySelector("#currentPageTitle");
|
||||||
|
if (pageTitle) pageTitle.textContent = page?.title || "小白复盘";
|
||||||
|
const pageSubtitle = document.querySelector("#currentPageSubtitle");
|
||||||
|
if (pageSubtitle && previousActiveView !== viewId) pageSubtitle.textContent = "";
|
||||||
document.querySelectorAll(".module-tab").forEach((button) => {
|
document.querySelectorAll(".module-tab").forEach((button) => {
|
||||||
button.classList.toggle("active", button.dataset.view === navigationId);
|
button.classList.toggle("active", button.dataset.view === navigationId);
|
||||||
button.classList.toggle(
|
button.classList.toggle(
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ function syncThemeControl() {
|
|||||||
button.setAttribute("aria-label", label);
|
button.setAttribute("aria-label", label);
|
||||||
button.setAttribute("aria-pressed", String(dark));
|
button.setAttribute("aria-pressed", String(dark));
|
||||||
button.querySelector("i")?.setAttribute("data-lucide", dark ? "sun" : "moon");
|
button.querySelector("i")?.setAttribute("data-lucide", dark ? "sun" : "moon");
|
||||||
|
const modeText = document.querySelector("#themeModeText");
|
||||||
|
if (modeText) modeText.textContent = dark ? "夜间模式" : "日间模式";
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearThemeTransitionEffects() {
|
function clearThemeTransitionEffects() {
|
||||||
|
|||||||
@@ -219,13 +219,12 @@
|
|||||||
--dragon-profile-weight-strong: 750;
|
--dragon-profile-weight-strong: 750;
|
||||||
--dragon-profile-weight-semibold: 600;
|
--dragon-profile-weight-semibold: 600;
|
||||||
|
|
||||||
--mentor-directory-width: 280px;
|
--mentor-directory-width: 300px;
|
||||||
--mentor-profile-width: 272px;
|
--mentor-pane-header-height: 60px;
|
||||||
--mentor-pane-header-height: 58px;
|
--mentor-avatar-size: 42px;
|
||||||
--mentor-avatar-size: 38px;
|
|
||||||
--mentor-profile-avatar-size: 68px;
|
--mentor-profile-avatar-size: 68px;
|
||||||
--mentor-composer-min-height: 94px;
|
--mentor-composer-min-height: 94px;
|
||||||
--mentor-message-max-width: 82%;
|
--mentor-message-max-width: 60%;
|
||||||
|
|
||||||
--chart-background: #fbfcfd;
|
--chart-background: #fbfcfd;
|
||||||
--chart-grid: #e2e8ec;
|
--chart-grid: #e2e8ec;
|
||||||
|
|||||||
+102
-22
@@ -526,7 +526,7 @@ test("admin shell opens every primary workspace and global search", async ({ pag
|
|||||||
const views = [
|
const views = [
|
||||||
"auctionView", "sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
|
"auctionView", "sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
|
||||||
"performanceView", "ladderView", "rotationView", "themeLibraryView", "popularityView", "dragonView", "screenerView",
|
"performanceView", "ladderView", "rotationView", "themeLibraryView", "popularityView", "dragonView", "screenerView",
|
||||||
"mentorView", "heavenView", "reviewWorkspaceView",
|
"heavenView", "reviewWorkspaceView", "mentorView",
|
||||||
];
|
];
|
||||||
for (const view of views) {
|
for (const view of views) {
|
||||||
await page.locator(`[data-view="${view}"]`).first().click();
|
await page.locator(`[data-view="${view}"]`).first().click();
|
||||||
@@ -567,7 +567,7 @@ test("every primary workspace shares the canonical desktop shell geometry", asyn
|
|||||||
const views = [
|
const views = [
|
||||||
"auctionView", "sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
|
"auctionView", "sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
|
||||||
"performanceView", "ladderView", "rotationView", "themeLibraryView", "popularityView", "dragonView",
|
"performanceView", "ladderView", "rotationView", "themeLibraryView", "popularityView", "dragonView",
|
||||||
"screenerView", "mentorView", "heavenView", "reviewWorkspaceView",
|
"screenerView", "heavenView", "reviewWorkspaceView",
|
||||||
];
|
];
|
||||||
let reference = null;
|
let reference = null;
|
||||||
for (const view of views) {
|
for (const view of views) {
|
||||||
@@ -2805,7 +2805,8 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
|||||||
await page.goto("/index.html");
|
await page.goto("/index.html");
|
||||||
await page.locator('[data-view="mentorView"]').first().click();
|
await page.locator('[data-view="mentorView"]').first().click();
|
||||||
|
|
||||||
await expect(page.locator("#mentorView .mentor-sidebar .mentor-evidence-filters")).toBeVisible();
|
await expect(page.locator("#mentorView .mentor-sidebar .mentor-directory-tools")).toBeVisible();
|
||||||
|
await expect(page.locator("#mentorFilterToggle")).toBeVisible();
|
||||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(22);
|
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(22);
|
||||||
await expect(page.locator('#mentorList [data-mentor-id="private-owner"] .mentor-badge.private')).toContainText("仅自己");
|
await expect(page.locator('#mentorList [data-mentor-id="private-owner"] .mentor-badge.private')).toContainText("仅自己");
|
||||||
await expect(page.locator('#mentorList [data-mentor-id="source-a"] .mentor-badge.grade-a')).toHaveText("A");
|
await expect(page.locator('#mentorList [data-mentor-id="source-a"] .mentor-badge.grade-a')).toHaveText("A");
|
||||||
@@ -2815,36 +2816,58 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
|||||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(1);
|
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(1);
|
||||||
await expect(page.locator("#mentorCount")).toHaveText("1 / 22 位");
|
await expect(page.locator("#mentorCount")).toHaveText("1 / 22 位");
|
||||||
await page.locator("#mentorSearchInput").fill("");
|
await page.locator("#mentorSearchInput").fill("");
|
||||||
|
await page.locator("#mentorFilterToggle").click();
|
||||||
await page.locator('[data-mentor-grade="B"]').click();
|
await page.locator('[data-mentor-grade="B"]').click();
|
||||||
|
await expect(page.locator("#mentorFilterLabel")).toHaveText("B级");
|
||||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(7);
|
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(7);
|
||||||
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
||||||
await expect(page.locator("#activeMentorName")).toHaveText("多源老师");
|
await expect(page.locator("#activeMentorName")).toHaveText("多源老师");
|
||||||
await expect(page.locator("#activeMentorBadges")).toHaveText("B");
|
await expect(page.locator("#activeMentorBadges")).toHaveText("B");
|
||||||
await expect(page.locator("#activeMentorEvidence")).toHaveText("公开访谈与多源材料");
|
|
||||||
const mentorLibrary = await page.locator("#mentorView .mentor-sidebar").boundingBox();
|
const mentorLibrary = await page.locator("#mentorView .mentor-sidebar").boundingBox();
|
||||||
const mentorChat = await page.locator("#mentorView .mentor-chat-panel").boundingBox();
|
const mentorChat = await page.locator("#mentorView .mentor-chat-panel").boundingBox();
|
||||||
const mentorProfile = await page.locator("#mentorView .mentor-profile-panel").boundingBox();
|
|
||||||
const mentorInput = await page.locator("#mentorQuestion").boundingBox();
|
const mentorInput = await page.locator("#mentorQuestion").boundingBox();
|
||||||
expect(Math.abs(mentorLibrary.width - 280)).toBeLessThanOrEqual(1);
|
const mentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||||
expect(Math.abs(mentorProfile.width - 272)).toBeLessThanOrEqual(1);
|
expect(Math.abs(mentorLibrary.width - 300)).toBeLessThanOrEqual(1);
|
||||||
expect(Math.abs(mentorChat.x - (mentorLibrary.x + mentorLibrary.width))).toBeLessThanOrEqual(1);
|
expect(Math.abs(mentorChat.x - (mentorLibrary.x + mentorLibrary.width))).toBeLessThanOrEqual(1);
|
||||||
expect(Math.abs(mentorProfile.x - (mentorChat.x + mentorChat.width))).toBeLessThanOrEqual(1);
|
expect(Math.abs(mentorChat.x + mentorChat.width - (mentorLayout.x + mentorLayout.width))).toBeLessThanOrEqual(1);
|
||||||
expect(mentorInput.height).toBeGreaterThanOrEqual(48);
|
const composerForm = await page.locator("#mentorView .mentor-chat-form").boundingBox();
|
||||||
|
expect(composerForm.height).toBeGreaterThanOrEqual(88);
|
||||||
|
expect(composerForm.height).toBeLessThanOrEqual(104);
|
||||||
|
expect(mentorInput.height).toBeGreaterThanOrEqual(24);
|
||||||
|
expect(mentorInput.height).toBeLessThanOrEqual(34);
|
||||||
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
||||||
await page.setViewportSize({ width: 1920, height: 947 });
|
await page.setViewportSize({ width: 1920, height: 947 });
|
||||||
const expandedMentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
const expandedMentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||||
const statusBar = await page.locator(".status-bar").boundingBox();
|
const footerBar = await page.locator("#mentorView .mentor-workspace-footer").boundingBox();
|
||||||
const lowerGap = statusBar.y - (expandedMentorLayout.y + expandedMentorLayout.height);
|
expect(Math.abs(footerBar.y + footerBar.height - 947)).toBeLessThanOrEqual(1);
|
||||||
expect(lowerGap).toBeGreaterThanOrEqual(0);
|
expect(Math.abs(expandedMentorLayout.y + expandedMentorLayout.height - footerBar.y)).toBeLessThanOrEqual(1);
|
||||||
expect(lowerGap).toBeLessThanOrEqual(16);
|
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
|
||||||
await page.locator("#overviewToggle").click();
|
|
||||||
const openOverviewLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
|
||||||
const openOverviewGap = statusBar.y - (openOverviewLayout.y + openOverviewLayout.height);
|
|
||||||
expect(openOverviewGap).toBeGreaterThanOrEqual(0);
|
|
||||||
expect(openOverviewGap).toBeLessThanOrEqual(16);
|
|
||||||
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mentor keeps two columns and opens the profile floating dialog on desktop", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 });
|
||||||
|
await mockApplication(page, session("admin", true));
|
||||||
|
await page.goto("/index.html");
|
||||||
|
await page.locator('[data-view="mentorView"]').first().click();
|
||||||
|
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
||||||
|
|
||||||
|
await expect(page.locator("#mentorView .mentor-profile-panel")).toHaveCount(0);
|
||||||
|
const mentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||||
|
const mentorLibrary = await page.locator("#mentorView .mentor-sidebar").boundingBox();
|
||||||
|
const mentorChat = await page.locator("#mentorView .mentor-chat-panel").boundingBox();
|
||||||
|
expect(Math.abs(mentorChat.x - (mentorLibrary.x + mentorLibrary.width))).toBeLessThanOrEqual(1);
|
||||||
|
expect(Math.abs(mentorChat.x + mentorChat.width - (mentorLayout.x + mentorLayout.width))).toBeLessThanOrEqual(1);
|
||||||
|
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
|
||||||
|
|
||||||
|
await page.locator("#mentorProfileButton").click();
|
||||||
|
await expect(page.locator("#mentorProfileDialog")).toBeVisible();
|
||||||
|
await expect(page.locator("#mentorProfileDialogName")).toHaveText("多源老师");
|
||||||
|
await expect(page.locator("#mentorProfileDialogEvidence")).toHaveText("公开访谈与多源材料");
|
||||||
|
await page.locator('[data-mentor-dialog-close="mentorProfileDialog"]').click();
|
||||||
|
await expect(page.locator("#mentorProfileDialog")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test("mentor pins, custom order and streamed replies work together", async ({ page }) => {
|
test("mentor pins, custom order and streamed replies work together", async ({ page }) => {
|
||||||
await mockApplication(page, session("admin", true));
|
await mockApplication(page, session("admin", true));
|
||||||
await page.goto("/index.html");
|
await page.goto("/index.html");
|
||||||
@@ -2883,8 +2906,8 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
}));
|
}));
|
||||||
expect(darkUserMessageStyle.background).not.toBe("rgb(255, 255, 255)");
|
expect(darkUserMessageStyle.background).not.toBe("rgb(255, 255, 255)");
|
||||||
expect(darkUserMessageStyle.border).not.toBe("rgb(255, 255, 255)");
|
expect(darkUserMessageStyle.border).not.toBe("rgb(255, 255, 255)");
|
||||||
expect(darkUserMessageStyle.contentBackground).toBe("rgb(35, 54, 74)");
|
expect(darkUserMessageStyle.contentBackground).toBe("rgb(53, 89, 140)");
|
||||||
expect(darkUserMessageStyle.contentColor).toBe("rgb(232, 234, 237)");
|
expect(darkUserMessageStyle.contentColor).toBe("rgb(234, 241, 251)");
|
||||||
const darkMessageStyle = await answer.evaluate((element) => {
|
const darkMessageStyle = await answer.evaluate((element) => {
|
||||||
const style = getComputedStyle(element);
|
const style = getComputedStyle(element);
|
||||||
const content = element.querySelector(".mentor-message-content");
|
const content = element.querySelector(".mentor-message-content");
|
||||||
@@ -2906,8 +2929,65 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
expect(darkMessageStyle.shadow).toBe("none");
|
expect(darkMessageStyle.shadow).toBe("none");
|
||||||
expect(darkMessageStyle.contentColor).toBe("rgb(232, 234, 237)");
|
expect(darkMessageStyle.contentColor).toBe("rgb(232, 234, 237)");
|
||||||
expect(darkMessageStyle.headingColor).toBe("rgb(232, 234, 237)");
|
expect(darkMessageStyle.headingColor).toBe("rgb(232, 234, 237)");
|
||||||
expect(darkMessageStyle.labelColor).toBe("rgb(127, 137, 147)");
|
expect(darkMessageStyle.labelColor).toBe("rgb(124, 130, 138)");
|
||||||
expect(darkMessageStyle.metaColor).toBe("rgb(127, 137, 147)");
|
expect(darkMessageStyle.metaColor).toBe("rgb(124, 130, 138)");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mentor floating dialogs open centered, save notes per account, and keep composer one-line", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 });
|
||||||
|
await mockApplication(page, session("admin", true));
|
||||||
|
await page.goto("/index.html");
|
||||||
|
await page.locator('[data-view="mentorView"]').first().click();
|
||||||
|
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
||||||
|
|
||||||
|
await page.locator("#mentorNoteButton").click();
|
||||||
|
await expect(page.locator("#mentorNoteDialog")).toBeVisible();
|
||||||
|
const noteDialog = await page.locator("#mentorNoteDialog").boundingBox();
|
||||||
|
expect(noteDialog.width).toBeLessThanOrEqual(400);
|
||||||
|
expect(Math.abs(noteDialog.x + noteDialog.width / 2 - 720)).toBeLessThanOrEqual(3);
|
||||||
|
expect(Math.abs(noteDialog.y + noteDialog.height / 2 - 450)).toBeLessThanOrEqual(3);
|
||||||
|
await page.locator("#mentorNoteInput").fill("多源老师:确认后再行动");
|
||||||
|
await expect(page.locator("#mentorNoteInput")).toHaveValue("多源老师:确认后再行动");
|
||||||
|
await page.locator('[data-mentor-dialog-close="mentorNoteDialog"]').click();
|
||||||
|
await expect(page.locator("#mentorNoteDialog")).not.toBeVisible();
|
||||||
|
|
||||||
|
await page.locator("#mentorNoteButton").click();
|
||||||
|
await expect(page.locator("#mentorNoteInput")).toHaveValue("多源老师:确认后再行动");
|
||||||
|
await page.locator('[data-mentor-dialog-close="mentorNoteDialog"]').click();
|
||||||
|
|
||||||
|
await page.locator("#mentorProfileButton").click();
|
||||||
|
await expect(page.locator("#mentorProfileDialog")).toBeVisible();
|
||||||
|
await expect(page.locator("#mentorProfileDialogName")).toHaveText("多源老师");
|
||||||
|
await expect(page.locator("#mentorProfileDialogEvidence")).toHaveText("公开访谈与多源材料");
|
||||||
|
const profileDialog = await page.locator("#mentorProfileDialog").boundingBox();
|
||||||
|
expect(profileDialog.width).toBeLessThanOrEqual(400);
|
||||||
|
expect(Math.abs(profileDialog.x + profileDialog.width / 2 - 720)).toBeLessThanOrEqual(3);
|
||||||
|
await page.locator('[data-mentor-dialog-close="mentorProfileDialog"]').click();
|
||||||
|
await expect(page.locator("#mentorProfileDialog")).not.toBeVisible();
|
||||||
|
|
||||||
|
const disclaimerStyle = await page.locator("#mentorView .mentor-disclaimer").evaluate((el) => ({
|
||||||
|
align: getComputedStyle(el).textAlign,
|
||||||
|
}));
|
||||||
|
expect(disclaimerStyle.align).toBe("center");
|
||||||
|
|
||||||
|
const composerHint = page.locator("#mentorView .mentor-composer-hint");
|
||||||
|
await expect(composerHint).toHaveCount(1);
|
||||||
|
await expect(composerHint).toBeVisible();
|
||||||
|
|
||||||
|
const composerInput = page.locator("#mentorQuestion");
|
||||||
|
const oneLineHeight = (await composerInput.boundingBox()).height;
|
||||||
|
expect(oneLineHeight).toBeGreaterThanOrEqual(24);
|
||||||
|
expect(oneLineHeight).toBeLessThanOrEqual(34);
|
||||||
|
await composerInput.fill("第一行\n第二行");
|
||||||
|
const grownHeight = (await composerInput.boundingBox()).height;
|
||||||
|
expect(grownHeight).toBeGreaterThan(oneLineHeight + 8);
|
||||||
|
await composerInput.press("Shift+Enter");
|
||||||
|
await composerInput.type("第三行");
|
||||||
|
await composerInput.fill("只发送这一行");
|
||||||
|
await page.locator("#sendMentorQuestion").click();
|
||||||
|
await expect(page.locator("#mentorMessages .mentor-message.user").last()).toContainText("只发送这一行");
|
||||||
|
const resetHeight = (await composerInput.boundingBox()).height;
|
||||||
|
expect(resetHeight).toBeLessThanOrEqual(34);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("global dialogs share the stage 18 geometry without changing account or admin access", async ({ page }) => {
|
test("global dialogs share the stage 18 geometry without changing account or admin access", async ({ page }) => {
|
||||||
|
|||||||
@@ -280,13 +280,18 @@ class FrontendContractTests(unittest.TestCase):
|
|||||||
self.assertNotIn('context.fillStyle = "#6c7983";', chart)
|
self.assertNotIn('context.fillStyle = "#6c7983";', chart)
|
||||||
|
|
||||||
def test_dark_mentor_tokens_and_sentiment_bottom_clearance_are_defined(self):
|
def test_dark_mentor_tokens_and_sentiment_bottom_clearance_are_defined(self):
|
||||||
self.assertIn(":root[data-theme=\"dark\"] #mentorView {", self.mentor_styles)
|
self.assertIn(":root[data-theme=\"dark\"] #mentorView.workspace-view {", self.mentor_styles)
|
||||||
self.assertIn("--mentor-ink: var(--text-primary);", self.mentor_styles)
|
self.assertIn("--qp-bg-chat: #232529;", self.mentor_styles)
|
||||||
self.assertIn("--mentor-sub: var(--text-secondary);", self.mentor_styles)
|
self.assertIn("--qp-bg-bubble-self: #35598C;", self.mentor_styles)
|
||||||
self.assertIn(":root[data-theme=\"dark\"] #mentorView .mentor-message {", self.mentor_styles)
|
self.assertIn("--qp-bg-selected: #2B3B58;", self.mentor_styles)
|
||||||
self.assertIn("border-color: var(--line-soft);", self.mentor_styles)
|
self.assertIn("--qp-link: #316FEF;", self.mentor_styles)
|
||||||
self.assertIn("background: var(--surface-subtle);", self.mentor_styles)
|
self.assertIn("--qp-accent-soft: #5B8DEF;", self.mentor_styles)
|
||||||
self.assertIn("box-shadow: none;", self.mentor_styles)
|
self.assertIn("--mentor-directory-width: 300px;", self.tokens)
|
||||||
|
self.assertIn("--mentor-composer-min-height: 94px;", self.tokens)
|
||||||
|
self.assertIn("#mentorView .mentor-message.user .mentor-message-content {", self.mentor_styles)
|
||||||
|
self.assertIn("background: var(--qp-bg-bubble-self);", self.mentor_styles)
|
||||||
|
self.assertIn("#mentorView .mentor-quick-prompts button {", self.mentor_styles)
|
||||||
|
self.assertIn("#mentorView .mentor-composer-hint {", self.mentor_styles)
|
||||||
self.assertIn("#sentimentCycleView .sentiment-history-frame {", self.sentiment_styles)
|
self.assertIn("#sentimentCycleView .sentiment-history-frame {", self.sentiment_styles)
|
||||||
self.assertIn("margin-bottom: var(--card-gap);", self.sentiment_styles)
|
self.assertIn("margin-bottom: var(--card-gap);", self.sentiment_styles)
|
||||||
self.assertIn("padding-bottom: var(--card-gap);", self.sentiment_styles)
|
self.assertIn("padding-bottom: var(--card-gap);", self.sentiment_styles)
|
||||||
|
|||||||
Reference in New Issue
Block a user