feat(minecraft): resizable debug dashboard

This commit is contained in:
Rin
2026-02-18 11:12:03 +08:00
committed by Neko Ayaka
parent b9c2569743
commit 7fa307592e
3 changed files with 245 additions and 42 deletions
+89
View File
@@ -895,9 +895,95 @@ class TimelinePanel {
// Application
// =============================================================================
// =============================================================================
// Layout Manager (Resizing & Maximizing)
// =============================================================================
class LayoutManager {
constructor() {
this.root = document.documentElement
this.activeSplitter = null
this.startPos = 0
this.startSize = 0
}
init() {
this.setupSplitters()
this.setupMaximizeButtons()
}
setupSplitters() {
const splitters = [
{ id: 'splitter-v1', var: '--col-left', type: 'v' },
{ id: 'splitter-h1', var: '--row-1', type: 'h' },
{ id: 'splitter-h2', var: '--row-2', type: 'h' },
{ id: 'splitter-h3', var: '--row-3', type: 'h' },
]
splitters.forEach((config) => {
const el = document.getElementById(config.id)
if (!el)
return
el.addEventListener('mousedown', (e) => {
this.activeSplitter = { el, ...config }
this.startPos = config.type === 'v' ? e.clientX : e.clientY
const style = getComputedStyle(this.root)
this.startSize = Number.parseInt(style.getPropertyValue(config.var), 10)
el.classList.add('dragging')
document.body.style.cursor = config.type === 'v' ? 'col-resize' : 'row-resize'
document.body.style.userSelect = 'none' // Prevent text selection
})
})
document.addEventListener('mousemove', e => this.handleDrag(e))
document.addEventListener('mouseup', () => this.handleDragEnd())
}
handleDrag(e) {
if (!this.activeSplitter)
return
const currentPos = this.activeSplitter.type === 'v' ? e.clientX : e.clientY
const delta = currentPos - this.startPos
const newSize = Math.max(50, this.startSize + delta) // Min 50px
this.root.style.setProperty(this.activeSplitter.var, `${newSize}px`)
}
handleDragEnd() {
if (!this.activeSplitter)
return
this.activeSplitter.el.classList.remove('dragging')
this.activeSplitter = null
document.body.style.cursor = ''
document.body.style.userSelect = ''
}
setupMaximizeButtons() {
document.querySelectorAll('.maximize-btn').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation() // Prevent bubbling
const panel = btn.closest('.panel')
if (!panel)
return
panel.classList.toggle('maximized')
const isMaximized = panel.classList.contains('maximized')
btn.textContent = isMaximized ? '✕' : '⤢'
btn.title = isMaximized ? 'Restore' : 'Maximize'
})
})
}
}
class DebugApp {
constructor() {
this.client = new DebugClient()
this.layoutManager = new LayoutManager()
this.queuePanel = new QueuePanel(this.client)
this.reflexPanel = new ReflexPanel(this.client)
this.blackboardPanel = new BlackboardPanel(this.client)
@@ -919,6 +1005,9 @@ class DebugApp {
}
init() {
// Initialize layout
this.layoutManager.init()
// Initialize all panels
Object.values(this.panels).forEach(panel => panel.init())
+34 -7
View File
@@ -47,7 +47,10 @@
<section id="queue-section" class="panel">
<div class="panel-header">
<h2>Queue & Processing</h2>
<span class="panel-badge" id="queue-count">0</span>
<div class="panel-controls">
<span class="panel-badge" id="queue-count">0</span>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
<div id="queue-list" class="queue-list"></div>
@@ -63,7 +66,10 @@
<section id="reflex-section" class="panel">
<div class="panel-header">
<h2>Reflex State</h2>
<span class="panel-badge" id="reflex-mode">idle</span>
<div class="panel-controls">
<span class="panel-badge" id="reflex-mode">idle</span>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
<div class="kv-row">
@@ -93,7 +99,10 @@
<section id="blackboard-section" class="panel">
<div class="panel-header">
<h2>Blackboard</h2>
<button id="blackboard-copy-btn" class="icon-btn" title="Copy JSON">📋</button>
<div class="panel-controls">
<button id="blackboard-copy-btn" class="icon-btn" title="Copy JSON">📋</button>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
<pre id="blackboard-json" class="json-display">{}</pre>
@@ -101,6 +110,9 @@
</section>
</div>
<!-- Splitter V1 (Left vs Logs/Timeline/etc) -->
<div class="splitter-v" id="splitter-v1"></div>
<!-- Right Column: Logs -->
<section id="logs-section" class="panel">
<div class="panel-header">
@@ -118,6 +130,7 @@
<input type="checkbox" id="auto-scroll" checked />
Auto-scroll
</label>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
@@ -125,8 +138,11 @@
</div>
</section>
<!-- Splitter H1 (Logs vs Timeline) -->
<div class="splitter-h" id="splitter-h1"></div>
<!-- Timeline: Event Trace View -->
<section id="timeline-section" class="panel panel-full-width">
<section id="timeline-section" class="panel">
<div class="panel-header">
<h2>Event Timeline</h2>
<div class="panel-controls">
@@ -137,6 +153,7 @@
<option value="signal:">Signals</option>
</select>
<button id="timeline-clear-btn" class="icon-btn" title="Clear timeline">🗑️</button>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
@@ -153,23 +170,33 @@
</div>
</section>
<!-- Splitter H2 (Timeline vs LLM) -->
<div class="splitter-h" id="splitter-h2"></div>
<!-- Bottom Row: LLM Traces -->
<section id="llm-section" class="panel panel-full-width">
<section id="llm-section" class="panel">
<div class="panel-header">
<h2>LLM Traces</h2>
<span class="panel-badge" id="llm-count">0</span>
<div class="panel-controls">
<span class="panel-badge" id="llm-count">0</span>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
<div id="llm-list" class="llm-list"></div>
</div>
</section>
<!-- Splitter H3 (LLM vs Saliency) -->
<div class="splitter-h" id="splitter-h3"></div>
<!-- Bottom Row: Saliency -->
<section id="saliency-section" class="panel panel-full-width">
<section id="saliency-section" class="panel">
<div class="panel-header">
<h2>Saliency Heatmap</h2>
<div class="panel-controls">
<span>Slot: <span id="saliency-slot" class="panel-badge">0</span></span>
<button class="icon-btn maximize-btn" title="Toggle maximize"></button>
</div>
</div>
<div class="panel-content">
+122 -35
View File
@@ -17,6 +17,12 @@
--accent-error: #f85149;
--font-mono: 'Consolas', 'Monaco', 'Courier New', monospace;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
/* Resizable Grid Defaults */
--col-left: 350px;
--row-1: 300px;
--row-2: 200px;
--row-3: 200px;
}
/* Base Styles */
@@ -138,9 +144,11 @@ button:hover,
/* Main Grid Layout */
#main-grid {
display: grid;
grid-template-columns: 350px 1fr;
grid-template-rows: minmax(300px, 1fr) minmax(200px, 0.6fr) minmax(200px, 0.4fr);
gap: 8px;
/* Columns: Left Panel | Splitter | Right Content */
grid-template-columns: var(--col-left) 8px 1fr;
/* Rows: Logs | Splitter | Timeline | Splitter | LLM | Splitter | Saliency */
grid-template-rows: var(--row-1) 8px var(--row-2) 8px var(--row-3) 8px 1fr;
gap: 0;
padding: 8px;
height: calc(100vh - 48px);
overflow: hidden;
@@ -150,33 +158,109 @@ button:hover,
display: flex;
flex-direction: column;
gap: 8px;
grid-column: 1 / 2;
grid-row: 1 / -1;
/* Span full height */
overflow: hidden;
padding-right: 4px;
/* Padding for visual separation */
}
/* Splitters */
.splitter-v {
grid-row: 1 / -1;
grid-column: 2 / 3;
cursor: col-resize;
background-color: transparent;
width: 8px;
z-index: 10;
position: relative;
}
.splitter-v::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 3px;
width: 2px;
background-color: var(--border-color);
transition: background-color 0.2s;
}
.splitter-v:hover::after,
.splitter-v.dragging::after {
background-color: var(--accent-info);
}
.splitter-h {
grid-column: 3 / 4;
cursor: row-resize;
background-color: transparent;
height: 8px;
z-index: 10;
position: relative;
}
.splitter-h::after {
content: '';
position: absolute;
left: 0;
right: 0;
top: 3px;
height: 2px;
background-color: var(--border-color);
transition: background-color 0.2s;
}
.splitter-h:hover::after,
.splitter-h.dragging::after {
background-color: var(--accent-info);
}
/* Grid Sections */
#logs-section {
grid-column: 3 / 4;
grid-row: 1 / 2;
}
#queue-section {
flex: 0 0 auto;
max-height: 40%;
min-height: 180px;
}
#blackboard-section {
flex: 1;
min-height: 0;
}
#logs-section {
grid-row: 1 / 2;
#splitter-v1 {
grid-column: 2 / 3;
grid-row: 1 / -1;
}
#splitter-h1 {
grid-column: 3 / 4;
grid-row: 2 / 3;
}
#timeline-section {
grid-column: 3 / 4;
grid-row: 3 / 4;
min-height: 0;
}
#splitter-h2 {
grid-column: 3 / 4;
grid-row: 4 / 5;
}
#llm-section {
grid-row: 2 / 3;
grid-column: 1 / 3;
grid-column: 3 / 4;
grid-row: 5 / 6;
min-height: 0;
}
#splitter-h3 {
grid-column: 3 / 4;
grid-row: 6 / 7;
}
#saliency-section {
grid-row: 3 / 4;
grid-column: 1 / 3;
grid-column: 3 / 4;
grid-row: 7 / 8;
min-height: 0;
}
/* Panel Styles */
@@ -232,6 +316,24 @@ button:hover,
border-radius: 3px;
}
.maximize-btn {
font-size: 14px;
margin-left: 0.5rem;
}
.maximized {
position: fixed !important;
top: 48px !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
z-index: 1000 !important;
margin: 0 !important;
width: auto !important;
height: auto !important;
border-radius: 0 !important;
}
.panel-controls input[type="search"] {
width: 200px;
}
@@ -557,23 +659,8 @@ button:hover,
Timeline Panel Styles
============================================================================= */
#timeline-section {
grid-row: 2 / 3;
grid-column: 1 / 3;
}
#llm-section {
grid-row: 3 / 4;
}
#saliency-section {
grid-row: 4 / 5;
}
/* Adjust grid for timeline */
#main-grid {
grid-template-rows: minmax(250px, 1fr) minmax(180px, 0.5fr) minmax(180px, 0.4fr) minmax(150px, 0.3fr);
}
/* REMOVED: Managed in main-grid definition now */
.timeline-container {
flex: 1;