'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;intentResults?:{action:string;status:string;detail:string}[];recoveries:{at:number;detail:string}[]}; type Insights={evaluation?:{since:number;onlineBotHours:number;activeBotHours:number;deaths:number;deathsPerBotHour:number|null;questTurnInsPerBotHour:number|null;intentsObserved:number;intentsUnverified:number;intentsInterrupted:number;nearbyPartyHours:number};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:'Permanent deaths',nativeDeaths:'Native deaths (all modes)',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,initialQuery=''}:{mode:string;initialQuery?:string}){ const [data,setData]=useState(null),[error,setError]=useState(''),[selected,setSelected]=useState('current'),[query,setQuery]=useState(initialQuery),[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.

} {data.evaluation&&

Gameplay evaluation

Since {stamp(data.evaluation.since)} · {data.evaluation.onlineBotHours.toFixed(1)} observed bot-hours · {data.evaluation.activeBotHours.toFixed(1)} active bot-hours

{data.evaluation.deaths} native deaths · {data.evaluation.deathsPerBotHour?.toFixed(2)??'—'} deaths / bot-hour · {data.evaluation.questTurnInsPerBotHour?.toFixed(2)??'—'} quest turn-ins / bot-hour

{data.evaluation.intentsObserved} intentions with observed results · {data.evaluation.intentsUnverified} without verified results · {data.evaluation.intentsInterrupted} interrupted

Fresh consecutive samples only; collection gaps are excluded. Activity includes movement, combat and casting. Results after an intention show correlation, not proof that the model caused them. Party proximity: {data.evaluation.nearbyPartyHours.toFixed(1)} bot-hours.

} {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)}

{b.intentResults?.map((r,i)=>

{r.action} · {r.status}
{r.detail}

)}
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.

}} ; }