Skip to content

Instantly share code, notes, and snippets.

@AlexandroMtzG
Created January 12, 2026 02:56
Show Gist options
  • Select an option

  • Save AlexandroMtzG/ff00bd94416a76de2bcd1163e065f480 to your computer and use it in GitHub Desktop.

Select an option

Save AlexandroMtzG/ff00bd94416a76de2bcd1163e065f480 to your computer and use it in GitHub Desktop.

AI Chatbot Architecture

This document explains the built-in AI chatbot feature in SaasRock. The chatbot uses Vercel AI SDK for streaming responses and provides context-specific tools across three contexts: Tenant, Admin, and Marketing.


Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              CLIENT ACCESS POINTS                               β”‚
β”‚                                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Nav Bar Icon   β”‚  β”‚  Tenant Chat    β”‚  β”‚  Admin Chat     β”‚  β”‚ Marketing β”‚  β”‚
β”‚  β”‚  (Dialog Mode)  β”‚  β”‚  /app/:t/chat   β”‚  β”‚  /admin/chat    β”‚  β”‚ Homepage  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚          β”‚                    β”‚                    β”‚                   β”‚        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                    β”‚                    β”‚                   β”‚
           β–Ό                    β–Ό                    β–Ό                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            CHATBOT WIDGET (React)                               β”‚
β”‚                                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  AI Elements Components (11 reusable)                                   β”‚    β”‚
β”‚  β”‚  conversation | message | response | reasoning | tool | sources         β”‚    β”‚
β”‚  β”‚  code-block | loader | actions | prompt-input | suggestion              β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                      β”‚ POST with context
                                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                               API ROUTES                                        β”‚
β”‚                                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ /api/ai/chat    β”‚  β”‚ /api/ai/chat    β”‚  β”‚ /api/ai/chat/marketing         β”‚  β”‚
β”‚  β”‚    /:tenant     β”‚  β”‚    /admin       β”‚  β”‚ (No auth required)             β”‚  β”‚
β”‚  β”‚ (Auth required) β”‚  β”‚ (Admin only)    β”‚  β”‚                                β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                                      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         STREAMING SERVICE                                       β”‚
β”‚                       (chatbot-stream.server.ts)                                β”‚
β”‚                                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Tenant Tools    β”‚  β”‚ Admin Tools     β”‚  β”‚ Marketing Tools                β”‚  β”‚
β”‚  β”‚ - session       β”‚  β”‚ - accounts_list β”‚  β”‚ - features_list                β”‚  β”‚
β”‚  β”‚ - info          β”‚  β”‚ - users_list    β”‚  β”‚ - pricing_info                 β”‚  β”‚
β”‚  β”‚ - navigate      β”‚  β”‚ - users_find    β”‚  β”‚                                β”‚  β”‚
β”‚  β”‚ - logs_recent   β”‚  β”‚ - stats         β”‚  β”‚                                β”‚  β”‚
β”‚  β”‚ + MCP tools     β”‚  β”‚ - navigate      β”‚  β”‚                                β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β–Ό                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         OPENAI API             β”‚  β”‚              DATABASE (Prisma)              β”‚
β”‚                                β”‚  β”‚                                             β”‚
β”‚  - GPT-4o (default)            β”‚  β”‚  Chat ──┬── ChatMessage ── ChatMessagePart  β”‚
β”‚  - GPT-4o-mini                 β”‚  β”‚         β”‚                                   β”‚
β”‚  - o1, o1-mini, o1-pro         β”‚  β”‚         └── GeneratedObject (tracking)      β”‚
β”‚  - o3-mini                     β”‚  β”‚                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Module Structure

app/modules/chatbot/
β”œβ”€β”€ chatbot-config.ts              # Centralized configuration
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ chatbot-widget.tsx         # Main chat UI component
β”‚   └── chat-list.tsx              # Chat history list
└── lib/
    β”œβ”€β”€ chatbot.db.server.ts       # Database operations
    β”œβ”€β”€ chatbot-stream.server.ts   # Shared streaming service
    β”œβ”€β”€ chatbot-dtos.ts            # Prisma-based DTOs
    β”œβ”€β”€ chatbot-context-dtos.ts    # Stream config types
    β”œβ”€β”€ chatbot-cookie.server.ts   # Session management (marketing)
    β”œβ”€β”€ tools-admin.server.ts      # Admin prompt + tools
    β”œβ”€β”€ tools-tenant.server.ts     # Tenant prompt + tools
    └── tools-marketing.server.ts  # Marketing prompt + tools

