import type {
BeastSnapshot,
EntitySnapshot,
HouseSnapshot,
ResourceNodeSnapshot,
} from "../types";
import { TooltipLabel } from "./TooltipLabel";
type AgentPanelProps = {
entity: EntitySnapshot | null;
beast?: BeastSnapshot | null;
resource?: ResourceNodeSnapshot | null;
house?: HouseSnapshot | null;
entities?: EntitySnapshot[];
onSelectEntity?: (id: string) => void;
};
export function AgentPanel({
entity,
beast = null,
resource = null,
house = null,
entities = [],
onSelectEntity,
}: AgentPanelProps) {
if (beast) {
return ;
}
if (resource) {
return ;
}
if (house) {
return ;
}
const selectedName = entity?.label ?? "No NPC";
const selectedRole = entity
? [entity.country_id, entity.special_status ?? entity.role].filter(Boolean).join(" / ")
: "world";
return (
);
}
function BeastPanel({ beast }: { beast: BeastSnapshot }) {
const suffix = beast.id.replace(/^beast[_-]?/, "");
const beastName = suffix ? `Beast ${suffix}` : "Beast";
const healthLabel = `${beast.health} / ${beast.max_health} HP`;
const positionLabel = `x ${beast.position.x.toFixed(1)} / z ${beast.position.z.toFixed(1)}`;
return (
);
}
function ResourcePanel({ resource }: { resource: ResourceNodeSnapshot }) {
const title = resource.type
? `${resource.type.charAt(0).toUpperCase()}${resource.type.slice(1)} node`
: "Resource node";
const amountLabel = resource.max_amount
? `${resource.amount} / ${resource.max_amount}`
: `${resource.amount} left`;
const positionLabel = `x ${resource.position.x.toFixed(1)} / z ${resource.position.z.toFixed(1)}`;
return (
);
}
type HousePanelProps = {
house: HouseSnapshot;
entities: EntitySnapshot[];
onSelectEntity?: (id: string) => void;
};
function HousePanel({ house, entities, onSelectEntity }: HousePanelProps) {
const isComplete = house.state === "completed";
const suffix = house.id.replace(/^house[_-]?/, "");
const title = suffix ? `Home ${suffix}` : "Home";
const buildRatio = Math.min(1, house.build_progress / 10);
const statusLabel = isComplete
? house.hp < house.max_hp
? "damaged"
: "intact"
: `building ${Math.round(buildRatio * 100)}%`;
const positionLabel = `x ${house.position.x.toFixed(1)} / z ${house.position.z.toFixed(1)}`;
return (
);
}
type HouseResidentListProps = {
label: string;
ids: string[];
emptyLabel: string;
entities: EntitySnapshot[];
onSelectEntity?: (id: string) => void;
};
function HouseResidentList({ label, ids, emptyLabel, entities, onSelectEntity }: HouseResidentListProps) {
return (
{label}
{ids.length === 0 ? (
{emptyLabel}
) : (
ids.map((id) => {
const npc = entities.find((entity) => entity.id === id);
return (
);
})
)}
);
}
type AgentDetailsProps = {
entity: EntitySnapshot;
};
function AgentDetails({ entity }: AgentDetailsProps) {
return (
<>
>
);
}
function SurvivalReadout({ entity }: { entity: EntitySnapshot }) {
const { hunger, fear, safety, age, max_age, importance, goal, inventory } = entity.state;
if (
hunger === undefined &&
fear === undefined &&
safety === undefined &&
age === undefined &&
importance === undefined &&
goal === undefined &&
!inventory
) {
return null;
}
const inventoryLabel = inventory
? `food ${inventory.food} / herbs ${inventory.herbs} / wood ${inventory.wood} / coins ${inventory.coins} / wpn ${inventory.weapon}`
: "-";
return (
{hunger !== undefined ? (
) : null}
{fear !== undefined ? (
) : null}
{safety !== undefined ? (
) : null}
{age !== undefined && max_age !== undefined ? (
) : null}
{importance !== undefined ? (
) : null}
{goal ? : null}
);
}
function AgentReadout({ entity }: { entity: EntitySnapshot }) {
const selectedHealth = `${entity.state.health} / ${entity.state.max_health} HP`;
const selectedAttackDamage = `DMG ${entity.state.attack_damage}`;
const selectedPosition = `x ${entity.position.x.toFixed(1)} / z ${entity.position.z.toFixed(1)}`;
return (
);
}
function ThinkingBlock({ entity }: { entity: EntitySnapshot }) {
const reasoning = entity.state.last_reasoning;
if (!reasoning || !reasoning.trim()) {
return null;
}
const tick = entity.state.last_reasoning_tick;
return (
);
}
function RelationshipList({ entity }: { entity: EntitySnapshot }) {
const relationships = Object.entries(entity.state.relationships ?? {})
.sort((left, right) => Math.abs(right[1]) - Math.abs(left[1]) || left[0].localeCompare(right[0]))
.slice(0, 3);
if (relationships.length === 0) {
return null;
}
return (
{relationships.map(([id, trust]) => (
= 0 ? "+" : ""}${trust.toFixed(2)}`}
/>
))}
);
}
function MemoryList({ entity }: { entity: EntitySnapshot }) {
const recentEpisodes = entity.state.recent_episodes ?? [];
return (
{entity.state.memory_summary ? (
{entity.state.memory_summary}
) : null}
{recentEpisodes
.slice()
.reverse()
.map((episode) => (
))}
{entity.state.memories
.slice()
.reverse()
.map((memory) => (
))}
);
}
function EmptyAgentReadout() {
return (
);
}