← Back to ChatAI

ChatAI — Technical Documentation

Architecture Overview

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.

Key Design Decisions

DecisionRationale
No backendZero infrastructure to maintain. Deploy to any static host (GitHub Pages, Netlify, S3).
IndexedDB for storageSurvives browser restarts, handles large conversations, supports object stores for different data types.
localStorage for settingsSimple key-value for small, frequently-read settings (active connection, preferences).
ES modulesAll JS files use import/export — no bundler needed. The browser loads them directly.
Service worker precacheApp shell (HTML, CSS, JS, icons) loads offline. Only AI API calls need network.
No build stepEdit files and reload. Deploy by pushing to a static host.

Module Breakdown

index.html — App Shell

The entire UI is a single HTML file containing:

app.js — Main Application Logic (~119KB)

The central module that imports and wires everything together:

providers.js — Connection and Agent Presets

Defines 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 Streaming

browser-agent.js — Browser Control Agent

An 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 Agent

Multi-step agent loop for folder/file coding tasks:

voice.js — Voice I/O

tools.js — External Tool Wrappers

rag.js — Knowledge Base RAG

memory.js — Agent Memory Layer

Stores short factual notes scoped to global/conversation/agent. Retrieval via keyword matching with recency and scope scoring.

fs-tools.js — File System Access API

Connects 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 Extraction

tool-parser.js — Tool Call Parser

Handles XML tool tags (<tool name="..."><param>value</param></tool>). Supports CDATA, markdown fences, and JSON fallback.

db.js — IndexedDB Schema

Version 4 schema with 4 object stores:

sw.js — Service Worker


Data Flow

Standard Chat Flow

User 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

Browser Agent Flow

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

Voice Flow

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

CSS Architecture

Dark/Light Mode

Uses CSS custom properties with prefers-color-scheme media query:

:root { /* dark palette */ }
@media (prefers-color-scheme: light) { :root { /* light palette */ } }

Apple System Palette

Responsive Breakpoints


Build & Deploy

No Build Step

This is a pure static site. No bundler, no transpiler, no build script. Edit files and reload the browser to see changes.

Deployment

  1. Push to any static host (GitHub Pages, Netlify, Vercel, Cloudflare Pages, S3)
  2. Ensure all files are served from the same directory
  3. The included GitHub Actions workflow (.github/workflows/deploy.yml) handles GitHub Pages deployment automatically

Local Development

python3 -m http.server 8000
# open http://localhost:8000
file:// URLs will NOT work because service workers require an http(s) origin.

Key Design Decisions Explained

Why Sequential Browser Tool Execution?

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.

Why No Backend?

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.

Why IndexedDB Over localStorage for Conversations?

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.

Why Fetch-Based Page Extraction for the Browser Agent?

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.


← Back to ChatAI · README