Chat Architecture
Two separate chat experiences exist: a static site chat (dumb, no auth) and a portal chat (AI-powered, auth required).
1. Static Site Chat (Dumb)
Purpose: SEO-friendly landing page with a simple Q&A widget. No AI, no login.
How it works:
- Business publishes a static site via
POST /api/site/publish(Firebase Function) template.tsrendersindex.html+ sub-pages as pure HTML/CSS/JS- QA data is generated at publish time from the business knowledge base and baked into the HTML as
<script type="application/json" id="chat-qa-data"> - Client-side JS does fuzzy pattern matching (bigram similarity) against the baked QA data — zero API calls
- Fallback answer links users to the portal for full AI chat
Hosting: GCS bucket humanlike-sites → {uid}/index.html, {uid}/{slug}.htmlURL: https://storage.googleapis.com/humanlike-sites/{uid}/index.htmlAuth: None Firebase SDK: None AI: None — pattern matching only Runtime cost: Zero (static file serving)
Files:
functions/src/site/template.ts— HTML generation (landing page, sub-pages)functions/src/site/generate-qa.ts— QA generation from knowledge base (at publish time)functions/src/site/storage.ts— GCS uploadfunctions/src/site/index.ts— Express routes: preview, publish, unpublish, import
Chat button in navbar: Links to portal (https://app.humanlike.world/portal/{slug}) for full AI chat.
2. Portal Chat (AI-powered)
Purpose: Full conversational AI chat. Requires sign-in. Talks to our API, uses tools (calendar, etc).
How it works:
- User visits
https://app.humanlike.world/portal/{slug} - React app resolves slug → business via
GET /api/portal/resolve?slug=X(Cloud Run API) - User signs in (Google OAuth or email/password via Firebase Auth)
- On sign-in, portal sets
visitorcustom claim and writes tovisitors/{uid}/businesses/{bizId}in Firestore ChatGPTcomponent fetches system prompt (GET /api/portal/prompt) and tool definitions (GET /api/portal/tools)- Messages sent to
POST /api/chat(OpenAI Chat Completions with streaming) - Tool calls executed via
POST /api/portal/tools(server-side, same tool registry as phone) - On unmount, interaction logged via
POST /api/portal/interaction→ Firestorebusinesses/{bizId}/interactions - Chat history persisted in
localStorageper business
Hosting: Firebase Hosting (React SPA) Route: /portal/:slug → Portal.tsxAuth: Firebase Auth (Google OAuth + email/password) Firebase SDK: Auth only (for sign-in + ID tokens). Firestore used server-side only. AI: OpenAI Chat Completions API (via our Cloud Run proxy) Runtime cost: OpenAI API usage per message
Files:
web/src/components/Portal.tsx— Portal page: slug resolution, auth gate, layout shell, interaction loggingweb/src/components/ChatGPT.tsx— Reusable chat component: streaming, tool calls, message displayweb/src/components/TexterHome.tsx— "Your Chats" page: lists businesses the visitor has chatted withservices/api/serve.js— API endpoints:/api/portal/resolve,/api/portal/prompt,/api/portal/tools,/api/portal/interaction,/api/chat
Comparison
| Aspect | Static Site Chat | Portal Chat |
|---|---|---|
| Auth required | No | Yes (Firebase Auth) |
| AI | No (pattern matching) | Yes (OpenAI Chat API) |
| Tools (calendar, etc) | No | Yes |
| Firebase SDK in client | No | Auth only |
| Firestore reads at runtime | No | Yes (server-side via API) |
| SEO crawlable | Yes | No (SPA, auth-gated) |
| Hosting | GCS (static files) | Firebase Hosting (React) |
| Runtime cost | Zero | OpenAI API per message |
| Chat history | None (stateless) | localStorage + Firestore interaction log |
| Conversation depth | Single Q&A turn | Multi-turn with context |
Flow: Static Site → Portal
- Visitor lands on static site (SEO, shared link, etc.)
- Uses dumb chat for basic questions (hours, location, services)
- For deeper questions, clicks "Chat" button in navbar → opens portal in new tab
- Signs in → full AI chat with tool access (booking, lookups, etc.)
- Interaction logged to business dashboard