Skip to content
57 linesCodeBlameRaw
1import { useMemo } from "react";
2
3import { G1tMark } from "./orchestrator";
4
5import { COLUMNS, ROWS, creature } from "../lib/pixel-creature";
6import { Avatar } from "./ui/avatar";
7
8/** What an agent's face is drawn from. */
9export type AgentLike = {
10 id?: string;
11 /** Its handle, or a member's name. */
12 handle?: string;
13 name?: string;
14 avatar?: string | null;
15 avatar_seed?: string | null;
16 builtin?: boolean;
17};
18
19/** The seed an agent's creature grows from: its own, else its id, else its handle. */
20export function seedOf(agent: AgentLike): string {
21 return agent.avatar_seed || agent.id || agent.handle || agent.name || "agent";
22}
23
24/** The pixel creature for a seed, on its own ground; wiggles a little on hover. */
25export function PixelCreature({ seed, size = 20, className = "" }: { seed: string; size?: number; className?: string }) {
26 const { cells, colour } = useMemo(() => creature(seed), [seed]);
27 // Cells of 10, the 5×7 sprite centred in a 90×90 square.
28 const x0 = (90 - COLUMNS * 10) / 2;
29 const y0 = (90 - ROWS * 10) / 2;
30 return (
31 <span
32 aria-hidden="true"
33 className={`inline-flex shrink-0 items-center justify-center hover:animate-wiggle motion-reduce:animate-none ${className}`}
34 style={{ width: size, height: size, borderRadius: Math.round(size * 0.28), background: colour.ground, boxShadow: `inset 0 0 0 1px ${colour.fg}33` }}
35 >
36 <svg viewBox="0 0 90 90" width={size} height={size} shapeRendering="crispEdges">
37 {cells.map((row, r) =>
38 row.map((cell, c) =>
39 cell === 0 ? null : (
40 <rect key={`${r}:${c}`} x={x0 + c * 10} y={y0 + r * 10} width={10} height={10} fill={cell === 2 ? colour.ground : colour.fg} />
41 ),
42 ),
43 )}
44 </svg>
45 </span>
46 );
47}
48
49/**
50 * An agent's face wherever it appears: an uploaded picture, g1t's pixel 1,
51 * or its own creature.
52 */
53export function AgentAvatar({ agent, size = 20 }: { agent: AgentLike; size?: number }) {
54 if (agent.builtin || agent.handle === "g1t" || agent.name === "g1t") return <G1tMark size={size} />;
55 if (agent.avatar) return <Avatar name={agent.handle ?? agent.name ?? "agent"} image={agent.avatar} size={size} square />;
56 return <PixelCreature seed={seedOf(agent)} size={size} />;
57}