CipherPhantom commited on
Commit
324f5c2
Β·
verified Β·
1 Parent(s): ad533ca

Upload 16 files

Browse files
.gitattributes CHANGED
@@ -95,3 +95,6 @@ assests/6.jpeg filter=lfs diff=lfs merge=lfs -text
95
  assests/7.jpeg filter=lfs diff=lfs merge=lfs -text
96
  assests/8.jpeg filter=lfs diff=lfs merge=lfs -text
97
  assests/9.jpg filter=lfs diff=lfs merge=lfs -text
 
 
 
 
95
  assests/7.jpeg filter=lfs diff=lfs merge=lfs -text
96
  assests/8.jpeg filter=lfs diff=lfs merge=lfs -text
97
  assests/9.jpg filter=lfs diff=lfs merge=lfs -text
98
+ static/AA240126014782K_RC03012026.pdf filter=lfs diff=lfs merge=lfs -text
99
+ static/CERTIFICATE[[:space:]]OF[[:space:]]RECOGNITION.pdf filter=lfs diff=lfs merge=lfs -text
100
+ static/new[[:space:]]Print[[:space:]]_[[:space:]]Udyam[[:space:]]Registration[[:space:]]Certificate.pdf filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables - keep this file private!
2
+ .env
3
+
4
+ # Python cache
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+
10
+ # Virtual environment
11
+ venv/
12
+ env/
13
+ ENV/
14
+
15
+ # Model cache
16
+ model_cache/
17
+
18
+ # IDE
19
+ .vscode/
20
+ .idea/
21
+ *.swp
22
+ *.swo
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # Logs
29
+ *.log
30
+
31
+ # Build
32
+ build/
33
+ dist/
34
+ *.egg-info/
Dockerfile CHANGED
@@ -1,31 +1,26 @@
1
- FROM python:3.11-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- curl \
9
- && rm -rf /var/lib/apt/lists/*
10
-
11
- # Copy requirements first
12
- COPY requirements.txt .
13
-
14
- # Install Python packages
15
- RUN pip install --no-cache-dir -r requirements.txt
16
-
17
- # Copy ALL files
18
- COPY . .
19
-
20
- # Create necessary directories
21
- RUN mkdir -p /app/model_cache /app/data
22
-
23
- # Set environment variables
24
- ENV PYTHONUNBUFFERED=1
25
- ENV PORT=7860
26
- ENV SENTENCE_TRANSFORMERS_HOME=/app/model_cache
27
-
28
- EXPOSE 7860
29
-
30
- # Run app3.py instead of app.py
31
- CMD ["python", "app3.py"]
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ RUN mkdir -p data model_cache
16
+
17
+ ENV FLASK_APP=app.py
18
+ ENV PYTHONUNBUFFERED=1
19
+ ENV SENTENCE_TRANSFORMERS_HOME=/app/model_cache
20
+
21
+ EXPOSE 5000
22
+
23
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
24
+ CMD curl -f http://localhost:5000/health || exit 1
25
+
26
+ CMD ["python", "app.py"]
 
 
 
 
 
README.md CHANGED
@@ -1,14 +1,99 @@
1
- ---
2
- title: RNGPIT AI Chatbot
3
- emoji: πŸ€–
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: docker
7
- sdk_version: "3.11"
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- # RNGPIT AI Assistant
13
-
14
- An AI-powered chatbot for RNG Patel Institute of Technology using RAG and Groq API.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RNGAI-WEB Chatbot
2
+
3
+ This is an AI-powered chatbot for RNG Patel Institute of Technology (RNGPIT) that uses RAG (Retrieval Augmented Generation) to answer questions about courses, admissions, facilities, placements, and campus life.
4
+
5
+ ## Features
6
+
7
+ - πŸŽ“ Comprehensive information about RNGPIT
8
+ - πŸ€– Powered by Groq AI (LLaMA models)
9
+ - πŸ“š RAG-based knowledge retrieval
10
+ - 🎨 Beautiful, modern UI with dark/light theme
11
+ - πŸ“Š Admin dashboard with analytics
12
+ - 🐳 Docker support for easy deployment
13
+
14
+ ## Setup
15
+
16
+ ### 1. Clone and Install
17
+
18
+ ```bash
19
+ cd RNGAI-WEB
20
+ pip install -r requirements.txt
21
+ ```
22
+
23
+ ### 2. Configure Environment Variables
24
+
25
+ Copy the example environment file:
26
+
27
+ ```bash
28
+ copy .env.example .env
29
+ ```
30
+
31
+ Then edit `.env` and add your credentials:
32
+
33
+ ```env
34
+ GROQ_API_KEY=your_groq_api_key_here
35
+ SUPABASE_URL=your_supabase_url_here
36
+ SUPABASE_KEY=your_supabase_key_here
37
+ ```
38
+
39
+ **Get your credentials:**
40
+ - **Groq API**: https://console.groq.com
41
+ - **Supabase**: https://supabase.com
42
+
43
+ ### 3. Run the Application
44
+
45
+ **Local Development:**
46
+
47
+ ```bash
48
+ python app.py
49
+ ```
50
+
51
+ **Using Docker:**
52
+
53
+ ```bash
54
+ docker-compose up -d
55
+ ```
56
+
57
+ The application will be available at: `http://localhost:5000`
58
+
59
+ ## Project Structure
60
+
61
+ ```
62
+ RNGAI-WEB/
63
+ β”œβ”€β”€ app.py # Main Flask application
64
+ β”œβ”€β”€ requirements.txt # Python dependencies
65
+ β”œβ”€β”€ .env # Environment variables (not in git)
66
+ β”œβ”€β”€ .env.example # Environment template
67
+ β”œβ”€β”€ Dockerfile # Docker configuration
68
+ β”œβ”€β”€ docker-compose.yml # Docker Compose config
69
+ β”œβ”€β”€ templates/
70
+ β”‚ └── index.html # Main chat interface
71
+ β”œβ”€β”€ static/
72
+ β”‚ └── RNGPIT.png # College logo
73
+ └── data/
74
+ └── link17.txt # Knowledge base
75
+ ```
76
+
77
+ ## Environment Variables
78
+
79
+ | Variable | Description | Required |
80
+ |----------|-------------|----------|
81
+ | `GROQ_API_KEY` | API key from Groq Console | Yes |
82
+ | `SUPABASE_URL` | Your Supabase project URL | Yes |
83
+ | `SUPABASE_KEY` | Your Supabase API key | Yes |
84
+
85
+ ## Tech Stack
86
+
87
+ - **Backend**: Flask, Python 3.11
88
+ - **AI**: Groq (LLaMA 3.3), Sentence Transformers
89
+ - **Vector DB**: ChromaDB
90
+ - **Database**: Supabase
91
+ - **Frontend**: HTML, CSS, JavaScript with Three.js
92
+
93
+ ## Security
94
+
95
+ ⚠️ **Important**: Never commit your `.env` file to version control. The `.env` file contains sensitive credentials and should be kept private.
96
+
97
+ ## License
98
+
99
+ Private project for RNG Patel Institute of Technology
app.py ADDED
@@ -0,0 +1,1120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, session, redirect, url_for, copy_current_request_context
2
+ from flask_cors import CORS
3
+ import os
4
+ import sys
5
+ import json
6
+ from sentence_transformers import SentenceTransformer
7
+ import chromadb
8
+ from chromadb.config import Settings
9
+ import re
10
+ import numpy as np
11
+ from typing import List, Dict, Tuple, Optional
12
+ from supabase import create_client, Client
13
+ from datetime import datetime
14
+ import uuid
15
+ import time
16
+ from functools import wraps, lru_cache
17
+ import threading
18
+ from queue import Queue
19
+ import gc
20
+ from dotenv import load_dotenv
21
+
22
+ # Load environment variables from .env file
23
+ load_dotenv()
24
+
25
+ # OpenAI SDK for NVIDIA API (DeepSeek model)
26
+ try:
27
+ from openai import OpenAI
28
+ NVIDIA_AVAILABLE = True
29
+ except ImportError:
30
+ NVIDIA_AVAILABLE = False
31
+ print("[WARN] openai package not installed. Run: pip install openai")
32
+
33
+ # Fix Windows console encoding
34
+ os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
35
+ if sys.platform == 'win32':
36
+ try:
37
+ sys.stdout.reconfigure(encoding='utf-8')
38
+ except:
39
+ pass
40
+
41
+ app = Flask(__name__)
42
+ CORS(app)
43
+ app.secret_key = 'rngai_secret_key_change_in_production_2025'
44
+
45
+ # ============================================
46
+ # CONFIGURATION
47
+ # ============================================
48
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
49
+ TEXT_FILE_PATH = os.path.join(SCRIPT_DIR, "data", "link17.txt")
50
+ MODEL_CACHE_DIR = os.path.join(SCRIPT_DIR, "model_cache")
51
+
52
+ # ============================================
53
+ # NVIDIA MODEL CONFIGURATION (NVIDIA API)
54
+ # ============================================
55
+ NVIDIA_MODEL_REGISTRY = {
56
+ "abacusai/dracarys-llama-3.1-70b-instruct": {
57
+ "name": "Dracarys Llama 3.1 70B",
58
+ "model_id": "abacusai/dracarys-llama-3.1-70b-instruct",
59
+ "context_length": 128000,
60
+ "max_new_tokens": 4096,
61
+ "description": "Dracarys Llama 3.1 - Powerful 70B Instruct Model",
62
+ },
63
+ }
64
+
65
+ DEFAULT_NVIDIA_MODEL = "abacusai/dracarys-llama-3.1-70b-instruct"
66
+
67
+ # Cost tracking (NVIDIA API pricing)
68
+ NVIDIA_COST_PER_1M_INPUT = 0.0 # Check NVIDIA pricing
69
+ NVIDIA_COST_PER_1M_OUTPUT = 0.0 # Check NVIDIA pricing
70
+
71
+ # ============================================
72
+ # NVIDIA CONFIGURATION
73
+ # ============================================
74
+ NVIDIA_API_KEY = os.environ.get('NVIDIA_API_KEY')
75
+ NVIDIA_API_KEY_2 = os.environ.get('NVIDIA_API_KEY_2') # Backup key
76
+ NVIDIA_BASE_URL = os.environ.get('NVIDIA_BASE_URL', 'https://integrate.api.nvidia.com/v1')
77
+ nvidia_client = None
78
+ current_nvidia_model = DEFAULT_NVIDIA_MODEL
79
+
80
+ # Initialize NVIDIA OpenAI client
81
+ if NVIDIA_API_KEY and NVIDIA_AVAILABLE:
82
+ nvidia_client = OpenAI(
83
+ base_url=NVIDIA_BASE_URL,
84
+ api_key=NVIDIA_API_KEY
85
+ )
86
+ print(f"[OK] NVIDIA API client initialized (Key 1)")
87
+ if NVIDIA_API_KEY_2:
88
+ print(f"[OK] Backup NVIDIA key configured")
89
+ else:
90
+ if not NVIDIA_API_KEY:
91
+ print("[WARN] NVIDIA_API_KEY not set in environment")
92
+ # Supabase Configuration (loaded from .env)
93
+ SUPABASE_URL = os.environ.get('SUPABASE_URL')
94
+ SUPABASE_KEY = os.environ.get('SUPABASE_KEY')
95
+
96
+ if not SUPABASE_URL:
97
+ print("[WARN] SUPABASE_URL is missing from environment variables!")
98
+ if not SUPABASE_KEY:
99
+ print("[WARN] SUPABASE_KEY is missing from environment variables!")
100
+
101
+ try:
102
+ if SUPABASE_URL and SUPABASE_KEY:
103
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
104
+ print("[OK] Supabase connected successfully")
105
+ else:
106
+ print("[WARN] Skipping Supabase connection due to missing config")
107
+ supabase = None
108
+ except Exception as e:
109
+ print(f"[WARN] Could not connect to Supabase: {e}")
110
+ supabase = None
111
+
112
+ # ============================================
113
+ # GLOBAL VARIABLES
114
+ # ============================================
115
+ embedding_model = None
116
+ collection = None
117
+ chroma_client = None
118
+ debug_mode = False
119
+
120
+ # OPTIMIZATION: Cache for embeddings
121
+ EMBEDDING_CACHE_SIZE = 200
122
+ embedding_cache = {}
123
+
124
+ # ============================================
125
+ # EMBEDDING MODEL (Same as original)
126
+ # ============================================
127
+
128
+ def load_embedding_model():
129
+ """Load embedding model with optimizations"""
130
+ global embedding_model
131
+
132
+ if embedding_model is not None:
133
+ return embedding_model
134
+
135
+ print("[INFO] Loading embedding model (optimized)...")
136
+ embedding_model = SentenceTransformer('mixedbread-ai/mxbai-embed-large-v1')
137
+
138
+ # Check if GPU is available for embeddings
139
+ try:
140
+ import torch
141
+ if torch.cuda.is_available():
142
+ embedding_model = embedding_model.to('cuda')
143
+ print("[PERF] Embedding model on GPU")
144
+ except:
145
+ print("[INFO] Embedding model on CPU")
146
+
147
+ print("[OK] Embedding model loaded!")
148
+ return embedding_model
149
+
150
+ @lru_cache(maxsize=EMBEDDING_CACHE_SIZE)
151
+ def get_cached_embedding(text: str) -> np.ndarray:
152
+ """Cache embeddings for frequently asked questions"""
153
+ global embedding_model
154
+ if embedding_model is None:
155
+ return None
156
+
157
+ instruction = "Represent this sentence for searching relevant passages: "
158
+ embedding = embedding_model.encode(
159
+ [instruction + text],
160
+ normalize_embeddings=True,
161
+ show_progress_bar=False,
162
+ batch_size=1,
163
+ convert_to_numpy=True
164
+ )[0]
165
+ return embedding
166
+
167
+ # ============================================
168
+ # AUTH HELPERS
169
+ # ============================================
170
+
171
+ def login_required(f):
172
+ @wraps(f)
173
+ def decorated_function(*args, **kwargs):
174
+ if 'admin_logged_in' not in session or not session['admin_logged_in']:
175
+ return redirect(url_for('admin_login'))
176
+ return f(*args, **kwargs)
177
+ return decorated_function
178
+
179
+ def get_or_create_session():
180
+ if 'chat_session_id' not in session:
181
+ session['chat_session_id'] = str(uuid.uuid4())
182
+
183
+ if supabase:
184
+ try:
185
+ supabase.table('chat_sessions').insert({
186
+ 'session_id': session['chat_session_id'],
187
+ 'ip_address': request.remote_addr,
188
+ 'user_agent': request.user_agent.string[:500] if request.user_agent.string else None,
189
+ 'started_at': datetime.utcnow().isoformat()
190
+ }).execute()
191
+ except Exception as e:
192
+ print(f"[WARN] Could not create session: {e}")
193
+
194
+ return session['chat_session_id']
195
+
196
+ def save_chat_to_supabase(session_id: str, user_question: str, ai_response: str,
197
+ response_time_ms: int, input_tokens: int = 0, output_tokens: int = 0):
198
+ """Save chat synchronously but non-blocking"""
199
+ if not supabase:
200
+ return
201
+
202
+ try:
203
+ result = supabase.table('chat_sessions').select('id').eq('session_id', session_id).execute()
204
+
205
+ total_cost = 0.0 # Groq is free tier
206
+
207
+ if result.data:
208
+ session_uuid = result.data[0]['id']
209
+
210
+ supabase.table('chat_messages').insert({
211
+ 'session_id': session_uuid,
212
+ 'user_question': user_question,
213
+ 'ai_response': ai_response[:5000],
214
+ 'response_time_ms': response_time_ms,
215
+ 'input_tokens': input_tokens,
216
+ 'output_tokens': output_tokens,
217
+ 'total_cost': total_cost,
218
+ 'created_at': datetime.utcnow().isoformat()
219
+ }).execute()
220
+ print(f"[OK] Chat saved (tokens: in={input_tokens}, out={output_tokens})")
221
+ except Exception as e:
222
+ print(f"[WARN] Failed to save chat: {e}")
223
+
224
+ # ============================================
225
+ # TEXT PROCESSING (Same as original)
226
+ # ============================================
227
+
228
+ @lru_cache(maxsize=1)
229
+ def load_and_process_data(file_path: str) -> List[Dict]:
230
+ """Cached data loading"""
231
+ try:
232
+ print(f"[INFO] Loading data from: {file_path}")
233
+
234
+ if not os.path.exists(file_path):
235
+ print(f"[ERROR] File not found: {file_path}")
236
+ return []
237
+
238
+ with open(file_path, 'r', encoding='utf-8') as f:
239
+ content = f.read()
240
+
241
+ print(f"[OK] Loaded {len(content):,} characters")
242
+
243
+ chunks = create_chunks(content)
244
+ print(f"[OK] Created {len(chunks)} chunks")
245
+
246
+ return chunks
247
+
248
+ except Exception as e:
249
+ print(f"[ERROR] Error loading file: {e}")
250
+ return []
251
+
252
+ def clean_text(text: str) -> str:
253
+ """Optimized text cleaning"""
254
+ text = text.replace('\t', ' ')
255
+ text = re.sub(r'[^\w\s.,!?;:()\-\'\"@/&|\[\]#]+', ' ', text)
256
+ text = re.sub(r' +', ' ', text)
257
+ text = re.sub(r'\n{3,}', '\n\n', text)
258
+ return text.strip()
259
+
260
+ def create_chunks(text: str, chunk_size: int = 1000, overlap: int = 200) -> List[Dict]:
261
+ """Balanced chunking - original parameters for quality"""
262
+ sections = re.split(r'\n\s*\n|\n-{4,}\n', text)
263
+
264
+ chunks = []
265
+ chunk_id = 0
266
+ current_chunk = []
267
+ current_length = 0
268
+
269
+ for section in sections:
270
+ section = section.strip()
271
+ if not section:
272
+ continue
273
+
274
+ section_lines = section.split('\n')
275
+
276
+ for line in section_lines:
277
+ line = line.strip()
278
+ if not line:
279
+ continue
280
+
281
+ line_words = len(line.split())
282
+
283
+ if current_length + line_words > chunk_size and current_chunk:
284
+ chunk_text = '\n'.join(current_chunk)
285
+ chunks.append({
286
+ 'text': chunk_text,
287
+ 'id': chunk_id,
288
+ })
289
+ chunk_id += 1
290
+
291
+ overlap_buffer = []
292
+ overlap_len = 0
293
+ for prev_line in reversed(current_chunk):
294
+ prev_len = len(prev_line.split())
295
+ if overlap_len + prev_len > overlap:
296
+ break
297
+ overlap_buffer.insert(0, prev_line)
298
+ overlap_len += prev_len
299
+
300
+ current_chunk = overlap_buffer
301
+ current_length = overlap_len
302
+
303
+ current_chunk.append(line)
304
+ current_length += line_words
305
+
306
+ if current_chunk:
307
+ chunks.append({
308
+ 'text': '\n'.join(current_chunk),
309
+ 'id': chunk_id,
310
+ })
311
+
312
+ return chunks
313
+
314
+ # ============================================
315
+ # RAG INITIALIZATION (Same as original)
316
+ # ============================================
317
+
318
+ def initialize_rag():
319
+ """Initialize RAG with optimizations"""
320
+ global collection, embedding_model, chroma_client
321
+
322
+ print("\n" + "="*60)
323
+ print("INITIALIZING RAG SYSTEM")
324
+ print("="*60)
325
+
326
+ embedding_model = load_embedding_model()
327
+
328
+ print("[INFO] Initializing vector database...")
329
+ chroma_client = chromadb.Client(Settings(
330
+ anonymized_telemetry=False,
331
+ is_persistent=False
332
+ ))
333
+
334
+ collection = chroma_client.get_or_create_collection(
335
+ name="rngpit_knowledge",
336
+ metadata={"description": "RNG Patel Institute Knowledge Base"}
337
+ )
338
+
339
+ chunks = load_and_process_data(TEXT_FILE_PATH)
340
+
341
+ if not chunks:
342
+ print("[WARN] No data loaded!")
343
+ return
344
+
345
+ print("[INFO] Generating embeddings (batched)...")
346
+ chunk_texts = [chunk['text'] for chunk in chunks]
347
+
348
+ # Optimized batch encoding
349
+ embeddings = embedding_model.encode(
350
+ chunk_texts,
351
+ show_progress_bar=True,
352
+ batch_size=64, # Balanced batch size
353
+ normalize_embeddings=True,
354
+ convert_to_numpy=True
355
+ )
356
+
357
+ print("[INFO] Adding to vector database...")
358
+ collection.add(
359
+ embeddings=embeddings.tolist(),
360
+ documents=chunk_texts,
361
+ ids=[f"chunk_{i}" for i in range(len(chunks))]
362
+ )
363
+
364
+ print("\n" + "="*60)
365
+ print(f"RAG READY! ({len(chunks)} chunks)")
366
+ print("="*60 + "\n")
367
+
368
+ def retrieve_context(query: str, top_k: int = 5) -> List[str]:
369
+ """Optimized retrieval with caching - BALANCED: 5 chunks for quality"""
370
+ global collection, embedding_model
371
+
372
+ if collection is None or embedding_model is None:
373
+ return []
374
+
375
+ try:
376
+ # Use cached embedding
377
+ query_embedding = get_cached_embedding(query)
378
+ if query_embedding is None:
379
+ return []
380
+
381
+ results = collection.query(
382
+ query_embeddings=[query_embedding.tolist()],
383
+ n_results=top_k
384
+ )
385
+
386
+ if results and results['documents']:
387
+ return results['documents'][0]
388
+ return []
389
+
390
+ except Exception as e:
391
+ print(f"[ERROR] Retrieval error: {e}")
392
+ return []
393
+
394
+ # ============================================
395
+ # NVIDIA RESPONSE GENERATION (OneAI SDK)
396
+ # ============================================
397
+
398
+ def generate_response_nvidia(query: str, context_chunks: List[str]) -> Dict:
399
+ """Generate response using NVIDIA API with Dracarys Llama 3.1 model"""
400
+ global NVIDIA_API_KEY, nvidia_client, current_nvidia_model
401
+
402
+ if not context_chunks:
403
+ return {
404
+ 'text': "I don't have specific information about that. Could you ask me something else about RNG Patel Institute?",
405
+ 'input_tokens': 0,
406
+ 'output_tokens': 0,
407
+ 'model_used': 'none'
408
+ }
409
+
410
+ if not NVIDIA_AVAILABLE:
411
+ return {
412
+ 'text': "OpenAI SDK not installed. Run: pip install openai",
413
+ 'input_tokens': 0,
414
+ 'output_tokens': 0,
415
+ 'model_used': 'error'
416
+ }
417
+
418
+ if not NVIDIA_API_KEY:
419
+ return {
420
+ 'text': "NVIDIA API key not configured. Please set NVIDIA_API_KEY environment variable.",
421
+ 'input_tokens': 0,
422
+ 'output_tokens': 0,
423
+ 'model_used': 'error'
424
+ }
425
+
426
+ try:
427
+ # Initialize NVIDIA client if needed
428
+ if nvidia_client is None:
429
+ nvidia_client = OpenAI(
430
+ base_url=NVIDIA_BASE_URL,
431
+ api_key=NVIDIA_API_KEY
432
+ )
433
+ print("[OK] Initialized NVIDIA client")
434
+
435
+ # Build context
436
+ context_parts = [chunk.strip() for chunk in context_chunks[:10] if chunk.strip()]
437
+ context = "\n\n".join(context_parts)[:40000]
438
+
439
+ # System prompt
440
+ system_prompt = """You are a friendly and knowledgeable student ambassador for RNGPIT (R.N.G. Patel Institute of Technology). Your goal is to help students and visitors by answering their questions warmly and directly.
441
+
442
+ **ABOUT THIS AI (Important):**
443
+ When asked about "who made this AI", "who created you", "who built you", or similar questions about the creators/developers, respond with:
444
+
445
+ "I was built by **Team InnoCrew**, a talented group of students from RNGPIT:
446
+
447
+ - **Shis Tushar Maheta** (Lead AI Engineer) - B.Tech Computer Science, Class of 2025
448
+ - **Zuveriya Meman** -B.Voc Software development, Class of 2025
449
+ - **Karan Chaudhary** - B.Voc Software Development, Class of 2023
450
+ - **Sem Surti** - B.Voc Software Development, Class of 2023
451
+ - **Shreyansh Vasava** - B.Voc Software Development, Class of 2023
452
+
453
+ Team InnoCrew developed this AI assistant to help students and visitors learn more about RNG Patel Institute of Technology!"
454
+
455
+ EXACT FACULTY DETIALS:
456
+
457
+ #### **1. Information Technology (IT) Department Faculty**
458
+
459
+ | Name | Designation | Education | Exp. | Email |
460
+ | --- | --- | --- | --- | --- |
461
+ | **Prof. Vivek C. Joshi** | I/C HOD & Asst. Prof | M.Tech (CSE), Ph.D. (Pursuing) | 13+ Yrs | vcjoshi@rngpit.ac.in |
462
+ | **Prof. Hardi A. Patel** | Assistant Professor | M.E. (CSE) | 6+ Yrs | hapatel@rngpit.ac.in |
463
+ | **Prof. Krina N. Desai** | Assistant Professor | M.E. (CE) | 3+ Yrs | kndesai@rngpit.ac.in |
464
+ | **Prof. Nishtha H. Tandel** | Assistant Professor | M.Tech (IT), GSET Qualified | 4+ Yrs | nhtandel@rngpit.ac.in |
465
+ | **Prof. Bhavisha S. Parmar** | Assistant Professor | M.E. (CE), Ph.D. (Pursuing) | 12+ Yrs | bsparmar@rngpit.ac.in |
466
+ | **Prof. Foram C. Shukla** | Assistant Professor | M.E. (CE) | 1 Yr | fcshukla@rngpit.ac.in |
467
+ | **Prof. Purvaj P. Vaidya** | Assistant Professor | M.Tech (Media Tech - Germany) | 1 Yr | ppvaidya@rngpit.ac.in |
468
+ | **Prof. Monali R. Gandhi** | Assistant Professor | M.E. (CE) | 11+ Yrs | mrgandhi@rngpit.ac.in |
469
+ | **Prof. Ekta R. Bhatia** | Assistant Professor | M.Tech (CSE) | 4 Yrs | erbhatia@rngpit.ac.in |
470
+ | **Prof. Pratik M. Gohil** | Assistant Professor | M.Tech (CE) | 5+ Yrs | pmgohil@rngpit.ac.in |
471
+ | **Prof. Zeel R. Bhatt** | Assistant Professor | M.Tech (CE) | 3 Mos | zrbhatt@rngpit.ac.in |
472
+ | **Prof. Pooja D. Patel** | Assistant Professor | M.E. (CE) | - | pdpatel@rngpit.ac.in |
473
+ | **Prof. Ayushi H. Gandhi** | Assistant Professor | M.E. (CE) | 4+ Yrs | ahgandhi@rngpit.ac.in |
474
+ | **Prof. Rinisha S. Patel** | Assistant Professor | M.E. (CE) | - | rspatel@rngpit.ac.in |
475
+
476
+ #### **2. Mechanical Engineering Department Faculty**
477
+
478
+ | Name | Designation | Education | Email |
479
+ | --- | --- | --- | --- |
480
+ | **Dr. Kanti B. Rathod** | HOD & Assoc. Professor | Ph.D. (Mechanical) | kbrathod@rngpit.ac.in |
481
+ | **Mr. Hardik B. Nayak** | Assistant Professor | Ph.D. (Pursuing) | hbnayak@rngpit.ac.in |
482
+ | **Mr. Niravsinh B. Rathod** | Assistant Professor | Ph.D. (Pursuing) | nbrathod@rngpit.ac.in |
483
+ | **Mr. Gaurang K. Champaneri** | Assistant Professor | M.Tech (CIM) | gkchampaneri@rngpit.ac.in |
484
+ | **Mr. Chirag K. Balar** | Assistant Professor | M.Tech (Mechanical) | ckbalar@rngpit.ac.in |
485
+ | **Mr. Dharmin M. Patel** | Assistant Professor | Ph.D. (Pursuing) | dmpatel@rngpit.ac.in |
486
+ | **Mr. Nevilkumar M. Patel** | Assistant Professor | M.E. (Machine Design) | nmpatel@rngpit.ac.in |
487
+ | **Mr. Yatin H. Chauhan** | Assistant Professor | M.Tech (ME) | yhchauhan@rngpit.ac.in |
488
+ | **Mr. Vikramkumar A. Mistry** | Assistant Professor | M.E. (IC Engine & Automobile) | vamistry@rngpit.ac.in |
489
+ | **Mr. Sushant K. Merai** | Assistant Professor | Ph.D. (Pursuing) | skmerai@rngpit.ac.in |
490
+ | **Mr. Sapan H. Joshi** | Assistant Professor | M.Tech (Thermal System) | shjoshi@rngpit.ac.in |
491
+ | **Dr. Ankursinh P. Solanki** | Assistant Professor | Ph.D., M.E. (Thermal) | apsolanki@rngpit.ac.in |
492
+ | **Mr. Nikhil M. Pandya** | Assistant Professor | M.E. (Production) | nmpandya@rngpit.ac.in |
493
+ | **Mr. Mehul P. Patel** | Assistant Professor | M.E. (Production) | mppatel@rngpit.ac.in |
494
+ | **Mr. Vikesh B. Patel** | Assistant Professor | M.Tech (CAD/CAM) | patelvikesh1988@gmail.com |
495
+ | **Mr. Shobhit Y. Varshney** | Assistant Professor | M.Tech (Thermal System) | syvarshney@rngpit.ac.in |
496
+
497
+ #### **3. Civil Engineering Department Faculty**
498
+
499
+ | Name | Designation | Qualification | Email | More Info |
500
+ | ---------------------------- | --------------------------- | ---------------------------------- | --------------------------------------------------------- | --------- |
501
+ | Dr. Kamalsinh M. Padhiar | HOD and Associate Professor | β€” | [kmpadhiar@rngpit.ac.in](mailto:kmpadhiar@rngpit.ac.in) | β€” |
502
+ | Mr. Gaurav P. Barot | Assistant Professor | M.Tech (Structural Engineering) | [gpbarot@rngpit.ac.in](mailto:gpbarot@rngpit.ac.in) | β€” |
503
+ | Mr. Mohammed Ahmed Qureshi | Assistant Professor | M.Tech (Structure), Ph.D. Pursuing | [maqureshi@rngpit.ac.in](mailto:maqureshi@rngpit.ac.in) | β€” |
504
+ | Mr. Nirav P. Desai | Assistant Professor | M.E (Transportation) | [npdesai@rngpit.ac.in](mailto:npdesai@rngpit.ac.in) | β€” |
505
+ | Mr. Sharukh M. Marfani | Assistant Professor | M.E (CE) | [smmarfani@rngpit.ac.in](mailto:smmarfani@rngpit.ac.in) | β€” |
506
+ | Mr. Hilay N. Prajapati | Assistant Professor | M.E (CED) | [hnprajapati@ngpit.ac.in](mailto:hnprajapati@ngpit.ac.in) | β€” |
507
+ | Mr. Ajay B. Patel | Assistant Professor | M.E. (Civil Engg.) | [ajaybpatel@rngpit.ac.in](mailto:ajaybpatel@rngpit.ac.in) | β€” |
508
+ | Ms. Srushti U. Joshi | Assistant Professor | M.E (Civil Engg.) | [sujoshi@rngpit.ac.in](mailto:sujoshi@rngpit.ac.in) | β€” |
509
+ | Mr. Priyank H. Patel | Assistant Professor | β€” | [phpatel@rngpit.ac.in](mailto:phpatel@rngpit.ac.in) | β€” |
510
+ | Mr. Atish P. More | Assistant Professor | Masters in Environmental Engg. | [apmore@rngpit.ac.in](mailto:apmore@rngpit.ac.in) | β€” |
511
+ | Ms. Hetvi J. Kania | Assistant Professor | M.E. in Environmental Engg. | [hjkania@rngpit.ac.in](mailto:hjkania@rngpit.ac.in) | β€” |
512
+ | Mr. Pritesh R. Bhandari | Assistant Professor | Diploma Civil | [prbhandari@rngpit.ac.in](mailto:prbhandari@rngpit.ac.in) | β€” |
513
+ | Mr. Viral Jagdishbhai Rathod | Assistant Professor | M.E. (Production) | [vjrathod@rngpit.ac.in](mailto:vjrathod@rngpit.ac.in) | β€” |
514
+
515
+ #### **GIVE THE DETIALS OF THE DEPARTMENT THAT IS ONLY ASKED DONT PROVIDE ANY OTHER RANDOM DATA**
516
+
517
+ **STRICT FORMATTING RULES:**
518
+ 1. **ALWAYS USE TABLES FOR DATA**: If you are listing more than 3 items (like faculty names, committee members, fees, courses, placements), you **MUST** use a Markdown table. Do not use bullet points for long lists.
519
+ *Example Table:*
520
+ | Name | Position | Location |
521
+ |------|----------|----------|
522
+ | John Doe | President | Surat |
523
+
524
+ 2. **DIRECT ANSWER**: Answer the question immediately. Do not start with "Okay", "Sure", or "Based on the context".
525
+
526
+ 3. **BEAUTIFUL FORMATTING**: Use **bold** for importance, `code` for emails/numbers, and clear paragraphs.
527
+
528
+ 4. **NO CITATIONS**: Do not say "according to the document". Speak as if you know the facts yourself.
529
+
530
+ 5. **GRACEFUL FALLBACK**: If you don't know, suggest contacting info@rngpit.ac.in."""
531
+
532
+ user_prompt = f"""Context Information:
533
+ {context}
534
+
535
+ User Question: {query}
536
+
537
+ Answer:"""
538
+
539
+ def make_api_call(client_to_use, model_to_use):
540
+ return client_to_use.chat.completions.create(
541
+ model=model_to_use,
542
+ messages=[
543
+ {"role": "system", "content": system_prompt},
544
+ {"role": "user", "content": user_prompt}
545
+ ],
546
+ temperature=0.5,
547
+ top_p=0.8,
548
+ max_tokens=4096,
549
+ stream=True
550
+ )
551
+
552
+ # Attempt 1: Primary Key
553
+ try:
554
+ completion = make_api_call(nvidia_client, current_nvidia_model)
555
+ except Exception as e:
556
+ error_msg = str(e)
557
+ # Check for Rate Limit (429) or Quota issues
558
+ if ("429" in error_msg or "quota" in error_msg.lower()) and NVIDIA_API_KEY_2 and NVIDIA_API_KEY != NVIDIA_API_KEY_2:
559
+ print(f"[WARN] detailed error: {error_msg}")
560
+ print(f"[WARN] Primary API key rate limited using fallback key...")
561
+
562
+ # Switch to backup key
563
+ NVIDIA_API_KEY = NVIDIA_API_KEY_2
564
+ nvidia_client = OpenAI(base_url=NVIDIA_BASE_URL, api_key=NVIDIA_API_KEY)
565
+
566
+ # Attempt 2: Backup Key
567
+ completion = make_api_call(nvidia_client, current_nvidia_model)
568
+ else:
569
+ raise e # Re-raise if not a rate limit issue or no backup key
570
+
571
+ # Collect streamed response
572
+ response_text = ""
573
+ for chunk in completion:
574
+ if chunk.choices[0].delta.content is not None:
575
+ response_text += chunk.choices[0].delta.content
576
+
577
+ # Estimate token counts
578
+ input_tokens = int(len((system_prompt + user_prompt).split()) * 1.3)
579
+ output_tokens = int(len(response_text.split()) * 1.3)
580
+
581
+ if not response_text:
582
+ response_text = "I couldn't generate a response. Please try again."
583
+
584
+ print(f"[DEBUG] NVIDIA generated ~{int(output_tokens)} tokens using {current_nvidia_model}")
585
+
586
+ return {
587
+ 'text': response_text.strip(),
588
+ 'input_tokens': int(input_tokens),
589
+ 'output_tokens': int(output_tokens),
590
+ 'model_used': current_nvidia_model
591
+ }
592
+
593
+ except Exception as e:
594
+ error_msg = str(e)
595
+ print(f"[ERROR] NVIDIA API error: {error_msg}")
596
+
597
+ if "API key" in error_msg.lower() or "authentication" in error_msg.lower() or "unauthorized" in error_msg.lower():
598
+ return {'text': "Invalid NVIDIA API key. Please check your API key.", 'input_tokens': 0, 'output_tokens': 0, 'model_used': 'error'}
599
+ elif "quota" in error_msg.lower() or "limit" in error_msg.lower() or "rate" in error_msg.lower():
600
+ return {'text': "API rate limit reached. Please wait a moment and try again.", 'input_tokens': 0, 'output_tokens': 0, 'model_used': 'error'}
601
+ else:
602
+ return {'text': f"Error processing request: {error_msg}", 'input_tokens': 0, 'output_tokens': 0, 'model_used': 'error'}
603
+
604
+ # ============================================
605
+ # MODEL INFO
606
+ # ============================================
607
+
608
+ def get_model_info():
609
+ """Get model information"""
610
+ return {
611
+ "available_models": {
612
+ model_id: {
613
+ "name": config["name"],
614
+ "description": config["description"],
615
+ "context_length": config["context_length"],
616
+ "max_new_tokens": config["max_new_tokens"],
617
+ }
618
+ for model_id, config in NVIDIA_MODEL_REGISTRY.items()
619
+ },
620
+ "current_model": current_nvidia_model,
621
+ "is_loaded": bool(NVIDIA_API_KEY),
622
+ "provider": "nvidia"
623
+ }
624
+
625
+ # ============================================
626
+ # ROUTES
627
+ # ============================================
628
+
629
+ @app.route('/')
630
+ def home():
631
+ return render_template('index.html')
632
+
633
+ @app.route('/chat', methods=['POST'])
634
+ def chat():
635
+ global debug_mode
636
+ try:
637
+ start_time = time.time()
638
+
639
+ data = request.json
640
+ user_message = data.get('message', '').strip()
641
+
642
+ if not user_message:
643
+ return jsonify({'error': 'No message provided'}), 400
644
+
645
+ print(f"\n[CHAT] Query: {user_message}")
646
+
647
+ # Get session ID within request context
648
+ current_session_id = get_or_create_session()
649
+
650
+ # Fast retrieval
651
+ context_chunks = retrieve_context(user_message, top_k=5)
652
+
653
+ if not context_chunks:
654
+ response_text = "I don't have information about that. Try asking about courses, admissions, fees, placements, or facilities at RNGPIT."
655
+ input_tokens = 0
656
+ output_tokens = 0
657
+ else:
658
+ print(f"[INFO] Found {len(context_chunks)} relevant chunks")
659
+ print("[INFO] Using NVIDIA API (DeepSeek) for generation...")
660
+ result = generate_response_nvidia(user_message, context_chunks)
661
+
662
+ response_text = result['text']
663
+ input_tokens = result['input_tokens']
664
+ output_tokens = result['output_tokens']
665
+ model_used = result.get('model_used', current_nvidia_model)
666
+
667
+
668
+ response_time_ms = int((time.time() - start_time) * 1000)
669
+
670
+ # Save to database (synchronous but fast)
671
+ save_chat_to_supabase(current_session_id, user_message, response_text, response_time_ms, input_tokens, output_tokens)
672
+
673
+ print(f"[OK] Response ({len(response_text)} chars) in {response_time_ms}ms [Provider: NVIDIA DeepSeek]")
674
+
675
+ response_data = {
676
+ 'response': response_text,
677
+ 'response_time_ms': response_time_ms
678
+ }
679
+
680
+ if debug_mode:
681
+ response_data['debug'] = {
682
+ 'enabled': True,
683
+ 'chunks_used': len(context_chunks),
684
+ 'model': model_used if context_chunks else current_nvidia_model,
685
+ 'provider': 'nvidia',
686
+ 'input_tokens': input_tokens,
687
+ 'output_tokens': output_tokens
688
+ }
689
+
690
+ return jsonify(response_data)
691
+
692
+ except Exception as e:
693
+ print(f"[ERROR] Chat error: {e}")
694
+ import traceback
695
+ traceback.print_exc()
696
+ return jsonify({'error': str(e)}), 500
697
+
698
+ @app.route('/health', methods=['GET'])
699
+ def health():
700
+ model_info = get_model_info()
701
+ return jsonify({
702
+ 'status': 'healthy',
703
+ 'chunks_loaded': collection.count() if collection else 0,
704
+ 'current_model': current_nvidia_model,
705
+ 'model_loaded': nvidia_client is not None,
706
+ 'provider': 'nvidia',
707
+ 'debug_mode': debug_mode,
708
+ 'nvidia_configured': bool(NVIDIA_API_KEY)
709
+ })
710
+
711
+ # ============================================
712
+ # MODEL MANAGEMENT API
713
+ # ============================================
714
+
715
+ @app.route('/api/models', methods=['GET'])
716
+ def api_get_models():
717
+ return jsonify(get_model_info())
718
+
719
+ @app.route('/api/models/switch', methods=['POST'])
720
+ @login_required
721
+ def api_switch_model():
722
+ global current_nvidia_model
723
+
724
+ data = request.json or {}
725
+ model_id = data.get('model_id', DEFAULT_NVIDIA_MODEL)
726
+
727
+ if model_id not in NVIDIA_MODEL_REGISTRY:
728
+ return jsonify({
729
+ 'success': False,
730
+ 'error': f"Model '{model_id}' not found in NVIDIA registry"
731
+ }), 400
732
+
733
+ current_nvidia_model = model_id
734
+ print(f"[INFO] Switched to NVIDIA model: {model_id}")
735
+
736
+ return jsonify({
737
+ 'success': True,
738
+ 'message': f"Switched to {NVIDIA_MODEL_REGISTRY[model_id]['name']}",
739
+ 'model_info': get_model_info()
740
+ })
741
+
742
+ @app.route('/api/embeddings/regenerate', methods=['POST'])
743
+ def api_regenerate_embeddings():
744
+ global collection
745
+
746
+ try:
747
+ if collection is not None:
748
+ chroma_client.delete_collection("rngpit_knowledge")
749
+
750
+ # Clear cache
751
+ get_cached_embedding.cache_clear()
752
+ load_and_process_data.cache_clear()
753
+
754
+ initialize_rag()
755
+
756
+ return jsonify({
757
+ 'success': True,
758
+ 'message': 'Embeddings regenerated',
759
+ 'chunks_loaded': collection.count() if collection else 0
760
+ })
761
+ except Exception as e:
762
+ return jsonify({'success': False, 'error': str(e)}), 500
763
+
764
+ @app.route('/api/debug/toggle', methods=['POST'])
765
+ def api_toggle_debug():
766
+ global debug_mode
767
+ debug_mode = not debug_mode
768
+ return jsonify({'success': True, 'debug_mode': debug_mode})
769
+
770
+ @app.route('/api/debug/status', methods=['GET'])
771
+ def api_debug_status():
772
+ return jsonify({'debug_mode': debug_mode})
773
+
774
+ @app.route('/api/nvidia-key', methods=['POST'])
775
+ @login_required
776
+ def api_set_nvidia_key():
777
+ global NVIDIA_API_KEY, nvidia_client
778
+
779
+ data = request.json or {}
780
+ api_key = data.get('api_key', '').strip()
781
+
782
+ if not api_key:
783
+ return jsonify({'success': False, 'error': 'API key required'}), 400
784
+
785
+ NVIDIA_API_KEY = api_key
786
+ nvidia_client = OpenAI(base_url=NVIDIA_BASE_URL, api_key=NVIDIA_API_KEY) # Reset client with new key
787
+ print("[INFO] NVIDIA API key updated")
788
+
789
+ return jsonify({
790
+ 'success': True,
791
+ 'message': 'NVIDIA API key configured',
792
+ 'key_preview': api_key[:8] + '...' + api_key[-4:] if len(api_key) > 12 else '***'
793
+ })
794
+
795
+ @app.route('/api/nvidia-key/status', methods=['GET'])
796
+ @login_required
797
+ def api_nvidia_key_status():
798
+ if NVIDIA_API_KEY:
799
+ return jsonify({
800
+ 'configured': True,
801
+ 'key_preview': NVIDIA_API_KEY[:8] + '...' + NVIDIA_API_KEY[-4:] if len(NVIDIA_API_KEY) > 12 else '***'
802
+ })
803
+ return jsonify({'configured': False})
804
+
805
+ # ============================================
806
+ # ADMIN ROUTES
807
+ # ============================================
808
+
809
+ @app.route('/admin/login', methods=['GET', 'POST'])
810
+ def admin_login():
811
+ if request.method == 'GET':
812
+ if session.get('admin_logged_in'):
813
+ return redirect(url_for('admin_dashboard'))
814
+ return render_template('login.html')
815
+
816
+ data = request.json
817
+ username = data.get('username', '').strip()
818
+ password = data.get('password', '').strip()
819
+
820
+ if not username or not password:
821
+ return jsonify({'success': False, 'error': 'Username and password required'}), 400
822
+
823
+ if not supabase:
824
+ return jsonify({'success': False, 'error': 'Database not available'}), 500
825
+
826
+ try:
827
+ result = supabase.table('admin_users').select('*').eq('username', username).execute()
828
+
829
+ if result.data and len(result.data) > 0:
830
+ user = result.data[0]
831
+
832
+ if user['password_hash'] == password:
833
+ session['admin_logged_in'] = True
834
+ session['admin_username'] = username
835
+ session['admin_id'] = user['id']
836
+
837
+ # Update last login
838
+ supabase.table('admin_users').update({
839
+ 'last_login': datetime.utcnow().isoformat()
840
+ }).eq('id', user['id']).execute()
841
+
842
+ return jsonify({'success': True, 'redirect': '/admin/dashboard'})
843
+
844
+ return jsonify({'success': False, 'error': 'Invalid credentials'}), 401
845
+
846
+ except Exception as e:
847
+ print(f"[ERROR] Login: {e}")
848
+ return jsonify({'success': False, 'error': str(e)}), 500
849
+
850
+ @app.route('/admin/logout')
851
+ def admin_logout():
852
+ session.pop('admin_logged_in', None)
853
+ session.pop('admin_username', None)
854
+ session.pop('admin_id', None)
855
+ return redirect(url_for('admin_login'))
856
+
857
+ @app.route('/admin/dashboard')
858
+ @login_required
859
+ def admin_dashboard():
860
+ return render_template('admin.html', username=session.get('admin_username', 'Admin'))
861
+
862
+ # ============================================
863
+ # ANALYTICS API (OPTIMIZED)
864
+ # ============================================
865
+
866
+ @app.route('/api/analytics/stats')
867
+ @login_required
868
+ def get_analytics_stats():
869
+ if not supabase:
870
+ return jsonify({'error': 'Database not available'}), 500
871
+
872
+ try:
873
+ total_result = supabase.table('chat_messages').select('id, input_tokens, output_tokens, total_cost').execute()
874
+ total_questions = len(total_result.data) if total_result.data else 0
875
+
876
+ total_input_tokens = sum(msg.get('input_tokens', 0) or 0 for msg in (total_result.data or []))
877
+ total_output_tokens = sum(msg.get('output_tokens', 0) or 0 for msg in (total_result.data or []))
878
+ total_cost = sum(msg.get('total_cost', 0) or 0 for msg in (total_result.data or []))
879
+
880
+ sessions_result = supabase.table('chat_sessions').select('id').execute()
881
+ total_sessions = len(sessions_result.data) if sessions_result.data else 0
882
+
883
+ today = datetime.utcnow().date().isoformat()
884
+ today_result = supabase.table('chat_messages').select('id, input_tokens, output_tokens, total_cost').gte('created_at', today).execute()
885
+ today_questions = len(today_result.data) if today_result.data else 0
886
+ today_input_tokens = sum(msg.get('input_tokens', 0) or 0 for msg in (today_result.data or []))
887
+ today_output_tokens = sum(msg.get('output_tokens', 0) or 0 for msg in (today_result.data or []))
888
+ today_cost = sum(msg.get('total_cost', 0) or 0 for msg in (today_result.data or []))
889
+
890
+ avg_input_tokens = total_input_tokens / total_questions if total_questions > 0 else 0
891
+ avg_output_tokens = total_output_tokens / total_questions if total_questions > 0 else 0
892
+ avg_cost_per_message = total_cost / total_questions if total_questions > 0 else 0
893
+
894
+ return jsonify({
895
+ 'total_questions': total_questions,
896
+ 'total_sessions': total_sessions,
897
+ 'today_questions': today_questions,
898
+ 'total_input_tokens': total_input_tokens,
899
+ 'total_output_tokens': total_output_tokens,
900
+ 'total_tokens': total_input_tokens + total_output_tokens,
901
+ 'total_cost': round(total_cost, 6),
902
+ 'today_input_tokens': today_input_tokens,
903
+ 'today_output_tokens': today_output_tokens,
904
+ 'today_tokens': today_input_tokens + today_output_tokens,
905
+ 'today_cost': round(today_cost, 6),
906
+ 'avg_input_tokens': round(avg_input_tokens, 1),
907
+ 'avg_output_tokens': round(avg_output_tokens, 1),
908
+ 'avg_cost_per_message': round(avg_cost_per_message, 6),
909
+ 'pricing': {
910
+ 'input_per_1m': GROQ_COST_PER_1M_INPUT,
911
+ 'output_per_1m': GROQ_COST_PER_1M_OUTPUT
912
+ },
913
+ 'provider': 'groq'
914
+ })
915
+ except Exception as e:
916
+ print(f"[ERROR] Stats: {e}")
917
+ return jsonify({'error': str(e)}), 500
918
+
919
+ @app.route('/api/analytics/top-questions')
920
+ @login_required
921
+ def get_top_questions():
922
+ if not supabase:
923
+ return jsonify({'error': 'Database not available'}), 500
924
+
925
+ try:
926
+ limit = request.args.get('limit', 10, type=int)
927
+ result = supabase.table('chat_messages').select('user_question, created_at').execute()
928
+
929
+ if not result.data:
930
+ return jsonify({'questions': []})
931
+
932
+ question_counts = {}
933
+ for msg in result.data:
934
+ q = msg['user_question'].lower().strip()
935
+ if q in question_counts:
936
+ question_counts[q]['count'] += 1
937
+ if msg['created_at'] > question_counts[q]['last_asked']:
938
+ question_counts[q]['last_asked'] = msg['created_at']
939
+ question_counts[q]['original'] = msg['user_question']
940
+ else:
941
+ question_counts[q] = {
942
+ 'count': 1,
943
+ 'last_asked': msg['created_at'],
944
+ 'original': msg['user_question']
945
+ }
946
+
947
+ sorted_questions = sorted(
948
+ [{'question': v['original'], 'count': v['count'], 'last_asked': v['last_asked']}
949
+ for v in question_counts.values()],
950
+ key=lambda x: x['count'],
951
+ reverse=True
952
+ )[:limit]
953
+
954
+ return jsonify({'questions': sorted_questions})
955
+ except Exception as e:
956
+ print(f"[ERROR] Top questions: {e}")
957
+ return jsonify({'error': str(e)}), 500
958
+
959
+ @app.route('/api/analytics/all-questions')
960
+ @login_required
961
+ def get_all_questions():
962
+ if not supabase:
963
+ return jsonify({'error': 'Database not available'}), 500
964
+
965
+ try:
966
+ page = request.args.get('page', 1, type=int)
967
+ per_page = request.args.get('per_page', 20, type=int)
968
+ search = request.args.get('search', '').strip()
969
+
970
+ offset = (page - 1) * per_page
971
+
972
+ query = supabase.table('chat_messages').select('id, user_question, ai_response, created_at, response_time_ms')
973
+
974
+ if search:
975
+ query = query.ilike('user_question', f'%{search}%')
976
+
977
+ result = query.order('created_at', desc=True).range(offset, offset + per_page - 1).execute()
978
+
979
+ count_query = supabase.table('chat_messages').select('id', count='exact')
980
+ if search:
981
+ count_query = count_query.ilike('user_question', f'%{search}%')
982
+ count_result = count_query.execute()
983
+ total = count_result.count if count_result.count else 0
984
+
985
+ return jsonify({
986
+ 'questions': result.data,
987
+ 'total': total,
988
+ 'page': page,
989
+ 'per_page': per_page,
990
+ 'total_pages': (total + per_page - 1) // per_page
991
+ })
992
+ except Exception as e:
993
+ print(f"[ERROR] All questions: {e}")
994
+ return jsonify({'error': str(e)}), 500
995
+
996
+ @app.route('/api/analytics/token-usage')
997
+ @login_required
998
+ def get_token_usage():
999
+ if not supabase:
1000
+ return jsonify({'error': 'Database not available'}), 500
1001
+
1002
+ try:
1003
+ result = supabase.table('chat_messages').select(
1004
+ 'created_at, input_tokens, output_tokens, total_cost'
1005
+ ).order('created_at', desc=False).execute()
1006
+
1007
+ if not result.data:
1008
+ return jsonify({'daily_usage': [], 'hourly_usage': []})
1009
+
1010
+ daily_data = {}
1011
+ hourly_data = {}
1012
+
1013
+ for msg in result.data:
1014
+ try:
1015
+ created_at = datetime.fromisoformat(msg['created_at'].replace('Z', '+00:00'))
1016
+ day_key = created_at.strftime('%Y-%m-%d')
1017
+ hour_key = created_at.strftime('%Y-%m-%d %H:00')
1018
+
1019
+ input_tokens = msg.get('input_tokens', 0) or 0
1020
+ output_tokens = msg.get('output_tokens', 0) or 0
1021
+ cost = msg.get('total_cost', 0) or 0
1022
+
1023
+ if day_key not in daily_data:
1024
+ daily_data[day_key] = {
1025
+ 'date': day_key,
1026
+ 'input_tokens': 0,
1027
+ 'output_tokens': 0,
1028
+ 'total_tokens': 0,
1029
+ 'cost': 0,
1030
+ 'messages': 0
1031
+ }
1032
+ daily_data[day_key]['input_tokens'] += input_tokens
1033
+ daily_data[day_key]['output_tokens'] += output_tokens
1034
+ daily_data[day_key]['total_tokens'] += input_tokens + output_tokens
1035
+ daily_data[day_key]['cost'] += cost
1036
+ daily_data[day_key]['messages'] += 1
1037
+
1038
+ if hour_key not in hourly_data:
1039
+ hourly_data[hour_key] = {
1040
+ 'hour': hour_key,
1041
+ 'input_tokens': 0,
1042
+ 'output_tokens': 0,
1043
+ 'total_tokens': 0,
1044
+ 'cost': 0,
1045
+ 'messages': 0
1046
+ }
1047
+ hourly_data[hour_key]['input_tokens'] += input_tokens
1048
+ hourly_data[hour_key]['output_tokens'] += output_tokens
1049
+ hourly_data[hour_key]['total_tokens'] += input_tokens + output_tokens
1050
+ hourly_data[hour_key]['cost'] += cost
1051
+ hourly_data[hour_key]['messages'] += 1
1052
+ except:
1053
+ continue
1054
+
1055
+ daily_usage = sorted(daily_data.values(), key=lambda x: x['date'])[-30:]
1056
+ hourly_usage = sorted(hourly_data.values(), key=lambda x: x['hour'])[-24:]
1057
+
1058
+ if daily_usage:
1059
+ avg_daily_tokens = sum(d['total_tokens'] for d in daily_usage) / len(daily_usage)
1060
+ avg_daily_cost = sum(d['cost'] for d in daily_usage) / len(daily_usage)
1061
+ avg_daily_messages = sum(d['messages'] for d in daily_usage) / len(daily_usage)
1062
+ else:
1063
+ avg_daily_tokens = avg_daily_cost = avg_daily_messages = 0
1064
+
1065
+ return jsonify({
1066
+ 'daily_usage': daily_usage,
1067
+ 'hourly_usage': hourly_usage,
1068
+ 'projections': {
1069
+ 'avg_daily_tokens': round(avg_daily_tokens, 0),
1070
+ 'avg_daily_cost': round(avg_daily_cost, 4),
1071
+ 'avg_daily_messages': round(avg_daily_messages, 1),
1072
+ 'projected_monthly_tokens': round(avg_daily_tokens * 30, 0),
1073
+ 'projected_monthly_cost': round(avg_daily_cost * 30, 2),
1074
+ 'projected_monthly_messages': round(avg_daily_messages * 30, 0)
1075
+ }
1076
+ })
1077
+ except Exception as e:
1078
+ print(f"[ERROR] Token usage: {e}")
1079
+ return jsonify({'error': str(e)}), 500
1080
+
1081
+ @app.route('/api/admin/check')
1082
+ def check_admin():
1083
+ return jsonify({
1084
+ 'is_admin': session.get('admin_logged_in', False),
1085
+ 'username': session.get('admin_username', None)
1086
+ })
1087
+
1088
+ # ============================================
1089
+ # MAIN
1090
+ # ============================================
1091
+
1092
+ if __name__ == '__main__':
1093
+ print("\n" + "="*60)
1094
+ print("RNGPIT AI ASSISTANT - NVIDIA API (Llama 3.1) VERSION")
1095
+ print("="*60)
1096
+ print(f"Data: {TEXT_FILE_PATH}")
1097
+ print(f"Available Models: {list(NVIDIA_MODEL_REGISTRY.keys())}")
1098
+ print(f"Default Model: {DEFAULT_NVIDIA_MODEL}")
1099
+ print(f"Supabase: {'βœ“' if supabase else 'βœ—'}")
1100
+ print(f"NVIDIA API Key: {'βœ“ Configured' if NVIDIA_API_KEY else 'βœ— Not set (use NVIDIA_API_KEY env var)'}")
1101
+ print("="*60)
1102
+
1103
+ initialize_rag()
1104
+
1105
+ print("\nπŸš€ Server starting...")
1106
+ print("πŸ“± Chatbot: http://localhost:5000")
1107
+ print("πŸ” Admin: http://localhost:5000/admin/login")
1108
+ print("\n⚑ NVIDIA API Features:")
1109
+ print(" βœ“ Dracarys Llama 3.1 70B via NVIDIA")
1110
+ print(" βœ“ Available models:")
1111
+ for model_id, config in NVIDIA_MODEL_REGISTRY.items():
1112
+ print(f" - {model_id}: {config['description']}")
1113
+ print(" βœ“ OpenAI-compatible API")
1114
+ print(" βœ“ Powerful 70B parameter model")
1115
+ print(" βœ“ Same RAG pipeline as original")
1116
+ print(" βœ“ Same admin panel and analytics")
1117
+ print(" βœ“ No local GPU required!")
1118
+ print("="*60 + "\n")
1119
+
1120
+ app.run(debug=False, port=5000, host='0.0.0.0', threaded=True)
docker-compose.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ chatbot:
5
+ build: .
6
+ ports:
7
+ - "5000:5000"
8
+ env_file:
9
+ - .env
10
+ restart: always
11
+ volumes:
12
+ - ./data:/app/data
13
+ - model_cache:/app/model_cache
14
+
15
+ volumes:
16
+ model_cache:
requirements.txt CHANGED
@@ -1,11 +1,11 @@
1
- flask==3.0.0
2
- flask-cors==4.0.0
3
- sentence-transformers==2.7.0
4
- torch==2.1.2
5
- transformers==4.40.0
6
- chromadb==0.4.22
7
- groq==0.4.2
8
- supabase==2.3.4
9
- numpy==1.24.3
10
- requests==2.31.0
11
- gunicorn==21.2.0
 
1
+ flask==3.0.0
2
+ flask-cors==4.0.0
3
+ sentence-transformers==2.7.0
4
+ torch==2.1.2
5
+ transformers==4.40.0
6
+ chromadb==0.4.22
7
+ openai>=1.0.0
8
+ supabase==2.3.4
9
+ numpy==1.24.3
10
+ requests==2.31.0
11
+ python-dotenv==1.0.0
static/AA240126014782K_RC03012026.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9eb54a528411216cb921030aace977c21618eb735ad64bfc46cc9906a44b788a
3
+ size 225869
static/CERTIFICATE OF RECOGNITION.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0d815225f96d39f540713bb1c8d0a12bed2c32650fbf79c4666ac62ceb647636
3
+ size 1071880
static/RNGPIT.png ADDED
static/new Print _ Udyam Registration Certificate.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa6965efe589669ec6a8f3e7e5b37565c8bc894460728b738e21cabfc1b8b882
3
+ size 260688
templates/index.html CHANGED
@@ -5,6 +5,7 @@
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>RNG Patel Institute AI Assistant</title>
 
8
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap"
9
  rel="stylesheet">
10
  <style>
@@ -101,18 +102,18 @@
101
  .logo {
102
  width: 48px;
103
  height: 48px;
104
- background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
105
  border-radius: 12px;
106
- display: flex;
107
- align-items: center;
108
- justify-content: center;
109
- font-weight: 800;
110
- font-size: 18px;
111
- color: white;
112
  box-shadow: 0 8px 32px var(--shadow-color);
113
  transition: transform 0.3s ease;
114
  }
115
 
 
 
 
 
 
 
116
  .logo:hover {
117
  transform: scale(1.05) rotate(5deg);
118
  }
@@ -202,19 +203,19 @@
202
  .welcome-logo {
203
  width: 120px;
204
  height: 120px;
205
- background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
206
  border-radius: 30px;
207
- display: flex;
208
- align-items: center;
209
- justify-content: center;
210
- font-weight: 800;
211
- font-size: 48px;
212
- color: white;
213
  margin-bottom: 32px;
214
  box-shadow: 0 20px 60px var(--shadow-color);
215
  animation: float 3s ease-in-out infinite;
216
  }
217
 
 
 
 
 
 
 
218
  @keyframes float {
219
 
220
  0%,
@@ -873,6 +874,53 @@
873
  .header-title {
874
  font-size: 14px;
875
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876
  }
877
  </style>
878
  </head>
@@ -883,26 +931,24 @@
883
  <div class="app-container">
884
  <div class="header">
885
  <div class="header-left">
886
- <div class="logo">RNG</div>
887
- <div class="header-title">RNG Patel Institute AI</div>
 
 
888
  </div>
889
  <div class="header-right">
890
  <button class="icon-btn" onclick="toggleTheme()" title="Toggle Theme">
891
  <span id="themeIcon">πŸŒ™</span>
892
  </button>
893
- <a href="/admin/login" class="icon-btn" title="Admin Dashboard" style="text-decoration: none;">
894
- πŸ“Š
895
- </a>
896
- <button class="icon-btn" onclick="clearChat()" title="New Chat">
897
- πŸ”„
898
- </button>
899
  </div>
900
  </div>
901
 
902
  <div class="chat-container" id="chatContainer">
903
  <div class="welcome-screen" id="welcomeScreen">
904
- <div class="welcome-logo">RNG</div>
905
- <div class="welcome-title">Welcome to RNG Patel Institute</div>
 
 
906
  <div class="welcome-subtitle">Your AI-powered assistant for everything about courses, admissions,
907
  facilities, placements, and campus life</div>
908
  <div class="quick-prompts">
@@ -1066,7 +1112,7 @@
1066
 
1067
  const messageHeader = document.createElement('div');
1068
  messageHeader.className = 'message-header';
1069
- messageHeader.textContent = isUser ? 'You' : 'RNG Patel AI';
1070
 
1071
  const messageText = document.createElement('div');
1072
  messageText.className = 'message-text';
@@ -1172,7 +1218,7 @@
1172
 
1173
  const messageHeader = document.createElement('div');
1174
  messageHeader.className = 'message-header';
1175
- messageHeader.textContent = 'RNG Patel AI';
1176
 
1177
  const messageText = document.createElement('div');
1178
  messageText.className = 'message-text';
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>RNG Patel Institute AI Assistant</title>
8
+ <link rel="icon" type="image/png" href="{{ url_for('static', filename='RNGPIT.png') }}">
9
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap"
10
  rel="stylesheet">
11
  <style>
 
102
  .logo {
103
  width: 48px;
104
  height: 48px;
 
105
  border-radius: 12px;
106
+ overflow: hidden;
 
 
 
 
 
107
  box-shadow: 0 8px 32px var(--shadow-color);
108
  transition: transform 0.3s ease;
109
  }
110
 
111
+ .logo img {
112
+ width: 100%;
113
+ height: 100%;
114
+ object-fit: cover;
115
+ }
116
+
117
  .logo:hover {
118
  transform: scale(1.05) rotate(5deg);
119
  }
 
203
  .welcome-logo {
204
  width: 120px;
205
  height: 120px;
 
206
  border-radius: 30px;
207
+ overflow: hidden;
 
 
 
 
 
208
  margin-bottom: 32px;
209
  box-shadow: 0 20px 60px var(--shadow-color);
210
  animation: float 3s ease-in-out infinite;
211
  }
212
 
213
+ .welcome-logo img {
214
+ width: 100%;
215
+ height: 100%;
216
+ object-fit: cover;
217
+ }
218
+
219
  @keyframes float {
220
 
221
  0%,
 
874
  .header-title {
875
  font-size: 14px;
876
  }
877
+
878
+ /* Optimized Mobile Layout */
879
+ .message-group {
880
+ display: grid;
881
+ grid-template-columns: auto 1fr;
882
+ column-gap: 12px;
883
+ row-gap: 4px;
884
+ align-items: center;
885
+ }
886
+
887
+ .avatar {
888
+ width: 32px;
889
+ height: 32px;
890
+ font-size: 16px;
891
+ grid-column: 1;
892
+ grid-row: 1;
893
+ }
894
+
895
+ .message-content {
896
+ display: contents;
897
+ /* Unwrap so children become grid items */
898
+ }
899
+
900
+ .message-header {
901
+ grid-column: 2;
902
+ grid-row: 1;
903
+ margin-bottom: 0;
904
+ font-size: 13px;
905
+ align-self: center;
906
+ /* Align with avatar center */
907
+ }
908
+
909
+ .message-text {
910
+ grid-column: 1 / -1;
911
+ /* Span full width (row 2) */
912
+ grid-row: 2;
913
+ max-width: 100%;
914
+ margin-top: 4px;
915
+ /* Small gap from header */
916
+ width: 100%;
917
+ /* Force width */
918
+ }
919
+
920
+ .debug-panel {
921
+ grid-column: 1 / -1;
922
+ grid-row: 3;
923
+ }
924
  }
