Restore mobile panel UI refinements

This commit is contained in:
Youzini-afk
2026-04-10 23:47:39 +08:00
parent 12c7f9ad20
commit 47a0fe8150
4 changed files with 560 additions and 304 deletions

327
style.css
View File

@@ -704,6 +704,12 @@
color: var(--bme-primary);
}
#bme-graph-canvas,
#bme-mobile-graph-canvas,
#bme-fullscreen-graph-canvas {
touch-action: none;
}
#bme-graph-canvas {
flex: 1;
width: 100%;
@@ -3691,26 +3697,84 @@
margin-bottom: 2px;
}
/* --- Node Detail Panel (sidebar overlay) --- */
.bme-node-detail {
/* --- Node detail:桌面右侧抽屉 / 移动端图谱内居中悬浮 + 遮罩 --- */
.bme-node-detail-scrim {
position: absolute;
inset: 0;
z-index: 9;
margin: 0;
padding: 0;
border: none;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
cursor: pointer;
}
.bme-node-detail-scrim[hidden] {
display: none !important;
}
/* 桌面图谱工作区(主画布):节点详情仍为右侧滑出面板 */
#bme-node-detail {
position: absolute;
top: 0;
right: 0;
left: auto;
width: 280px;
height: 100%;
max-height: none;
background: var(--bme-surface-container);
border-left: 1px solid var(--bme-border);
border-radius: 0;
padding: 12px;
overflow-y: auto;
box-shadow: none;
transform: translateX(100%);
opacity: 1;
visibility: visible;
pointer-events: auto;
transition: transform 0.2s ease;
z-index: 10;
}
.bme-node-detail.open {
#bme-node-detail.open {
transform: translateX(0);
}
/* 移动端「图谱」Tab节点详情居中悬浮窗 */
#bme-mobile-node-detail {
position: absolute;
left: 50%;
top: 50%;
right: auto;
width: min(400px, calc(100% - 24px));
max-height: min(78vh, 640px);
height: auto;
background: var(--bme-surface-container);
border: 1px solid var(--bme-border);
border-radius: 14px;
padding: 12px;
overflow-y: auto;
box-shadow: 0 20px 48px rgba(0, 0, 0, 0.5);
transform: translate(-50%, -48%) scale(0.96);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 0.22s ease,
transform 0.22s ease,
visibility 0.22s;
z-index: 10;
}
#bme-mobile-node-detail.open {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.bme-node-detail-header {
display: flex;
align-items: center;
@@ -3848,13 +3912,149 @@
border-radius: 2px;
}
/* 移动端图谱预览 - 桌面端默认隐藏 */
/* 移动端图谱预览 - 桌面端默认隐藏(旧组件已移除) */
.bme-mobile-graph-preview,
.bme-mobile-graph-status,
.bme-mobile-graph-section {
.bme-mobile-graph-status {
display: none;
}
/* --- Graph Sub-Tabs (Mobile Graph Pane) --- */
.bme-graph-subtabs {
display: flex;
gap: 4px;
padding: 10px 12px 6px;
background: var(--bme-surface-container);
border-bottom: 1px solid var(--bme-border);
flex-shrink: 0;
}
.bme-graph-subtab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 7px 10px;
border: none;
border-radius: 20px;
background: rgba(255, 255, 255, 0.04);
color: var(--bme-on-surface-dim);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.bme-graph-subtab i {
font-size: 12px;
}
.bme-graph-subtab:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--bme-on-surface);
}
.bme-graph-subtab.active {
background: var(--bme-primary);
color: #fff;
box-shadow: 0 2px 8px rgba(233, 69, 96, 0.35);
}
.bme-graph-view-content {
flex: 1;
min-height: 0;
overflow: hidden;
position: relative;
}
.bme-mobile-graph-pane {
display: none;
width: 100%;
height: 100%;
flex-direction: column;
overflow: auto;
}
.bme-mobile-graph-pane.active {
display: flex;
}
.bme-mobile-canvas-wrap {
flex: 1;
position: relative;
min-height: 0;
}
.bme-mobile-canvas-wrap canvas {
display: block;
width: 100%;
height: 100%;
}
.bme-mobile-graph-float-controls {
position: absolute;
top: 10px;
right: 10px;
display: flex;
flex-direction: column;
gap: 6px;
z-index: 5;
}
.bme-mobile-graph-float-controls button {
width: 36px;
height: 36px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(10, 10, 15, 0.65);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: var(--bme-on-surface);
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.15s ease;
}
.bme-mobile-graph-float-controls button:hover {
background: rgba(233, 69, 96, 0.25);
border-color: var(--bme-primary);
color: var(--bme-primary);
}
/* Graph pane layout */
#bme-pane-graph {
display: none;
flex-direction: column;
height: 100%;
overflow: hidden;
position: relative;
}
#bme-pane-graph.active {
display: flex;
}
#bme-pane-graph .bme-graph-legend {
flex-shrink: 0;
padding: 6px 12px;
}
#bme-pane-graph .bme-graph-statusbar {
flex-shrink: 0;
padding: 4px 12px;
font-size: 10px;
}
#bme-pane-graph .bme-cognition-workspace {
padding: 12px;
overflow-y: auto;
}
/* ═══════ ⑤ 高级设置折叠 (desktop+mobile) ═══════ */
.bme-advanced-settings {
@@ -3954,29 +4154,21 @@
gap: 4px;
}
/* ⑥ 图谱 tab 移动端全屏覆盖 */
.bme-panel-main.mobile-visible {
display: flex;
position: absolute;
inset: 0;
z-index: 20;
border-radius: 0;
}
.bme-panel-main.mobile-visible ~ .bme-panel-tabbar {
z-index: 21;
}
/* ② 底部 Tabbar safe-area + 触控增大 */
/* ② 底部 Tabbar safe-area + 6 列 icon-only */
.bme-panel-tabbar {
display: flex;
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.bme-panel-tabbar .bme-tab-btn {
min-height: 52px;
gap: 3px;
font-size: 10px;
min-height: 44px;
gap: 2px;
font-size: 0;
padding: 6px 2px;
}
.bme-panel-tabbar .bme-tab-btn span {
display: none;
}
.bme-panel-tabbar .bme-tab-btn i {
@@ -4289,9 +4481,10 @@
font-size: 22px;
}
/* 节点详情移动端全宽 */
.bme-node-detail {
width: 100%;
/* 移动端图谱内悬浮窗略收窄(桌面 #bme-node-detail 在窄屏下不可见) */
#bme-mobile-node-detail {
width: min(400px, calc(100% - 16px));
max-height: min(82vh, 680px);
}
/* 图例 */
@@ -4299,88 +4492,6 @@
font-size: 9px;
}
/* 移动端图谱区重设计 */
.bme-mobile-graph-section {
display: block;
margin: 12px 0 0;
border: 1px solid var(--bme-border);
border-radius: 12px;
background: var(--bme-surface-lowest);
overflow: hidden;
}
.bme-mobile-graph-tabs {
display: flex;
border-bottom: 1px solid var(--bme-border);
}
.bme-mobile-graph-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 8px;
border: none;
background: transparent;
color: var(--bme-on-surface-dim);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
}
.bme-mobile-graph-tab:hover {
color: var(--bme-on-surface);
background: rgba(255, 255, 255, 0.04);
}
.bme-mobile-graph-tab.active {
color: var(--bme-primary);
}
.bme-mobile-graph-tab.active::after {
content: "";
position: absolute;
bottom: 0;
left: 20%;
right: 20%;
height: 2px;
background: var(--bme-primary);
border-radius: 2px 2px 0 0;
}
.bme-mobile-view-pane {
display: none;
padding: 12px;
min-height: 120px;
}
.bme-mobile-view-pane.active {
display: block;
}
.bme-mobile-fullscreen-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 12px;
border: none;
border-top: 1px solid var(--bme-border);
background: rgba(255, 255, 255, 0.02);
color: var(--bme-primary);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s ease;
}
.bme-mobile-fullscreen-btn:hover {
background: rgba(255, 255, 255, 0.06);
}
}
/* ═══════ Help Tip (? 图标 + 气泡) ═══════ */

View File

@@ -205,6 +205,8 @@ export class GraphRenderer {
this.isDragging = false;
this.isPanning = false;
this.lastMouse = { x: 0, y: 0 };
/** @type {{ startX: number, startY: number, lastX: number, lastY: number, nodeCandidate: object|null, moved: boolean } | null} */
this._touchSession = null;
this.animId = null;
@@ -771,19 +773,55 @@ export class GraphRenderer {
c.addEventListener('wheel', (e) => this._onWheel(e), { passive: false });
c.addEventListener('dblclick', (e) => this._onDoubleClick(e));
// 触摸:单指始终平移画布,松手时在未移动过的情况下视为「点击」选中节点(避免拖动画布时误拖节点)
c.addEventListener('touchstart', (e) => {
if (e.touches.length === 1) {
const t = e.touches[0];
this._onMouseDown({ clientX: t.clientX, clientY: t.clientY, button: 0 });
if (e.touches.length !== 1) {
this._touchSession = null;
return;
}
}, { passive: true });
e.preventDefault();
const t = e.touches[0];
const { x, y } = this._canvasToWorld(t.clientX, t.clientY);
this._touchSession = {
startX: t.clientX,
startY: t.clientY,
lastX: t.clientX,
lastY: t.clientY,
nodeCandidate: this._findNodeAt(x, y),
moved: false,
};
}, { passive: false });
c.addEventListener('touchmove', (e) => {
if (e.touches.length === 1) {
const t = e.touches[0];
this._onMouseMove({ clientX: t.clientX, clientY: t.clientY });
if (!this._touchSession || e.touches.length !== 1) return;
e.preventDefault();
const t = e.touches[0];
const dx = t.clientX - this._touchSession.lastX;
const dy = t.clientY - this._touchSession.lastY;
const fromStartX = t.clientX - this._touchSession.startX;
const fromStartY = t.clientY - this._touchSession.startY;
if (Math.abs(fromStartX) > 5 || Math.abs(fromStartY) > 5) {
this._touchSession.moved = true;
}
}, { passive: true });
c.addEventListener('touchend', () => this._onMouseUp({}));
this.offsetX += dx;
this.offsetY += dy;
this._touchSession.lastX = t.clientX;
this._touchSession.lastY = t.clientY;
this._render();
}, { passive: false });
c.addEventListener('touchend', (e) => {
if (!this._touchSession) return;
const sess = this._touchSession;
this._touchSession = null;
if (!sess.moved && sess.nodeCandidate) {
this.selectedNode = sess.nodeCandidate;
if (this.onNodeSelect) this.onNodeSelect(sess.nodeCandidate);
if (this.onNodeClick) this.onNodeClick(sess.nodeCandidate);
this._render();
}
});
c.addEventListener('touchcancel', () => {
this._touchSession = null;
});
}
_canvasToWorld(clientX, clientY) {

View File

@@ -250,22 +250,6 @@
<ul class="bme-recent-list" id="bme-ai-monitor-list"></ul>
</div>
<div class="bme-mobile-graph-section" id="bme-mobile-graph-section">
<div class="bme-mobile-graph-tabs">
<button class="bme-mobile-graph-tab active" data-mobile-view="summary" type="button">
<i class="fa-solid fa-chart-pie"></i> 图谱摘要
</button>
<button class="bme-mobile-graph-tab" data-mobile-view="cognition" type="button">
<i class="fa-solid fa-brain"></i> 认知视图
</button>
</div>
<div class="bme-mobile-view-pane active" id="bme-mobile-summary-pane" data-mobile-view="summary"></div>
<div class="bme-mobile-view-pane" id="bme-mobile-cognition-pane" data-mobile-view="cognition"></div>
<button class="bme-mobile-fullscreen-btn" id="bme-mobile-open-fullscreen" type="button">
<i class="fa-solid fa-expand"></i> 打开全屏图谱
</button>
</div>
<div class="bme-section-header">最近提取</div>
<ul class="bme-recent-list" id="bme-recent-extract"></ul>
@@ -483,6 +467,104 @@
</div>
</div>
</div>
<div class="bme-tab-pane" id="bme-pane-graph">
<div class="bme-graph-subtabs">
<button class="bme-graph-subtab active" data-mobile-graph-view="graph" type="button">
<i class="fa-solid fa-diagram-project"></i>
<span>实时图谱</span>
</button>
<button class="bme-graph-subtab" data-mobile-graph-view="cognition" type="button">
<i class="fa-solid fa-brain"></i>
<span>认知视图</span>
</button>
<button class="bme-graph-subtab" data-mobile-graph-view="summary" type="button">
<i class="fa-solid fa-layer-group"></i>
<span>总结视图</span>
</button>
</div>
<div class="bme-graph-view-content">
<div class="bme-mobile-graph-pane active" id="bme-mobile-graph-pane" data-mobile-graph-view="graph">
<div class="bme-mobile-canvas-wrap">
<canvas id="bme-mobile-graph-canvas"></canvas>
<div class="bme-graph-overlay" id="bme-mobile-graph-overlay" hidden>
<div class="bme-graph-overlay__text" id="bme-mobile-graph-overlay-text">
正在加载当前聊天图谱
</div>
</div>
<div class="bme-mobile-graph-float-controls">
<button id="bme-mobile-zoom-in" title="放大" type="button">
<i class="fa-solid fa-plus"></i>
</button>
<button id="bme-mobile-zoom-out" title="缩小" type="button">
<i class="fa-solid fa-minus"></i>
</button>
<button id="bme-mobile-zoom-reset" title="重置" type="button">
<i class="fa-solid fa-arrows-rotate"></i>
</button>
</div>
</div>
<div class="bme-graph-legend" id="bme-mobile-graph-legend"></div>
<div class="bme-graph-statusbar">
<span><span class="bme-status-dot"></span><span id="bme-mobile-status-text">READY</span></span>
<span id="bme-mobile-status-meta">NODES: 0 | EDGES: 0</span>
</div>
</div>
<div class="bme-mobile-graph-pane" id="bme-mobile-cognition-pane" data-mobile-graph-view="cognition">
<div class="bme-cognition-workspace" id="bme-mobile-cognition-full">
<div class="bme-cog-status-strip" id="bme-mobile-cog-status-strip"></div>
<div class="bme-cog-body">
<div class="bme-cog-column bme-cog-column--owners">
<div class="bme-cog-section-title">
<i class="fa-solid fa-users"></i> 角色认知清单
</div>
<div class="bme-cog-owner-scroll" id="bme-mobile-cog-owner-list"></div>
<div class="bme-cog-owner-detail" id="bme-mobile-cog-owner-detail"></div>
</div>
<div class="bme-cog-column bme-cog-column--space">
<div class="bme-cog-section-title">
<i class="fa-solid fa-map-location-dot"></i> 空间控制台
</div>
<div class="bme-cog-space-tools" id="bme-mobile-cog-space-tools"></div>
<div class="bme-cog-section-title" style="margin-top:16px">
<i class="fa-solid fa-wave-square"></i> 任务监视器
</div>
<div class="bme-cog-monitor-mini" id="bme-mobile-cog-monitor-mini"></div>
</div>
</div>
</div>
</div>
<div class="bme-mobile-graph-pane" id="bme-mobile-summary-pane-full" data-mobile-graph-view="summary">
<div class="bme-cognition-workspace" id="bme-mobile-summary-full"></div>
</div>
</div>
<div
class="bme-node-detail-scrim"
id="bme-mobile-node-detail-scrim"
hidden
></div>
<div class="bme-node-detail" id="bme-mobile-node-detail">
<div class="bme-node-detail-header">
<h3 id="bme-mobile-detail-title">节点详情</h3>
<div class="bme-node-detail-actions">
<button class="bme-detail-action-btn bme-detail-action-danger" id="bme-mobile-detail-delete" type="button" title="删除节点">
<i class="fa-solid fa-trash"></i>
</button>
<button class="bme-detail-action-btn" id="bme-mobile-detail-save" type="button" title="保存修改">
<i class="fa-solid fa-floppy-disk"></i>
</button>
<button class="bme-panel-close" id="bme-mobile-detail-close" type="button" title="关闭">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</div>
<div id="bme-mobile-detail-body"></div>
</div>
</div>
</div>
</div>
@@ -2775,6 +2857,10 @@
</div>
<div class="bme-panel-tabbar">
<button class="bme-tab-btn" data-tab="graph" type="button">
<i class="fa-solid fa-diagram-project"></i>
<span>图谱</span>
</button>
<button class="bme-tab-btn active" data-tab="dashboard" type="button">
<i class="fa-solid fa-chart-simple"></i>
<span>总览</span>

View File

@@ -286,6 +286,7 @@ let showGlobalRegexPanel = false;
let currentGlobalRegexRuleId = "";
let currentCognitionOwnerKey = "";
let currentGraphView = "graph";
let currentMobileGraphView = "graph";
let fetchedMemoryLLMModels = [];
let fetchedBackendEmbeddingModels = [];
let fetchedDirectEmbeddingModels = [];
@@ -630,6 +631,9 @@ function bindViewportSync() {
const update = () => {
syncViewportCssVars();
syncFabPosition();
if (!_isMobile() && currentTabId === "graph") {
_switchTab("dashboard");
}
};
window.addEventListener("resize", update);
window.addEventListener("orientationchange", update);
@@ -1000,7 +1004,13 @@ function _bindTabs() {
}
function _switchTab(tabId) {
currentTabId = tabId || "dashboard";
let next = tabId || "dashboard";
// 「图谱」仅移动端底部 Tab 可用;桌面端图谱在右侧主工作区,侧栏不设该 Tab
if (!_isMobile() && next === "graph") {
next = "dashboard";
}
currentTabId = next;
_closeNodeDetailUi();
panelEl?.querySelectorAll(".bme-tab-btn").forEach((btn) => {
btn.classList.toggle("active", btn.dataset.tab === currentTabId);
});
@@ -1009,12 +1019,6 @@ function _switchTab(tabId) {
pane.classList.toggle("active", pane.id === `bme-pane-${currentTabId}`);
});
// ⑥ 移动端图谱 tab 全屏覆盖
const mainEl = panelEl?.querySelector(".bme-panel-main");
if (mainEl) {
mainEl.classList.toggle("mobile-visible", currentTabId === "graph");
}
_applyWorkspaceMode();
switch (currentTabId) {
@@ -1030,6 +1034,9 @@ function _switchTab(tabId) {
case "config":
_refreshConfigTab();
break;
case "graph":
_refreshMobileGraphTab();
break;
default:
break;
}
@@ -1102,6 +1109,9 @@ function _switchGraphView(view) {
if (legend) legend.style.display = isGraph ? "" : "none";
if (statusbar) statusbar.style.display = isGraph ? "" : "none";
if (nodeDetail) nodeDetail.style.display = isGraph ? "" : "none";
if (!isGraph) {
nodeDetail?.classList.remove("open");
}
if (graphControls) graphControls.style.display = isGraph ? "" : "none";
if (cogWorkspace) cogWorkspace.hidden = !isCognition;
if (summaryWorkspace) summaryWorkspace.hidden = !isSummary;
@@ -1112,6 +1122,67 @@ function _switchGraphView(view) {
if (isSummary) _refreshSummaryWorkspace();
}
// ==================== 移动端图谱 Tab ====================
function _switchMobileGraphSubView(view) {
currentMobileGraphView = view || "graph";
const pane = document.getElementById("bme-pane-graph");
if (!pane) return;
pane.querySelectorAll(".bme-graph-subtab").forEach((tab) => {
tab.classList.toggle("active", tab.dataset.mobileGraphView === currentMobileGraphView);
});
pane.querySelectorAll(".bme-mobile-graph-pane").forEach((p) => {
p.classList.toggle("active", p.dataset.mobileGraphView === currentMobileGraphView);
});
if (currentMobileGraphView !== "graph") {
_closeNodeDetailUi();
}
_refreshMobileGraphTab();
}
function _refreshMobileGraphTab() {
if (currentMobileGraphView === "graph") {
_refreshGraph();
_buildMobileLegend();
} else if (currentMobileGraphView === "cognition") {
_refreshMobileCognitionFull();
} else if (currentMobileGraphView === "summary") {
_refreshMobileSummaryFull();
}
}
function _buildMobileLegend() {
const legend = document.getElementById("bme-mobile-graph-legend");
if (!legend) return;
const desktopLegend = document.getElementById("bme-graph-legend");
if (desktopLegend) {
legend.innerHTML = desktopLegend.innerHTML;
}
}
function _refreshMobileCognitionFull() {
const graph = _getGraph?.();
const loadInfo = _getGraphPersistenceSnapshot();
if (!graph) return;
const canRender =
Boolean(graph) &&
(_canRenderGraphData(loadInfo) || loadInfo.loadState === "empty-confirmed");
_renderCogStatusStrip(graph, loadInfo, canRender, document.getElementById("bme-mobile-cog-status-strip"));
_renderCogOwnerList(graph, canRender, document.getElementById("bme-mobile-cog-owner-list"));
_renderCogOwnerDetail(graph, loadInfo, canRender, document.getElementById("bme-mobile-cog-owner-detail"));
_renderCogSpaceTools(graph, loadInfo, canRender, document.getElementById("bme-mobile-cog-space-tools"));
_renderCogMonitorMini(document.getElementById("bme-mobile-cog-monitor-mini"));
}
function _refreshMobileSummaryFull() {
_refreshSummaryWorkspace(document.getElementById("bme-mobile-summary-full"));
}
function _ownerAvatarHsl(name) {
let hash = 0;
const str = String(name || "");
@@ -1232,8 +1303,8 @@ function _refreshCognitionWorkspace() {
_renderCogMonitorMini();
}
function _renderCogStatusStrip(graph, loadInfo, canRender) {
const el = document.getElementById("bme-cog-status-strip");
function _renderCogStatusStrip(graph, loadInfo, canRender, targetEl) {
const el = targetEl || document.getElementById("bme-cog-status-strip");
if (!el) return;
if (!canRender) {
@@ -1305,8 +1376,8 @@ function _renderCogStatusStrip(graph, loadInfo, canRender) {
`;
}
function _renderCogOwnerList(graph, canRender) {
const el = document.getElementById("bme-cog-owner-list");
function _renderCogOwnerList(graph, canRender, targetEl) {
const el = targetEl || document.getElementById("bme-cog-owner-list");
if (!el) return;
if (!canRender) {
@@ -1351,8 +1422,8 @@ function _renderCogOwnerList(graph, canRender) {
.join("");
}
function _renderCogOwnerDetail(graph, loadInfo, canRender) {
const el = document.getElementById("bme-cog-owner-detail");
function _renderCogOwnerDetail(graph, loadInfo, canRender, targetEl) {
const el = targetEl || document.getElementById("bme-cog-owner-detail");
if (!el) return;
if (!canRender) {
@@ -1480,8 +1551,8 @@ function _renderCogOwnerDetail(graph, loadInfo, canRender) {
`;
}
function _renderCogSpaceTools(graph, loadInfo, canRender) {
const el = document.getElementById("bme-cog-space-tools");
function _renderCogSpaceTools(graph, loadInfo, canRender, targetEl) {
const el = targetEl || document.getElementById("bme-cog-space-tools");
if (!el) return;
if (!canRender) { el.innerHTML = ""; return; }
@@ -1541,8 +1612,8 @@ function _renderCogSpaceTools(graph, loadInfo, canRender) {
`;
}
function _renderCogMonitorMini() {
const el = document.getElementById("bme-cog-monitor-mini");
function _renderCogMonitorMini(targetEl) {
const el = targetEl || document.getElementById("bme-cog-monitor-mini");
if (!el) return;
const settings = _getSettings?.() || {};
@@ -1586,141 +1657,6 @@ function _renderCogMonitorMini() {
.join("");
}
// ==================== 移动端图谱视图 ====================
function _switchMobileGraphView(view) {
const section = document.getElementById("bme-mobile-graph-section");
if (!section) return;
section.querySelectorAll(".bme-mobile-graph-tab").forEach((tab) => {
tab.classList.toggle("active", tab.dataset.mobileView === view);
});
section.querySelectorAll(".bme-mobile-view-pane").forEach((pane) => {
pane.classList.toggle("active", pane.dataset.mobileView === view);
});
if (view === "summary") _refreshMobileSummary();
if (view === "cognition") _refreshMobileCognition();
}
function _refreshMobileSummary() {
const el = document.getElementById("bme-mobile-summary-pane");
if (!el) return;
const graph = _getGraph?.();
const loadInfo = _getGraphPersistenceSnapshot();
if (!graph || !_canRenderGraphData(loadInfo)) {
el.innerHTML = `<div class="bme-cog-monitor-empty">${_escHtml(_getGraphLoadLabel(loadInfo?.loadState))}</div>`;
return;
}
const activeNodes = graph.nodes.filter((n) => !n.archived);
const archivedCount = graph.nodes.filter((n) => n.archived).length;
const typeMap = {};
for (const node of activeNodes) {
const t = String(node.type || "unknown");
typeMap[t] = (typeMap[t] || 0) + 1;
}
const typePills = Object.entries(typeMap)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([type, count]) => `<span class="bme-cog-chip">${_escHtml(type)} ${count}</span>`)
.join("");
const recentNodes = [...activeNodes]
.sort((a, b) => (Number(b.seqRange?.[1] || b.seqRange?.[0] || 0)) - (Number(a.seqRange?.[1] || a.seqRange?.[0] || 0)))
.slice(0, 5);
const recentHtml = recentNodes.map((n) => {
const name = getNodeDisplayName(n);
const type = String(n.type || "");
return `<div class="bme-cog-monitor-entry is-success" style="border-left-color:var(--bme-primary)">
<span class="bme-cog-monitor-badge">${_escHtml(type)}</span>
<span class="bme-cog-monitor-info">${_escHtml(name)}</span>
</div>`;
}).join("");
el.innerHTML = `
<div class="bme-cog-status-strip" style="grid-template-columns:repeat(3,1fr);margin-bottom:10px">
<div class="bme-cog-status-card"><div class="bme-cog-status-card__label">活跃</div><div class="bme-cog-status-card__value">${activeNodes.length}</div></div>
<div class="bme-cog-status-card"><div class="bme-cog-status-card__label">边</div><div class="bme-cog-status-card__value">${graph.edges.length}</div></div>
<div class="bme-cog-status-card"><div class="bme-cog-status-card__label">归档</div><div class="bme-cog-status-card__value">${archivedCount}</div></div>
</div>
<div class="bme-cog-chip-section" style="margin-bottom:10px">
<div class="bme-cog-chip-label">类型分布</div>
<div class="bme-cog-chip-wrap">${typePills || '<span class="bme-cog-chip is-empty">暂无</span>'}</div>
</div>
<div class="bme-cog-chip-label" style="margin-bottom:6px">最近节点</div>
<div class="bme-cog-monitor-mini">${recentHtml || '<div class="bme-cog-monitor-empty">暂无</div>'}</div>
`;
}
function _refreshMobileCognition() {
const el = document.getElementById("bme-mobile-cognition-pane");
if (!el) return;
const graph = _getGraph?.();
const loadInfo = _getGraphPersistenceSnapshot();
if (!graph) { el.innerHTML = ""; return; }
const canRender =
Boolean(graph) &&
(_canRenderGraphData(loadInfo) || loadInfo.loadState === "empty-confirmed");
if (!canRender) {
el.innerHTML = `<div class="bme-cog-monitor-empty">${_escHtml(_getGraphLoadLabel(loadInfo.loadState))}</div>`;
return;
}
const { owners, activeOwnerKey, activeOwner, activeOwnerKeys, activeOwnerLabels } =
_getCurrentCognitionOwnerSummary(graph);
const collisionIndex = _buildOwnerCollisionIndex(owners);
const historyState = graph?.historyState || {};
const regionState = graph?.regionState || {};
const activeRegion = String(historyState.activeRegion || historyState.lastExtractedRegion || regionState.manualActiveRegion || "").trim();
const adjacentRegions = Array.isArray(regionState?.adjacencyMap?.[activeRegion]?.adjacent)
? regionState.adjacencyMap[activeRegion].adjacent : [];
const ownerCards = owners.map((owner) => {
const displayInfo = _getOwnerDisplayInfo(owner, collisionIndex);
const bgColor = _ownerAvatarHsl(displayInfo.avatarSeed);
const anchor =
owner.ownerKey === activeOwnerKey || activeOwnerKeys.includes(owner.ownerKey)
? "is-active-anchor"
: "";
return `
<div class="bme-cog-owner-card ${anchor}" style="min-width:unset;max-width:unset" title="${_escHtml(displayInfo.tooltip)}">
<div class="bme-cog-avatar" style="background:${bgColor}">${_escHtml(displayInfo.avatarText)}</div>
<div class="bme-cog-owner-card__info">
<div class="bme-cog-owner-card__name-row">
<div class="bme-cog-owner-card__name">${_escHtml(displayInfo.title)}</div>
<span class="bme-cog-owner-card__badge">${_escHtml(displayInfo.typeLabel)}</span>
</div>
<div class="bme-cog-owner-card__stats">已知 ${Number(owner.knownCount || 0)} · 误解 ${Number(owner.mistakenCount || 0)}</div>
</div>
</div>`;
}).join("");
el.innerHTML = `
<div class="bme-cog-status-strip" style="grid-template-columns:repeat(2,1fr);margin-bottom:10px">
<div class="bme-cog-status-card">
<div class="bme-cog-status-card__label"><i class="fa-solid fa-user"></i> 场景锚点</div>
<div class="bme-cog-status-card__value">${_escHtml(
activeOwnerLabels.length > 0
? activeOwnerLabels.join(" / ")
: activeOwner
? _getOwnerDisplayInfo(activeOwner, collisionIndex).title
: "—",
)}</div>
</div>
<div class="bme-cog-status-card">
<div class="bme-cog-status-card__label"><i class="fa-solid fa-location-dot"></i> 当前地区</div>
<div class="bme-cog-status-card__value">${_escHtml(activeRegion || "—")}</div>
</div>
</div>
<div class="bme-cog-chip-label" style="margin-bottom:6px">认知角色 (${owners.length})</div>
<div style="display:flex;flex-direction:column;gap:6px">${ownerCards || '<div class="bme-cog-monitor-empty">暂无</div>'}</div>
`;
}
function _formatSummaryEntryCard(entry = {}) {
const messageRange = Array.isArray(entry?.dialogueRange)
@@ -1766,10 +1702,10 @@ function _formatSummaryEntryCard(entry = {}) {
`;
}
function _refreshSummaryWorkspace() {
function _refreshSummaryWorkspace(targetEl) {
const graph = _getGraph?.();
const loadInfo = _getGraphPersistenceSnapshot();
const workspace = document.getElementById("bme-summary-workspace");
const workspace = targetEl || document.getElementById("bme-summary-workspace");
if (!workspace) return;
if (!graph || !_canRenderGraphData(loadInfo)) {
@@ -1977,7 +1913,6 @@ function _refreshDashboard() {
_refreshCognitionDashboard(graph);
_refreshAiMonitorDashboard();
_refreshMobileSummary();
_renderRecentList("bme-recent-extract", _getLastExtract?.() || []);
_renderRecentList("bme-recent-recall", _getLastRecall?.() || []);
}
@@ -2012,7 +1947,9 @@ function _setInputValueIfIdle(elementId, value = "") {
function _getSelectedGraphNode(graph = _getGraph?.()) {
const detailNodeId = String(
document.getElementById("bme-node-detail")?.dataset?.editNodeId || "",
document.getElementById("bme-node-detail")?.dataset?.editNodeId ||
document.getElementById("bme-mobile-node-detail")?.dataset?.editNodeId ||
"",
).trim();
const rendererNodeId = String(
_getActiveGraphRenderer()?.selectedNode?.id || "",
@@ -3019,6 +2956,9 @@ function _refreshGraph() {
} else if (currentGraphView === "summary") {
_refreshSummaryWorkspace();
}
if (currentTabId === "graph") {
_refreshMobileGraphTab();
}
}
function _buildLegend() {
@@ -3175,11 +3115,45 @@ function _appendNodeDetailTextareaField(
container.appendChild(row);
}
function _useMobileGraphNodeDetail() {
return _isMobile() && currentTabId === "graph";
}
function _getNodeDetailEls() {
const mobile = _useMobileGraphNodeDetail();
const detailEl = document.getElementById(
mobile ? "bme-mobile-node-detail" : "bme-node-detail",
);
const titleEl = document.getElementById(
mobile ? "bme-mobile-detail-title" : "bme-detail-title",
);
const bodyEl = document.getElementById(
mobile ? "bme-mobile-detail-body" : "bme-detail-body",
);
const scrimEl = mobile
? document.getElementById("bme-mobile-node-detail-scrim")
: null;
if (!detailEl || !titleEl || !bodyEl) return null;
return { detailEl, titleEl, bodyEl, scrimEl, mobile };
}
function _closeNodeDetailUi() {
document.getElementById("bme-node-detail")?.classList.remove("open");
document.getElementById("bme-mobile-node-detail")?.classList.remove("open");
document.getElementById("bme-mobile-node-detail-scrim")?.setAttribute("hidden", "");
}
function _showNodeDetail(node) {
const detailEl = document.getElementById("bme-node-detail");
const titleEl = document.getElementById("bme-detail-title");
const bodyEl = document.getElementById("bme-detail-body");
if (!detailEl || !titleEl || !bodyEl) return;
const els = _getNodeDetailEls();
if (!els) return;
const { detailEl, titleEl, bodyEl, scrimEl, mobile } = els;
if (mobile) {
document.getElementById("bme-node-detail")?.classList.remove("open");
} else {
document.getElementById("bme-mobile-node-detail")?.classList.remove("open");
document.getElementById("bme-mobile-node-detail-scrim")?.setAttribute("hidden", "");
}
const raw = node.raw || node;
const fields = raw.fields || {};
@@ -3300,12 +3274,16 @@ function _showNodeDetail(node) {
}
bodyEl.replaceChildren(fragment);
if (mobile) {
scrimEl?.removeAttribute("hidden");
}
detailEl.classList.add("open");
}
function _saveNodeDetail() {
const detailEl = document.getElementById("bme-node-detail");
const bodyEl = document.getElementById("bme-detail-body");
const els = _getNodeDetailEls();
const detailEl = els?.detailEl;
const bodyEl = els?.bodyEl;
const nodeId = detailEl?.dataset?.editNodeId;
if (!nodeId || !bodyEl) return;
if (_isGraphWriteBlocked()) {
@@ -3425,10 +3403,21 @@ function _bindNodeDetailPanel() {
deleteBtn.addEventListener("click", () => _deleteNodeDetail());
deleteBtn.dataset.bmeBound = "true";
}
const saveMob = document.getElementById("bme-mobile-detail-save");
if (saveMob && saveMob.dataset.bmeBound !== "true") {
saveMob.addEventListener("click", () => _saveNodeDetail());
saveMob.dataset.bmeBound = "true";
}
const delMob = document.getElementById("bme-mobile-detail-delete");
if (delMob && delMob.dataset.bmeBound !== "true") {
delMob.addEventListener("click", () => _deleteNodeDetail());
delMob.dataset.bmeBound = "true";
}
}
function _deleteNodeDetail() {
const detailEl = document.getElementById("bme-node-detail");
const els = _getNodeDetailEls();
const detailEl = els?.detailEl;
const nodeId = detailEl?.dataset?.editNodeId;
if (!nodeId) return;
if (_isGraphWriteBlocked()) {
@@ -3461,8 +3450,11 @@ function _deleteNodeDetail() {
} else {
toastr.success("节点已删除", "ST-BME");
}
detailEl?.classList.remove("open");
if (detailEl) delete detailEl.dataset.editNodeId;
_closeNodeDetailUi();
const dDesk = document.getElementById("bme-node-detail");
const dMob = document.getElementById("bme-mobile-node-detail");
if (dDesk) delete dDesk.dataset.editNodeId;
if (dMob) delete dMob.dataset.editNodeId;
graphRenderer?.highlightNode?.("__cleared__");
mobileGraphRenderer?.highlightNode?.("__cleared__");
refreshLiveState();
@@ -3473,7 +3465,13 @@ function _bindClose() {
.getElementById("bme-panel-close")
?.addEventListener("click", closePanel);
document.getElementById("bme-detail-close")?.addEventListener("click", () => {
document.getElementById("bme-node-detail")?.classList.remove("open");
_closeNodeDetailUi();
});
document.getElementById("bme-mobile-detail-close")?.addEventListener("click", () => {
_closeNodeDetailUi();
});
document.getElementById("bme-mobile-node-detail-scrim")?.addEventListener("click", () => {
_closeNodeDetailUi();
});
overlayEl?.addEventListener("click", (event) => {
if (event.target === overlayEl) closePanel();
@@ -4058,18 +4056,31 @@ function _bindActions() {
});
});
// 移动端图谱/认知 tab 切换
document.querySelectorAll(".bme-mobile-graph-tab").forEach((tab) => {
// 移动端图谱子 Tab 切换
document.querySelectorAll(".bme-graph-subtab").forEach((tab) => {
tab.addEventListener("click", () => {
_switchMobileGraphView(tab.dataset.mobileView);
_switchMobileGraphSubView(tab.dataset.mobileGraphView);
});
});
// 移动端图谱浮动控件
document.getElementById("bme-mobile-zoom-in")?.addEventListener("click", () => {
const r = _getActiveGraphRenderer?.();
r?.zoomIn?.();
});
document.getElementById("bme-mobile-zoom-out")?.addEventListener("click", () => {
const r = _getActiveGraphRenderer?.();
r?.zoomOut?.();
});
document.getElementById("bme-mobile-zoom-reset")?.addEventListener("click", () => {
const r = _getActiveGraphRenderer?.();
r?.resetView?.();
});
// 全屏图谱
document.getElementById("bme-mobile-open-fullscreen")?.addEventListener("click", _openFullscreenGraph);
document.getElementById("bme-fs-close")?.addEventListener("click", _closeFullscreenGraph);
// 认知视图角色列表点击
// 认知视图角色列表点击(桌面端)
document.getElementById("bme-cog-owner-list")?.addEventListener("click", (e) => {
const card = e.target.closest("[data-owner-key]");
if (!card) return;
@@ -4077,6 +4088,14 @@ function _bindActions() {
_refreshCognitionWorkspace();
});
// 认知视图角色列表点击(移动端)
document.getElementById("bme-mobile-cog-owner-list")?.addEventListener("click", (e) => {
const card = e.target.closest("[data-owner-key]");
if (!card) return;
currentCognitionOwnerKey = card.dataset.ownerKey;
_refreshMobileCognitionFull();
});
// Dashboard 跳转认知视图
document.getElementById("bme-cognition-jump-to-view")?.addEventListener("click", () => {
_switchTab("dashboard");
@@ -9809,6 +9828,8 @@ function _refreshRuntimeStatus() {
const meta = runtimeStatus.meta || "准备就绪";
_setText("bme-status-text", text);
_setText("bme-status-meta", meta);
_setText("bme-mobile-status-text", text);
_setText("bme-mobile-status-meta", meta);
_setText("bme-panel-status", text);
_renderCloudStorageModeStatus(_getSettings?.() || {}, _getGraphPersistenceSnapshot());
_refreshGraphAvailabilityState();