Skip to content

Shahi235/AI-ChatBot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TeamChat AI

A multi-tenant collaborative AI chat platform where multiple users inside the same organization can chat in shared rooms and invoke Gemini AI using @Gemini or @AI. The AI understands full conversation history with speaker attribution and streams its response back to all room members in real-time.


Tech Stack

Layer Technology
Frontend React + TypeScript + Vite (Developer 1)
Backend Node.js + TypeScript + Express + Socket.IO
Auth Firebase Authentication
Database Cloud Firestore (persistence only)
AI Vertex AI — Gemini 1.5 Flash
Backend hosting Google Cloud Run
Frontend hosting Firebase Hosting
Admin SDK Firebase Admin SDK v12

Architecture

┌──────────────────────────────────────────────────────────────┐
│                  Browser (Firebase Hosting)                  │
│  React + TypeScript + Socket.IO client                       │
│                                                              │
│  1. Sign in ──────────────────────────► Firebase Auth        │
│  2. GET /api/me, /api/rooms ──────────► Backend REST         │
│  3. GET /api/rooms/:id/messages ──────► Backend REST         │
│  4. socket.io connect (ID token) ─────► Backend WebSocket    │
│     room:join / message:send / typing:start ...              │
│  ◄─── message:new / ai:message_chunk / presence:update ─── │
└──────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────┐
│            Cloud Run Backend (Express + Socket.IO)           │
│                                                              │
│  authMiddleware     verifyIdToken ─────► Firebase Auth       │
│  socketAuth         verifyIdToken ─────► Firebase Auth       │
│                     getUserByUid ──────► Firestore           │
│                                                              │
│  roomManager        in-memory presence & typing state        │
│                                                              │
│  message:send ──────────────────────── saveMessage ─────────► Firestore  │
│               ──────────────────────── broadcast ──────────► All clients │
│               ──── if @Gemini ──────── Vertex AI stream                   │
│                                        updateMessage ────────► Firestore  │
│                                        ai:message_chunk ────► All clients │
└──────────────────────────────────────────────────────────────┘

Important: Real-time via centralized WebSocket — NOT Firestore listeners

The frontend does not use onSnapshot or any Firestore real-time listener for chat. All real-time events (messages, typing, presence, AI streaming) flow through a centralized Socket.IO server running inside the Cloud Run backend.

  • Frontend connects once via Socket.IO with a Firebase ID token.
  • The backend verifies the token, loads the user profile, and authorizes all events.
  • Messages are persisted to Firestore by the backend, then broadcast to connected clients.
  • The frontend reads Firestore only for the initial history load via GET /api/rooms/:roomId/messages.

Firestore Schema

users/{uid}                          ← global index for fast socket auth
  uid, email, name, orgId, orgName, role

organizations/{orgId}
  id, name, slug, createdAt

  users/{userId}
    uid, name, email, orgId, orgName, role, createdAt

  rooms/{roomId}
    id, orgId, name, description, memberIds[], createdBy, createdAt

    messages/{messageId}
      id, orgId, roomId, senderId, senderName,
      senderType ("user"|"ai"|"system"),
      content, status ("streaming"|"complete"|"error"), createdAt

    typing/{userId}    ← not used by clients; managed in backend memory
    presence/{userId}  ← not used by clients; managed in backend memory

REST API

Method Path Auth Description
GET /health none Health check
GET /api/me Bearer token Returns current user profile
GET /api/rooms Bearer token Lists rooms user is member of
GET /api/rooms/:roomId/messages Bearer token Last 50 messages (asc)
POST /api/rooms Bearer token (admin) Creates a new room

WebSocket Events

Client → Server

Event Payload Description
room:join { roomId } Join a room (auth + membership verified)
room:leave { roomId } Leave a room
message:send { roomId, content } Send a message; triggers Gemini if @Gemini/@AI
typing:start { roomId } Start typing indicator
typing:stop { roomId } Stop typing indicator

Server → Client

Event Payload Description
socket:ready { userId, orgId } Emitted after successful connection
message:new { id, orgId, roomId, senderId, senderName, senderType, content, status, createdAt } New user message broadcast
typing:update { roomId, users: [{userId, name}] } Current typing users
presence:update { roomId, onlineUsers: [{userId, name}] } Current online users in room
ai:message_started { id, orgId, roomId, senderId, senderName, senderType, content, status, createdAt } Gemini starts responding
ai:message_chunk { id, roomId, chunk, content } Streaming chunk + accumulated text
ai:message_completed { id, roomId, status } Gemini response complete
ai:error { roomId, message } Gemini failed
error { message } General socket error

Socket room key format

org:${orgId}:room:${roomId}

This guarantees tenant isolation at the Socket.IO room level. Even if two organizations both have a room called general, they are always separate Socket.IO rooms.


Security & Tenant Isolation

What is trusted

Field Source Trusted?
uid Firebase ID token (verified by Admin SDK) Yes
orgId Backend Firestore profile (loaded by uid) Yes
senderName Backend Firestore profile Yes
role Backend Firestore profile Yes
orgId from request body/payload Frontend Never
senderId from client Frontend Never

Firestore security rules

Since all writes go through the backend Admin SDK (which bypasses rules), the client-facing rules are deliberately strict:

  • users/{uid} — user can read only their own global profile
  • organizations/{orgId} — org members can read
  • organizations/{orgId}/users — org members can read; no client writes
  • organizations/{orgId}/rooms/{roomId} — room members can read; no client writes
  • organizations/{orgId}/rooms/{roomId}/messages — room members can read only; no client writes
  • typing / presencedeny all client reads and writes (managed in backend memory)

