mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
The tail "Hermes is thinking" indicator resets on every flush, but its timer never did: with no timer key, useElapsedSeconds anchors to mount, and the indicator mounts with the assistant message. A stall two minutes into a turn therefore claimed two minutes of silence instead of the two seconds that had actually passed. Give the hook an explicit epoch and hand it the timestamp of the activity the quiet spell followed. Compaction still counts from the turn's start, which is the span it owns.
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
|
|
// Module-level registry so timers survive component unmount/remount (e.g.
|
|
// when a tool row scrolls out and back). Keyed by caller-supplied timerKey;
|
|
// anonymous timers (no key) start fresh each mount.
|
|
const startedAtByKey = new Map<string, number>()
|
|
|
|
function startedAt(key?: string): number {
|
|
if (!key) {
|
|
return Date.now()
|
|
}
|
|
|
|
const existing = startedAtByKey.get(key)
|
|
|
|
if (existing !== undefined) {
|
|
return existing
|
|
}
|
|
|
|
const now = Date.now()
|
|
startedAtByKey.set(key, now)
|
|
|
|
return now
|
|
}
|
|
|
|
export function formatElapsed(seconds: number): string {
|
|
if (seconds < 60) {
|
|
return `${seconds}s`
|
|
}
|
|
|
|
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`
|
|
}
|
|
|
|
/**
|
|
* Seconds since the timer's origin, reported once a second while `active`.
|
|
*
|
|
* Origin, in order: an explicit `since` timestamp, else the `timerKey`'s
|
|
* registry entry (survives unmount/remount), else mount time. Pass `since` when
|
|
* the thing being measured started at a moment the caller knows and that moment
|
|
* isn't the mount — otherwise an anonymous timer reports the component's age,
|
|
* which is only the same number by accident.
|
|
*/
|
|
export function useElapsedSeconds(active = true, timerKey?: string, since?: number): number {
|
|
const start = useRef(since ?? startedAt(timerKey))
|
|
const lastKey = useRef(timerKey)
|
|
const [elapsed, setElapsed] = useState(() => Math.max(0, Math.floor((Date.now() - start.current) / 1000)))
|
|
|
|
if (lastKey.current !== timerKey) {
|
|
start.current = since ?? startedAt(timerKey)
|
|
lastKey.current = timerKey
|
|
}
|
|
|
|
// eslint-disable-next-line no-restricted-syntax -- legitimate non-atom ref write (see eslint rule comment)
|
|
useEffect(() => {
|
|
if (!active) {
|
|
return
|
|
}
|
|
|
|
if (since !== undefined) {
|
|
start.current = since
|
|
} else if (timerKey) {
|
|
start.current = startedAt(timerKey)
|
|
}
|
|
|
|
const tick = () => setElapsed(Math.max(0, Math.floor((Date.now() - start.current) / 1000)))
|
|
tick()
|
|
const id = window.setInterval(tick, 1000)
|
|
|
|
return () => window.clearInterval(id)
|
|
}, [active, since, timerKey])
|
|
|
|
return elapsed
|
|
}
|
|
|
|
export function __resetElapsedTimerRegistryForTests() {
|
|
startedAtByKey.clear()
|
|
}
|