Skip to content

Commit e1556aa

Browse files
localai-botmudler
andauthored
fix(react-ui): make agent chat timestamps format-agnostic (#9867) (#10290)
fix(agents): make React agent chat timestamps format-agnostic The agent SSE bridge emits the json_message timestamp in three different encodings depending on deploy mode: an RFC3339 string (standalone agent pool), Unix milliseconds (local dispatcher), and Unix nanoseconds (the older NATS path). The React AgentChat handler passed data.timestamp straight through, so the standalone string and any numeric value outside the millisecond range rendered as "Invalid Timestamp" or a constant epoch-ish time. Add a small pure helper, normalizeTimestampMs, that accepts an RFC3339 string or a numeric epoch in s/ms/us/ns and returns JS milliseconds, falling back to Date.now() on null/empty/unparseable input. Use it in the json_message handler so the rendered time is correct regardless of which backend path produced it. Fixes #9867 Assisted-by: claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 53cbb57 commit e1556aa

2 files changed

Lines changed: 24 additions & 3 deletions

File tree

core/http/react-ui/src/pages/AgentChat.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import CanvasPanel from '../components/CanvasPanel'
88
import ResourceCards from '../components/ResourceCards'
99
import ConfirmDialog from '../components/ConfirmDialog'
1010
import { useAgentChat } from '../hooks/useAgentChat'
11-
import { relativeTime } from '../utils/format'
11+
import { relativeTime, normalizeTimestampMs } from '../utils/format'
1212
import { copyToClipboard } from '../utils/clipboard'
1313

1414
function getLastMessagePreview(conv) {
@@ -139,8 +139,9 @@ export default function AgentChat() {
139139
id: nextId(),
140140
sender,
141141
content: data.content || data.message || '',
142-
// Backend sends Unix milliseconds (see core/services/agents events).
143-
timestamp: data.timestamp || Date.now(),
142+
// Backend timestamp encoding varies by deploy mode (RFC3339 string,
143+
// Unix ms, or Unix ns); normalize to JS milliseconds.
144+
timestamp: normalizeTimestampMs(data.timestamp),
144145
}
145146
if (data.metadata && Object.keys(data.metadata).length > 0) {
146147
msg.metadata = data.metadata

core/http/react-ui/src/utils/format.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ export function percentColor(pct) {
1212
return 'var(--color-success)'
1313
}
1414

15+
// normalizeTimestampMs converts a timestamp emitted by the backend into JS
16+
// milliseconds, regardless of its encoding. The agent SSE bridge emits the
17+
// json_message timestamp in three different shapes depending on deploy mode:
18+
// an RFC3339 string (standalone agent pool), Unix milliseconds (local
19+
// dispatcher), or Unix nanoseconds (older NATS path). A numeric value is
20+
// classified by magnitude (s / ms / us / ns) so any of them yields a sane
21+
// epoch. Falls back to Date.now() for null/empty/unparseable input.
22+
export function normalizeTimestampMs(ts) {
23+
if (ts === null || ts === undefined || ts === '') return Date.now()
24+
if (typeof ts === 'string') {
25+
const parsed = Date.parse(ts)
26+
return Number.isNaN(parsed) ? Date.now() : parsed
27+
}
28+
if (typeof ts !== 'number' || !Number.isFinite(ts)) return Date.now()
29+
if (ts > 1e17) return Math.floor(ts / 1e6) // nanoseconds
30+
if (ts > 1e14) return Math.floor(ts / 1e3) // microseconds
31+
if (ts > 1e11) return ts // milliseconds
32+
return ts * 1000 // seconds
33+
}
34+
1535
export function formatTimestamp(ts) {
1636
if (!ts) return '-'
1737
const d = new Date(ts)

0 commit comments

Comments
 (0)