{user: {id, spent, by_app}, breakdown: {"24h": [...], "7d": [...]}} // breakdown rows are {app, user, calls, tokens, in, out}. There are no budgets (PLAN belief 7): // this exhibit shows what was spent, not what is left. function spend_by_app(): array { [$s, $out] = call_app("ai", "/?api=usage", true); $r = json_decode($out, true); if ($s !== 200 || !isset($r["user"]["id"], $r["breakdown"])) return ["error" => "ai's usage answered $s", "status" => 502]; $apps = []; foreach (["24h" => "today", "7d" => "week"] as $k => $name) { foreach ($r["breakdown"][$k] as $row) { if ($row["user"] !== $r["user"]["id"]) continue; // the person's rows only, not the zoo's other askers $apps[$row["app"]][$name] = $row["tokens"]; if ($name === "week") $apps[$row["app"]]["calls"] = $row["calls"]; } } foreach ($apps as &$a) $a += ["today" => 0, "week" => 0, "calls" => 0]; return ["today" => (int)$r["user"]["spent"], "apps" => $apps, "week" => array_sum(array_column($apps, "week"))]; } return [ "n" => 15, "wing" => "Intelligence", "kind" => "self", "title" => "You can see what you spent", "promise" => "Every question through ai is on your ledger, per app.", "block" => "ai writes every call to a ledger; GET ?api=usage tells an app what the signed-in person spent today and this week, by app.", "show" => function (?array $me): string { if (!$me) return '

Sign in to see what you spent.

'; return '

You spent … tokens today and … this week. ' . 'Ask the model above and watch it rise.

'; }, "api" => function (string $do, ?array $me, array $in, bool $post): ?array { if ($do !== "usage") return null; if (!$me) return ["error" => "sign in first", "status" => 401]; return spend_by_app(); }, // The robot's spend must go up when it asks. (Exhibit 14 asks too; this does its own ask so // the check never depends on the order of two checks: it calls 14's question, as the page does.) "check" => function (): array { require_once ZOO_ROOT . "/lib/robot.php"; $b = robot_at_zoo(); $host = "https://" . env("RC_HOST"); [$c1, , $before] = $b->post("$host/api/15/usage"); if ($c1 !== 200) return [false, "reading the spend failed ($c1)"]; [$c2, , $ask] = $b->post("$host/api/14/ask", ["question" => "Reply with the single word: ok", "max_tokens" => 20]); if ($c2 !== 200) return [false, "the robot's question failed ($c2): " . ($ask["error"] ?? "")]; [$c3, , $after] = $b->post("$host/api/15/usage"); if ($c3 !== 200) return [false, "reading the spend again failed ($c3)"]; $was = $before["apps"]["zoo"]["today"] ?? 0; $now = $after["apps"]["zoo"]["today"] ?? 0; if ($now <= $was) return [false, "asked, but the robot's spend by the zoo stayed at $was tokens", $after]; if ($after["today"] <= $before["today"] || ($after["apps"]["zoo"]["week"] ?? 0) <= ($before["apps"]["zoo"]["week"] ?? 0)) return [false, "the zoo's spend rose but today's or this week's total did not", $after]; return [true, "the robot's spend by the zoo rose from $was to $now tokens today after one question (week {$after['week']})", $after]; }, "script" => <<<'JS' (() => { const today = document.getElementById("spent-today"); if (!today) return; const n = (x) => x.toLocaleString("en"); const read = async () => { const j = await zoo.call("/api/15/usage"); if (j.error) { today.textContent = j.error; return; } today.textContent = n(j.today); document.getElementById("spent-week").textContent = n(j.week); const by = Object.entries(j.apps || {}).map(([a, r]) => a + " " + n(r.today) + " today, " + n(r.week) + " this week").join("; "); document.getElementById("spent-by").textContent = by ? "By app: " + by : "Nothing spent this week."; }; read(); document.getElementById("spent-refresh").addEventListener("click", read); window.addEventListener("zoo-asked", read); // exhibit 14 just asked: re-read, the number rises })(); JS, ];