A local, private AI chat running entirely on your browser and saved on your Val Town. Your conversations are encrypted client-side before being stored, and your password never leaves your device.
Your password is never sent to the server. Encryption happens entirely in your browser.
If you don't already have one, create a free account on Val.Town.
main.ts, api/storage.ts, etc.)Note: In Val.Town, this is one val/project with multiple files. Each file that exports a default function gets its own HTTP endpoint when Val.Town serves it.
After forking, you need to find the HTTP endpoint for your api/storage.ts file:
In your forked val, navigate to the api/storage.ts file
Val.Town will show you the HTTP endpoint URL for this file (usually displayed at the top)
Copy this URL - it will look something like:
https://yourname--a1b2c3d4e5f6.web.val.run(The actual format depends on how Val.Town generates endpoints for your val files)
This is your STORAGE_URL - you'll need it in the next step
Now configure the environment variables for your entire val/project.
Important: Environment variables in Val.Town are global for the entire project, not per-file. When you set them, they apply to both main.ts and api/storage.ts.
Visit your main val's URL (the main.ts endpoint) in your browser
Check the browser console or the Val.Town logs. You'll see messages like:
ā ļø STORAGE_API_KEY not set! Auto-generated key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
ā ļø Save this key to your STORAGE_API_KEY environment variable!
ā STORAGE_URL not set! Please set it in your environment variables.
In your Val.Town project settings, go to Environment Variables
Add these two variables:
STORAGE_API_KEY
Value: The auto-generated key from the console logsSTORAGE_URL
Value: The HTTP endpoint URL for api/storage.ts (from Step 3)
Example: https://yourname--a1b2c3d4e5f6.web.val.runSave the environment variables
Since environment variables are global, both main.ts and api/storage.ts will now have access to the same STORAGE_API_KEY and can communicate securely.
Visit your main val's URL (the main.ts endpoint):
ā SQL Injection Prevention - All database queries use parameterized statements ā API Authentication - Storage endpoint requires API key ā Client-side Encryption - AES-256-GCM with PBKDF2 key derivation ā Master Password Protection - Single password encrypts all conversations ā Session Management - Password stored only in sessionStorage, cleared on lock
Your Val Town SQLite database stores:
Without your password, the encrypted data is unreadable.
| Variable | Required | Description |
|---|---|---|
STORAGE_URL | Yes | URL of your storage val (e.g., https://yourname--storage.web.val.run) |
STORAGE_API_KEY | Yes | Protects your storage API from unauthorized access (auto-generated on first run) |
FRONTEND_URL | No | Optional CORS restriction (e.g., https://yourapp.val.run) |
āāāāāāāāāāāāāāāāāāā
ā Browser ā
ā (React App) ā
ā ā
ā ⢠WebLLM AI ā
ā ⢠Encryption ā
ā ⢠UI ā
āāāāāāāāāā¬āāāāāāāāā
ā
ā HTTPS + API Key
ā
āāāāāāāāāā¼āāāāāāāāā
ā Val Town ā
ā Storage API ā
ā ā
ā ⢠SQLite DB ā
ā ⢠Auth Check ā
āāāāāāāāāāāāāāāāāāā
āāā main.ts # Entry point, serves HTML
āāā App.tsx # Main app with master password flow
āāā api/
ā āāā storage.ts # Storage API with authentication
āāā lib/
ā āāā api.ts # Client-side API calls
ā āāā crypto.ts # Encryption/decryption logic
ā āāā models.ts # AI model definitions
āāā hooks/
ā āāā useEncryption.ts # Encryption state management
ā āāā useWebLLM.ts # AI model integration
āāā screens/
ā āāā MasterPasswordScreen.tsx # Password creation/verification
ā āāā MainScreen.tsx # Main chat interface with sidebar
āāā components/
āāā MessageBubble.tsx # Message display
āāā StatusBadge.tsx # AI status indicator
āāā EmptyState.tsx # Empty state UI
āāā ModelSelector.tsx # AI model selection dropdown
āāā OptionsMenu.tsx # Lock/delete options menu
āāā ChatNameModal.tsx # Create/rename chat modal
Use a strong, unique master password. Since encryption happens client-side, your master password is the only thing protecting ALL your data.
There is no password recovery. If you forget your master password, ALL your chat data is permanently inaccessible. This is by design - the server (in this case Val-Town) don't have access to your data or password.
The app offers three AI models:
Models download to your browser once and persist. You can switch models anytime in the chat interface.
Problem: API calls return 401 Unauthorized
Solution:
STORAGE_API_KEY is set in environment variablesProblem: Can't unlock the app
Solution:
Problem: No chats appear on home screen
Solution:
STORAGE_URL environment variable is set correctly in Val Town
settingsSTORAGE_API_KEYProblem: WebLLM status shows error
Solution:
For stronger encryption, increase iterations in lib/crypto.ts (line 25):
iterations: 600000, // Increased from 100,000
Trade-off: Higher security, slower encryption/decryption.
To only allow requests from your frontend:
FRONTEND_URL environment variablehttps://your-main-val.val.runThis prevents other websites from calling your API (browser-level protection).
To add additional AI models beyond the three included:
lib/models.ts to add model definitionsAVAILABLE_MODELS array with model ID, name, size, and
descriptionNone. This app doesn't collect, track, or transmit any data beyond what's necessary for functionality:
Only you. When you fork this val:
No one else (including the original author) can access your chats.
Found a bug or want to improve the code? Feel free to:
This is open source - make it your own.
While this app implements strong encryption and security practices, please note:
For maximum security:
STORAGE_API_KEYBuilt with:
MIT License - Feel free to fork, remix, and make it your own!
Remember: Your master password is your key. Keep it safe, keep it secret.