feat(minecraft): resizable debug dashboard
This commit is contained in:
@@ -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())
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user