Adieee5 commited on
Commit
3a1960e
·
verified ·
1 Parent(s): d047624

Upload 13 files

Browse files
.gitattributes CHANGED
@@ -39,3 +39,5 @@ public/JUIT_icon.png filter=lfs diff=lfs merge=lfs -text
39
  public/raman.png filter=lfs diff=lfs merge=lfs -text
40
  backend/chroma_store/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
41
  backend/ragg_pdf.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
39
  public/raman.png filter=lfs diff=lfs merge=lfs -text
40
  backend/chroma_store/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
41
  backend/ragg_pdf.pdf filter=lfs diff=lfs merge=lfs -text
42
+ chroma_store/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
43
+ static/JUIT_icon.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
+ import os
5
+ from flask import Flask, request, jsonify, render_template
6
+ from flask_cors import CORS
7
+
8
+ from langchain_community.embeddings import HuggingFaceEmbeddings
9
+ from langchain_community.vectorstores import Chroma
10
+ from langchain_google_genai import ChatGoogleGenerativeAI
11
+ from langchain_core.prompts import PromptTemplate
12
+ from langchain.chains import RetrievalQA
13
+
14
+ # === Step 1: Load API Key ===
15
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
16
+ if not GOOGLE_API_KEY:
17
+ raise ValueError("GOOGLE_API_KEY not found in environment variables.")
18
+
19
+ # === Step 2: Initialize LLM (Gemini) ===
20
+ llm = ChatGoogleGenerativeAI(
21
+ model="gemini-2.0-flash-lite",
22
+ google_api_key=GOOGLE_API_KEY,
23
+ convert_system_message_to_human=True
24
+ )
25
+
26
+ # === Step 3: Load Chroma Vector Store ===
27
+ embedding_model = HuggingFaceEmbeddings(model_name="BAAI/bge-large-en-v1.5")
28
+ vectordb = Chroma(
29
+ persist_directory="./chroma_store",
30
+ embedding_function=embedding_model,
31
+ collection_name="pdf_search_chroma"
32
+ )
33
+ retriever = vectordb.as_retriever(search_kwargs={"k": 6})
34
+
35
+ # === Step 4: Custom Prompt Template ===
36
+ prompt_template = PromptTemplate.from_template("""
37
+ You are an intelligent assistant for students asking about their university.
38
+ If answer is not defined or not clearly understood, ask for clarification.
39
+ Answer clearly and helpfully based on the retrieved context. Do not make up information or suggestions.
40
+
41
+ Context:
42
+ {context}
43
+
44
+ Question:
45
+ {question}
46
+
47
+ Answer:
48
+ """)
49
+
50
+ # === Step 5: Create Retrieval-QA Chain ===
51
+ qa_chain = RetrievalQA.from_chain_type(
52
+ llm=llm,
53
+ chain_type="stuff",
54
+ retriever=retriever,
55
+ chain_type_kwargs={"prompt": prompt_template}
56
+ )
57
+
58
+ # === Step 6: Flask Setup ===
59
+ app = Flask(__name__, static_folder="static", template_folder="templates")
60
+ CORS(app)
61
+
62
+ # === Step 7: Serve Frontend ===
63
+ @app.route("/")
64
+ def index():
65
+ return render_template("index.html")
66
+
67
+ # === Step 8: API Endpoint ===
68
+ @app.route('/api/chat', methods=['POST'])
69
+ def chat():
70
+ data = request.json
71
+ query = data.get('message', '').strip()
72
+
73
+ if not query:
74
+ return jsonify({"error": "No message provided."}), 400
75
+
76
+ try:
77
+ response = qa_chain.run(query)
78
+ return jsonify({"response": response})
79
+ except Exception as e:
80
+ return jsonify({"error": str(e)}), 500
81
+
82
+ # === Step 9: Run the App ===
83
+ if __name__ == '__main__':
84
+ app.run(debug=False)
chroma_store/chroma.sqlite3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5900bc44d5a9fec47cfb353452cf8faee6a10189fe79729cda9fa6624548f043
3
+ size 18706432
chroma_store/e83a4737-70af-4828-8889-50c4503e6ab0/data_level0.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a862c80124476fcce189beca5e8a849c6f19a7f2cc5fa6747733ed959842ac0
3
+ size 42360000
chroma_store/e83a4737-70af-4828-8889-50c4503e6ab0/header.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d518530a7dd80842478852354c422f510f6994e1505ce55ede365a2e814be408
3
+ size 100
chroma_store/e83a4737-70af-4828-8889-50c4503e6ab0/index_metadata.pickle ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e936a553c66e3a5ba6652e14049ad293f8c3315336fdb26bbff6c06c78a6fe48
3
+ size 168676
chroma_store/e83a4737-70af-4828-8889-50c4503e6ab0/length.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:25c0423f0088744f3f3606e907c4d99c5dd98e71183539fb25f08e115d34dd60
3
+ size 40000
chroma_store/e83a4737-70af-4828-8889-50c4503e6ab0/link_lists.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f21db32ed94e165bb58c49426a1db5b75edd2d35c46cb61bd767db9b0d0653c6
3
+ size 16372
requirements.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ chromadb==1.0.4
2
+ faiss-cpu==1.7.4
3
+ fastapi==0.115.9
4
+ Flask==3.1.0
5
+ flask-cors==5.0.1
6
+ fpdf2==2.7.7
7
+ google-ai-generativelanguage==0.4.0
8
+ google-api-core==2.24.2
9
+ google-auth==2.38.0
10
+ google-generativeai==0.4.1
11
+ gunicorn==23.0.0
12
+ huggingface-hub==0.30.2
13
+ importlib_metadata==8.4.0
14
+ importlib_resources==6.5.2
15
+ langchain==0.1.13
16
+ langchain-community==0.0.29
17
+ langchain-google-genai==0.0.11
18
+ numpy==1.26.4
19
+ pypdf==5.4.0
20
+ PyPDF2==3.0.1
21
+ python-dotenv==1.0.1
22
+ sentence-transformers==4.0.2
23
+ db-sqlite3
24
+ transformers==4.51.3
25
+ torch==2.6.0
26
+ uvicorn==0.34.1
27
+ gunicorn
static/JUIT_icon.png ADDED

Git LFS Details

  • SHA256: 53ae1a2a1b07c18f0d6e29c81722a8088bf0a3fe1a4fb337b6311f210eeb7804
  • Pointer size: 131 Bytes
  • Size of remote file: 374 kB
static/profile.png ADDED
static/script.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const chatContainer = document.getElementById("chat-container");
2
+ const chatInput = document.getElementById("chat-input");
3
+ const sendButton = document.getElementById("send-button");
4
+ const themeToggle = document.getElementById("theme-toggle");
5
+
6
+ let isTyping = false;
7
+
8
+ const initialMessage = {
9
+ id: "1",
10
+ text: "Hello! I'm the JUIT University Assistant. How can I help you today?",
11
+ isUser: false,
12
+ };
13
+
14
+ let messages = [initialMessage];
15
+
16
+ function renderMessages() {
17
+ chatContainer.innerHTML = "";
18
+ messages.forEach((msg) => {
19
+ const bubble = document.createElement("div");
20
+ bubble.className = `flex items-start gap-2 ${msg.isUser ? "flex-row-reverse" : "flex-row"}`;
21
+
22
+ const avatar = document.createElement("div");
23
+ avatar.className =
24
+ "h-6 w-6 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-sm";
25
+ avatar.innerText = msg.isUser ? "👤" : "🎓";
26
+
27
+ const msgDiv = document.createElement("div");
28
+ msgDiv.className = `max-w-[75%] break-words rounded-md px-3 py-1 text-sm ${
29
+ msg.isUser
30
+ ? "bg-blue-600 text-white rounded-tr-none"
31
+ : "bg-gray-200 dark:bg-gray-700 text-black dark:text-white rounded-tl-none"
32
+ }`;
33
+
34
+ msgDiv.innerText = msg.text;
35
+ bubble.appendChild(avatar);
36
+ bubble.appendChild(msgDiv);
37
+ chatContainer.appendChild(bubble);
38
+ });
39
+
40
+ if (isTyping) {
41
+ const typing = document.createElement("div");
42
+ typing.className = "flex items-start gap-2";
43
+ typing.innerHTML = `
44
+ <div class="h-6 w-6 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-sm">🎓</div>
45
+ <div class="max-w-[75%] rounded-md rounded-tl-none bg-gray-200 dark:bg-gray-700 px-3 py-1 text-sm animate-pulse">Typing...</div>
46
+ `;
47
+ chatContainer.appendChild(typing);
48
+ }
49
+
50
+ chatContainer.scrollTop = chatContainer.scrollHeight;
51
+ }
52
+
53
+ async function simulateBotResponse(userInput) {
54
+ const response = await fetch("http://localhost:8080/api/chat", {
55
+ method: "POST",
56
+ headers: {
57
+ "Content-Type": "application/json",
58
+ },
59
+ body: JSON.stringify({ message: userInput }),
60
+ });
61
+
62
+ if (!response.ok) {
63
+ throw new Error("API error");
64
+ }
65
+
66
+ const data = await response.json();
67
+ if (data.error) {
68
+ throw new Error(data.error);
69
+ }
70
+
71
+ return data;
72
+ }
73
+
74
+ async function handleSendMessage() {
75
+ const text = chatInput.value.trim();
76
+ if (!text) return;
77
+
78
+ const userMessage = {
79
+ id: Date.now().toString(),
80
+ text,
81
+ isUser: true,
82
+ };
83
+
84
+ messages.push(userMessage);
85
+ chatInput.value = "";
86
+ isTyping = true;
87
+ renderMessages();
88
+
89
+ try {
90
+ const res = await simulateBotResponse(text);
91
+ const botMessage = {
92
+ id: (Date.now() + 1).toString(),
93
+ text: res.response,
94
+ isUser: false,
95
+ };
96
+ messages.push(botMessage);
97
+ } catch (error) {
98
+ messages.push({
99
+ id: Date.now().toString(),
100
+ text: "Error: " + error.message,
101
+ isUser: false,
102
+ });
103
+ }
104
+
105
+ isTyping = false;
106
+ renderMessages();
107
+ }
108
+
109
+ sendButton.addEventListener("click", handleSendMessage);
110
+
111
+ chatInput.addEventListener("keydown", (e) => {
112
+ if (e.key === "Enter" && !e.shiftKey) {
113
+ e.preventDefault();
114
+ handleSendMessage();
115
+ }
116
+ });
117
+
118
+ themeToggle.addEventListener("click", () => {
119
+ document.body.classList.toggle("dark");
120
+ const isDark = document.body.classList.contains("dark");
121
+ themeToggle.textContent = isDark ? "☀️ Light" : "🌙 Dark";
122
+ });
123
+
124
+ renderMessages();
templates/index.html ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>University Assistant</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script>
10
+ tailwind.config = {
11
+ darkMode: 'class',
12
+ };
13
+ </script>
14
+ <style>
15
+ html,
16
+ body {
17
+ margin: 0;
18
+ padding: 0;
19
+ height: 100%;
20
+ font-family: 'Inter', sans-serif;
21
+ }
22
+
23
+ @keyframes wave {
24
+ 0%,100% {
25
+ transform: translateY(0);
26
+ }
27
+
28
+ 50% {
29
+ transform: translateY(-5px);
30
+ }
31
+ }
32
+
33
+ /* @keyframes wave {
34
+ 0%,100% {
35
+ transform: translateY(0);
36
+ }
37
+
38
+ 50% {
39
+ transform: translateY(-5px);
40
+ }
41
+ } */
42
+
43
+ .dot-typing span {
44
+ display: inline-block;
45
+ animation: wave 1.4s infinite ease-in-out;
46
+ font-size: 1.5rem;
47
+ margin: 0 2px;
48
+ color: black;
49
+ /* Or white in dark mode */
50
+ }
51
+
52
+ .dot-typing span:nth-child(1) {
53
+ animation-delay: 0s;
54
+ }
55
+
56
+ .dot-typing span:nth-child(2) {
57
+ animation-delay: 0.2s;
58
+ }
59
+
60
+ .dot-typing span:nth-child(3) {
61
+ animation-delay: 0.4s;
62
+ }
63
+ </style>
64
+ </head>
65
+
66
+ <body class="bg-zinc-100 dark:bg-slate-950 text-black dark:text-white flex items-center justify-center min-h-screen">
67
+ <div
68
+ class="flex w-full w-full flex-col overflow-hidden border border-slate-800 dark:border-gray-700 shadow-lg rounded-lg h-[90vh] h-full">
69
+
70
+ <div class="flex flex-col flex-grow overflow-hidden">
71
+ <div id="chat-container" class="flex-grow overflow-y-auto p-3 space-y-3 bg-zinc-100 dark:bg-slate-950">
72
+ </div>
73
+ <div class="border-t p-2 border-slate-800 dark:border-slate-800">
74
+ <div class="flex gap-2">
75
+ <input id="chat-input"
76
+ class="flex-grow rounded-md border border-slate-800 dark:border-slate-800 dark:bg-slate-950 bg-zinc-100 dark:bg-slate-800 px-3 py-1 text-sm text-black dark:text-white focus:outline-none"
77
+ type="text" placeholder="Type your question here..." />
78
+ <button id="send-button"
79
+ class="rounded-md bg-blue-600 px-3 py-1 text-white hover:bg-blue-700">➤</button>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <script>
86
+ window.addEventListener("message", (event) => {
87
+ if (event.origin !== "https://your-domain.com") return; // security
88
+ const {
89
+ type,
90
+ theme
91
+ } = event.data;
92
+
93
+ if (type === "set-theme" && (theme === "light" || theme === "dark")) {
94
+ document.documentElement.setAttribute("data-theme", theme);
95
+ // Or, toggle class if your app uses class-based dark mode:
96
+ document.documentElement.classList.toggle("dark", theme === "dark");
97
+ }
98
+ });
99
+
100
+ const chatContainer = document.getElementById("chat-container");
101
+ const chatInput = document.getElementById("chat-input");
102
+ const sendButton = document.getElementById("send-button");
103
+
104
+ const initialMessage = {
105
+ id: "1",
106
+ text: "Hello! I'm the JUIT University Assistant. How can I help you today?",
107
+ isUser: false,
108
+ };
109
+
110
+ let messages = [initialMessage];
111
+ let isTyping = false;
112
+
113
+ function renderMessages() {
114
+ chatContainer.innerHTML = "";
115
+ messages.forEach((msg) => {
116
+ const bubble = document.createElement("div");
117
+ bubble.className = `flex items-start gap-2 ${msg.isUser ? "flex-row-reverse" : "flex-row"}`;
118
+ const avatar = document.createElement("img");
119
+ avatar.className = "h-9 w-9 rounded-full object-cover";
120
+ avatar.src = msg.isUser ? "../static/profile.png" :
121
+ "../static/JUIT_icon.png"; // adjust path if needed
122
+ avatar.alt = msg.isUser ? "User" : "Bot";
123
+ const msgDiv = document.createElement("div");
124
+ msgDiv.className = `max-w-[75%] break-words rounded-md px-3 py-1 text-sm ${
125
+ msg.isUser
126
+ ? "bg-blue-600 text-white rounded-tr-none"
127
+ : "bg-gray-200 text-black dark:bg-gray-700 dark:text-white rounded-tl-none"
128
+ }`;
129
+ msgDiv.innerText = msg.text;
130
+ bubble.appendChild(avatar);
131
+ bubble.appendChild(msgDiv);
132
+ chatContainer.appendChild(bubble);
133
+ });
134
+ if (isTyping) {
135
+ const typing = document.createElement("div");
136
+ typing.className = "flex items-start gap-2";
137
+ typing.innerHTML = `
138
+ <img src="../static/JUIT_icon.png" alt="Bot" class="h-9 w-9 rounded-full object-cover" />
139
+ <div class="max-w-[75%] rounded-md rounded-tl-none bg-gray-200 dark:bg-gray-700 px-3 py-1 text-sm">
140
+ <div class="dot-typing text-black dark:text-white">
141
+ <span>.</span><span>.</span><span>.</span>
142
+ </div>
143
+ </div>
144
+ `;
145
+ chatContainer.appendChild(typing);
146
+ }
147
+
148
+ chatContainer.scrollTop = chatContainer.scrollHeight;
149
+ }
150
+
151
+ async function simulateBotResponse(userInput) {
152
+ const response = await fetch("http://localhost:8080/api/chat", {
153
+ method: "POST",
154
+ headers: {
155
+ "Content-Type": "application/json",
156
+ },
157
+ body: JSON.stringify({
158
+ message: userInput
159
+ }),
160
+ });
161
+
162
+ if (!response.ok) throw new Error("API error");
163
+
164
+ const data = await response.json();
165
+ if (data.error) throw new Error(data.error);
166
+
167
+ return data;
168
+ }
169
+
170
+ async function handleSendMessage() {
171
+ const text = chatInput.value.trim();
172
+ if (!text) return;
173
+
174
+ const userMessage = {
175
+ id: Date.now().toString(),
176
+ text,
177
+ isUser: true,
178
+ };
179
+
180
+ messages.push(userMessage);
181
+ chatInput.value = "";
182
+ isTyping = true;
183
+ renderMessages();
184
+
185
+ try {
186
+ const res = await simulateBotResponse(text);
187
+ const botMessage = {
188
+ id: (Date.now() + 1).toString(),
189
+ text: res.response,
190
+ isUser: false,
191
+ };
192
+ messages.push(botMessage);
193
+ } catch (error) {
194
+ messages.push({
195
+ id: Date.now().toString(),
196
+ text: "Error: " + error.message,
197
+ isUser: false,
198
+ });
199
+ }
200
+
201
+ isTyping = false;
202
+ renderMessages();
203
+ }
204
+
205
+ sendButton.addEventListener("click", handleSendMessage);
206
+ chatInput.addEventListener("keydown", (e) => {
207
+ if (e.key === "Enter" && !e.shiftKey) {
208
+ e.preventDefault();
209
+ handleSendMessage();
210
+ }
211
+ });
212
+
213
+ renderMessages();
214
+ </script>
215
+ </body>
216
+
217
+ </html>