Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import { cleanText, notCute } from "../core.ts";import type { TurnKind } from "../types.ts";import { gen } from "./brain.ts";import { writeDiary } from "./diary.ts";import { changed, petEngine } from "./engine.ts";import { brain, emit, liveBubble, store, view } from "./store.ts";import { canRunLocally } from "./hosting.ts";
// Turn scheduling lives in the shared engine: "react" turns answer something// that happened (a message, an offer, a pat), "idle" turns let the pet live on// its own, and diary pages are written at bedtime or for missed days. This// module runs whatever the engine asks for while the brain is free. A hidden// page still answers reactions but does not idle or write.export function wantTurn(kind: TurnKind = "react") { petEngine().wantTurn(kind); setTimeout(pump, 0);}export function pump() { if ( !canRunLocally() || !store.tabOwned || brain.busy || brain.state !== "ready" || !store.state.pet || view.game ) return; const e = petEngine(), now = Date.now(); const task = e.chooseTask(now, !document.hidden); if (!task) return; e.beginTurn(now); const run = task.type === "chat" ? petTurn(task.kind) : writeDiary(task.date); void run.finally(() => { if (petEngine().pending) setTimeout(pump, 120); });}/** The first idle turn comes this long after the brain is ready. */export function scheduleIdle(delay: number) { petEngine().idleAt(Date.now() + delay);}
// One pet line: the engine builds each request, appends lookup results and// filters the text; the worker only generates. Idle turns run without the// thinking bubble.export async function petTurn(kind: TurnKind = "react") { if ( !canRunLocally() || !store.tabOwned || !store.state.pet || brain.state !== "ready" || brain.busy ) return false; const e = petEngine(), execution = store.executionRevision, seen = e.contextRevision; const turn = e.chatTurn(kind, Date.now()); brain.busy = true; view.thinking = kind === "react"; emit(); let bubble = kind === "react" ? liveBubble() : null; const live = (t: string) => { const shown = notCute(t) ? "…" : cleanText(t); if (!bubble && !shown) return; // silent so far: nothing to show (bubble ||= liveBubble()).update(shown); }; try { for (;;) { const r = await gen(turn.request(), (partial) => live(turn.text() + partial)); brain.tps = r.tps || brain.tps; brain.prefillMs = r.prefillMs; if (!turn.feed(r.text, r.reason)) break; live(turn.text()); } bubble?.remove(); if (execution !== store.executionRevision || !canRunLocally() || !store.tabOwned) return false; // Something happened while it was thinking (a message, an offer, a pat, falling // asleep): the line is stale. Whatever changed asked for the next turn. if (e.contextRevision !== seen) return true; e.commitLine(turn.body() ?? "", Date.now()); changed(); } catch (err) { bubble?.remove(); console.warn(err); } finally { turn.free(); if (execution === store.executionRevision) { brain.busy = false; view.thinking = false; emit(); } } return true;}