Files
openchamber/packages/ui/src/components/session/SessionActivityDuration.tsx
T

59 lines
2.1 KiB
TypeScript
Raw Normal View History

import React from 'react';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { useDurationTickerNow } from '@/hooks/useDurationTicker';
import {
useSessionActivityStartedAt,
useSessionSettledDurationMs,
} from '@/sync/session-activity-timing';
import { formatSessionActivityDuration } from './sessionActivityDurationFormat';
/** One update per second: the readout is the animation, at 1 fps instead of 60. */
const TICK_MS = 1000;
/**
* Elapsed time of a session's current turn, or of the turn that just finished.
* Colored to match the row's status dot in each state, so the pair reads as one
* indicator rather than two.
*
* Deliberately a leaf. The tick re-renders this span alone rather than the
* session row around it, which is what makes a live counter cheaper than the
* spinner it replaced — that spinner repainted a composited layer per row every
* frame for as long as the session ran.
*/
export const SessionActivityDuration: React.FC<{
sessionId: string;
/** Turn still running (`busy` or `retry`); false renders the settled total. */
running: boolean;
className?: string;
}> = ({ sessionId, running, className }) => {
const { t } = useI18n();
const startedAt = useSessionActivityStartedAt(sessionId);
const settledMs = useSessionSettledDurationMs(sessionId);
const now = useDurationTickerNow(running, TICK_MS);
const durationMs = running ? Math.max(0, now - (startedAt ?? now)) : settledMs;
if (durationMs === undefined) return null;
const label = formatSessionActivityDuration(durationMs, t);
const description = running
? t('sessions.sidebar.session.status.activeFor', { duration: label })
: t('sessions.sidebar.session.status.lastTurnDuration', { duration: label });
return (
<span
className={cn(
'shrink-0 tabular-nums',
// The readout wears its dot's color, so the row reads as one signal:
// primary while the turn runs, info once it is waiting to be read.
running ? 'text-primary' : 'text-[var(--status-info)]',
className,
)}
aria-label={description}
title={description}
>
{label}
</span>
);
};