From 88580445d7f4264a16e03e3d3135969f72fb473a Mon Sep 17 00:00:00 2001 From: "Josefo (jozrftamson)" <107008476+jozrftamson@users.noreply.github.com> Date: Sat, 14 Mar 2026 04:00:19 +0100 Subject: [PATCH] feat add agent zoo browser (#13) --- demo/zoo.ts | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 2 +- zoo.html | 99 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 demo/zoo.ts create mode 100644 zoo.html diff --git a/demo/zoo.ts b/demo/zoo.ts new file mode 100644 index 0000000..8c952b4 --- /dev/null +++ b/demo/zoo.ts @@ -0,0 +1,125 @@ +import { initAgent } from "../src/index.ts"; +import * as agents from "../src/agents/index.ts"; + +const stage = document.getElementById("zoo-stage") as HTMLDivElement; +const status = document.getElementById("zoo-status") as HTMLSpanElement; +const count = document.getElementById("zoo-count") as HTMLSpanElement; +const animateAllBtn = document.getElementById("animate-all") as HTMLButtonElement; +const speakAllBtn = document.getElementById("speak-all") as HTMLButtonElement; +const resetAllBtn = document.getElementById("reset-all") as HTMLButtonElement; +const sharedAnimationSelect = document.getElementById("zoo-animation") as HTMLSelectElement; +const playSharedBtn = document.getElementById("play-shared") as HTMLButtonElement; + +const entries = Object.entries(agents); +const zooAgents: { name: string; agent: any; x: number; y: number; animations: string[] }[] = []; + +function layoutFor(index: number) { + const columns = 4; + const cellWidth = 230; + const cellHeight = 170; + const x = 36 + (index % columns) * cellWidth; + const y = 40 + Math.floor(index / columns) * cellHeight; + return { x, y }; +} + +function buildSharedAnimationList() { + const animationCounts = new Map(); + + for (const entry of zooAgents) { + for (const animation of entry.animations) { + animationCounts.set(animation, (animationCounts.get(animation) || 0) + 1); + } + } + + const sharedAnimations = [...animationCounts.entries()] + .filter(([, count]) => count > 1) + .sort((a, b) => a[0].localeCompare(b[0])); + + sharedAnimationSelect.innerHTML = ""; + + if (sharedAnimations.length === 0) { + const option = document.createElement("option"); + option.textContent = "No shared animations"; + sharedAnimationSelect.appendChild(option); + sharedAnimationSelect.disabled = true; + playSharedBtn.disabled = true; + return; + } + + for (const [animation, supportedBy] of sharedAnimations) { + const option = document.createElement("option"); + option.value = animation; + option.textContent = `${animation} (${supportedBy})`; + sharedAnimationSelect.appendChild(option); + } + + sharedAnimationSelect.disabled = false; + playSharedBtn.disabled = false; +} + +async function loadZoo() { + status.textContent = "Loading all agents..."; + + for (const [index, [name, loader]] of entries.entries()) { + const position = layoutFor(index); + const agent = await initAgent(loader); + const animations = agent.animations().sort(); + + agent.show(true); + agent.moveTo(position.x, position.y, 0); + + zooAgents.push({ name, agent, x: position.x, y: position.y, animations }); + } + + buildSharedAnimationList(); + count.textContent = `${zooAgents.length} agents`; + status.textContent = "All agents loaded."; +} + +animateAllBtn.addEventListener("click", () => { + for (const entry of zooAgents) { + entry.agent.animate(); + } + status.textContent = "Animating all agents."; +}); + +speakAllBtn.addEventListener("click", () => { + zooAgents.forEach((entry, index) => { + window.setTimeout(() => { + entry.agent.speak(`Hello from ${entry.name}.`); + entry.agent.animate(); + }, index * 250); + }); + status.textContent = "Starting roll call."; +}); + +resetAllBtn.addEventListener("click", () => { + for (const entry of zooAgents) { + entry.agent.stop(); + entry.agent.moveTo(entry.x, entry.y, 0); + } + status.textContent = "Reset all agents."; +}); + +playSharedBtn.addEventListener("click", () => { + const animation = sharedAnimationSelect.value; + let played = 0; + + for (const entry of zooAgents) { + if (!entry.animations.includes(animation)) { + continue; + } + + entry.agent.stop(); + entry.agent.play(animation); + played += 1; + } + + status.textContent = `Playing ${animation} on ${played} matching agents.`; +}); + +loadZoo().catch((error) => { + console.error(error); + status.textContent = "Failed to load the zoo demo."; + stage.textContent = error instanceof Error ? error.message : String(error); +}); diff --git a/index.html b/index.html index 4024e5e..8206e52 100755 --- a/index.html +++ b/index.html @@ -115,7 +115,7 @@

Welcome to Clippy.js!

This is a demo. Click an agent below to try it out.
See GitHub for usage and - docs. + docs. Want every agent on one screen? Open the agent zoo.

diff --git a/zoo.html b/zoo.html new file mode 100644 index 0000000..d7723f9 --- /dev/null +++ b/zoo.html @@ -0,0 +1,99 @@ + + + + + + 📎 Clippy Agent Zoo + + + + +
+
+
+ Clippy.js Agent Zoo +
+ +
+
+
+

All agents, one page

+

+ Preview every bundled agent at once and trigger shared animations without switching back + and forth. +

+
+ + + + + + +
+

+ Loading agents... +

+
+
+
+ 0 agents + + Back to main demo + +
+
+
+ + + +