app/modules/ai-gen/
β”œβ”€β”€ db/
β”‚   └── ai-generation.db.server.ts # Generation tracking
β”œβ”€β”€ lib/
β”‚   └── ai-models.ts               # Available AI models
β”œβ”€β”€ dtos/
β”‚   └── ai-generation-dto.ts       # Generation DTOs
└── services/
    └── ai-tools.server.ts         # AI tool utilities

app/components/ai-elements/        # 11 reusable components
β”œβ”€β”€ conversation.tsx               # Chat container with scroll
β”œβ”€β”€ message.tsx                    # Individual message wrapper
β”œβ”€β”€ prompt-input.tsx               # Input with model selector
β”œβ”€β”€ response.tsx                   # AI response with markdown
β”œβ”€β”€ reasoning.tsx                  # Collapsible reasoning display
β”œβ”€β”€ tool.tsx                       # Tool call visualization
β”œβ”€β”€ sources.tsx                    # Citation display
β”œβ”€β”€ code-block.tsx                 # Syntax highlighting
β”œβ”€β”€ loader.tsx                     # Loading indicators
β”œβ”€β”€ actions.tsx                    # Message action buttons
└── suggestion.tsx                 # Quick suggestion buttons

Database Schema

model Chat {
  id         String         @id @default(cuid())
  createdAt  DateTime       @default(now())
  updatedAt  DateTime       @updatedAt
  type       String         // "tenant" | "admin" | "marketing"
  tenantId   String?        // For tenant chats
  userId     String?        // For authenticated users
  sessionId  String?        // For anonymous marketing chats
  title      String
  messages   ChatMessage[]

  tenant     Tenant?        @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user       User?          @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model ChatMessage {
  id        String            @id @default(cuid())
  createdAt DateTime          @default(now())
  updatedAt DateTime          @updatedAt
  chatId    String
  role      String            // "user" | "assistant"
  parts     ChatMessagePart[]

  chat      Chat              @relation(fields: [chatId], references: [id], onDelete: Cascade)
}

model ChatMessagePart {
  id        String      @id @default(cuid())
  createdAt DateTime    @default(now())
  messageId String
  order     Int
  type      String      // "text" | "reasoning" | "tool-*" | "source-url"
  content   String      // JSON stringified

  message   ChatMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
}

model GeneratedObject {
  id            String   @id @default(cuid())
  createdAt     DateTime @default(now())
  userId        String
  chatId        String?  // Nullable, SetNull on delete
  object        String   // "ChatMessage"
  type          String   // "chat"
  model         String
  status        String   // "pending" | "success" | "error"
  inputTokens   Int      @default(0)
  outputTokens  Int      @default(0)
  costInCents   Float    @default(0)
  timeMs        Int      @default(0)

  user          User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  chat          Chat?    @relation(fields: [chatId], references: [id], onDelete: SetNull)
}

Three Chat Contexts

Context Route Auth Tools Persistence
Tenant /app/:tenant/chat Required session, info, navigate, logs, MCP User + Tenant ID
Admin /admin/chat Admin only accounts, users, stats, navigate User ID
Marketing Homepage dialog None features, pricing Session cookie

Tenant Tools

Tool Description
system_session Get current account and user info
system_info Platform features and capabilities
system_navigate Navigate to platform sections
tenant_logs_recent View recent application logs
+ MCP tools Extended tools via MCP server at /mcp

Admin Tools

Tool Description
system_session Get current user profile
admin_accounts_list List all tenant accounts
admin_users_list List platform users
admin_users_find Search users by email/name
admin_stats_platform Platform statistics
admin_navigate Navigate to admin sections

Marketing Tools

Tool Description
system_session Visitor session info
marketing_features_list SaasRock features
marketing_pricing_info Pricing page link

API Routes

Endpoint Method Context Auth Required
/api/ai/chat/:tenant POST Tenant Yes
/api/ai/chat/admin POST Admin Yes (admin)
/api/ai/chat/marketing POST Marketing No

Request Format

{
  chatId?: string;           // Existing chat ID or undefined for new
  messages: Message[];       // Conversation history
  model?: string;            // Model override (optional)
}

Response Format

Server-Sent Events (SSE) stream with text deltas, tool calls, and message parts.


Configuration

Chatbot Config (chatbot-config.ts)

type ChatbotContextConfig = {
  enabled: boolean;            // Enable/disable this context
  name: string;                // Assistant name (e.g., "Sales Assistant")
  defaultModel: LanguageModel; // Default AI model
  maxSteps: number;            // Max tool execution steps
  showModelSelector: boolean;  // Show model dropdown in UI
  showAttachments: boolean;    // Allow file attachments
  showTools: boolean;          // Show tool invocations in chat
  suggestions: string[];       // Quick action suggestions
  welcomeMessages: string[];   // Welcome messages when chat is empty
  pages?: { path: string; exact: boolean }[]; // Marketing pages whitelist
};

Default Settings by Context

Setting Marketing Admin Tenant
enabled true true true
name Sales Assistant Admin Assistant User Assistant
maxSteps 2 5 5
showModelSelector false false false
showAttachments false true true
showTools false true true

Environment Variables

OPENAI_API_KEY=sk-...         # Required for OpenAI models
ANTHROPIC_API_KEY=...         # Required for Claude models (future)

Available Models

Configured in app/modules/ai-gen/lib/ai-models.ts:

Model Provider Cost (per 1M input tokens)
gpt-4o OpenAI $0.25
gpt-4o-mini OpenAI $0.015
o1 OpenAI $1.50
o1-mini OpenAI $0.30
o1-pro OpenAI $15.00
o3-mini OpenAI $1.10

Adding Custom Tools

Tools are defined per context in:

app/modules/chatbot/lib/
β”œβ”€β”€ tools-admin.server.ts
β”œβ”€β”€ tools-tenant.server.ts
└── tools-marketing.server.ts

Each file exports:

  • CONTEXT_SYSTEM_PROMPT - System prompt function
  • getContextChatTools - Tool definitions using Zod schemas
  • ContextChatContext - TypeScript type for the context

Tool Definition Example

import { z } from "zod";
import { tool } from "ai";

my_custom_tool: tool({
  description: "What this tool does",
  inputSchema: z.object({
    param: z.string().describe("Parameter description"),
  }),
  execute: async ({ param }) => {
    // Tool logic here
    return { result: "data" };
  },
}),

Admin Monitoring

Routes

Route Purpose
/admin/ai Overview + Stats
/admin/ai/generations All AI Generations table
/admin/ai/chats All Chats table
/admin/ai/chats/:id Chat Detail (Read-Only)

Stats Dashboard

  • Total Generations
  • Total Cost
  • Average Cost per Object
  • Average Time per Object
  • Breakdown by Object Type
  • Breakdown by Model

Request/Response Flow

1. User types message in ChatbotWidget
2. Widget calls API route with chatId + messages
3. API route calls createChatStreamResponse()
4. StreamService:
   - Ensures chat exists in DB
   - Saves user message
   - Loads conversation history
   - Creates GeneratedObject (pending)
   - Calls streamText() with context tools
5. Tool Execution Loop:
   - AI requests tool call
   - Execute tool
   - Return result to AI
6. AI streams response chunks
7. StreamService sends SSE to client
8. Widget displays streaming text
9. StreamService saves assistant message
10. Updates GeneratedObject (success + metrics)

Quick Reference

Adding Chatbot to a Page

import { ChatbotWidget } from "~/modules/chatbot/components/chatbot-widget";

<ChatbotWidget
  context="tenant"      // "tenant" | "admin" | "marketing"
  chatId={chatId}       // Optional: existing chat
  initialMessages={[]}  // Optional: preload messages
  readOnly={false}      // Optional: disable input
/>

Creating a New Context

  1. Create tools-{context}.server.ts in app/modules/chatbot/lib/
  2. Export CONTEXT_SYSTEM_PROMPT, getContextChatTools, ContextChatContext
  3. Create API route at app/routes/api/ai.chat.{context}.ts
  4. Add context config to chatbot-config.ts
  5. Create chat routes at app/routes/{path}/chat/

Enabling MCP Integration

The tenant chat automatically connects to the MCP server at /mcp when available. MCP tools extend the chatbot's capabilities with additional data access.

See MCP Server documentation for available tools.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment