Files
memby/admin-ui/src/pages/Recommendations.tsx
T
2026-08-14 09:40:03 +12:00

104 lines
3.9 KiB
TypeScript

import { useState } from 'react';
import { Link } from 'react-router-dom';
import { api } from '../api/client';
import { useAction } from '../lib/hooks';
import { useGateway } from '../lib/gateway';
import { useToast } from '../lib/toast';
import { num, when } from '../lib/format';
import { Banner, Button, Card, Confirm, Loading, PageHead, Tiles } from '../components/ui';
export function RecommendationsPage() {
const { status, error, loading, reload } = useGateway();
const { wrap } = useToast();
const { busy, run } = useAction();
const [confirming, setConfirming] = useState(false);
const forYou = status?.forYou;
const running = Boolean(status?.forYouRunning);
const act = (action: string, key: string, message: string) =>
run(key, async () => {
await wrap(() => api.post('/admin/api/for-you', { action }), message);
setConfirming(false);
await reload();
});
return (
<>
<PageHead title="For You" intro="The prepared pools personalised rows are drawn from." />
<Banner message={error} />
{loading ? (
<Loading rows={2} />
) : (
<>
<Tiles
tiles={[
{ label: 'Tracearr sessions', value: num(forYou?.tracearrSessions ?? 0), icon: 'play', tone: 'info' },
{ label: 'user profiles', value: num(forYou?.profiles ?? 0), icon: 'people', tone: 'note' },
{ label: 'ranked candidates', value: num(forYou?.candidates ?? 0), icon: 'sparkle', tone: 'note' },
{ label: 'last full import', value: when(forYou?.lastFullImport), small: true, icon: 'clock' },
]}
/>
<Card
title="Pool maintenance"
intro="Prepared pools refresh in the background; these are the manual versions of the same work. A rebuild is safe at any time — televisions read the last finished pool until a new one lands."
icon="sparkle"
tone="note"
footer={
<span className="hint">
{running ? 'For You maintenance running…' : 'Prepared pools normally refresh in the background.'}
</span>
}
>
<div className="row">
<Button
variant="primary"
icon="download"
disabled={running}
busy={busy === 'import'}
onClick={() => void act('incremental-import', 'import', 'Import started.')}
>
Import recent sessions
</Button>
<Button icon="database" disabled={running} onClick={() => setConfirming(true)}>
Full Tracearr backfill
</Button>
<Button
icon="sync"
disabled={running}
busy={busy === 'rebuild'}
onClick={() => void act('rebuild-all', 'rebuild', 'Rebuild started.')}
>
Rebuild all pools
</Button>
</div>
</Card>
<Card
title="Reading a person's scores"
intro="The inspector re-runs the shared weighted scorer over one person's prepared pool, after Emby permission and parental-control filtering, and shows every component and evidence reason behind the order."
icon="search"
tone="info"
actions={<Link to="/admin/inspector">Open the inspector</Link>}
>
<></>
</Card>
</>
)}
{confirming ? (
<Confirm
title="Backfill all Tracearr history?"
body="Every session is re-read and every active user's pool is rebuilt. It is safe at any time — televisions keep reading the last finished pool — but on a long history it takes a while."
confirmLabel="Backfill"
busy={busy === 'full'}
onConfirm={() => void act('full-import', 'full', 'Backfill started.')}
onCancel={() => setConfirming(false)}
/>
) : null}
</>
);
}