Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157import { cleanMessage, cleanName } from "../core.ts";import { forgetBrains, loadBrain, stopBrain } from "./brain.ts";import { changed, localCommand, petEngine } from "./engine.ts";import { BAD_KEY, cancelSave, KEY, parseSave } from "./storage.ts";import { brain, emit, store, toast, upgrade } from "./store.ts";import { dateKey, download, errorMessage } from "./utils.ts";import { isRemote, moveToRemote, moveToLocal, remoteCommand, recoverySave, deleteRemote,} from "./hosting.ts";import type { HostingMode, PetCommand } from "../types.ts";import { CFG } from "./config.ts";import { ui } from "./store.ts";
/** Owner commands run in the local engine, or go unchanged to the pet's host. */export async function petCommand(command: PetCommand) { if (!store.tabOwned || ui.hostingBusy) return false; return isRemote() ? remoteCommand(command) : localCommand(command);}
export function getEgg( name: string, mode: HostingMode = "local", endpoint = CFG.remoteEndpoint || "",) { if (!store.tabOwned || store.badEgg || ui.hostingBusy || !cleanName(name, 24)) return; petEngine().layEgg(name, Date.now()); changed(); if (mode === "remote") void moveToRemote(endpoint); else void loadBrain();}export function hatchPet(name: string) { const value = cleanName(name, 16); if (value && store.tabOwned && !store.badEgg && brain.state === "ready") void petCommand({ type: "hatch", name: value });}export async function sendMessage(value: string) { const text = cleanMessage(value); if (!text || !store.state.pet || brain.state !== "ready") return false; return petCommand({ type: "message", text });}export function renameOwner(value: string) { const name = cleanName(value, 24); if (name) void petCommand({ type: "rename", name });}export function setChatter(value: boolean) { void petCommand({ type: "chatter", value });}export function forgetMemory(index: number) { const fact = store.state.memories[index]; if (fact !== undefined) void petCommand({ type: "forgetMemory", index, fact });}export async function deleteBrain() { if (ui.hostingBusy || isRemote()) return; if ( !confirm( `Delete the downloaded brain from this browser? ${store.state.pet?.name || "Your pet"} will doze until you download it again. The pet and diary stay.`, ) ) return; stopBrain(); upgrade.state = "idle"; await forgetBrains([]); emit();}export function exportSave() { if (ui.hostingBusy) return; download( JSON.stringify(recoverySave(), null, 1), `tokenling-${store.state.pet?.name || "egg"}-${dateKey(Date.now())}.json`, );}export async function importSave(file: File) { if (!store.tabOwned || ui.hostingBusy || store.resetting) return; try { const next = parseSave(await file.text()); if ( !confirm( `Replace ${store.badEgg ? "the unreadable save" : "your current pet"} with ${next.pet?.name || "this save"}?`, ) ) return; if (isRemote()) { await moveToLocal(); if (isRemote()) throw new Error("Bring your current pet back locally before importing a save."); } // In-flight replies belong to the previous pet. Reload also resets animation, // queued offers/turns and ensures Web Lock ownership before reading the import. store.resetting = true; cancelSave(); stopBrain(); next.hosting.mode = "local"; try { localStorage.setItem(KEY, JSON.stringify(next)); } catch (error) { store.resetting = false; throw error; } location.reload(); } catch (error) { store.resetting = false; toast(`Could not import: ${errorMessage(error)}`); }}export async function resetSave() { if (!store.tabOwned || ui.hostingBusy || store.resetting) return; if ( !confirm( `Say goodbye to ${store.state.pet?.name || "your egg"} and start over? This deletes the pet, your name, chat, memories, gifts and diary from ${isRemote() ? "your server and this browser" : "this browser"}.`, ) ) return; store.resetting = true; cancelSave(); stopBrain(); if (isRemote()) { try { await deleteRemote(); } catch (error) { store.resetting = false; toast(`Could not delete the server's pet: ${errorMessage(error)}`); return; } } try { localStorage.removeItem(KEY); } catch { /* storage unavailable */ } location.reload();}export function exportBadEgg() { if (store.badEgg) download(store.badEgg.raw, `tokenling-bad-egg-${dateKey(Date.now())}.json`);}export function replaceBadEgg() { if ( !store.badEgg || !confirm( "Start with a new egg? The unreadable save is moved aside in this browser (tokenling.bad), replacing any older one there. Download it first if you might want it back.", ) ) return; try { localStorage.setItem(BAD_KEY, store.badEgg.raw); localStorage.removeItem(KEY); } catch { toast("Couldn't move the old save aside, so it's still there. Download it, then try again."); return; } location.reload();}