925
  </style>
926
  </head>
 
931
  <div class="app-container">
932
  <div class="header">
933
  <div class="header-left">
934
+ <div class="logo">
935
+ <img src="{{ url_for('static', filename='RNGPIT.png') }}" alt="RNGPIT Logo">
936
+ </div>
937
+ <div class="header-title">RNG Patel Institute of Technology AI</div>
938
  </div>
939
  <div class="header-right">
940
  <button class="icon-btn" onclick="toggleTheme()" title="Toggle Theme">
941
  <span id="themeIcon">πŸŒ™</span>
942
  </button>
 
 
 
 
 
 
943
  </div>
944
  </div>
945
 
946
  <div class="chat-container" id="chatContainer">
947
  <div class="welcome-screen" id="welcomeScreen">
948
+ <div class="welcome-logo">
949
+ <img src="{{ url_for('static', filename='RNGPIT.png') }}" alt="RNGPIT Logo">
950
+ </div>
951
+ <div class="welcome-title">Welcome to RNG Patel Institute of Technology</div>
952
  <div class="welcome-subtitle">Your AI-powered assistant for everything about courses, admissions,
953
  facilities, placements, and campus life</div>
954
  <div class="quick-prompts">
 
1112
 
1113
  const messageHeader = document.createElement('div');
1114
  messageHeader.className = 'message-header';
1115
+ messageHeader.textContent = isUser ? 'You' : 'RNGPIT AI';
1116
 
1117
  const messageText = document.createElement('div');
1118
  messageText.className = 'message-text';
 
1218
 
1219
  const messageHeader = document.createElement('div');
1220
  messageHeader.className = 'message-header';
1221
+ messageHeader.textContent = 'RNGPIT AI';
1222
 
1223
  const messageText = document.createElement('div');
1224
  messageText.className = 'message-text';