mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
LiveDuration returned a bare string with proportional digits, so the statusbar reflowed every second as the timer ticked. Extract a shared StableText component that renders each character in its own 1ch-wide cell, preventing any digit from shifting the layout — works with the proportional sans font, no need for font-mono. Both LiveDuration (statusbar session/running timers) and ActivityTimerText (tool activity timers) now use StableText, dropping the font-mono + tabular-nums workaround from the latter. Also renames statusbar.ts → statusbar.tsx since LiveDuration now returns JSX.
81 lines
2.2 KiB
TypeScript
81 lines
2.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
|
|
import { StableText } from '@/components/chat/stable-text'
|
|
import { compactNumber } from '@/lib/format'
|
|
import type { UsageStats } from '@/types/hermes'
|
|
|
|
export function formatDuration(elapsedMs: number): string {
|
|
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000))
|
|
const seconds = totalSeconds % 60
|
|
const minutes = Math.floor(totalSeconds / 60) % 60
|
|
const hours = Math.floor(totalSeconds / 3600)
|
|
const ss = String(seconds).padStart(2, '0')
|
|
const mm = String(minutes).padStart(2, '0')
|
|
|
|
return hours > 0 ? `${hours}:${mm}:${ss}` : `${minutes}:${ss}`
|
|
}
|
|
|
|
export function compactPath(path: string, max = 44): string {
|
|
const trimmed = path.trim()
|
|
|
|
if (trimmed.length <= max) {
|
|
return trimmed
|
|
}
|
|
|
|
const segments = trimmed.split('/').filter(Boolean)
|
|
|
|
if (segments.length < 2) {
|
|
return `…${trimmed.slice(-(max - 1))}`
|
|
}
|
|
|
|
const tail = segments.slice(-2).join('/')
|
|
|
|
return tail.length + 2 >= max ? `…${tail.slice(-(max - 1))}` : `…/${tail}`
|
|
}
|
|
|
|
export function contextBar(percent: number | undefined, width = 10): string {
|
|
const bounded = Math.max(0, Math.min(100, percent ?? 0))
|
|
const filled = Math.round((bounded / 100) * width)
|
|
|
|
return `${'█'.repeat(filled)}${'░'.repeat(width - filled)}`
|
|
}
|
|
|
|
export function usageContextLabel(usage: UsageStats): string {
|
|
if (usage.context_max) {
|
|
return `${compactNumber(usage.context_used ?? 0)}/${compactNumber(usage.context_max)}`
|
|
}
|
|
|
|
return usage.total > 0 ? `${compactNumber(usage.total)} tok` : ''
|
|
}
|
|
|
|
export function contextBarLabel(usage: UsageStats): string {
|
|
if (!usage.context_max) {
|
|
return ''
|
|
}
|
|
|
|
const pct = Math.max(0, Math.min(100, Math.round(usage.context_percent ?? 0)))
|
|
|
|
return `[${contextBar(usage.context_percent)}] ${pct}%`
|
|
}
|
|
|
|
export function LiveDuration({ since }: { since: number | null | undefined }) {
|
|
const [now, setNow] = useState(() => Date.now())
|
|
|
|
useEffect(() => {
|
|
if (!since) {
|
|
return
|
|
}
|
|
|
|
const tick = () => setNow(Date.now())
|
|
tick()
|
|
const timer = window.setInterval(tick, 1000)
|
|
|
|
return () => window.clearInterval(timer)
|
|
}, [since])
|
|
|
|
if (!since) {
|
|
return null
|
|
}
|
|
|
|
return <StableText>{formatDuration(now - since)}</StableText>
|
|
}
|