refactor(dashboard): extract modular Preact component structure
Replace the monolithic single-file dashboards (dashboard.html,
dashboard-preact.html) with a proper modular directory structure:
dashboard/
index.html - Entry point, loads main.js
main.js - App bootstrap, mounts <App> to #root
styles.css - Global styles (dark theme, typography)
components/
App.js - Root component, state management, polling
Header.js - Top bar with refresh/timing info
Sidebar.js - Project tree navigation
SessionCard.js - Individual session card with status/actions
SessionGroup.js - Group sessions by project path
Modal.js - Full conversation viewer overlay
ChatMessages.js - Message list with role styling
MessageBubble.js - Individual message with markdown
QuestionBlock.js - User question input with quick options
EmptyState.js - "No sessions" placeholder
OptionButton.js - Quick response button component
SimpleInput.js - Text input with send button
lib/
preact.js - Preact + htm ESM bundle (CDN shim)
markdown.js - Lightweight markdown-to-HTML renderer
utils/
api.js - fetch wrappers for /api/* endpoints
formatting.js - Time formatting, truncation helpers
status.js - Session status logic, action availability
This structure enables:
- Browser-native ES modules (no build step required)
- Component reuse and isolation
- Easier styling and theming
- IDE support for component navigation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
66
dashboard/utils/formatting.js
Normal file
66
dashboard/utils/formatting.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// Formatting utilities
|
||||
|
||||
export function formatDuration(isoStart) {
|
||||
if (!isoStart) return '';
|
||||
const start = new Date(isoStart);
|
||||
const now = new Date();
|
||||
const mins = Math.max(0, Math.floor((now - start) / 60000));
|
||||
if (mins < 60) return mins + 'm';
|
||||
const hrs = Math.floor(mins / 60);
|
||||
const remainMins = mins % 60;
|
||||
return hrs + 'h ' + remainMins + 'm';
|
||||
}
|
||||
|
||||
export function formatTime(isoTime) {
|
||||
if (!isoTime) return '';
|
||||
const date = new Date(isoTime);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
export function formatTokenCount(value) {
|
||||
if (!Number.isFinite(value)) return '';
|
||||
return Math.round(value).toLocaleString('en-US');
|
||||
}
|
||||
|
||||
export function getContextUsageSummary(usage) {
|
||||
if (!usage || typeof usage !== 'object') return null;
|
||||
|
||||
const current = Number(usage.current_tokens);
|
||||
const windowTokens = Number(usage.window_tokens);
|
||||
const sessionTotal = usage.session_total_tokens != null ? Number(usage.session_total_tokens) : null;
|
||||
const hasCurrent = Number.isFinite(current) && current > 0;
|
||||
const hasWindow = Number.isFinite(windowTokens) && windowTokens > 0;
|
||||
const hasSessionTotal = sessionTotal != null && Number.isFinite(sessionTotal) && sessionTotal > 0;
|
||||
|
||||
if (hasCurrent && hasWindow) {
|
||||
const percent = (current / windowTokens) * 100;
|
||||
return {
|
||||
headline: `${percent >= 10 ? percent.toFixed(0) : percent.toFixed(1)}% ctx`,
|
||||
detail: `${formatTokenCount(current)} / ${formatTokenCount(windowTokens)}`,
|
||||
trail: hasSessionTotal ? `Σ ${formatTokenCount(sessionTotal)}` : '',
|
||||
title: `Context window usage: ${formatTokenCount(current)} / ${formatTokenCount(windowTokens)} tokens`,
|
||||
};
|
||||
}
|
||||
|
||||
if (hasCurrent) {
|
||||
const inputTokens = Number(usage.input_tokens);
|
||||
const outputTokens = Number(usage.output_tokens);
|
||||
const hasInput = Number.isFinite(inputTokens);
|
||||
const hasOutput = Number.isFinite(outputTokens);
|
||||
const ioDetail = hasInput || hasOutput
|
||||
? ` • in ${formatTokenCount(hasInput ? inputTokens : 0)} out ${formatTokenCount(hasOutput ? outputTokens : 0)}`
|
||||
: '';
|
||||
return {
|
||||
headline: 'Ctx usage',
|
||||
detail: `${formatTokenCount(current)} tok${ioDetail}`,
|
||||
trail: '',
|
||||
title: `Token usage: ${formatTokenCount(current)} tokens`,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user