# NIPRO JMI Pharma — AI Chatbot Complete Documentation

---

## Table of Contents

1. [What is this Chatbot?](#1-what-is-this-chatbot)
2. [Technology Stack](#2-technology-stack)
3. [File & Folder Structure](#3-file--folder-structure)
4. [How It Works — Step by Step](#4-how-it-works--step-by-step)
5. [Backend Implementation](#5-backend-implementation)
6. [Frontend Implementation](#6-frontend-implementation)
7. [Data Sources](#7-data-sources)
8. [Knowledge Base Sections](#8-knowledge-base-sections)
9. [Conversation Memory (History)](#9-conversation-memory-history)
10. [Token Management Strategy](#10-token-management-strategy)
11. [Anti-Hallucination Rules](#11-anti-hallucination-rules)
12. [SSL Certificate Handling](#12-ssl-certificate-handling)
13. [Rate Limit Handling](#13-rate-limit-handling)
14. [Environment Configuration](#14-environment-configuration)
15. [Production Deployment](#15-production-deployment)
16. [How to Update Company Information](#16-how-to-update-company-information)
17. [Database Models Used](#17-database-models-used)
18. [Troubleshooting Guide](#18-troubleshooting-guide)
19. [Future Improvements](#19-future-improvements)

---

## 1. What is this Chatbot?

This is a **RAG-based (Retrieval-Augmented Generation) AI chatbot** built specifically for the NIPRO JMI Pharma website.

**What RAG means in simple terms:**
- RAG = your own data + AI model combined
- Instead of relying only on what the AI was trained on, we fetch **real data from your database** and **static company knowledge**, then pass it to the AI as context
- The AI then answers the user's question using that context — not from memory or the internet

**What the chatbot can do:**
- Answer product questions using live database data (120+ products)
- Explain company info, leadership, history, mission/vision
- Provide depot contact details for all 22 distribution locations
- Answer job/career questions from live job listings
- Remember the last few messages in a conversation (context memory)
- Reply in English or Bengali based on user's language

---

## 2. Technology Stack

| Layer | Technology |
|---|---|
| Frontend Framework | React (Vite) |
| Backend Framework | Laravel 11 (PHP) |
| Database | MySQL |
| AI Provider | Groq API (free tier) |
| AI Model | `llama-3.1-8b-instant` (Meta Llama 3.1) |
| API Communication | Laravel HTTP Client (Guzzle) |
| Auth | Laravel Sanctum (not needed for chatbot — public endpoint) |
| Local Dev | Laravel Herd (Windows) |

**Why Groq + Llama 3.1?**
- 100% free tier — no credit card required
- 14,400 requests/day, 6,000 tokens/minute
- Response time under 1 second (fastest free LLM available)
- No data is stored by Groq on free tier

---

## 3. File & Folder Structure

### New Files Created

```
laravel-backend/
├── CHATBOT.md                                          ← This documentation
├── cacert.pem                                          ← SSL cert bundle (local dev only)
│
├── app/Http/Controllers/Api/
│   ├── ChatbotController.php                           ← Main chatbot logic (NEW)
│   └── ChatbotKnowledge.php                            ← Static company knowledge base (NEW)
│
nipro-frontend/src/
└── components/
    └── Chatbot/
        ├── Chatbot.jsx                                 ← Chat widget UI component (NEW)
        └── Chatbot.css                                 ← Chat widget styles (NEW)
```

### Modified Files

```
laravel-backend/
├── .env                                                ← Added GEMINI_API_KEY (Groq key)
├── .gitignore                                          ← Added cacert.pem
├── config/services.php                                 ← Added gemini.api_key config
└── routes/api.php                                      ← Added POST /chatbot route

nipro-frontend/src/
├── App.jsx                                             ← Added Chatbot import + ChatbotWrapper
├── components/ScrollToTop.css                          ← Repositioned scroll button above chatbot
└── ApiService/api.jsx                                  ← Added sendChatMessage export
```

---

## 4. How It Works — Step by Step

```
STEP 1 — User types a message in the chat widget
         Example: "tell me about glucofast"

STEP 2 — React frontend sends POST request to Laravel API
         Payload: { message: "tell me about glucofast", history: [...last 4 messages] }
         Endpoint: POST /api/chatbot

STEP 3 — Laravel ChatbotController::chat() receives the request
         - Validates message (max 500 chars) and history array
         - Calls buildContext($message) to gather relevant data

STEP 4 — buildContext() assembles context from multiple sources:

         A) ChatbotKnowledge::getRelevantSections($message)
            → Checks message keywords → loads only relevant knowledge sections
            → Example: "glucofast" = product name → skips distribution/careers sections

         B) Settings table (MySQL)
            → Reads phone, email, address from admin settings

         C) Products table (MySQL) — only if product-related question
            → Searches by product name LIKE '%glucofast%'
            → Falls back to generic/brand/therapeutic search if no name match
            → Returns: name, generic, dosage form, brand, therapeutic, description

         D) Jobs table — only if career-related question
         E) Blogs table — only if news-related question

STEP 5 — callGemini() builds the API payload:
         - System prompt with strict anti-hallucination rules
         - Conversation history (last 4 messages, trimmed to 200 chars each)
         - Context from Step 4
         - Current user message

STEP 6 — Groq API (llama-3.1-8b-instant) receives the full payload
         → Generates a natural language response based ONLY on the context provided
         → Returns response in same language as user (English or Bengali)

STEP 7 — Laravel returns { "reply": "Glucofast is a Metformin..." } as JSON

STEP 8 — React frontend receives the reply
         → Adds it to the messages state
         → Renders as a chat bubble with timestamp
         → Auto-scrolls to the latest message
```

---

## 5. Backend Implementation

### 5.1 Route

**File:** `routes/api.php`

```php
// Public route — no authentication required
Route::post('/chatbot', [App\Http\Controllers\Api\ChatbotController::class, 'chat']);
```

**Full URL:** `POST http://laravel-backend.test/api/chatbot`

**Request body:**
```json
{
  "message": "tell me about glucofast",
  "history": [
    { "role": "user", "text": "what antidiabetic medicines do you have?" },
    { "role": "assistant", "text": "We have several antidiabetic products..." }
  ]
}
```

**Response:**
```json
{ "reply": "Glucofast contains Metformin Hydrochloride..." }
```

---

### 5.2 ChatbotController.php

**File:** `app/Http/Controllers/Api/ChatbotController.php`

**Three methods:**

#### `chat()` — Entry point
```
1. Validates: message (required, max 500), history (optional array, max 10 items)
2. Calls buildContext() → gets assembled context string
3. Calls callGemini() → gets AI reply
4. Returns JSON { reply: "..." }
```

#### `buildContext()` — Context assembler
```
Loads data from multiple sources based on keywords detected in the message:

SOURCE 1: ChatbotKnowledge::getRelevantSections()
  → Smart loader — only returns sections relevant to the message
  → See Section 8 for all knowledge sections

SOURCE 2: Settings table
  → Always loads: phone, email, address, about (if set in admin)

SOURCE 3: Products table (MySQL) — triggered by:
  → Product keywords: 'product', 'medicine', 'tablet', 'capsule', etc.
  → Short messages ≤4 words (likely a product name lookup)
  → NOT triggered for: leadership, contact, depot, history queries

  Product search flow:
  a) Search by product name: WHERE name LIKE '%keyword%' LIMIT 8
  b) Fallback: search by generic/brand/therapeutic name
  c) "All products" intent: fetch ALL active products (names only)
  d) Single match → full description (400 chars)
  e) Multiple matches → short description (120 chars each)

SOURCE 4: Jobs table — triggered by: 'job', 'career', 'vacancy', 'hiring', etc.
  → WHERE is_active=true AND deadline >= today LIMIT 6

SOURCE 5: Blogs table — triggered by: 'news', 'blog', 'update', 'latest', etc.
  → WHERE is_active=true AND is_archived=false ORDER BY created_at DESC LIMIT 5
```

#### `callGemini()` — AI caller
```
1. Builds system prompt with anti-hallucination rules
2. Builds messages array:
   - [system] = system prompt + full context
   - [history messages] = last 4, each trimmed to 200 chars
   - [user] = current message
3. Calls Groq API: POST https://api.groq.com/openai/v1/chat/completions
4. If 429 rate limit → waits 10-12 seconds → retries once
5. Returns AI text response
```

**Model settings:**
```php
'model'       => 'llama-3.1-8b-instant'
'temperature' => 0.4       // Lower = more factual, less creative
'max_tokens'  => 600       // Max length of AI response
```

---

### 5.3 ChatbotKnowledge.php

**File:** `app/Http/Controllers/Api/ChatbotKnowledge.php`

This file contains all static company information. **Edit this file to update company data — no controller changes needed.**

The class has **modular sections** — only relevant sections are loaded per request to minimize token usage:

| Method | What it contains | ~Tokens |
|---|---|---|
| `core()` | Company name, CEO, website — always included | ~50 |
| `contact()` | Phone, email, office hours, address | ~80 |
| `aboutCompany()` | History, mission, vision, motto, leadership, workforce | ~300 |
| `qualityPolicy()` | Quality policy text, certifications, metrics | ~150 |
| `manufacturing()` | Plant location, capacity, facilities | ~150 |
| `rnd()` | R&D team, labs, milestones | ~120 |
| `globalOperations()` | 14 exporting + 16 developing countries | ~100 |
| `distribution()` | 22 depots by division (summary) | ~150 |
| `distributionDetails()` | Full addresses + phones for all 22 depots | ~500 |
| `careers()` | Why work here, HR contact | ~80 |

**Smart loader — `getRelevantSections(string $messageLower)`:**

```
Message keyword → Section loaded
─────────────────────────────────────────────────────
phone/email/contact/address/hours    → contact()
about/who/njpl/ceo/chairman/employ   → aboutCompany()
quality/iso/gmp/certif               → qualityPolicy()
manufactur/facilit/plant/capacity    → manufacturing()
research/rnd/lab/formulat            → rnd()
global/export/country/international  → globalOperations()
depot/network/dhaka/chittagong/etc   → distribution()
phone/address + depot keywords       → distribution() + distributionDetails()
career/job/vacancy/hiring            → careers()
(nothing matched)                    → aboutCompany() + contact()  ← default
```

---

## 6. Frontend Implementation

### 6.1 Chatbot.jsx

**File:** `src/components/Chatbot/Chatbot.jsx`

**State managed:**
```js
messages      // Array of { id, role, text, timestamp }
input         // Current textarea value
isLoading     // Shows typing indicator
isOpen        // Chat window open/closed
hasNewMessage // Unread badge on FAB button
```

**Component flow:**
```
1. FAB button (bottom-right) → onClick → setIsOpen(true)
2. Chat window slides in with CSS animation
3. Initial greeting message shown with 3 quick suggestion chips
4. User types message → Enter key or Send button
5. sendMessage() called:
   a. Adds user message to messages state
   b. Builds history from last 8 messages (trimmed to 150 chars each)
   c. POST /api/chatbot with { message, history }
   d. Shows typing indicator (animated 3 dots)
   e. Receives reply → adds to messages state
6. Auto-scroll to bottom on new message
7. Escape key closes the window
```

**Quick suggestions shown on open:**
```
Who is the CEO? | Who is the Chairman? | Tell me about the Joint Venture
```

**Message formatting:**
```js
// **bold text** → <strong>bold text</strong>
// \n → <br />
formatMessageText(text)
```

### 6.2 ChatbotWrapper in App.jsx

```jsx
// Hides chatbot on all /admin/* routes
function ChatbotWrapper() {
  const location = useLocation();
  if (location.pathname.startsWith('/admin')) return null;
  return <Chatbot />;
}

// Mounted inside <Router>, outside <Routes>, before <ToastContainer>
<ChatbotWrapper />
```

### 6.3 Chatbot.css

**Brand color:** `#0153A5` (matches Header phone link color)

**Key CSS variables:**
```css
--chat-primary:    #0153A5
--chat-primary-dk: #013d7d
--chat-window-w:   370px
--chat-window-h:   540px
```

**Responsive:** On mobile (≤480px), chat window becomes full-width bottom sheet.

**ScrollToTop repositioning** (`ScrollToTop.css`):
```css
/* Moved up to sit above the chatbot FAB */
bottom: 100px;  /* desktop (was: 2rem = 32px) */
bottom: 90px;   /* mobile  (was: 1.5rem) */
```

---

## 7. Data Sources

| Data Type | Source | When Loaded |
|---|---|---|
| Product info | `products` MySQL table | Product-related questions |
| Brand names | `brands` MySQL table | Via Product relationship |
| Generic names | `generics` MySQL table | Via Product relationship |
| Dosage forms | `dosage_forms` MySQL table | Via Product relationship |
| Therapeutics | `therapeutics` MySQL table | Via Product relationship |
| Job openings | `career_openings` MySQL table | Career-related questions |
| Blog/news titles | `blogs` MySQL table | News-related questions |
| Contact settings | `settings` MySQL table | Always loaded |
| Company profile | `ChatbotKnowledge.php` | Keyword-based sections |
| Leadership info | `ChatbotKnowledge.php` | Who/leadership questions |
| Depot contacts | `ChatbotKnowledge.php` | Location/depot questions |

---

## 8. Knowledge Base Sections

The full knowledge base in `ChatbotKnowledge.php` contains:

**Company Identity:**
- Full name, acronym (NJPL), established year, type
- Joint venture structure (NIPRO 51% + JMI 49%)
- Official website

**Contact:**
- Phone: (+88)-0255138726 | (+88)-0255138728
- Email: info@niprojmipharma.com
- HR Email: hrd@niprojmipharma.com
- Office Hours: 9:00 AM – 6:00 PM (Sunday–Thursday)
- Corporate address + factory address

**Leadership:**
- Founder & MD: Md. Abdur Razzaq
- Chairman: Jabed Iqbal Pathan
- CEO: Md. Mizanur Rahman
- President Nipro Corp Japan: YOSHIHIKO SANO
- Head of IT: Russel Ibne Bashar (+880 1777-742465)
- Website Developer: Md. Faisal Rabbani (Full Stack Developer)

**Workforce:**
- Total: ~1,100–1,200 employees
- IT Department: 10+ members

**Mission / Vision / Motto / Quality Policy**

**Manufacturing:**
- Location: Cumilla
- Units: Tablets (50M/month), Capsules (30M/month), Liquid (2M bottles/month), Packaging (100M units/month)
- Certifications: WHO-GMP, ISO 9001:2015, BSTI, DGDA, ISO 14001, HACCP

**R&D:** Team composition, 3 labs, 50+ formulations

**Global Operations:** 14 exporting + 16 developing countries

**Distribution Network:** All 22 depots with addresses, phone numbers, and division groupings

---

## 9. Conversation Memory (History)

Each request now includes the last few messages so the AI understands follow-up questions.

**How it works:**

Frontend sends:
```json
{
  "message": "give me the details",
  "history": [
    { "role": "user",      "text": "tell me about glucofast" },
    { "role": "assistant", "text": "Glucofast is Metformin Hydrochlori..." }
  ]
}
```

Backend passes to Groq as messages array:
```
[system]    → system prompt + company data context
[user]      → "tell me about glucofast"         ← from history
[assistant] → "Glucofast is Metformin..."        ← from history (trimmed to 200 chars)
[user]      → "give me the details"              ← current message
```

**Limits to control token usage:**
- Frontend sends last **8 messages** max, each trimmed to **150 characters**
- Backend uses last **4 messages** from history, each trimmed to **200 characters**
- This gives enough context for follow-up questions without exceeding TPM limits

---

## 10. Token Management Strategy

Groq free tier limit: **6,000 tokens per minute (TPM)**

**How we stay within limits:**

| Component | Tokens (approx) |
|---|---|
| System prompt | ~400 |
| Knowledge sections (1-2 loaded) | ~200–500 |
| DB products/jobs context | ~200–800 |
| Conversation history (4 msgs × 200 chars) | ~200 |
| Current user message | ~20 |
| **Total input** | **~1,000–1,900** |
| AI response (max_tokens: 600) | ~600 |
| **Total per request** | **~1,600–2,500** |

This means 2-3 requests can run per minute comfortably within the 6,000 TPM limit.

**Key optimizations:**
1. Knowledge base split into sections — only load what's relevant
2. Distribution details only loaded when asking for specific depot phone/address
3. Products not loaded for company/leadership questions
4. History messages trimmed to 200 chars each
5. `max_tokens` capped at 600 for responses
6. Auto-retry with 10-12 second wait on 429 rate limit errors

---

## 11. Anti-Hallucination Rules

The system prompt enforces strict rules to prevent the AI from inventing facts:

```
RULE 1: Company facts (employees, staff names, contacts, depots, products)
        → ONLY use COMPANY DATA. Never use training knowledge.

RULE 2: Products
        → ONLY list products explicitly in COMPANY DATA.
        → Never invent NJPL product names.

RULE 3: Numbers
        → Never state a specific number unless it appears in COMPANY DATA.
        → (Prevents "650 employees" type hallucinations)

RULE 4: Uncertainty
        → If unsure whether info is from training or COMPANY DATA,
          say: "I don't have verified info. Contact info@niprojmipharma.com"

ALLOWED: General pharma/medical knowledge
        → Can explain drug classes, how medicines work in general
        → Cannot apply general knowledge to specific NJPL products
```

---

## 12. SSL Certificate Handling

**Problem:** PHP/cURL on Windows doesn't have a CA bundle → SSL errors connecting to Groq API.

**Solution:** Downloaded `cacert.pem` from curl.se into the project root.

**Code (auto-detects environment):**
```php
$certPath = base_path('cacert.pem');

if (file_exists($certPath)) {
    // Local Windows dev — use the cert bundle
    $httpClient = $httpClient->withOptions(['verify' => $certPath]);
}
// Production Linux server — file doesn't exist → native SSL used automatically
```

**`cacert.pem` is in `.gitignore`** — never uploaded to production server.

---

## 13. Rate Limit Handling

**Groq free tier: 6,000 TPM**

**Backend — auto retry:**
```php
// If Groq returns 429 (rate limited)
if ($response->status() === 429) {
    sleep(10);  // wait 10 seconds
    $response = $httpClient->post(...); // retry once
}
```

**Frontend — friendly error message:**
```jsx
error.response?.status === 429
  ? "⏳ I'm a bit busy right now. Please wait a few seconds and try again."
  : "Sorry, I'm having trouble connecting..."
```

---

## 14. Environment Configuration

**`laravel-backend/.env`:**
```
GEMINI_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxx    ← Groq API key (stored under this name)
```

**`laravel-backend/config/services.php`:**
```php
'gemini' => [
    'api_key' => env('GEMINI_API_KEY'),
],
```

**After changing `.env`:**
```bash
php artisan config:clear
php artisan route:clear
```

---

## 15. Production Deployment

**Checklist:**

| Item | Action |
|---|---|
| `cacert.pem` | Do NOT upload — already in `.gitignore` |
| `GEMINI_API_KEY` | Add to production `.env` |
| SSL | Works automatically on Linux servers — no changes needed |
| Rate limits | Same free tier limits apply on production |
| Model | `llama-3.1-8b-instant` works on any server |

**No code changes needed between local and production.**

---

## 16. How to Update Company Information

Open `app/Http/Controllers/Api/ChatbotKnowledge.php` and edit the relevant section.

**Example — add a new section:**
```php
public static function awards(): string
{
    return <<<TEXT
=== AWARDS & RECOGNITION ===
- Best Pharma Company Bangladesh 2024
- ISO Excellence Award 2023
TEXT;
}
```

**Then add to `getRelevantSections()`:**
```php
if (preg_match('/award|recognition|achievement|prize/i', $messageLower)) {
    $parts[] = self::awards();
}
```

**No controller changes needed. No server restart needed.**

---

## 17. Database Models Used

| Model | Table | Key Fields Used |
|---|---|---|
| `Product` | `products` | name, slug, description, is_active |
| `Brand` | `brands` | name |
| `Generic` | `generics` | name |
| `DosageForm` | `dosage_forms` | name |
| `Therapeutic` | `therapeutics` | name |
| `Job` | `career_openings` | title, department, location, employment_type, experience_level, deadline, vacancies, is_active |
| `Blog` | `blogs` | title, category, is_active, is_archived, created_at |
| `Setting` | `settings` | key, value (phone, email, address, about) |

---

## 18. Troubleshooting Guide

| Error | Cause | Fix |
|---|---|---|
| `cURL error 60: SSL certificate problem` | Missing CA bundle on Windows | `cacert.pem` already in project root — auto-used |
| `429 Rate limit exceeded` | Too many tokens per minute | Auto-retry is built in; wait 1 minute |
| `401 Invalid API Key` | Wrong Groq key in `.env` | Update `GEMINI_API_KEY`, run `php artisan config:clear` |
| `Something went wrong` | Timeout or exception | Check `storage/logs/laravel.log` for details |
| Bot gives wrong employee count | AI hallucination from training data | Fixed by Rule 3 in system prompt |
| Bot invents products not in DB | AI hallucination | Fixed by Rule 2 in system prompt |
| "I don't have that product" for real product | Keyword not triggering product search | Check product `is_active = true` in admin |
| Follow-up question not understood | History not being sent | Check browser network tab — history array in payload |
| Chat not showing on site | Mounted only on public routes | ChatbotWrapper hides it on `/admin/*` |
| Scroll button hidden behind chatbot | CSS positioning conflict | Fixed — scroll button at `bottom: 100px` |

---

## 19. Future Improvements

- [ ] Vector/semantic search — find products by meaning, not just keyword (e.g. "blood pressure medicine" finds Losarva)
- [ ] Conversation logs in admin dashboard — view what users are asking
- [ ] Rate limit display — show user how many requests remain
- [ ] Product page deep links in chat — clickable product names linking to `/products/detail/slug`
- [ ] Image thumbnails in product responses
- [ ] "Was this helpful?" feedback buttons
- [ ] Webhook notifications to admin when users ask about unavailable products
- [ ] Multi-language support beyond English/Bengali
- [ ] Upgrade to `llama-3.3-70b-versatile` for better answer quality (still free on Groq)

---

## Built By

**Website & Chatbot:** Md. Faisal Rabbani  
**Full Stack Developer — IT Department**  
NIPRO JMI Pharma Ltd.

**IT Department Head:** Russel Ibne Bashar  
Senior Manager, Information Technology  
NIPRO JMI Pharma Ltd. | Cell: +880 1777-742465
