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