mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
d208f2c2c0
- chat-messages: match tool rows by overlapping query/context/preview values so preview-first `tool.progress` rows reliably adopt later stable-id `tool.start` payloads instead of spawning ghost rows or mis-merging parallel same-name calls; preserve prior args/result across phases. - tui_gateway: emit full args + parsed result on `tool.start` / `tool.complete`, drop redundant `tool.started` re-emit from `tool.progress`. - electron/main: prefer SOURCE_REPO_ROOT before PATH `hermes` in dev so local backend edits actually run; split hardening helpers into `electron/hardening.cjs` with tests. - thread/tool UI: one-shot enter animation keyed by stable ids, braille spinner for running rows, Cursor-like disclosure rows, drill-down + duration/count formatting via new tool-fallback-model. - composer: extract `text-utils`, drop liquid-glass overrides. - right-rail: split preview-pane into preview-console / preview-file. - runtime: incremental external-store runtime + runtime-readiness gate; onboarding store + tests; route-resume hook test. - regression tests for live tool reconciliation (parallel tools, id-less progress, preview-first rows, structured args/results).
63 lines
1.6 KiB
TypeScript
63 lines
1.6 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')}`
|
|
}
|
|
|
|
export function useElapsedSeconds(active = true, timerKey?: string): number {
|
|
const start = useRef(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 = startedAt(timerKey)
|
|
lastKey.current = timerKey
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!active) {
|
|
return
|
|
}
|
|
|
|
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, timerKey])
|
|
|
|
return elapsed
|
|
}
|
|
|
|
export function __resetElapsedTimerRegistryForTests() {
|
|
startedAtByKey.clear()
|
|
}
|