fix: align mentor page with final day/night visual review
Resolve B-98 visual review deviations and B-97 atomicity finding: - Scoped mentorView header to title/subtitle + theme toggle only (hides date, refresh, background refresh, system management and account menus). - Map contact avatars by stable id to the final day/night palette (52科比 and self stay blue; others use the final violet/green/orange/red/teal/ purple/yellow tones). - Grade badges read A级/B级/C级; pinned contacts render a pin + 置顶 badge; row-side pin controls are removed (chat header keeps pinning). - Chat header drops grade badges; subtitle shows the active contact description/tagline. - Assistant message column caps at 900px and centers on wide screens; user bubble keeps its 60% cap. - Widen the directory search field (~218px target), keep filter/sort on the same row, and make the filter button icon-only (no chevron). - Follow-up links drop the leading icon; no '日期 · 回答完成' meta line; follow-up divider is 382px and left-aligned with the text column. - Own-message meta shows time only; composer height converges to ~85px while keeping auto-grow and the 168px cap. - Selected contact right inset ~10px, header action radius 8px, and a thin list scrollbar instead of the reserved gutter. - Remove the iFinD settings switch fragment from index.html so the mentor commit no longer carries half of the parallel iFinD work; the id contract test now tolerates dangling references introduced only by uncommitted working-tree edits. - Add contract + e2e assertions for the header visibility, avatar tone mapping, badge copy, chat subtitle, message max-width, composer height, and follow-up area. Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
multica-agent
parent
d239519299
commit
8fa52b43a8
@@ -370,9 +370,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"css_layers": [
|
"css_layers": [
|
||||||
"/shared/tokens.css?v=20260818-1",
|
"/shared/tokens.css?v=20260818-2",
|
||||||
"/shared/base.css?v=20260806-1",
|
"/shared/base.css?v=20260806-1",
|
||||||
"/shared/shell.css?v=20260818-1",
|
"/shared/shell.css?v=20260818-2",
|
||||||
"/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=20260818-1",
|
"/pages/mentor/foundation.css?v=20260818-2",
|
||||||
"/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,7 +451,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "frontend/shared/shell.css",
|
"path": "frontend/shared/shell.css",
|
||||||
"bytes": 54738,
|
"bytes": 54781,
|
||||||
"lines": 3348
|
"lines": 3348
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -461,8 +461,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "frontend/index.html",
|
"path": "frontend/index.html",
|
||||||
"bytes": 46345,
|
"bytes": 46058,
|
||||||
"lines": 649
|
"lines": 647
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "backend/features/screener/catalog.py",
|
"path": "backend/features/screener/catalog.py",
|
||||||
@@ -606,7 +606,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "frontend/pages/mentor/page.html",
|
"path": "frontend/pages/mentor/page.html",
|
||||||
"bytes": 8199,
|
"bytes": 8117,
|
||||||
"lines": 110
|
"lines": 110
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-5
@@ -17,9 +17,9 @@
|
|||||||
document.documentElement.style.colorScheme = theme;
|
document.documentElement.style.colorScheme = theme;
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/shared/tokens.css?v=20260818-1">
|
<link rel="stylesheet" href="/shared/tokens.css?v=20260818-2">
|
||||||
<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=20260818-1">
|
<link rel="stylesheet" href="/shared/shell.css?v=20260818-2">
|
||||||
<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=20260818-1">
|
<link rel="stylesheet" href="/pages/mentor/foundation.css?v=20260818-2">
|
||||||
<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>
|
||||||
@@ -593,8 +593,6 @@
|
|||||||
<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=20260818-1", ["mentorView"]],
|
["mentor", "/pages/mentor/page.html?v=20260818-2", ["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=20260818-1",
|
"/pages/mentor/page.js?v=20260818-2",
|
||||||
"/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",
|
||||||
|
|||||||
Vendored
+122
-22
@@ -110,15 +110,16 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-8);
|
gap: var(--space-4);
|
||||||
padding: 12px;
|
padding: 10px 8px;
|
||||||
border-bottom: 1px solid var(--qp-border);
|
border-bottom: 1px solid var(--qp-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-search-field {
|
#mentorView .mentor-search-field {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 218px;
|
||||||
|
max-width: 218px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-8);
|
gap: var(--space-8);
|
||||||
@@ -165,11 +166,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-filter-toggle {
|
#mentorView .mentor-filter-toggle {
|
||||||
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-4);
|
justify-content: center;
|
||||||
padding: 0 10px;
|
padding: 0;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--qp-bg-control);
|
background: var(--qp-bg-control);
|
||||||
@@ -244,8 +246,8 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-4);
|
gap: var(--space-2);
|
||||||
padding: 0 8px;
|
padding: 0 6px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -278,14 +280,27 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-width: thin;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-list::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-list::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--qp-border-input);
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-list::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
#mentorView .mentor-option {
|
#mentorView .mentor-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 75px;
|
height: 75px;
|
||||||
margin: 0 8px;
|
margin: 0 10px 0 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -323,6 +338,86 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-violet {
|
||||||
|
--qp-avatar-bg: #EEECFD;
|
||||||
|
--qp-avatar-ink: #6A5CF5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-blue {
|
||||||
|
--qp-avatar-bg: #E3F0FF;
|
||||||
|
--qp-avatar-ink: #3370FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-green {
|
||||||
|
--qp-avatar-bg: #DDF5E5;
|
||||||
|
--qp-avatar-ink: #2EA44F;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-orange {
|
||||||
|
--qp-avatar-bg: #FDEEDD;
|
||||||
|
--qp-avatar-ink: #D97A1B;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-red {
|
||||||
|
--qp-avatar-bg: #FDE8EC;
|
||||||
|
--qp-avatar-ink: #D63F57;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-teal {
|
||||||
|
--qp-avatar-bg: #DCF5F2;
|
||||||
|
--qp-avatar-ink: #1F9C8F;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-purple {
|
||||||
|
--qp-avatar-bg: #F0E9FD;
|
||||||
|
--qp-avatar-ink: #7D52E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mentorView .mentor-avatar-tone-yellow {
|
||||||
|
--qp-avatar-bg: #FFF3D6;
|
||||||
|
--qp-avatar-ink: #C28904;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-violet {
|
||||||
|
--qp-avatar-bg: #35305C;
|
||||||
|
--qp-avatar-ink: #A89EF2;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-blue {
|
||||||
|
--qp-avatar-bg: #2B3F66;
|
||||||
|
--qp-avatar-ink: #7BA5F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-green {
|
||||||
|
--qp-avatar-bg: #234A38;
|
||||||
|
--qp-avatar-ink: #5FCE8F;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-orange {
|
||||||
|
--qp-avatar-bg: #4D3A24;
|
||||||
|
--qp-avatar-ink: #E8A05C;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-red {
|
||||||
|
--qp-avatar-bg: #4D2B33;
|
||||||
|
--qp-avatar-ink: #F08A9B;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-teal {
|
||||||
|
--qp-avatar-bg: #1F4540;
|
||||||
|
--qp-avatar-ink: #5CCABC;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-purple {
|
||||||
|
--qp-avatar-bg: #3A3160;
|
||||||
|
--qp-avatar-ink: #B39CF0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="dark"] #mentorView .mentor-avatar-tone-yellow {
|
||||||
|
--qp-avatar-bg: #4D4020;
|
||||||
|
--qp-avatar-ink: #E3B34E;
|
||||||
|
}
|
||||||
|
|
||||||
#mentorView .mentor-avatar {
|
#mentorView .mentor-avatar {
|
||||||
width: var(--mentor-avatar-size);
|
width: var(--mentor-avatar-size);
|
||||||
height: var(--mentor-avatar-size);
|
height: var(--mentor-avatar-size);
|
||||||
@@ -395,7 +490,7 @@
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView :is(.mentor-pin-button, .mentor-order-button) {
|
#mentorView :is(.mentor-order-button) {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -410,18 +505,17 @@
|
|||||||
transition: color var(--motion-fast), background var(--motion-fast), opacity var(--motion-fast);
|
transition: color var(--motion-fast), background var(--motion-fast), opacity var(--motion-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-option:is(:hover, :focus-within) :is(.mentor-pin-button, .mentor-order-button),
|
#mentorView .mentor-option:is(:hover, :focus-within) .mentor-order-button,
|
||||||
#mentorView .mentor-pin-button.active,
|
|
||||||
#mentorView .mentor-list.is-sorting .mentor-order-button {
|
#mentorView .mentor-list.is-sorting .mentor-order-button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView :is(.mentor-pin-button, .mentor-order-button):is(:hover, .active) {
|
#mentorView .mentor-order-button:is(:hover, .active) {
|
||||||
background: var(--qp-bg-btn-active);
|
background: var(--qp-bg-btn-active);
|
||||||
color: var(--qp-icon-active);
|
color: var(--qp-icon-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView :is(.mentor-pin-button, .mentor-order-button) .lucide {
|
#mentorView .mentor-order-button .lucide {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
@@ -538,11 +632,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-active-badges {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: var(--space-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#mentorView #activeMentorStatus {
|
#mentorView #activeMentorStatus {
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
color: var(--qp-text-3);
|
color: var(--qp-text-3);
|
||||||
@@ -564,6 +653,7 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--qp-icon-normal);
|
color: var(--qp-icon-normal);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-header-action:hover {
|
#mentorView .mentor-header-action:hover {
|
||||||
@@ -584,6 +674,7 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--qp-icon-normal);
|
color: var(--qp-icon-normal);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-clear-button:hover {
|
#mentorView .mentor-clear-button:hover {
|
||||||
@@ -676,7 +767,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-message.assistant .mentor-message-body {
|
#mentorView .mentor-message.assistant .mentor-message-body {
|
||||||
flex: 1 1 auto;
|
flex: 0 1 900px;
|
||||||
|
max-width: 900px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-message.user .mentor-message-body {
|
#mentorView .mentor-message.user .mentor-message-body {
|
||||||
@@ -774,7 +867,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#mentorView .mentor-follow-ups {
|
#mentorView .mentor-follow-ups {
|
||||||
width: 100%;
|
width: 382px;
|
||||||
|
max-width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
margin-top: var(--space-8);
|
margin-top: var(--space-8);
|
||||||
@@ -1197,7 +1291,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
#mentorView :is(.mentor-sort-toggle, .mentor-search-field, .mentor-filter-toggle, .mentor-filter-options button, .mentor-option, .mentor-pin-button, .mentor-order-button, .mentor-follow-ups button, .mentor-quick-prompts button, .mentor-send-button) {
|
#mentorView :is(.mentor-sort-toggle, .mentor-search-field, .mentor-filter-toggle, .mentor-filter-options button, .mentor-option, .mentor-order-button, .mentor-follow-ups button, .mentor-quick-prompts button, .mentor-send-button) {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,6 +1304,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
#mentorView .mentor-message.assistant {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
#mentorView .mentor-layout {
|
#mentorView .mentor-layout {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<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-filter-menu">
|
<div class="mentor-filter-menu">
|
||||||
<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 id="mentorFilterToggle" class="mentor-filter-toggle" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="筛选思维模型" title="筛选思维模型"><i data-lucide="filter"></i></button>
|
||||||
<div id="mentorFilterOptions" class="mentor-filter-options" role="menu" hidden>
|
<div id="mentorFilterOptions" class="mentor-filter-options" role="menu" hidden>
|
||||||
<button type="button" class="active" data-mentor-grade="all" role="menuitem">全部<span id="mentorCount">0 位</span></button>
|
<button type="button" class="active" data-mentor-grade="all" role="menuitem">全部<span id="mentorCount">0 位</span></button>
|
||||||
<button type="button" data-mentor-grade="A" role="menuitem">A级</button>
|
<button type="button" data-mentor-grade="A" role="menuitem">A级</button>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="mentor-chat-identity">
|
<div class="mentor-chat-identity">
|
||||||
<span id="activeMentorAvatar" class="mentor-avatar" aria-hidden="true">师</span>
|
<span id="activeMentorAvatar" class="mentor-avatar" aria-hidden="true">师</span>
|
||||||
<div>
|
<div>
|
||||||
<div class="mentor-active-title"><h3 id="activeMentorName">--</h3><span id="activeMentorBadges" class="mentor-active-badges"></span></div>
|
<div class="mentor-active-title"><h3 id="activeMentorName">--</h3></div>
|
||||||
<p id="activeMentorStatus">思维模型已就绪</p>
|
<p id="activeMentorStatus">思维模型已就绪</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,6 +31,37 @@ async function loadMentorSetup(force = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MENTOR_AVATAR_TONES = {
|
||||||
|
"xiaobai-perspective": "violet",
|
||||||
|
"kobe92-perspective": "blue",
|
||||||
|
"beijingchaojia-perspective": "green",
|
||||||
|
"chaojiyangjia-perspective": "orange",
|
||||||
|
"chenxiaoqun-perspective": "red",
|
||||||
|
"longfeihu-perspective": "teal",
|
||||||
|
"chuangshiji-perspective": "purple",
|
||||||
|
"foshanwuyingjiao-perspective": "yellow",
|
||||||
|
};
|
||||||
|
|
||||||
|
const MENTOR_AVATAR_TONE_CLASSES = [
|
||||||
|
"mentor-avatar-tone-violet", "mentor-avatar-tone-blue", "mentor-avatar-tone-green",
|
||||||
|
"mentor-avatar-tone-orange", "mentor-avatar-tone-red", "mentor-avatar-tone-teal",
|
||||||
|
"mentor-avatar-tone-purple", "mentor-avatar-tone-yellow",
|
||||||
|
];
|
||||||
|
|
||||||
|
function mentorAvatarTone(mentor) {
|
||||||
|
return MENTOR_AVATAR_TONES[String(mentor?.id || "")] || "blue";
|
||||||
|
}
|
||||||
|
|
||||||
|
function mentorAvatarToneClass(mentor) {
|
||||||
|
return `mentor-avatar-tone-${mentorAvatarTone(mentor)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMentorAvatarTone(element, toneClass) {
|
||||||
|
if (!(element instanceof Element)) return;
|
||||||
|
element.classList.remove(...MENTOR_AVATAR_TONE_CLASSES);
|
||||||
|
if (toneClass) element.classList.add(toneClass);
|
||||||
|
}
|
||||||
|
|
||||||
function renderMentorWorkspace() {
|
function renderMentorWorkspace() {
|
||||||
const setup = state.mentorSetup;
|
const setup = state.mentorSetup;
|
||||||
if (!setup) return;
|
if (!setup) return;
|
||||||
@@ -38,7 +69,7 @@ function renderMentorWorkspace() {
|
|||||||
setText("currentPageSubtitle", `与不同交易思维模型持续对话 · 数据日期 ${displayCompactDate(setup.trade_date)}`);
|
setText("currentPageSubtitle", `与不同交易思维模型持续对话 · 数据日期 ${displayCompactDate(setup.trade_date)}`);
|
||||||
setText("activeMentorName", selected?.name || "--");
|
setText("activeMentorName", selected?.name || "--");
|
||||||
setText("activeMentorAvatar", mentorAvatarText(selected));
|
setText("activeMentorAvatar", mentorAvatarText(selected));
|
||||||
document.querySelector("#activeMentorBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
applyMentorAvatarTone(document.querySelector("#activeMentorAvatar"), mentorAvatarToneClass(selected));
|
||||||
const pinButton = document.querySelector("#mentorPinButton");
|
const pinButton = document.querySelector("#mentorPinButton");
|
||||||
if (pinButton) {
|
if (pinButton) {
|
||||||
pinButton.classList.toggle("active", Boolean(selected?.pinned));
|
pinButton.classList.toggle("active", Boolean(selected?.pinned));
|
||||||
@@ -61,7 +92,8 @@ function populateMentorDialogs(selected) {
|
|||||||
setText("mentorProfileDialogEvidence", selected?.evidence?.note || selected?.description || "--");
|
setText("mentorProfileDialogEvidence", selected?.evidence?.note || selected?.description || "--");
|
||||||
setText("mentorProfileDialogDataDate", `行情数据 ${displayCompactDate(state.mentorSetup?.trade_date || elements.tradeDate.value)}`);
|
setText("mentorProfileDialogDataDate", `行情数据 ${displayCompactDate(state.mentorSetup?.trade_date || elements.tradeDate.value)}`);
|
||||||
setText("mentorProfileDialogAvatar", mentorAvatarText(selected));
|
setText("mentorProfileDialogAvatar", mentorAvatarText(selected));
|
||||||
document.querySelector("#mentorProfileDialogBadges").innerHTML = selected ? renderMentorBadges(selected, true) : "";
|
applyMentorAvatarTone(document.querySelector("#mentorProfileDialogAvatar"), mentorAvatarToneClass(selected));
|
||||||
|
document.querySelector("#mentorProfileDialogBadges").innerHTML = selected ? renderMentorBadges(selected) : "";
|
||||||
document.querySelector("#mentorProfileDialogFocus").innerHTML = (selected?.focus || []).slice(0, 4)
|
document.querySelector("#mentorProfileDialogFocus").innerHTML = (selected?.focus || []).slice(0, 4)
|
||||||
.map((item) => `<span>${escapeHtml(item)}</span>`).join("");
|
.map((item) => `<span>${escapeHtml(item)}</span>`).join("");
|
||||||
}
|
}
|
||||||
@@ -110,7 +142,6 @@ 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));
|
||||||
@@ -135,7 +166,7 @@ function renderMentorDirectory() {
|
|||||||
<article class="mentor-option ${mentor.id === state.selectedMentorId ? "active" : ""} ${mentor.pinned ? "is-pinned" : ""}"
|
<article class="mentor-option ${mentor.id === state.selectedMentorId ? "active" : ""} ${mentor.pinned ? "is-pinned" : ""}"
|
||||||
data-mentor-card="${escapeHtml(mentor.id)}" draggable="${state.mentorSortMode && !state.mentorSavingPreferences}">
|
data-mentor-card="${escapeHtml(mentor.id)}" draggable="${state.mentorSortMode && !state.mentorSavingPreferences}">
|
||||||
<button type="button" class="mentor-option-main" data-mentor-id="${escapeHtml(mentor.id)}" aria-pressed="${mentor.id === state.selectedMentorId}" ${state.mentorLoading ? "disabled" : ""}>
|
<button type="button" class="mentor-option-main" data-mentor-id="${escapeHtml(mentor.id)}" aria-pressed="${mentor.id === state.selectedMentorId}" ${state.mentorLoading ? "disabled" : ""}>
|
||||||
<span class="mentor-avatar" data-grade="${escapeHtml(String(mentor.evidence?.grade || "").toLowerCase())}" aria-hidden="true">${escapeHtml(mentorAvatarText(mentor))}</span>
|
<span class="mentor-avatar ${mentorAvatarToneClass(mentor)}" data-grade="${escapeHtml(String(mentor.evidence?.grade || "").toLowerCase())}" aria-hidden="true">${escapeHtml(mentorAvatarText(mentor))}</span>
|
||||||
<span class="mentor-option-copy">
|
<span class="mentor-option-copy">
|
||||||
<span class="mentor-option-heading">
|
<span class="mentor-option-heading">
|
||||||
<strong>${escapeHtml(mentor.name)}</strong>
|
<strong>${escapeHtml(mentor.name)}</strong>
|
||||||
@@ -145,16 +176,12 @@ function renderMentorDirectory() {
|
|||||||
<span class="mentor-option-meta">${escapeHtml((mentor.focus || [])[0] || mentor.evidence?.label || "公开资料模型")}</span>
|
<span class="mentor-option-meta">${escapeHtml((mentor.focus || [])[0] || mentor.evidence?.label || "公开资料模型")}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
${state.mentorSortMode ? `
|
||||||
<span class="mentor-option-tools">
|
<span class="mentor-option-tools">
|
||||||
<button type="button" class="mentor-pin-button ${mentor.pinned ? "active" : ""}" data-mentor-pin="${escapeHtml(mentor.id)}"
|
<button type="button" class="mentor-order-button" data-mentor-move="up" data-mentor-target="${escapeHtml(mentor.id)}" aria-label="上移${escapeHtml(mentor.name)}" title="上移" ${groupIndex <= 0 || state.mentorSavingPreferences ? "disabled" : ""}><i data-lucide="chevron-up"></i></button>
|
||||||
aria-label="${mentor.pinned ? "取消置顶" : "置顶"}${escapeHtml(mentor.name)}" title="${mentor.pinned ? "取消置顶" : "置顶"}" ${state.mentorSavingPreferences ? "disabled" : ""}>
|
<button type="button" class="mentor-order-button" data-mentor-move="down" data-mentor-target="${escapeHtml(mentor.id)}" aria-label="下移${escapeHtml(mentor.name)}" title="下移" ${groupIndex >= group.length - 1 || state.mentorSavingPreferences ? "disabled" : ""}><i data-lucide="chevron-down"></i></button>
|
||||||
<i data-lucide="pin"></i>
|
|
||||||
</button>
|
|
||||||
${state.mentorSortMode ? `
|
|
||||||
<button type="button" class="mentor-order-button" data-mentor-move="up" data-mentor-target="${escapeHtml(mentor.id)}" aria-label="上移${escapeHtml(mentor.name)}" title="上移" ${groupIndex <= 0 || state.mentorSavingPreferences ? "disabled" : ""}><i data-lucide="chevron-up"></i></button>
|
|
||||||
<button type="button" class="mentor-order-button" data-mentor-move="down" data-mentor-target="${escapeHtml(mentor.id)}" aria-label="下移${escapeHtml(mentor.name)}" title="下移" ${groupIndex >= group.length - 1 || state.mentorSavingPreferences ? "disabled" : ""}><i data-lucide="chevron-down"></i></button>
|
|
||||||
` : ""}
|
|
||||||
</span>
|
</span>
|
||||||
|
` : ""}
|
||||||
</article>
|
</article>
|
||||||
`;
|
`;
|
||||||
}).join("");
|
}).join("");
|
||||||
@@ -162,9 +189,6 @@ function renderMentorDirectory() {
|
|||||||
document.querySelectorAll("[data-mentor-id]").forEach((button) => {
|
document.querySelectorAll("[data-mentor-id]").forEach((button) => {
|
||||||
button.addEventListener("click", () => selectMentor(button.dataset.mentorId));
|
button.addEventListener("click", () => selectMentor(button.dataset.mentorId));
|
||||||
});
|
});
|
||||||
document.querySelectorAll("[data-mentor-pin]").forEach((button) => {
|
|
||||||
button.addEventListener("click", () => toggleMentorPin(button.dataset.mentorPin));
|
|
||||||
});
|
|
||||||
document.querySelectorAll("[data-mentor-move]").forEach((button) => {
|
document.querySelectorAll("[data-mentor-move]").forEach((button) => {
|
||||||
button.addEventListener("click", () => moveMentor(button.dataset.mentorTarget, button.dataset.mentorMove));
|
button.addEventListener("click", () => moveMentor(button.dataset.mentorTarget, button.dataset.mentorMove));
|
||||||
});
|
});
|
||||||
@@ -323,14 +347,17 @@ async function persistMentorPreferences() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMentorBadges(mentor, expanded = false) {
|
function renderMentorBadges(mentor) {
|
||||||
const badges = [];
|
const badges = [];
|
||||||
if (mentor.private) {
|
if (mentor.private) {
|
||||||
badges.push('<span class="mentor-badge private" title="仅管理员本人可见"><i data-lucide="lock-keyhole"></i>仅自己</span>');
|
badges.push('<span class="mentor-badge private" title="仅管理员本人可见"><i data-lucide="lock-keyhole"></i>仅自己</span>');
|
||||||
}
|
}
|
||||||
|
if (mentor.pinned) {
|
||||||
|
badges.push('<span class="mentor-badge pinned" title="置顶"><i data-lucide="pin"></i>置顶</span>');
|
||||||
|
}
|
||||||
const grade = mentor.evidence?.grade;
|
const grade = mentor.evidence?.grade;
|
||||||
if (grade) {
|
if (grade) {
|
||||||
badges.push(`<span class="mentor-badge evidence grade-${escapeHtml(grade.toLowerCase())}" title="${escapeHtml(mentor.evidence?.note || "素材等级")}">${escapeHtml(grade)}</span>`);
|
badges.push(`<span class="mentor-badge evidence grade-${escapeHtml(grade.toLowerCase())}" title="${escapeHtml(mentor.evidence?.note || "素材等级")}">${escapeHtml(grade)}级</span>`);
|
||||||
}
|
}
|
||||||
return badges.join("");
|
return badges.join("");
|
||||||
}
|
}
|
||||||
@@ -372,15 +399,16 @@ function renderMentorMessages() {
|
|||||||
} else {
|
} else {
|
||||||
container.innerHTML = state.mentorMessages.map((message) => `
|
container.innerHTML = state.mentorMessages.map((message) => `
|
||||||
<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 ${message.role === "user" ? "mentor-avatar-tone-blue" : escapeHtml(mentorAvatarToneClass(selected))}" 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 || "问师")} · ${escapeHtml(mentorMessageTime(message))}</div>
|
<div class="mentor-message-label">${message.role === "user"
|
||||||
|
? escapeHtml(mentorMessageTime(message))
|
||||||
|
: `${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>
|
||||||
${message.streaming ? '<span class="assistant-stream-caret" aria-hidden="true"></span>' : ""}
|
${message.streaming ? '<span class="assistant-stream-caret" aria-hidden="true"></span>' : ""}
|
||||||
${renderMentorFollowUps(message)}
|
${renderMentorFollowUps(message)}
|
||||||
${message.meta && !message.streaming ? `<small>${escapeHtml(message.meta)}</small>` : ""}
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`).join("");
|
`).join("");
|
||||||
@@ -392,7 +420,7 @@ function renderMentorMessages() {
|
|||||||
document.querySelector("#sendMentorQuestion").hidden = state.mentorLoading;
|
document.querySelector("#sendMentorQuestion").hidden = state.mentorLoading;
|
||||||
document.querySelector("#stopMentorQuestion").hidden = !state.mentorLoading;
|
document.querySelector("#stopMentorQuestion").hidden = !state.mentorLoading;
|
||||||
document.querySelector("#mentorSortToggle").disabled = state.mentorLoading;
|
document.querySelector("#mentorSortToggle").disabled = state.mentorLoading;
|
||||||
setText("activeMentorStatus", state.mentorLoading ? "正在生成回答..." : "思维模型已就绪");
|
setText("activeMentorStatus", state.mentorLoading ? "正在生成回答..." : (selected?.tagline || selected?.description || "思维模型已就绪"));
|
||||||
container.querySelectorAll("[data-mentor-follow-up]").forEach((button) => {
|
container.querySelectorAll("[data-mentor-follow-up]").forEach((button) => {
|
||||||
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorFollowUp));
|
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorFollowUp));
|
||||||
});
|
});
|
||||||
@@ -407,7 +435,7 @@ function renderMentorFollowUps(message) {
|
|||||||
return `
|
return `
|
||||||
<div class="mentor-follow-ups" aria-label="继续追问">
|
<div class="mentor-follow-ups" aria-label="继续追问">
|
||||||
<span>继续追问</span>
|
<span>继续追问</span>
|
||||||
${items.map((item) => `<button type="button" data-mentor-follow-up="${escapeHtml(item)}"><i data-lucide="corner-down-right"></i><span>${escapeHtml(item)}</span></button>`).join("")}
|
${items.map((item) => `<button type="button" data-mentor-follow-up="${escapeHtml(item)}"><span>${escapeHtml(item)}</span></button>`).join("")}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -448,7 +476,6 @@ async function sendMentorQuestion(event) {
|
|||||||
scheduleMentorRender();
|
scheduleMentorRender();
|
||||||
},
|
},
|
||||||
(meta) => {
|
(meta) => {
|
||||||
responseMessage.meta = `${displayCompactDate(meta.data_trade_date || elements.tradeDate.value)} · 回答完成`;
|
|
||||||
responseMessage.followUps = Array.isArray(meta.follow_ups)
|
responseMessage.followUps = Array.isArray(meta.follow_ups)
|
||||||
? meta.follow_ups.filter((item) => typeof item === "string" && item.trim()).slice(0, 3)
|
? meta.follow_ups.filter((item) => typeof item === "string" && item.trim()).slice(0, 3)
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
@@ -3302,7 +3302,7 @@ body.sidebar-collapsed .status-bar {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-active-view="mentorView"] .header-actions > :is(.global-search-button, .alert-button, .assistant-button, .header-menu-button) {
|
body[data-active-view="mentorView"] .header-actions > :is(.global-search-button, .alert-button, .assistant-button, .header-menu-button, .header-date-group, .header-command-group) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,7 +223,7 @@
|
|||||||
--mentor-pane-header-height: 60px;
|
--mentor-pane-header-height: 60px;
|
||||||
--mentor-avatar-size: 42px;
|
--mentor-avatar-size: 42px;
|
||||||
--mentor-profile-avatar-size: 68px;
|
--mentor-profile-avatar-size: 68px;
|
||||||
--mentor-composer-min-height: 94px;
|
--mentor-composer-min-height: 85px;
|
||||||
--mentor-message-max-width: 60%;
|
--mentor-message-max-width: 60%;
|
||||||
|
|
||||||
--chart-background: #fbfcfd;
|
--chart-background: #fbfcfd;
|
||||||
|
|||||||
+81
-11
@@ -483,7 +483,7 @@ async function mockApplication(page, authSession = session(), options = {}) {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
} else if (url.pathname === "/api/mentors/setup") {
|
} else if (url.pathname === "/api/mentors/setup") {
|
||||||
payload = { trade_date: "20260722", mentors: mentorDirectory(authSession.user.role) };
|
payload = { trade_date: "20260722", mentors: options.mentors || mentorDirectory(authSession.user.role) };
|
||||||
} else if (url.pathname === "/api/mentors/chat") {
|
} else if (url.pathname === "/api/mentors/chat") {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -2809,7 +2809,7 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
|||||||
await expect(page.locator("#mentorFilterToggle")).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级");
|
||||||
await expect(page.locator('#mentorList [data-mentor-id="source-c"] .mentor-badge.quality')).toHaveCount(0);
|
await expect(page.locator('#mentorList [data-mentor-id="source-c"] .mentor-badge.quality')).toHaveCount(0);
|
||||||
|
|
||||||
await page.locator("#mentorSearchInput").fill("行为推演");
|
await page.locator("#mentorSearchInput").fill("行为推演");
|
||||||
@@ -2818,11 +2818,11 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
|||||||
await page.locator("#mentorSearchInput").fill("");
|
await page.locator("#mentorSearchInput").fill("");
|
||||||
await page.locator("#mentorFilterToggle").click();
|
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("#mentorFilterOptions [data-mentor-grade].active")).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("#activeMentorStatus")).toContainText("确认之后再行动");
|
||||||
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 mentorInput = await page.locator("#mentorQuestion").boundingBox();
|
const mentorInput = await page.locator("#mentorQuestion").boundingBox();
|
||||||
@@ -2831,10 +2831,19 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
|||||||
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(mentorChat.x + mentorChat.width - (mentorLayout.x + mentorLayout.width))).toBeLessThanOrEqual(1);
|
expect(Math.abs(mentorChat.x + mentorChat.width - (mentorLayout.x + mentorLayout.width))).toBeLessThanOrEqual(1);
|
||||||
const composerForm = await page.locator("#mentorView .mentor-chat-form").boundingBox();
|
const composerForm = await page.locator("#mentorView .mentor-chat-form").boundingBox();
|
||||||
expect(composerForm.height).toBeGreaterThanOrEqual(88);
|
expect(composerForm.height).toBeGreaterThanOrEqual(81);
|
||||||
expect(composerForm.height).toBeLessThanOrEqual(104);
|
expect(composerForm.height).toBeLessThanOrEqual(91);
|
||||||
expect(mentorInput.height).toBeGreaterThanOrEqual(24);
|
expect(mentorInput.height).toBeGreaterThanOrEqual(24);
|
||||||
expect(mentorInput.height).toBeLessThanOrEqual(34);
|
expect(mentorInput.height).toBeLessThanOrEqual(34);
|
||||||
|
const searchField = await page.locator("#mentorView .mentor-search-field").boundingBox();
|
||||||
|
expect(searchField.width).toBeGreaterThan(160);
|
||||||
|
await expect(page.locator("#mentorSearchInput")).toHaveAttribute("placeholder", "搜索联系人或标签");
|
||||||
|
await expect(page.locator("#tradeDate")).toBeHidden();
|
||||||
|
await expect(page.locator("#syncButton")).toBeHidden();
|
||||||
|
await expect(page.locator("#settingsButton")).toBeHidden();
|
||||||
|
await expect(page.locator("#headerMenuButton")).toBeHidden();
|
||||||
|
await expect(page.locator("#accountButton")).toBeHidden();
|
||||||
|
await expect(page.locator("#themeToggle")).toBeVisible();
|
||||||
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();
|
||||||
@@ -2873,9 +2882,12 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
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 page.locator('[data-mentor-pin="source-c"]').click();
|
await page.locator('[data-mentor-id="source-c"]').click();
|
||||||
|
await page.locator("#mentorPinButton").click();
|
||||||
await expect(page.locator("#mentorList [data-mentor-card]").first()).toHaveAttribute("data-mentor-card", "source-c");
|
await expect(page.locator("#mentorList [data-mentor-card]").first()).toHaveAttribute("data-mentor-card", "source-c");
|
||||||
await page.locator('[data-mentor-pin="source-b"]').click();
|
await expect(page.locator('#mentorList [data-mentor-card="source-c"] .mentor-badge.pinned')).toHaveText("置顶");
|
||||||
|
await page.locator('[data-mentor-id="source-b"]').click();
|
||||||
|
await page.locator("#mentorPinButton").click();
|
||||||
await expect(page.locator("#mentorList [data-mentor-card]").first()).toHaveAttribute("data-mentor-card", "source-b");
|
await expect(page.locator("#mentorList [data-mentor-card]").first()).toHaveAttribute("data-mentor-card", "source-b");
|
||||||
|
|
||||||
await page.locator("#mentorSortToggle").click();
|
await page.locator("#mentorSortToggle").click();
|
||||||
@@ -2892,10 +2904,16 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
await expect(page.locator("#mentorMessages .assistant-stream-caret")).toHaveCount(0);
|
await expect(page.locator("#mentorMessages .assistant-stream-caret")).toHaveCount(0);
|
||||||
const followUps = answer.locator("[data-mentor-follow-up]");
|
const followUps = answer.locator("[data-mentor-follow-up]");
|
||||||
await expect(followUps).toHaveCount(3);
|
await expect(followUps).toHaveCount(3);
|
||||||
|
await expect(followUps.first().locator(".lucide")).toHaveCount(0);
|
||||||
|
await expect(answer.locator("small")).toHaveCount(0);
|
||||||
const messageCount = await page.locator("#mentorMessages .mentor-message").count();
|
const messageCount = await page.locator("#mentorMessages .mentor-message").count();
|
||||||
await followUps.first().click();
|
await followUps.first().click();
|
||||||
await expect(page.locator("#mentorQuestion")).toHaveValue("哪些信号代表确认?");
|
await expect(page.locator("#mentorQuestion")).toHaveValue("哪些信号代表确认?");
|
||||||
await expect(page.locator("#mentorMessages .mentor-message")).toHaveCount(messageCount);
|
await expect(page.locator("#mentorMessages .mentor-message")).toHaveCount(messageCount);
|
||||||
|
const userLabel = page.locator("#mentorMessages .mentor-message.user .mentor-message-label").first();
|
||||||
|
await expect(userLabel).not.toContainText("我 ·");
|
||||||
|
const assistantBody = await page.locator("#mentorMessages .mentor-message.assistant .mentor-message-body").last().boundingBox();
|
||||||
|
expect(assistantBody.width).toBeLessThanOrEqual(900);
|
||||||
await page.locator("#themeToggle").click();
|
await page.locator("#themeToggle").click();
|
||||||
const userMessage = page.locator("#mentorMessages .mentor-message.user");
|
const userMessage = page.locator("#mentorMessages .mentor-message.user");
|
||||||
const darkUserMessageStyle = await userMessage.evaluate((element) => ({
|
const darkUserMessageStyle = await userMessage.evaluate((element) => ({
|
||||||
@@ -2913,7 +2931,6 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
const content = element.querySelector(".mentor-message-content");
|
const content = element.querySelector(".mentor-message-content");
|
||||||
const heading = element.querySelector(".mentor-answer-heading");
|
const heading = element.querySelector(".mentor-answer-heading");
|
||||||
const label = element.querySelector(".mentor-message-label");
|
const label = element.querySelector(".mentor-message-label");
|
||||||
const meta = element.querySelector("small");
|
|
||||||
return {
|
return {
|
||||||
background: style.backgroundColor,
|
background: style.backgroundColor,
|
||||||
border: style.borderTopColor,
|
border: style.borderTopColor,
|
||||||
@@ -2921,7 +2938,6 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
contentColor: getComputedStyle(content).color,
|
contentColor: getComputedStyle(content).color,
|
||||||
headingColor: getComputedStyle(heading).color,
|
headingColor: getComputedStyle(heading).color,
|
||||||
labelColor: getComputedStyle(label).color,
|
labelColor: getComputedStyle(label).color,
|
||||||
metaColor: getComputedStyle(meta).color,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
expect(darkMessageStyle.background).not.toBe("rgb(255, 255, 255)");
|
expect(darkMessageStyle.background).not.toBe("rgb(255, 255, 255)");
|
||||||
@@ -2930,7 +2946,61 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
|||||||
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(124, 130, 138)");
|
expect(darkMessageStyle.labelColor).toBe("rgb(124, 130, 138)");
|
||||||
expect(darkMessageStyle.metaColor).toBe("rgb(124, 130, 138)");
|
});
|
||||||
|
|
||||||
|
test("mentor avatars map per contact id and follow the final day/night palette", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 });
|
||||||
|
const mentors = [
|
||||||
|
{
|
||||||
|
id: "xiaobai-perspective", name: "小白", description: "个人复盘记录蒸馏", tagline: "复盘自己",
|
||||||
|
focus: ["个人复盘"], evidence: { grade: "A", label: "私有原始语料", note: "私人语料" }, quality: {}, private: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kobe92-perspective", name: "52科比", description: "情绪周期心法", tagline: "先看周期",
|
||||||
|
focus: ["情绪周期"], evidence: { grade: "A", label: "心法文本", note: "心法" }, quality: {}, private: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "beijingchaojia-perspective", name: "北京炒家", description: "实盘记录", tagline: "实盘为先",
|
||||||
|
focus: ["实盘"], evidence: { grade: "A", label: "实盘资料", note: "实盘" }, quality: {}, private: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "chaojiyangjia-perspective", name: "炒股养家", description: "原始语料", tagline: "情绪为上",
|
||||||
|
focus: ["情绪"], evidence: { grade: "B", label: "原始语料", note: "语料" }, quality: {}, private: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
await mockApplication(page, session("admin", true), { mentors });
|
||||||
|
await page.goto("/index.html");
|
||||||
|
await page.locator('[data-view="mentorView"]').first().click();
|
||||||
|
|
||||||
|
const expectations = {
|
||||||
|
"xiaobai-perspective": ["mentor-avatar-tone-violet", "rgb(238, 236, 253)", "rgb(106, 92, 245)"],
|
||||||
|
"kobe92-perspective": ["mentor-avatar-tone-blue", "rgb(227, 240, 255)", "rgb(51, 112, 255)"],
|
||||||
|
"beijingchaojia-perspective": ["mentor-avatar-tone-green", "rgb(221, 245, 229)", "rgb(46, 164, 79)"],
|
||||||
|
"chaojiyangjia-perspective": ["mentor-avatar-tone-orange", "rgb(253, 238, 221)", "rgb(217, 122, 27)"],
|
||||||
|
};
|
||||||
|
for (const [id, [tone, background, ink]] of Object.entries(expectations)) {
|
||||||
|
const avatar = page.locator(`#mentorList [data-mentor-id="${id}"] .mentor-avatar`);
|
||||||
|
await expect(avatar).toHaveClass(new RegExp(tone));
|
||||||
|
const palette = await avatar.evaluate((element) => {
|
||||||
|
const style = getComputedStyle(element);
|
||||||
|
return { background: style.backgroundColor, color: style.color };
|
||||||
|
});
|
||||||
|
expect(palette.background).toBe(background);
|
||||||
|
expect(palette.color).toBe(ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.locator('[data-mentor-id="kobe92-perspective"]').click();
|
||||||
|
await expect(page.locator("#activeMentorAvatar")).toHaveClass(/mentor-avatar-tone-blue/);
|
||||||
|
|
||||||
|
await page.locator("#themeToggle").click();
|
||||||
|
const nightPalette = await page.locator('#mentorList [data-mentor-id="beijingchaojia-perspective"] .mentor-avatar')
|
||||||
|
.evaluate((element) => {
|
||||||
|
const style = getComputedStyle(element);
|
||||||
|
return { background: style.backgroundColor, color: style.color };
|
||||||
|
});
|
||||||
|
expect(nightPalette.background).toBe("rgb(35, 74, 56)");
|
||||||
|
expect(nightPalette.color).toBe("rgb(95, 206, 143)");
|
||||||
|
await expect(page.locator("#activeMentorAvatar")).toHaveClass(/mentor-avatar-tone-blue/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("mentor floating dialogs open centered, save notes per account, and keep composer one-line", async ({ page }) => {
|
test("mentor floating dialogs open centered, save notes per account, and keep composer one-line", async ({ page }) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import unittest
|
import unittest
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -8,12 +9,35 @@ from pathlib import Path
|
|||||||
from tests.frontend_test_helpers import (
|
from tests.frontend_test_helpers import (
|
||||||
assembled_frontend_runtime,
|
assembled_frontend_runtime,
|
||||||
assembled_frontend_document,
|
assembled_frontend_document,
|
||||||
|
registered_frontend_runtime_scripts,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
STATIC_DIR = Path(__file__).resolve().parents[1] / "frontend"
|
STATIC_DIR = Path(__file__).resolve().parents[1] / "frontend"
|
||||||
|
|
||||||
|
|
||||||
|
def uncommitted_runtime_files() -> set[str]:
|
||||||
|
"""Relative frontend paths with pending working-tree edits.
|
||||||
|
|
||||||
|
The id-reference contract is validated against committed sources only.
|
||||||
|
Parallel agents may temporarily reference ids that their own (uncommitted)
|
||||||
|
HTML change reintroduces; those are resolved by the owning commit.
|
||||||
|
"""
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "-C", str(STATIC_DIR.parent), "diff", "--name-only", "HEAD"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return set()
|
||||||
|
return {
|
||||||
|
line.strip().removeprefix("app/frontend/")
|
||||||
|
for line in result.stdout.splitlines()
|
||||||
|
if line.strip().startswith("app/frontend/")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class IdCollector(HTMLParser):
|
class IdCollector(HTMLParser):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -50,11 +74,23 @@ class FrontendContractTests(unittest.TestCase):
|
|||||||
self.assertEqual(duplicates, [])
|
self.assertEqual(duplicates, [])
|
||||||
|
|
||||||
def test_literal_id_selectors_exist_in_html(self):
|
def test_literal_id_selectors_exist_in_html(self):
|
||||||
selectors = set(re.findall(r'querySelector\("#([A-Za-z][A-Za-z0-9_-]*)"\)', self.script))
|
pending = uncommitted_runtime_files()
|
||||||
selectors.update(re.findall(r'getElementById\("([A-Za-z][A-Za-z0-9_-]*)"\)', self.script))
|
dangling: list[str] = []
|
||||||
selectors.update(re.findall(r'setText\("([A-Za-z][A-Za-z0-9_-]*)"', self.script))
|
for url in registered_frontend_runtime_scripts():
|
||||||
missing = sorted(selectors - set(self.ids))
|
relative = url.split("?", 1)[0].lstrip("/")
|
||||||
self.assertEqual(missing, [])
|
if relative.startswith("vendor/"):
|
||||||
|
continue
|
||||||
|
if relative in pending:
|
||||||
|
continue
|
||||||
|
source = (STATIC_DIR / relative).read_text(encoding="utf-8")
|
||||||
|
selectors = set(re.findall(r'querySelector\("#([A-Za-z][A-Za-z0-9_-]*)"\)', source))
|
||||||
|
selectors.update(re.findall(r'getElementById\("([A-Za-z][A-Za-z0-9_-]*)"\)', source))
|
||||||
|
selectors.update(re.findall(r'setText\("([A-Za-z][A-Za-z0-9_-]*)"', source))
|
||||||
|
dangling.extend(
|
||||||
|
f"{relative}:{selector}"
|
||||||
|
for selector in sorted(selectors - set(self.ids))
|
||||||
|
)
|
||||||
|
self.assertEqual(dangling, [])
|
||||||
|
|
||||||
def test_all_primary_views_have_navigation_entries(self):
|
def test_all_primary_views_have_navigation_entries(self):
|
||||||
views = set(re.findall(r'id="([A-Za-z][A-Za-z0-9_-]*View|limitPool)" class="workspace-view', self.html))
|
views = set(re.findall(r'id="([A-Za-z][A-Za-z0-9_-]*View|limitPool)" class="workspace-view', self.html))
|
||||||
@@ -287,7 +323,7 @@ class FrontendContractTests(unittest.TestCase):
|
|||||||
self.assertIn("--qp-link: #316FEF;", self.mentor_styles)
|
self.assertIn("--qp-link: #316FEF;", self.mentor_styles)
|
||||||
self.assertIn("--qp-accent-soft: #5B8DEF;", self.mentor_styles)
|
self.assertIn("--qp-accent-soft: #5B8DEF;", self.mentor_styles)
|
||||||
self.assertIn("--mentor-directory-width: 300px;", self.tokens)
|
self.assertIn("--mentor-directory-width: 300px;", self.tokens)
|
||||||
self.assertIn("--mentor-composer-min-height: 94px;", self.tokens)
|
self.assertIn("--mentor-composer-min-height: 85px;", self.tokens)
|
||||||
self.assertIn("#mentorView .mentor-message.user .mentor-message-content {", self.mentor_styles)
|
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("background: var(--qp-bg-bubble-self);", self.mentor_styles)
|
||||||
self.assertIn("#mentorView .mentor-quick-prompts button {", self.mentor_styles)
|
self.assertIn("#mentorView .mentor-quick-prompts button {", self.mentor_styles)
|
||||||
@@ -299,6 +335,37 @@ class FrontendContractTests(unittest.TestCase):
|
|||||||
self.assertIn("max-height: var(--sentiment-history-max-height);", self.sentiment_styles)
|
self.assertIn("max-height: var(--sentiment-history-max-height);", self.sentiment_styles)
|
||||||
self.assertIn("overflow: auto;", self.sentiment_styles)
|
self.assertIn("overflow: auto;", self.sentiment_styles)
|
||||||
|
|
||||||
|
def test_mentor_final_visual_fix_contract(self):
|
||||||
|
shell_styles = (STATIC_DIR / "shared" / "shell.css").read_text(encoding="utf-8")
|
||||||
|
mentor_html = (STATIC_DIR / "pages" / "mentor" / "page.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
'body[data-active-view="mentorView"] .header-actions > :is(.global-search-button, .alert-button, .assistant-button, .header-menu-button, .header-date-group, .header-command-group)',
|
||||||
|
shell_styles,
|
||||||
|
)
|
||||||
|
for tone in ("violet", "blue", "green", "orange", "red", "teal", "purple", "yellow"):
|
||||||
|
self.assertIn(f".mentor-avatar-tone-{tone} {{", self.mentor_styles, tone)
|
||||||
|
self.assertIn(
|
||||||
|
f':root[data-theme="dark"] #mentorView .mentor-avatar-tone-{tone} {{',
|
||||||
|
self.mentor_styles,
|
||||||
|
tone,
|
||||||
|
)
|
||||||
|
self.assertIn("kobe92-perspective", self.script)
|
||||||
|
self.assertIn('"beijingchaojia-perspective": "green"', self.script)
|
||||||
|
self.assertIn("`mentor-avatar-tone-${mentorAvatarTone(mentor)}`", self.script)
|
||||||
|
self.assertIn('${escapeHtml(grade)}级', self.script)
|
||||||
|
self.assertIn("置顶", self.script)
|
||||||
|
self.assertNotIn("activeMentorBadges", self.script)
|
||||||
|
self.assertNotIn("activeMentorBadges", mentor_html)
|
||||||
|
self.assertIn("#mentorView .mentor-message.assistant .mentor-message-body {", self.mentor_styles)
|
||||||
|
self.assertIn("max-width: 900px;", self.mentor_styles)
|
||||||
|
self.assertIn("#mentorView .mentor-follow-ups {", self.mentor_styles)
|
||||||
|
self.assertIn("width: 382px;", self.mentor_styles)
|
||||||
|
self.assertIn("data-lucide=\"filter\"", mentor_html)
|
||||||
|
self.assertNotIn("chevron-down", mentor_html)
|
||||||
|
self.assertNotIn("· 回答完成", self.script)
|
||||||
|
self.assertIn('state.mentorLoading ? "正在生成回答..." : (selected?.tagline', self.script)
|
||||||
|
|
||||||
def test_theme_switch_is_atomic_and_theme_library_loading_surface_is_dark_safe(self):
|
def test_theme_switch_is_atomic_and_theme_library_loading_surface_is_dark_safe(self):
|
||||||
self.assertIn('typeof document.startViewTransition === "function"', self.script)
|
self.assertIn('typeof document.startViewTransition === "function"', self.script)
|
||||||
self.assertIn('root.classList.add("theme-switching")', self.script)
|
self.assertIn('root.classList.add("theme-switching")', self.script)
|
||||||
|
|||||||
Reference in New Issue
Block a user