'use client'; import {useEffect,useState} from 'react'; import {Input} from '@/components/ui/input'; import type {summarize} from '@/core/realm-insights'; type Report=ReturnType; type Activity={name:string;zone:string;action:string;label:string;detail:string;observedAt:number|null;lastProgressAt:number|null;recoveries:{at:number;detail:string}[]}; type Insights={collectedAt:number|null;startedAt:number|null;nextReportAt:number;current:Report;reports:Report[];activity:Activity[]}; const stamp=(at:number|null)=>at?new Date(at*1000).toLocaleString():'Not yet observed'; const metrics:Record={levels:'Levels gained',deaths:'Verified deaths',arrivals:'First arrivals',conversations:'Bot exchanges',bonds:'New positive bonds',parties:'Party invitations',recruits:'Guild admissions',guild:'Guild goal progress',recoveries:'Recovery attempts'}; export function RealmInsights({mode}:{mode:string}){ const [data,setData]=useState(null),[error,setError]=useState(''),[selected,setSelected]=useState('current'),[query,setQuery]=useState(''),[attention,setAttention]=useState(false); useEffect(()=>{let stopped=false;const poll=async()=>{try{const r=await fetch('/api/realm-insights');if(!r.ok)throw new Error('Insights temporarily unavailable');const d=await r.json() as Insights;if(!stopped){setData(d);setError('');}}catch(e){if(!stopped)setError(e instanceof Error?e.message:'Unable to load insights');}};void poll();const timer=setInterval(poll,15000);return()=>{stopped=true;clearInterval(timer);};},[]); if(!data)return
{error||'Loading realm observations…'}
; const report=selected==='current'?data.current:data.reports.find(r=>String(r.start)===selected)??data.current; const rows=data.activity.filter(b=>`${b.name} ${b.zone} ${b.label}`.toLowerCase().includes(query.toLowerCase())&&(!attention||['Possible stall','Telemetry delayed'].includes(b.label))); return <>

Last collection: {stamp(data.collectedAt)} · Refreshes every 15 seconds.

{error&&

{error}; showing the last available observations.

}{(!data.collectedAt||Date.now()/1000-data.collectedAt>180)&&

Collection is pending or delayed. Report totals may be incomplete.

} {mode==='Nightly Report'?<>

{stamp(report.start)} — {selected==='current'?'now':stamp(report.end)}

Reports close daily at 06:00 UTC. Next close: {stamp(data.nextReportAt)}. Reports are kept for 30 days.

{report.partial?'Partial period · ':''}Observations since {stamp(report.observedSince)} · {report.samples} minute samples. Gaps can miss activity; these are observed changes, not a complete game log.

{Object.entries(metrics).map(([key,label])=>
{label}
{report.totals[key]??0}
)}

Realm highlights

{report.highlights.length?report.highlights.map(e=>
{e.name}

{e.detail}

{stamp(e.at)}
):

No changes recorded in this period yet.

}

Exchanges count confirmed bot replies, excluding synthetic openers and private player messages. Positive bonds need not be mutual friendships. Arrivals mean first observed online, not necessarily newly created. Recovery attempts do not prove recovery succeeded.

:<>
setQuery(e.target.value)}/>

{rows.length} of {data.activity.length} online residents. Progress means observed movement, level gain, or health change. Intentions describe plans, not completed game actions.

{rows.map(b=>

{b.name}

{b.label}

{b.zone} · Intent: {b.action}

{b.detail}

Game observation: {stamp(b.observedAt)}
Last observed progress: {stamp(b.lastProgressAt)}

Recent recovery attempts ({b.recoveries.length}){b.recoveries.length?b.recoveries.map((r,i)=>

{stamp(r.at)} · {r.detail}

):

No retained attempts. Recovery requires ten minutes without native progress, safe conditions, and an expired 30-minute cooldown.

}
)}
{!rows.length&&

No residents match this filter.

}} ; }