ChatAI is a zero-backend, offline-first AI chat PWA. Everything runs in the browser — conversations, settings, API keys, and even vector embeddings for RAG are stored locally in IndexedDB and localStorage. AI API requests go directly from the browser to the configured endpoint; there is no intermediary server, no analytics, and no accounts.
| Decision | Rationale |
|---|---|
| No backend | Zero infrastructure to maintain. Deploy to any static host (GitHub Pages, Netlify, S3). |
| IndexedDB for storage | Survives browser restarts, handles large conversations, supports object stores for different data types. |
| localStorage for settings | Simple key-value for small, frequently-read settings (active connection, preferences). |
| ES modules | All JS files use import/export — no bundler needed. The browser loads them directly. |
| Service worker precache | App shell (HTML, CSS, JS, icons) loads offline. Only AI API calls need network. |
| No build step | Edit files and reload. Deploy by pushing to a static host. |
index.html — App ShellThe entire UI is a single HTML file containing:
app.js — Main Application Logic (~119KB)The central module that imports and wires everything together:
renderChatList() for sidebar, renderMessages() for chat viewsendMessage() routes to standard chat, image generation, web search, RAG, folder coding, or browser agentproviders.js — Connection and Agent PresetsDefines 40+ API connection templates (Ollama, OpenAI, Anthropic, Gemini, etc.) with default endpoints, models, tags, and accent colors. Also defines the 9 default agent presets (Auto, Minimal, Bullet, Table, Research, Max, Coding, PWA, Image).
model-router.js — API Routing and Streamingbrowser-agent.js — Browser Control AgentAn AI-driven browser agent that uses fetch-based page content extraction (rather than iframe DOM access) for cross-origin support:
agent-loop.js — Autonomous Coding AgentMulti-step agent loop for folder/file coding tasks:
voice.js — Voice I/Otools.js — External Tool Wrappersrag.js — Knowledge Base RAGmemory.js — Agent Memory LayerStores short factual notes scoped to global/conversation/agent. Retrieval via keyword matching with recency and scope scoring.
fs-tools.js — File System Access APIConnects a folder or single file via the browser File System Access API. Persists the handle in IndexedDB. Provides read/write/search/patch operations. Requires Chrome/Edge on desktop over https or localhost.
doc-parser.js — Document Extractionpdfjs-distmammoth.jstool-parser.js — Tool Call ParserHandles XML tool tags (<tool name="..."><param>value</param></tool>). Supports CDATA, markdown fences, and JSON fallback.
db.js — IndexedDB SchemaVersion 4 schema with 4 object stores:
conversations — chat historyfs-handles — linked folder/file handlesknowledge-base — RAG document metadata and vectorsagent-memory — agent memory notessw.js — Service WorkerUser types message → Enter key
→ sendMessage() called
→ detect task type (chat, image, search, KB, folder, browser)
→ runChatFlow() for standard chat
→ selectConnection() picks best connection
→ callModel() streams response
→ onToken() updates streaming element in real-time
→ save message + response to IndexedDB
→ renderChatList() updates sidebar
User types message in browser mode → Enter key
→ sendMessage() → runBrowserFlow()
→ build browserState with iframe navigation callback
→ runBrowserAgentLoop()
→ call model with browser system prompt
→ parse XML tool calls from response
→ execute tools sequentially
→ feed results back to model
→ repeat until <tool name="done"> or 8 iterations
→ return final answer with page text/URL/title
User clicks mic button → toggleVoiceRecording()
→ startVoiceRecording()
→ Web Speech API: createSpeechRecognizer() → start()
→ OR MediaRecorder + Whisper API
→ setVoiceRecordingUI(true) — shows recording bar, mic turns red
→ User clicks mic again or X button → stopVoiceInput()
→ transcript inserted into textarea
→ auto-send if text is non-empty
Uses CSS custom properties with prefers-color-scheme media query:
:root { /* dark palette */ }
@media (prefers-color-scheme: light) { :root { /* light palette */ } }
--bg, --bg-elev, --bg-elev-2--text, --text-secondary, --text-tertiary--accent (#0a84ff blue)--danger (red), --ok (green), --warn (orange)transform: translateX(), browser panel stacks verticallyThis is a pure static site. No bundler, no transpiler, no build script. Edit files and reload the browser to see changes.
.github/workflows/deploy.yml) handles GitHub Pages deployment automaticallypython3 -m http.server 8000
# open http://localhost:8000
file:// URLs will NOT work because service workers require an http(s) origin.
The browser agent processes tool calls one at a time in a for loop. This is intentional: each browser action (navigate to a page, extract its content, click a link, fill a form) depends on the state produced by the previous action. Parallel execution would mean trying to click a link before the page has loaded, or filling a form that hasn't been discovered yet. The bottleneck is the AI API call, not the tool execution, so sequential processing adds negligible latency.
A backend would require server infrastructure, authentication, and ongoing maintenance. By running entirely in the browser, ChatAI can be deployed to any static host with zero operational overhead. API keys stay in the user's browser and are never sent to any server except the API endpoint the user chooses.
localStorage has a ~5MB limit and only stores strings. IndexedDB can handle much larger datasets (hundreds of MB), supports structured objects, and provides indexed queries. Conversations with many messages and large responses can easily exceed localStorage limits.
The iframe is sandboxed and cannot access the DOM of cross-origin pages due to same-origin policy. By using fetch() + DOMParser to extract page content server-side-style, the browser agent can read any publicly accessible web page regardless of origin. The iframe is kept for visual display only.