Something went wrong. Try again.
a tiny pet with a GPT-2 brain, living in your browser
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137import { disturb } from "./animation.ts";import { forgetBrains, loadBrain, stopBrain } from "./brain.ts";import { hatch } from "./care.ts";import { pushLine, scheduleIdle, wantTurn } from "./conversation.ts";import { logToday } from "./simulation.ts";import { BAD_KEY, cancelSave, KEY, parseSave, save } from "./storage.ts";import { brain, emit, store, toast, upgrade } from "./store.ts";import { cleanName, dateKey, download, errorMessage } from "./utils.ts";
export function getEgg(name: string) { if (!store.tabOwned || store.badEgg) return; const value = cleanName(name, 24); if (!value) return; store.state.owner = value; store.state.egg ||= { laid: Date.now() }; save(); emit(); void loadBrain();}export function hatchPet(name: string) { const value = cleanName(name, 16); if (value && store.tabOwned && !store.badEgg && brain.state === "ready") hatch(value);}export function sendMessage(value: string) { const text = value .replace(/[\n\[\]{}*]/g, " ") .trim() .slice(0, 200); if (!text || !store.state.pet || brain.state !== "ready" || !store.tabOwned) return false; store.state.lastActive = Date.now(); disturb("talk"); pushLine(`${store.state.owner}: ${text}`); if (text.length > 12 && Math.random() < 0.5) logToday(`${store.state.owner} said: ${text.slice(0, 48).toLowerCase()}`); wantTurn(); return true;}export function renameOwner(value: string) { const name = cleanName(value, 24); if (name) { store.state.owner = name; save(); emit(); }}export function setChatter(value: boolean) { store.state.settings.chatter = value; scheduleIdle(); save(); emit();}export function forgetMemory(index: number) { store.state.memories.splice(index, 1); save(); emit();}export async function deleteBrain() { 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() { download( JSON.stringify(store.state, null, 1), `tokenling-${store.state.pet?.name || "egg"}-${dateKey(Date.now())}.json`, );}export async function importSave(file: File) { 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; // 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(); try { localStorage.setItem(KEY, JSON.stringify(next)); } catch (error) { store.resetting = false; throw error; } location.reload(); } catch (error) { toast(`Could not import: ${errorMessage(error)}`); }}export function resetSave() { 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 this browser.`, ) ) return; store.resetting = true; cancelSave(); stopBrain(); 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();}