This permanently deletes every message in this conversation. It does not touch the memory summary or the character itself — only the chat log. This can't be undone.
⚠️ Delete this character?
This permanently deletes the character along with every chat, image, and memory tied to them. This can't be undone.
Add provider
Requests go to <base URL>/chat/completions.
One model name per line. The first one becomes the default when you switch to this provider.
Sent right after the global prompt below it (if any), before the character's own instructions. Just for this provider.
Add global prompt
Exact prompt sent to the model
This is the full system prompt, built fresh right now from your current settings — global prompt, provider prompt, personality, bio, appearance, scenario, and example dialogue, in the order they're actually sent. Recent chat messages and memory are sent separately alongside this.
Add TTS provider
Deepgram's API is shaped differently enough (different endpoint, auth header, and request format) that it needs its own handling rather than assuming everything speaks the same protocol.
Requests go to <base URL>/audio/speech.
The exact model name this provider expects. For OpenAI directly: tts-1. For OpenRouter: a provider-prefixed name like elevenlabs/eleven-turbo-v2 — check their model list for the exact string.
One voice name per line — whatever names this provider's API expects. The first one becomes the default.
Character
✦
Bio
Personality
Appearance
Scenario
Example dialogue
Create a character
Set up how your character looks, speaks, and behaves in chat.
Image
Optional. Used as the avatar in the character list and chat.
🖼️
Drag & drop an image here, or click to select
Additional photos
Optional, up to 4. Shown alongside the avatar when you click it in chat. Kept small on purpose — every photo added here also gets carried along in backups and cloud sync, so more photos means more to back up and sync each time.
Basics
Definition
The heart of your character — how they speak, behave, and what they know. Use {{char}} and {{user}} anywhere below to reference the character and the person chatting.
0 tokens
Sends the character's photo to your active provider and asks it to describe what it sees, in the same tag format. Only works if that provider's model supports image input — some do, some don't. Replaces whatever's currently in this field.
0 tokens
The first message from your character. Add more than one to give people options when they start a chat.
0 tokens
Teaches the character how to speak. Best practice: {{char}}: Hey, im Mark {{user}}: hello Mark {{char}}: nice to meet you :)
✦
No character selected
0 unsummarized messages
Personality0
Persona0
Memory0
Recent chat0
Total this message0
Personality, persona, and memory are always sent in full, every message. Only "Recent chat" is subject to the context budget — once that's exceeded, the oldest of those turns get folded into memory (not dropped) and removed from here.
Memory • 0 unsummarized messages
Memory
0 / 400 tokens✓ saved
Extracting scene…
Your persona
Given to the AI as context about you, alongside your name.
Settings
Source
Browser: free, built into your device, works offline. API: usually sounds more natural but costs per use — needs a provider added below.
Pulled from your browser's own built-in voices — varies by device and OS. No account or API needed.
API providers
Targets OpenAI-compatible TTS endpoints (POST <base URL>/audio/speech) — same idea as the text-generation providers, several alternative TTS services implement this same format.
Specific to whichever TTS provider is active above. Same privacy handling as your text-generation keys — kept only in this page's memory.
Playback
1.0x
Off by default. Either way, you can always tap 🔊 on any message to read just that one.
Reads just the text inside "quotes", skipping narration and action text in between. If a message has no quoted dialogue at all, the full message is read instead.
Uses your own local Stability Matrix / ComfyUI install — nothing here is hosted for you, and it only works from the same computer ComfyUI is running on.
Connection
Requires a tiny helper script running on your PC (downloaded separately — a browser can never launch a desktop app directly). One-time setup: edit the path inside it, run it, leave it running.
Point this at http://127.0.0.1:8790 — the local relay (comfy-relay.py), not ComfyUI's own address directly. ComfyUI itself rejects requests from a page hosted on a different domain (like this app's Cloudflare address) regardless of any CORS setting — the relay is what makes that work. See comfy-relay.py for one-time setup.
ComfyUI itself still needs --enable-cors-header in its launch arguments (Stability Matrix's launch options) — the relay handles the cross-site rejection, but ComfyUI's plain CORS headers are separate and still required underneath it.
Model & workflow
Only matters if your workflow uses a separate Load VAE node — leave as-is if your checkpoint has its own VAE baked in.
The .json file exported from ComfyUI (Dev Mode on → Export Workflow / Save API Format). One-time setup — this defines the node graph used for every generation.
Generation settings
Applied to every generation automatically — standard "don't generate this" tags. Edit freely.
20
5.0
1.00
Leave at 1.00 for normal text-to-image. Lower values only matter if your workflow is set up for image-to-image.
Unlocked (default): a fresh random seed every generation. Automatically fills in with whatever seed was just used — if a result comes out looking right, tick "Lock" to keep generating with that exact seed instead of rolling a new one each time.
Storage
Chrome/Edge desktop only. Images are never included in backups or cloud sync either way — this just optionally also drops a copy on disk as its own file, outside the app's own data entirely.
14px
Tick "Custom" on any part below to override it — leave it unticked and that part keeps following whichever theme you pick, even if you switch themes later.
Global prompt
Sent at the very top of every request, before the character's own instructions and before any provider-specific custom prompt. Shared across every provider you use.
Provider
Add each API you use (OpenAI, a proxy, a local relay, etc.) once, then switch between them here any time one's down — no need to re-enter URLs.
Specific to whichever provider is active above. Kept only in this page's memory — never saved to localStorage in plain text, never sent anywhere but that provider's base URL.
Encrypts the key for every provider you've entered one for (AES-GCM, in-browser) and saves the result to localStorage. One password unlocks all of them next visit, instead of pasting each raw key again. This protects against casually opening your browser storage — it isn't a substitute for keeping the password itself safe.
🔒 Encrypted keys are saved on this device.
Add models to a provider by editing it above.
Generation
0.9
Lower = more consistent, higher = more creative/unpredictable.
A ceiling, not a target — the model decides on its own when a reply feels finished. It also can't see this number directly (it's not part of the prompt, just where the API cuts things off), so it's converted to an approximate word count and mentioned in the prompt alongside the minimum below, framed as a range.
0 disables this. When set, tells the model to aim for a range between this and the max above (not to treat either as a strict quota), and opportunistically sends a min-token hint to the API too (only honored by some backends). Stating both ends together works much better than a floor alone — a lone minimum tends to get read as a fixed target to hit exactly, overshooting past the real max and getting cut off mid-sentence.
Displays the model's reasoning in a collapsible panel above each reply, separate from the in-character answer. If the model doesn't return reasoning on its own, this also asks it to think inside a hidden block first. The panel stays collapsed until you tap it — it no longer opens on its own.
Turn off if you'd rather the chat stay put while you're scrolled up reading — new replies still arrive, they just won't yank you back down.
Caps the total sent to the model each turn — recent chat history plus the memory summary combined. Once that total passes this, the oldest unsummarized messages are automatically folded into the memory log before your next message goes out, keeping the rest in full. Set to 0 to disable and always send the full history.
Caps how many raw messages get folded into a single memory chunk at once (both auto-condensing and "Add new to memory"). Keeps each summary focused instead of one giant, lossy fold covering dozens of exchanges — if there's a big backlog, it catches up gradually over a few chunks rather than all at once.
Target length for each new chunk added to memory — whether from auto-condensing or Summarize. Chunks are appended, so the memory log's running total can grow well beyond this single number; see the limit below.
Ceiling for the accumulated memory log. Once adding a new chunk would push the total past this, the existing log is compressed down to about half this limit first, then the new chunk is added — keeping the running total near this ceiling instead of growing forever.
Diagnostics
Characters and chat history already stay saved in this browser automatically. Export makes a JSON backup file (no API key included) you can keep or move to another browser/computer with Import.
Uses a Cloudflare R2 bucket you control, through a small serverless function bundled into your Cloudflare Pages deployment — no per-file size limit to worry about the way GitHub's Gist API had. One-time setup, done through the Cloudflare dashboard:
1. R2 → Create bucket (any name).
2. Your Pages project → Settings → Functions → R2 bucket bindings → add one named SYNC_BUCKET, pointing at that bucket.
3. Same project → Settings → Environment variables → add SYNC_SECRET (mark it "Secret") with a password you make up.
4. Redeploy once so the binding takes effect.
5. Paste your Pages URL + /api/sync below, and the same secret from step 3.
Do this once per device, pointing at the same URL and secret.
This is manual, not automatic — push before you switch devices, pull when you pick the new one up. Whichever side you pull on overwrites its local characters and chats, so push first if you made changes there. Your API base URL stays whatever this device already has (useful if one device uses a local relay). The secret is stored in this browser same as other settings — treat it like a password.
Chrome or Edge on desktop only. Links a real file on your disk that updates automatically every time anything changes — a safety net if browser storage ever gets cleared. Put it in an iCloud Drive/Dropbox/OneDrive folder and it'll sync between your computers too (not your iPhone — Safari doesn't support this).
Not supported in this browser — use cloud sync above instead.
⚠ This file hasn't updated in a while — its write permission was lost (browsers do this on their own sometimes). Click Reconnect below, or your recent chats won't be backed up here until you do.
Version
Bumped by one on every real update — the fastest way to check whether a deploy actually took, especially if you're not sure which URL you're on or whether a redeploy went through.