amanpay / web /src /components /FederatedStatusCard.tsx
MHamdan's picture
CI deploy d3d1cf1
0f4d9ea verified
Raw
History Blame Contribute Delete
2.81 kB
import { useEffect, useState } from 'react'
import { useI18n } from '../i18n'
import { Callout, Spinner } from './ui'
import { federatedStatus, type FederatedStatus } from '../api/ai'
/** Honest, non-authoritative view of the SIMULATED federated-learning demo (PR C3/C4).
* Reports synthetic-server-simulation status, secure-aggregation DESIGN_ONLY, DP SIMULATED with an
* exhausted-budget warning, and no payment authority. Never claims "data stays on your device". */
export function FederatedStatusCard({ detailed = false }: { detailed?: boolean }) {
const { t } = useI18n()
const [status, setStatus] = useState<FederatedStatus | null>(null)
const [loading, setLoading] = useState(true)
const [err, setErr] = useState(false)
useEffect(() => {
const c = new AbortController()
federatedStatus(c.signal)
.then(setStatus)
.catch(() => setErr(true))
.finally(() => setLoading(false))
return () => c.abort()
}, [])
if (loading) return <Spinner label={t('common.loading')} />
if (err || !status) return <Callout tone="warn">{t('fed.unavailable')}</Callout>
const dp = status.differential_privacy_detail
const rows: [string, string][] = [
['client_type', status.client_type],
['federation', status.federation],
['secure_aggregation', status.secure_aggregation],
['differential_privacy', status.differential_privacy],
['real_device_training', status.real_device_training],
['native_local_only_training', status.native_local_only_training],
]
return (
<div className="federated-status" data-testid="federated-status">
<ul className="status-list">
{rows.map(([key, value]) => (
<li key={key}>
<span>{t(`federated.label.${key}` as never)}</span>
<span className="badge tone-info mono" data-testid={`fed-label-${key}`}>{value}</span>
</li>
))}
</ul>
{dp?.budget_exhausted && (
<Callout tone="warn">
<span data-testid="dp-exhausted">{t('fed.dp.exhausted')}</span>
</Callout>
)}
<p className="muted small" data-testid="fed-no-authority">{t('federated.noAuthority')}</p>
{detailed && (
<>
<p className="muted small" data-testid="fed-synthetic">{t('fed.syntheticClients')}</p>
{status.aggregators && status.aggregators.length > 0 && (
<p className="muted small">{t('fed.aggregators')}: <span className="mono">{status.aggregators.join(', ')}</span></p>
)}
<p className="muted small">{t('fed.candidate')}: <span className="mono">{status.candidate_status}</span>
{' · '}<span data-testid="fed-not-promotable">{t('fed.notPromotable')}</span></p>
<p className="muted small">{status.disclaimer}</p>
</>
)}
</div>
)
}