feat: add two-column admin panel with LLM analytics display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-10 13:28:01 +00:00
parent 7b8ba95189
commit 28ce0822e6
+178 -6
View File
@@ -1,4 +1,4 @@
import type { TunableConstants, TunableKey } from '@dflike/shared';
import type { TunableConstants, TunableKey, LlmStatsData } from '@dflike/shared';
const LABELS: Record<TunableKey, string> = {
TICK_RATE: 'Tick Rate (ticks/sec)',
@@ -44,6 +44,10 @@ export class AdminPanel {
onAuth: ((password: string) => void) | null = null;
onUpdate: ((key: TunableKey, value: number) => void) | null = null;
onReset: (() => void) | null = null;
onRequestLlmStats: (() => void) | null = null;
onToggleLlmTracking: ((enabled: boolean) => void) | null = null;
private statsColumn: HTMLDivElement | null = null;
private trackingToggle: HTMLInputElement | null = null;
constructor() {
this.container = document.createElement('div');
@@ -118,9 +122,9 @@ export class AdminPanel {
this.editorView = document.createElement('div');
this.editorView.style.cssText = `
display: none;
flex-direction: column;
gap: 4px;
overflow-y: auto;
flex-direction: row;
gap: 8px;
overflow: hidden;
flex: 1;
`;
@@ -152,10 +156,130 @@ export class AdminPanel {
}
}
requestStatsRefresh(): void {
if (this.authenticated) {
this.onRequestLlmStats?.();
}
}
updateLlmStats(data: LlmStatsData): void {
if (!this.statsColumn) return;
if (this.trackingToggle) {
this.trackingToggle.checked = data.trackingEnabled;
}
// Remove everything after the toggle row
while (this.statsColumn.children.length > 1) {
this.statsColumn.removeChild(this.statsColumn.lastChild!);
}
const fmt = (n: number) => n < 0.01 ? `$${n.toFixed(6)}` : `$${n.toFixed(4)}`;
if (data.types.length === 0) {
const empty = document.createElement('div');
empty.style.cssText = 'color: #555; font-size: 8px; padding: 8px 0; text-align: center;';
empty.textContent = 'No LLM calls recorded yet';
this.statsColumn.appendChild(empty);
return;
}
for (const type of data.types) {
const row = document.createElement('div');
row.style.cssText = `
display: flex;
flex-direction: column;
gap: 1px;
padding: 4px 0;
border-bottom: 1px solid #1c1450;
`;
const header = document.createElement('div');
header.style.cssText = 'display: flex; justify-content: space-between; align-items: baseline;';
const nameEl = document.createElement('span');
nameEl.style.cssText = 'color: #e0d0b0; font-size: 9px; font-weight: bold;';
nameEl.textContent = type.label;
const countEl = document.createElement('span');
countEl.style.cssText = 'color: #8878a8; font-size: 8px;';
countEl.textContent = `${type.count} calls`;
header.appendChild(nameEl);
header.appendChild(countEl);
row.appendChild(header);
const costLine = document.createElement('div');
costLine.style.cssText = 'color: #a0a0c0; font-size: 7px;';
costLine.textContent = `min ${fmt(type.minCost)} / avg ${fmt(type.avgCost)} / max ${fmt(type.maxCost)}`;
row.appendChild(costLine);
const totalLine = document.createElement('div');
totalLine.style.cssText = 'display: flex; justify-content: space-between; font-size: 7px;';
const totalCostEl = document.createElement('span');
totalCostEl.style.cssText = 'color: #a0a0c0;';
totalCostEl.textContent = `total: ${fmt(type.totalCost)}`;
const ratesEl = document.createElement('span');
ratesEl.style.cssText = `color: ${type.failPct > 0 ? '#ff6666' : '#a0a0c0'};`;
ratesEl.textContent = `retry ${type.retryPct}% / fail ${type.failPct}%`;
totalLine.appendChild(totalCostEl);
totalLine.appendChild(ratesEl);
row.appendChild(totalLine);
this.statsColumn.appendChild(row);
}
// Totals row
const totalsRow = document.createElement('div');
totalsRow.style.cssText = `
display: flex;
flex-direction: column;
gap: 1px;
padding: 6px 0;
border-top: 2px solid #8878a8;
margin-top: 4px;
`;
const totalsHeader = document.createElement('div');
totalsHeader.style.cssText = 'display: flex; justify-content: space-between; align-items: baseline;';
const totalsLabel = document.createElement('span');
totalsLabel.style.cssText = 'color: #e0d0b0; font-size: 9px; font-weight: bold;';
totalsLabel.textContent = 'TOTAL';
const totalsCalls = document.createElement('span');
totalsCalls.style.cssText = 'color: #8878a8; font-size: 8px;';
totalsCalls.textContent = `${data.totalCount} calls`;
totalsHeader.appendChild(totalsLabel);
totalsHeader.appendChild(totalsCalls);
totalsRow.appendChild(totalsHeader);
const totalsCost = document.createElement('div');
totalsCost.style.cssText = 'color: #a0a0c0; font-size: 7px;';
totalsCost.textContent = `total cost: ${fmt(data.totalCost)} / retries: ${data.totalRetries} / failures: ${data.totalFailures}`;
totalsRow.appendChild(totalsCost);
this.statsColumn.appendChild(totalsRow);
}
private buildEditor(constants: TunableConstants): void {
this.editorView.innerHTML = '';
this.inputs.clear();
// Left column: existing constants editor
const leftCol = document.createElement('div');
leftCol.style.cssText = `
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
`;
const debounceTimers = new Map<TunableKey, ReturnType<typeof setTimeout>>();
for (const key of Object.keys(LABELS) as TunableKey[]) {
@@ -219,7 +343,7 @@ export class AdminPanel {
row.appendChild(labelRow);
row.appendChild(inputEl);
this.editorView.appendChild(row);
leftCol.appendChild(row);
}
// Reset button
@@ -239,6 +363,54 @@ export class AdminPanel {
resetBtn.addEventListener('click', () => {
this.onReset?.();
});
this.editorView.appendChild(resetBtn);
leftCol.appendChild(resetBtn);
// Right column: LLM analytics
const rightCol = document.createElement('div');
rightCol.style.cssText = `
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
`;
this.statsColumn = rightCol;
// Tracking toggle
const toggleRow = document.createElement('div');
toggleRow.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 0;
border-bottom: 1px solid #1c1450;
flex-shrink: 0;
`;
const toggleLabel = document.createElement('span');
toggleLabel.style.cssText = 'color: #a0a0c0; font-size: 8px;';
toggleLabel.textContent = 'LLM Tracking';
this.trackingToggle = document.createElement('input');
this.trackingToggle.type = 'checkbox';
this.trackingToggle.checked = true;
this.trackingToggle.style.cssText = 'accent-color: #8878a8; cursor: pointer;';
this.trackingToggle.addEventListener('change', () => {
this.onToggleLlmTracking?.(this.trackingToggle!.checked);
});
toggleRow.appendChild(toggleLabel);
toggleRow.appendChild(this.trackingToggle);
rightCol.appendChild(toggleRow);
// Stats placeholder
const statsPlaceholder = document.createElement('div');
statsPlaceholder.style.cssText = 'color: #555; font-size: 8px; padding: 8px 0; text-align: center;';
statsPlaceholder.textContent = 'Loading stats...';
rightCol.appendChild(statsPlaceholder);
this.editorView.appendChild(leftCol);
this.editorView.appendChild(rightCol);
}
}