Local Setup

Prerequisites

  • Node.js 20+
  • Firebase CLI: npm install -g firebase-tools
  • Firebase project with Firestore and Authentication (Email/Password) enabled
  • GCP project with Vertex AI API enabled
  • Service account key downloaded locally (for local dev — do not commit)

Backend

cd backend
cp .env.example .env
# Fill in GOOGLE_CLOUD_PROJECT, FIREBASE_PROJECT_ID, GOOGLE_APPLICATION_CREDENTIALS
npm install
npm run dev

Server starts at http://localhost:8080 with WebSocket on the same port.

Verify:

curl http://localhost:8080/health
# { "status": "ok", "service": "teamchat-ai-backend" }

Frontend (Developer 1)

cd frontend
cp .env.example .env
# Set VITE_BACKEND_URL=http://localhost:8080
npm install
npm run dev

Environment Variables

backend/.env

Variable Required Default Description
PORT No 8080 Server port
NODE_ENV No development development or production
GOOGLE_CLOUD_PROJECT Yes GCP project ID (Vertex AI)
FIREBASE_PROJECT_ID Yes Firebase project ID
GOOGLE_APPLICATION_CREDENTIALS Local dev only Path to service account key JSON
VERTEX_LOCATION No us-central1 Vertex AI region
GEMINI_MODEL No gemini-1.5-flash Gemini model name
FRONTEND_ORIGIN No http://localhost:5173 CORS-allowed frontend URL

Seeding Test Data

cd backend

GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json \
FIREBASE_PROJECT_ID=your-project-id \
GOOGLE_CLOUD_PROJECT=your-project-id \
npm run seed

Creates:

  • Firebase Auth users for all 6 test accounts
  • organizations/{orgId}/users/{uid} profile documents
  • users/{uid} global index documents
  • Rooms and sample messages for both orgs

The script is idempotent — safe to run multiple times.


Deployment

1. Enable GCP services

gcloud services enable \
  run.googleapis.com \
  aiplatform.googleapis.com \
  firestore.googleapis.com \
  --project YOUR_PROJECT_ID

2. Deploy Firestore rules and indexes

firebase deploy --only firestore --project YOUR_PROJECT_ID

3. Build and deploy backend to Cloud Run

cd backend

# Build and push image
gcloud builds submit \
  --tag gcr.io/YOUR_PROJECT_ID/teamchat-ai-backend \
  --project YOUR_PROJECT_ID

# Deploy — max-instances=1 is required for the in-memory WebSocket state
gcloud run deploy teamchat-ai-backend \
  --image gcr.io/YOUR_PROJECT_ID/teamchat-ai-backend \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --max-instances 1 \
  --min-instances 1 \
  --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID,FIREBASE_PROJECT_ID=YOUR_PROJECT_ID,VERTEX_LOCATION=us-central1,GEMINI_MODEL=gemini-1.5-flash,FRONTEND_ORIGIN=https://YOUR_PROJECT_ID.web.app \
  --service-account YOUR_SA@YOUR_PROJECT_ID.iam.gserviceaccount.com \
  --project YOUR_PROJECT_ID

Service account IAM roles needed:

  • roles/aiplatform.user
  • roles/datastore.user
  • roles/firebase.sdkAdminServiceAgent

4. Run seed script against production Firestore

cd backend
FIREBASE_PROJECT_ID=YOUR_PROJECT_ID \
GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID \
GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json \
npm run seed

5. Deploy frontend to Firebase Hosting

cd frontend
VITE_BACKEND_URL=https://YOUR_CLOUD_RUN_URL npm run build
cd ..
firebase deploy --only hosting --project YOUR_PROJECT_ID

Cloud Run — max instances note

MVP constraint: This deployment uses --max-instances 1. The in-memory roomManager (presence, typing) is local to the process. With multiple instances, users on different instances would not see each other's presence or typing.

Production scaling path: Use the Socket.IO Redis adapter (@socket.io/redis-adapter) with Google Cloud Memorystore (Redis), which allows horizontal scaling without losing shared state.


Live Demo

URL: [Add your deployed Firebase Hosting URL here]

Backend health: [Add your Cloud Run URL here]/health


Tenant Isolation Test

  1. Open Browser A. Log in as sarah@acme.com / Test@123.
  2. Confirm only Acme Corp rooms (general, engineering) are listed.
  3. Join the engineering room and send a message.
  4. Open Browser B (Incognito). Log in as mike@acme.com / Test@123.
  5. Confirm Mike receives Sarah's message through WebSocket in real-time.
  6. Send @Gemini what do you recommend? — confirm the AI response streams to both browsers.
  7. Open Browser C (another Incognito). Log in as john@beta.com / Test@123.
  8. Confirm John sees only Beta Labs rooms (general, product).
  9. John cannot receive any Acme Corp messages — they are on a different Socket.IO room key.
  10. Any attempt to emit room:join with an Acme room ID from John's socket will be rejected with an error event.

Known Limitations

  • Single Cloud Run instance required for MVP — in-memory presence/typing state is not shared across instances. Set --max-instances 1.
  • No rate limiting — add express-rate-limit before production.
  • Typing indicators are not persisted to Firestore — they only exist in memory and are lost on server restart.
  • Firebase Auth password reset flows are out of scope.
  • Room listing uses a Firestore array-contains query — performs well for up to ~100 rooms per org.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors