mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 14:55:55 +00:00
126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
import { useT } from '../../lib/i18n/I18nContext';
|
|
|
|
interface MemoryStatsBarProps {
|
|
totalDocs: number;
|
|
totalFiles: number;
|
|
totalNamespaces: number;
|
|
totalRelations: number;
|
|
totalSessions: number | null;
|
|
totalTokens: number | null;
|
|
/** Estimated storage in bytes (sum of document content lengths). */
|
|
estimatedStorageBytes: number;
|
|
/** Unix-epoch seconds of the oldest document. */
|
|
oldestDocTimestamp: number | null;
|
|
/** Unix-epoch seconds of the newest document. */
|
|
newestDocTimestamp: number | null;
|
|
docsToday: number;
|
|
loading?: boolean;
|
|
}
|
|
|
|
function formatBytes(bytes: number): string {
|
|
if (bytes === 0) return '0 B';
|
|
const units = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
const value = bytes / Math.pow(1024, i);
|
|
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[i]}`;
|
|
}
|
|
|
|
function formatTimeAgo(epochSeconds: number): string {
|
|
const now = Date.now() / 1000;
|
|
const diff = now - epochSeconds;
|
|
if (diff < 60) return 'just now';
|
|
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
if (diff < 2592000) return `${Math.floor(diff / 86400)}d ago`;
|
|
if (diff < 31536000) return `${Math.floor(diff / 2592000)}mo ago`;
|
|
return `${(diff / 31536000).toFixed(1)}y ago`;
|
|
}
|
|
|
|
function formatNumber(value: number): string {
|
|
return new Intl.NumberFormat().format(value);
|
|
}
|
|
|
|
export function MemoryStatsBar(props: MemoryStatsBarProps) {
|
|
const { t } = useT();
|
|
const {
|
|
totalDocs,
|
|
totalFiles,
|
|
totalNamespaces,
|
|
totalRelations,
|
|
totalSessions,
|
|
totalTokens,
|
|
estimatedStorageBytes,
|
|
oldestDocTimestamp,
|
|
newestDocTimestamp,
|
|
docsToday,
|
|
loading,
|
|
} = props;
|
|
|
|
const stats = [
|
|
{
|
|
label: t('stats.storage'),
|
|
value: estimatedStorageBytes > 0 ? formatBytes(estimatedStorageBytes) : '--',
|
|
sub: totalFiles > 0 ? `${formatNumber(totalFiles)} ${t('stats.files')}` : undefined,
|
|
color: 'text-primary-500',
|
|
},
|
|
{
|
|
label: t('stats.documents'),
|
|
value: formatNumber(totalDocs),
|
|
sub: docsToday > 0 ? `+${docsToday} ${t('stats.today')}` : undefined,
|
|
color: 'text-emerald-600 dark:text-emerald-300',
|
|
},
|
|
{
|
|
label: t('stats.namespaces'),
|
|
value: formatNumber(totalNamespaces),
|
|
sub: undefined,
|
|
color: 'text-amber-600 dark:text-amber-300',
|
|
},
|
|
{
|
|
label: t('stats.relations'),
|
|
value: formatNumber(totalRelations),
|
|
sub: undefined,
|
|
color: 'text-lavender-600 dark:text-lavender-300',
|
|
},
|
|
{
|
|
label: t('stats.firstMemory'),
|
|
value: oldestDocTimestamp ? formatTimeAgo(oldestDocTimestamp) : '--',
|
|
sub: newestDocTimestamp
|
|
? `${t('stats.latest')}: ${formatTimeAgo(newestDocTimestamp)}`
|
|
: undefined,
|
|
color: 'text-sky-600 dark:text-sky-300',
|
|
},
|
|
{
|
|
label: t('stats.sessions'),
|
|
value: totalSessions !== null ? formatNumber(totalSessions) : '--',
|
|
sub: totalTokens !== null ? `${formatNumber(totalTokens)} ${t('stats.tokens')}` : undefined,
|
|
color: 'text-rose-600 dark:text-rose-300',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
|
|
{stats.map(stat => (
|
|
<div
|
|
key={stat.label}
|
|
className="rounded-xl border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/60 p-3 transition-colors hover:bg-stone-100 dark:hover:bg-neutral-800 dark:bg-neutral-800">
|
|
<div className="text-[11px] uppercase tracking-wide text-stone-500 dark:text-neutral-400 mb-1">
|
|
{stat.label}
|
|
</div>
|
|
<div className={`text-xl font-semibold ${stat.color}`}>
|
|
{loading ? (
|
|
<div className="h-7 w-16 rounded bg-stone-200 dark:bg-neutral-800 animate-pulse" />
|
|
) : (
|
|
stat.value
|
|
)}
|
|
</div>
|
|
{stat.sub && (
|
|
<div className="text-[11px] text-stone-500 dark:text-neutral-400 mt-0.5">
|
|
{stat.sub}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|