Param20h commited on
Commit
48838ad
·
unverified ·
1 Parent(s): ade0dfc

NEW UI and UPdated RAG modelx

Browse files
rag/chunker.py DELETED
@@ -1,87 +0,0 @@
1
- import fitz # PyMuPDF
2
- import docx
3
- from config import CHUNK_SIZE, CHUNK_OVERLAP
4
-
5
- # ── Extract Text from PDF ────────────────────────────
6
- def load_pdf(filepath):
7
- doc = fitz.open(filepath)
8
- text_pages = []
9
-
10
- for page_num, page in enumerate(doc):
11
- text = page.get_text()
12
- text_pages.append({
13
- "text": text,
14
- "page": page_num + 1
15
- })
16
-
17
- doc.close()
18
- return text_pages
19
-
20
-
21
- # ── Extract Text from DOCX ───────────────────────────
22
- def load_docx(filepath):
23
- doc = docx.Document(filepath)
24
- text_pages = []
25
- full_text = ""
26
-
27
- for para in doc.paragraphs:
28
- full_text += para.text + "\n"
29
-
30
- text_pages.append({
31
- "text": full_text,
32
- "page": 1
33
- })
34
-
35
- return text_pages
36
-
37
-
38
- # ── Extract Text from TXT ────────────────────────────
39
- def load_txt(filepath):
40
- with open(filepath, "r", encoding="utf-8") as f:
41
- text = f.read()
42
-
43
- return [{
44
- "text": text,
45
- "page": 1
46
- }]
47
-
48
-
49
- # ── Split Text into Chunks ───────────────────────────
50
- def split_text(text, page_num):
51
- chunks = []
52
- start = 0
53
-
54
- while start < len(text):
55
- end = start + CHUNK_SIZE
56
- chunk = text[start:end]
57
-
58
- if chunk.strip():
59
- chunks.append({
60
- "text": chunk,
61
- "page": page_num
62
- })
63
-
64
- start = end - CHUNK_OVERLAP
65
-
66
- return chunks
67
-
68
-
69
- # ── Main Function ────────────────────────────────────
70
- def load_and_chunk(filepath):
71
- ext = filepath.rsplit(".", 1)[1].lower()
72
-
73
- if ext == "pdf":
74
- pages = load_pdf(filepath)
75
- elif ext == "docx":
76
- pages = load_docx(filepath)
77
- elif ext in ["txt", "md"]:
78
- pages = load_txt(filepath)
79
- else:
80
- raise ValueError(f"Unsupported file type: {ext}")
81
-
82
- all_chunks = []
83
- for page in pages:
84
- chunks = split_text(page["text"], page["page"])
85
- all_chunks.extend(chunks)
86
-
87
- return all_chunks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag/embeddings.py DELETED
@@ -1,98 +0,0 @@
1
- from google import genai
2
- from google.genai import types
3
- from pinecone import Pinecone
4
- from config import CHUNK_SIZE, CHUNK_OVERLAP
5
-
6
- # ── Gemini Embedding ─────────────────────────────────
7
- def embed_text(text, gemini_key):
8
- """Generate embedding using Gemini's free gemini-embedding-001 model."""
9
- client = genai.Client(api_key=gemini_key)
10
- result = client.models.embed_content(
11
- model="gemini-embedding-001",
12
- contents=text
13
- )
14
- return result.embeddings[0].values # 3072-dimensional vector
15
-
16
- # ── Get Pinecone Index ───────────────────────────────
17
- def get_pinecone_index(pinecone_key, index_name):
18
- """Connect to user's Pinecone index."""
19
- pc = Pinecone(api_key=pinecone_key)
20
- return pc.Index(index_name)
21
-
22
- # ── Store Embeddings in Pinecone ─────────────────────
23
- def store_embeddings(chunks, filename, user):
24
- """Embed chunks using Gemini and upsert into user's Pinecone index."""
25
- gemini_key = user.get_gemini_key()
26
- pinecone_key = user.get_pinecone_key()
27
- index_name = user.pinecone_index_name
28
-
29
- if not gemini_key:
30
- raise ValueError("Gemini API key is required for embeddings. Please add it in your Profile.")
31
- if not pinecone_key or not index_name:
32
- raise ValueError("Pinecone API key and index name are required. Please add them in your Profile.")
33
-
34
- index = get_pinecone_index(pinecone_key, index_name)
35
- namespace = user.username
36
-
37
- # Batch upsert vectors
38
- batch_size = 50
39
- for i in range(0, len(chunks), batch_size):
40
- batch = chunks[i:i + batch_size]
41
- vectors = []
42
-
43
- for j, chunk in enumerate(batch):
44
- embedding = embed_text(chunk["text"], gemini_key)
45
- vector_id = f"{filename}_{i + j}"
46
-
47
- vectors.append({
48
- "id": vector_id,
49
- "values": embedding,
50
- "metadata": {
51
- "text": chunk["text"],
52
- "filename": filename,
53
- "page": chunk["page"],
54
- "chunk_index": i + j
55
- }
56
- })
57
-
58
- index.upsert(vectors=vectors, namespace=namespace)
59
-
60
- # ── Delete Vectors by Filename ───────────────────────
61
- def delete_embeddings(filename, user):
62
- """Delete all vectors for a specific file from user's Pinecone index."""
63
- pinecone_key = user.get_pinecone_key()
64
- index_name = user.pinecone_index_name
65
-
66
- if not pinecone_key or not index_name:
67
- return
68
-
69
- index = get_pinecone_index(pinecone_key, index_name)
70
- namespace = user.username
71
-
72
- try:
73
- dummy_vector = [0.0] * 3072
74
- results = index.query(
75
- vector=dummy_vector,
76
- top_k=10000,
77
- namespace=namespace,
78
- filter={"filename": {"$eq": filename}},
79
- include_metadata=False
80
- )
81
-
82
- if results.matches:
83
- ids_to_delete = [match.id for match in results.matches]
84
- index.delete(ids=ids_to_delete, namespace=namespace)
85
- except Exception as e:
86
- print(f"Error deleting embeddings: {e}")
87
-
88
- # ── Clear All Vectors for User ───────────────────────
89
- def clear_all_embeddings(user):
90
- """Delete all vectors in user's namespace."""
91
- pinecone_key = user.get_pinecone_key()
92
- index_name = user.pinecone_index_name
93
-
94
- if not pinecone_key or not index_name:
95
- return
96
-
97
- index = get_pinecone_index(pinecone_key, index_name)
98
- index.delete(delete_all=True, namespace=user.username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag/generator.py DELETED
@@ -1,75 +0,0 @@
1
- import os
2
- from google import genai
3
- from google.genai import types
4
- from dotenv import load_dotenv
5
-
6
- load_dotenv()
7
-
8
- from groq import Groq
9
- from config import GROQ_MODEL
10
-
11
- def get_groq_client(user_key):
12
- if not user_key:
13
- raise ValueError("No Groq API Key available. Please add it to your profile.")
14
- return Groq(api_key=user_key)
15
-
16
- def generate_answer(question, context_chunks, user=None):
17
- try:
18
- if not context_chunks:
19
- context = "No specific document context found for this query."
20
- else:
21
- context = "\n\n".join([
22
- f"📄 File: {chunk['filename']} | Page: {chunk['page']}\n{chunk['text']}"
23
- for chunk in context_chunks
24
- ])
25
-
26
- prompt = f"""You are a helpful AI assistant. Answer the user's question based on the provided document context.
27
-
28
- Document Context:
29
- {context}
30
-
31
- User Question: {question}
32
-
33
- Instructions:
34
- - If the question is a greeting or general chat (like "hi" or "how are you"), just reply naturally and explain you are here to help with their PDF documents.
35
- - If the question is about the document, use the provided context to answer.
36
- - If the context doesn't contain the answer, just say you couldn't find it in the document.
37
- - Try to be clear, helpful, and concise.
38
-
39
- Answer:"""
40
-
41
- pref_model = user.preferred_model if user else "groq"
42
-
43
- if pref_model == "gemini":
44
- key = user.get_gemini_key() if user else None
45
- if not key:
46
- return "❌ No Gemini API key available. Please add it in your Profile settings."
47
-
48
- client = genai.Client(api_key=key, http_options=types.HttpOptions(api_version="v1"))
49
- response = client.models.generate_content(
50
- model="gemini-2.0-flash",
51
- contents=prompt
52
- )
53
- return response.text
54
- else:
55
- key = user.get_groq_key() if user else None
56
- client = get_groq_client(key)
57
- response = client.chat.completions.create(
58
- model=GROQ_MODEL,
59
- messages=[
60
- {
61
- "role": "system",
62
- "content": "You are a helpful document assistant."
63
- },
64
- {
65
- "role": "user",
66
- "content": prompt
67
- }
68
- ],
69
- temperature=0.3,
70
- max_tokens=1024,
71
- )
72
- return response.choices[0].message.content
73
-
74
- except Exception as e:
75
- return f"❌ Error generating answer: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag/retriever.py DELETED
@@ -1,71 +0,0 @@
1
- from google import genai
2
- from google.genai import types
3
- from pinecone import Pinecone
4
- from config import TOP_K
5
-
6
- # ── Gemini Embedding ─────────────────────────────────
7
- def embed_query(query, gemini_key):
8
- """Generate query embedding using Gemini's gemini-embedding-001."""
9
- client = genai.Client(api_key=gemini_key)
10
- result = client.models.embed_content(
11
- model="gemini-embedding-001",
12
- contents=query
13
- )
14
- return result.embeddings[0].values
15
-
16
- # ── Retrieve Chunks from Pinecone ────────────────────
17
- def retrieve_chunks(query, filename=None, user=None):
18
- """Query user's Pinecone index for relevant chunks."""
19
- if not user:
20
- return []
21
-
22
- gemini_key = user.get_gemini_key()
23
- pinecone_key = user.get_pinecone_key()
24
- index_name = user.pinecone_index_name
25
-
26
- if not gemini_key or not pinecone_key or not index_name:
27
- return []
28
-
29
- try:
30
- # Generate query embedding
31
- query_embedding = embed_query(query, gemini_key)
32
-
33
- # Connect to Pinecone
34
- pc = Pinecone(api_key=pinecone_key)
35
- index = pc.Index(index_name)
36
-
37
- # Build metadata filter
38
- filter_dict = None
39
- if filename:
40
- filter_dict = {"filename": {"$eq": filename}}
41
-
42
- # Query Pinecone
43
- results = index.query(
44
- vector=query_embedding,
45
- top_k=TOP_K,
46
- namespace=user.username,
47
- filter=filter_dict,
48
- include_metadata=True
49
- )
50
-
51
- # Format results
52
- chunks = []
53
- if results.matches:
54
- max_score = max(m.score for m in results.matches) if results.matches else 1
55
-
56
- for match in results.matches:
57
- confidence = round((match.score / max_score) * 100, 2) if max_score > 0 else 0
58
-
59
- chunks.append({
60
- "text": match.metadata.get("text", ""),
61
- "filename": match.metadata.get("filename", ""),
62
- "page": match.metadata.get("page", 1),
63
- "score": round(match.score, 4),
64
- "confidence": confidence
65
- })
66
-
67
- return chunks
68
-
69
- except Exception as e:
70
- print(f"Retrieval error: {e}")
71
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/profile_pics/param20h.jpeg DELETED

Git LFS Details

  • SHA256: fea25f73ad78a46fb54d03f3c03be5b302904592bb8d02b0330fadf7b83d0927
  • Pointer size: 130 Bytes
  • Size of remote file: 38.3 kB
static/script.js DELETED
@@ -1,17 +0,0 @@
1
- // Check for saved user preference, if any, on load of the website
2
- document.addEventListener("DOMContentLoaded", () => {
3
- const currentTheme = localStorage.getItem("theme");
4
- if (currentTheme === "light") {
5
- document.body.classList.add("light-mode");
6
- }
7
- });
8
-
9
- // Expose toggle function to global scope
10
- function toggleLightMode() {
11
- document.body.classList.toggle("light-mode");
12
- let theme = "dark";
13
- if (document.body.classList.contains("light-mode")) {
14
- theme = "light";
15
- }
16
- localStorage.setItem("theme", theme);
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/style.css DELETED
@@ -1,614 +0,0 @@
1
- /* ── Google Fonts ───────────────────────────────────── */
2
- @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
3
-
4
- /* ── Global Variables ───────────────────────────────── */
5
- :root {
6
- --bg-gradient: #09090b;
7
- --glass-bg: rgba(255, 255, 255, 0.02);
8
- --glass-border: rgba(255, 255, 255, 0.08);
9
- --glass-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
10
-
11
- --primary-color: #fafafa;
12
- --primary-hover: #e4e4e7;
13
- --primary-text: #09090b;
14
- --accent-color: #3b82f6;
15
- --danger-color: #ef4444;
16
- --danger-hover: #dc2626;
17
- --success-color: #10b981;
18
-
19
- --text-main: #fafafa;
20
- --text-muted: #a1a1aa;
21
-
22
- --transition-speed: 0.25s;
23
-
24
- --header-bg: rgba(9, 9, 11, 0.85);
25
- --input-bg: rgba(255, 255, 255, 0.03);
26
- --item-hover-bg: rgba(255, 255, 255, 0.05);
27
- }
28
-
29
- body.light-mode {
30
- --bg-gradient: #ffffff;
31
- --glass-bg: #ffffff;
32
- --glass-border: rgba(0, 0, 0, 0.1);
33
- --glass-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
34
-
35
- --primary-color: #18181b;
36
- --primary-hover: #27272a;
37
- --primary-text: #fafafa;
38
-
39
- --text-main: #18181b;
40
- --text-muted: #71717a;
41
-
42
- --header-bg: rgba(255, 255, 255, 0.85);
43
- --input-bg: #fdfdfd;
44
- --item-hover-bg: #f4f4f5;
45
- }
46
-
47
- /* ── Global Styles ──────────────────────────────────── */
48
- * {
49
- margin: 0;
50
- padding: 0;
51
- box-sizing: border-box;
52
- font-family: 'Outfit', sans-serif;
53
- }
54
-
55
- body {
56
- background: var(--bg-gradient);
57
- color: var(--text-main);
58
- min-height: 100vh;
59
- display: flex;
60
- flex-direction: column;
61
- }
62
-
63
- /* ── Header ─────────────────────────────────────────── */
64
- header {
65
- display: flex;
66
- justify-content: space-between;
67
- align-items: center;
68
- padding: 15px 40px;
69
- background: var(--header-bg);
70
- backdrop-filter: blur(12px);
71
- border-bottom: 1px solid var(--glass-border);
72
- box-shadow: var(--glass-shadow);
73
- position: sticky;
74
- top: 0;
75
- z-index: 100;
76
- }
77
-
78
- header h1 {
79
- font-size: 24px;
80
- font-weight: 600;
81
- margin: 0;
82
- background: linear-gradient(to right, #8b5cf6, #0ea5e9);
83
- -webkit-background-clip: text;
84
- background-clip: text;
85
- -webkit-text-fill-color: transparent;
86
- display: flex;
87
- align-items: center;
88
- gap: 8px;
89
- }
90
-
91
- header a {
92
- color: var(--text-muted);
93
- text-decoration: none;
94
- font-size: 15px;
95
- font-weight: 500;
96
- transition: color var(--transition-speed) ease;
97
- }
98
-
99
- header a:hover {
100
- color: var(--text-main);
101
- }
102
-
103
- .header-right {
104
- display: flex;
105
- align-items: center;
106
- gap: 16px;
107
- }
108
-
109
- /* ── Container ──────────────────────────────────────── */
110
- .container {
111
- max-width: 850px;
112
- width: 100%;
113
- margin: 40px auto;
114
- padding: 0 20px;
115
- flex-grow: 1;
116
- animation: fadeIn 0.6s ease-out;
117
- }
118
-
119
- @keyframes fadeIn {
120
- from {
121
- opacity: 0;
122
- transform: translateY(10px);
123
- }
124
-
125
- to {
126
- opacity: 1;
127
- transform: translateY(0);
128
- }
129
- }
130
-
131
- /* ── Glassmorphism Cards ────────────────────────────── */
132
- .upload-box,
133
- .files-box,
134
- .clear-box,
135
- .auth-box,
136
- .chat-box {
137
- background: var(--glass-bg);
138
- backdrop-filter: blur(16px);
139
- -webkit-backdrop-filter: blur(16px);
140
- border: 1px solid var(--glass-border);
141
- border-radius: 16px;
142
- padding: 30px;
143
- box-shadow: var(--glass-shadow);
144
- margin-bottom: 25px;
145
- transition: transform var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
146
- }
147
-
148
- .upload-box:hover,
149
- .files-box:hover,
150
- .clear-box:hover {
151
- transform: translateY(-2px);
152
- box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.45);
153
- }
154
-
155
- .upload-box h2,
156
- .files-box h2,
157
- .clear-box h2,
158
- .auth-box h2 {
159
- margin-bottom: 20px;
160
- color: var(--text-main);
161
- font-weight: 600;
162
- font-size: 20px;
163
- }
164
-
165
- /* ── Form Inputs & Uploads ──────────────────────────── */
166
- input[type="file"] {
167
- display: block;
168
- width: 100%;
169
- margin: 0 auto 20px auto;
170
- padding: 12px;
171
- background: var(--input-bg);
172
- border: 1px dashed var(--glass-border);
173
- border-radius: 8px;
174
- color: var(--text-muted);
175
- cursor: pointer;
176
- transition: all var(--transition-speed);
177
- }
178
-
179
- input[type="file"]:hover {
180
- border-color: var(--primary-color);
181
- background: var(--item-hover-bg);
182
- }
183
-
184
- input[type="text"],
185
- input[type="password"],
186
- input[type="email"] {
187
- width: 100%;
188
- padding: 14px 16px;
189
- margin-bottom: 16px;
190
- background: var(--input-bg);
191
- border: 1px solid var(--glass-border);
192
- border-radius: 10px;
193
- color: var(--text-main);
194
- font-size: 15px;
195
- outline: none;
196
- transition: all var(--transition-speed) ease;
197
- }
198
-
199
- input[type="text"]:focus,
200
- input[type="password"]:focus,
201
- input[type="email"]:focus {
202
- border-color: var(--primary-color);
203
- box-shadow: 0 0 0 2px var(--glass-border);
204
- }
205
-
206
- /* ── Buttons ────────────────────────────────────────── */
207
- button {
208
- background: var(--primary-color);
209
- color: var(--primary-text);
210
- border: 1px solid var(--glass-border);
211
- padding: 12px 24px;
212
- border-radius: 8px;
213
- cursor: pointer;
214
- font-size: 14px;
215
- font-weight: 500;
216
- transition: all var(--transition-speed) ease;
217
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
218
- }
219
-
220
- button:hover {
221
- transform: translateY(-1px);
222
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
223
- background: var(--primary-hover);
224
- }
225
-
226
- #askBtn {
227
- border-radius: 8px;
228
- padding: 12px 20px;
229
- }
230
-
231
- #clearBtn {
232
- background: rgba(255, 255, 255, 0.1);
233
- box-shadow: none;
234
- color: var(--text-muted);
235
- }
236
-
237
- #clearBtn:hover {
238
- background: rgba(255, 255, 255, 0.15);
239
- color: var(--text-main);
240
- }
241
-
242
- .logout-btn {
243
- background: var(--danger-color);
244
- color: white;
245
- border-color: transparent;
246
- box-shadow: none;
247
- padding: 8px 16px;
248
- font-size: 14px;
249
- }
250
-
251
- .logout-btn:hover {
252
- background: var(--danger-hover);
253
- box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4);
254
- }
255
-
256
- .delete-btn {
257
- background: transparent;
258
- color: var(--danger-color);
259
- border: 1px solid rgba(239, 68, 68, 0.3);
260
- padding: 6px 12px;
261
- border-radius: 6px;
262
- font-size: 13px;
263
- box-shadow: none;
264
- }
265
-
266
- .delete-btn:hover {
267
- background: var(--danger-color);
268
- color: white;
269
- transform: none;
270
- }
271
-
272
- .clear-vector-btn {
273
- background: rgba(239, 68, 68, 0.1);
274
- color: var(--danger-color);
275
- border: 1px solid rgba(239, 68, 68, 0.3);
276
- box-shadow: none;
277
- margin-top: 10px;
278
- }
279
-
280
- .clear-vector-btn:hover {
281
- background: var(--danger-color);
282
- color: white;
283
- }
284
-
285
- .google-btn {
286
- background: white;
287
- color: #333;
288
- width: 100%;
289
- display: flex;
290
- justify-content: center;
291
- align-items: center;
292
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
293
- }
294
-
295
- .google-btn:hover {
296
- background: #f1f5f9;
297
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
298
- }
299
-
300
- /* ── Nav Button ─────────────────────────────────────── */
301
- .nav-btn {
302
- text-align: center;
303
- margin-top: 30px;
304
- }
305
-
306
- .nav-btn button {
307
- padding: 14px 32px;
308
- font-size: 16px;
309
- border-radius: 30px;
310
- }
311
-
312
- /* ── Status Texts ───────────────────────────────────── */
313
- #uploadStatus,
314
- #clearStatus {
315
- margin-top: 15px;
316
- font-size: 14px;
317
- font-weight: 500;
318
- }
319
-
320
- /* ── Files List ─────────────────────────────────────── */
321
- .files-box ul {
322
- list-style: none;
323
- }
324
-
325
- .file-item {
326
- display: flex;
327
- justify-content: space-between;
328
- align-items: center;
329
- padding: 14px 18px;
330
- background: var(--input-bg);
331
- border: 1px solid var(--glass-border);
332
- margin-bottom: 10px;
333
- border-radius: 10px;
334
- font-size: 15px;
335
- transition: all var(--transition-speed) ease;
336
- }
337
-
338
- .file-item.active-file {
339
- background: rgba(139, 92, 246, 0.25);
340
- border-color: var(--primary-color);
341
- }
342
-
343
- .file-item:last-child {
344
- margin-bottom: 0;
345
- }
346
-
347
- .file-item:hover {
348
- background: var(--item-hover-bg);
349
- border-color: rgba(139, 92, 246, 0.3);
350
- }
351
-
352
- .file-item span {
353
- display: flex;
354
- align-items: center;
355
- gap: 10px;
356
- }
357
-
358
- /* ── Chat Box ───────────────────────────────────────── */
359
- .chat-box {
360
- height: 500px;
361
- overflow-y: auto;
362
- display: flex;
363
- flex-direction: column;
364
- padding: 20px 25px;
365
- gap: 16px;
366
- margin-bottom: 20px;
367
- }
368
-
369
- /* Custom Scrollbar for Chat */
370
- .chat-box::-webkit-scrollbar {
371
- width: 6px;
372
- }
373
-
374
- .chat-box::-webkit-scrollbar-track {
375
- background: rgba(0, 0, 0, 0.1);
376
- border-radius: 10px;
377
- }
378
-
379
- .chat-box::-webkit-scrollbar-thumb {
380
- background: var(--text-muted);
381
- border-radius: 10px;
382
- }
383
-
384
- .chat-box::-webkit-scrollbar-thumb:hover {
385
- background: var(--primary-color);
386
- }
387
-
388
- /* ── Messages ───────────────────────────────────────── */
389
- .message {
390
- padding: 14px 18px;
391
- border-radius: 14px;
392
- line-height: 1.6;
393
- max-width: 85%;
394
- font-size: 15px;
395
- animation: messagePop 0.3s ease-out;
396
- }
397
-
398
- @keyframes messagePop {
399
- from {
400
- opacity: 0;
401
- transform: scale(0.95) translateY(10px);
402
- }
403
-
404
- to {
405
- opacity: 1;
406
- transform: scale(1) translateY(0);
407
- }
408
- }
409
-
410
- .message.user {
411
- background: var(--primary-color);
412
- color: var(--primary-text);
413
- border: 1px solid var(--glass-border);
414
- align-self: flex-end;
415
- border-bottom-right-radius: 4px;
416
- }
417
-
418
- .message.bot {
419
- background: var(--input-bg);
420
- border: 1px solid var(--glass-border);
421
- align-self: flex-start;
422
- border-bottom-left-radius: 4px;
423
- }
424
-
425
- .message.error {
426
- background: rgba(239, 68, 68, 0.1);
427
- border: 1px solid rgba(239, 68, 68, 0.3);
428
- color: var(--danger-color);
429
- align-self: flex-start;
430
- }
431
-
432
- /* ── Input Area ─────────────────────────────────────── */
433
- .input-area {
434
- display: flex;
435
- gap: 12px;
436
- background: var(--glass-bg);
437
- padding: 15px;
438
- border-radius: 16px;
439
- border: 1px solid var(--glass-border);
440
- backdrop-filter: blur(16px);
441
- }
442
-
443
- .input-area input {
444
- flex: 1;
445
- margin-bottom: 0;
446
- border: none;
447
- background: var(--input-bg);
448
- }
449
-
450
- #loader {
451
- text-align: center;
452
- font-size: 14px;
453
- color: var(--primary-color);
454
- margin-top: -10px;
455
- margin-bottom: 10px;
456
- font-weight: 500;
457
- animation: pulse 1.5s infinite;
458
- }
459
-
460
- @keyframes pulse {
461
- 0% {
462
- opacity: 0.6;
463
- }
464
-
465
- 50% {
466
- opacity: 1;
467
- }
468
-
469
- 100% {
470
- opacity: 0.6;
471
- }
472
- }
473
-
474
- /* ── Sources & Badge ────────────────────────────────── */
475
- .sources {
476
- margin-top: 10px;
477
- background: var(--input-bg);
478
- border-left: 3px solid var(--accent-color);
479
- padding: 12px 16px;
480
- border-radius: 6px;
481
- font-size: 13px;
482
- color: var(--text-muted);
483
- }
484
-
485
- .sources ul {
486
- margin-top: 8px;
487
- padding-left: 0;
488
- list-style: none;
489
- }
490
-
491
- .sources li {
492
- display: flex;
493
- justify-content: space-between;
494
- padding: 6px 0;
495
- border-bottom: 1px solid var(--glass-border);
496
- }
497
-
498
- .sources li:last-child {
499
- border-bottom: none;
500
- padding-bottom: 0;
501
- }
502
-
503
- .confidence {
504
- color: var(--success-color);
505
- font-weight: 600;
506
- }
507
-
508
- .file-badge {
509
- text-align: center;
510
- padding: 10px 15px;
511
- border-radius: 20px;
512
- display: inline-block;
513
- margin: 0 auto 20px;
514
- font-size: 14px;
515
- font-weight: 500;
516
- background: rgba(16, 185, 129, 0.2);
517
- border: 1px solid rgba(16, 185, 129, 0.3);
518
- color: var(--success-color);
519
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
520
- }
521
-
522
- /* ── Auth Box ───────────────────────────────────────── */
523
- .auth-box {
524
- max-width: 420px;
525
- margin: 60px auto;
526
- text-align: center;
527
- }
528
-
529
- .auth-box p {
530
- margin-top: 20px;
531
- font-size: 14px;
532
- color: var(--text-muted);
533
- }
534
-
535
- .auth-box a {
536
- color: var(--primary-color);
537
- font-weight: 600;
538
- text-decoration: none;
539
- transition: color var(--transition-speed);
540
- }
541
-
542
- .auth-box a:hover {
543
- color: var(--primary-hover);
544
- text-decoration: underline;
545
- }
546
-
547
- .error-msg {
548
- background: rgba(239, 68, 68, 0.1);
549
- color: var(--danger-color);
550
- padding: 12px;
551
- border-radius: 8px;
552
- margin-bottom: 20px;
553
- font-size: 14px;
554
- border: 1px solid rgba(239, 68, 68, 0.3);
555
- }
556
-
557
- .divider {
558
- display: flex;
559
- align-items: center;
560
- margin: 20px 0;
561
- color: var(--text-muted);
562
- font-size: 13px;
563
- }
564
-
565
- .divider::before,
566
- .divider::after {
567
- content: "";
568
- flex: 1;
569
- border-bottom: 1px solid var(--glass-border);
570
- margin: 0 15px;
571
- }
572
-
573
- /* ── Profile Pic ────────────────────────────────────── */
574
- .profile-container {
575
- position: relative;
576
- display: inline-block;
577
- }
578
-
579
- .profile-pic {
580
- width: 44px;
581
- height: 44px;
582
- border-radius: 50%;
583
- object-fit: cover;
584
- border: 2px solid var(--primary-color);
585
- cursor: pointer;
586
- transition: all 0.3s ease;
587
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
588
- }
589
-
590
- .profile-pic:hover {
591
- transform: scale(1.05);
592
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
593
- filter: brightness(0.8);
594
- }
595
-
596
- .profile-pic:hover::after {
597
- content: "Edit";
598
- position: absolute;
599
- bottom: 0;
600
- left: 0;
601
- background: var(--input-bg);
602
- color: var(--text-main);
603
- font-size: 11px;
604
- width: 100%;
605
- text-align: center;
606
- border-radius: 0 0 50% 50%;
607
- pointer-events: none;
608
- }
609
-
610
- .username-text {
611
- font-size: 16px;
612
- color: var(--text-main);
613
- font-weight: 500;
614
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/admin.html DELETED
@@ -1,106 +0,0 @@
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>Admin Dashboard</title>
8
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
- <script src="{{ url_for('static', filename='script.js') }}"></script>
10
- <style>
11
- .admin-table {
12
- width: 100%;
13
- border-collapse: collapse;
14
- margin-top: 20px;
15
- color: var(--text-main);
16
- }
17
-
18
- .admin-table th,
19
- .admin-table td {
20
- padding: 12px;
21
- border: 1px solid var(--glass-border);
22
- text-align: left;
23
- }
24
-
25
- .admin-table th {
26
- background: rgba(139, 92, 246, 0.2);
27
- }
28
-
29
- .admin-table td {
30
- background: var(--input-bg);
31
- }
32
-
33
- .file-list {
34
- margin: 0;
35
- padding-left: 20px;
36
- }
37
-
38
- .file-link {
39
- color: var(--primary-color);
40
- text-decoration: none;
41
- font-weight: bold;
42
- }
43
-
44
- .file-link:hover {
45
- text-decoration: underline;
46
- }
47
- </style>
48
- </head>
49
-
50
- <body>
51
- <header>
52
- <h1>🛡️ Admin Dashboard</h1>
53
- <div class="header-right">
54
- <button class="logout-btn" style="background-color: var(--primary-color);"
55
- onclick="toggleLightMode()">🌗</button>
56
- <a href="/">← Dashboard</a>
57
- <a href="/logout"><button class="logout-btn">Logout</button></a>
58
- </div>
59
- </header>
60
-
61
- <div class="container" style="max-width: 1000px;">
62
- <div class="upload-box" style="text-align: left;">
63
- <h2>👥 Registered Users & Files</h2>
64
- <table class="admin-table">
65
- <thead>
66
- <tr>
67
- <th>ID</th>
68
- <th>User</th>
69
- <th>Email</th>
70
- <th>Preferred Model</th>
71
- <th>Files (Click to Download)</th>
72
- </tr>
73
- </thead>
74
- <tbody>
75
- {% for user in users %}
76
- <tr>
77
- <td>{{ user.id }}</td>
78
- <td>
79
- {% if user.is_admin %}⭐{% endif %}
80
- {{ user.username }}
81
- </td>
82
- <td>{{ user.email }}</td>
83
- <td>{{ user.preferred_model }}</td>
84
- <td>
85
- {% if user_files[user.username] %}
86
- <ul class="file-list">
87
- {% for f in user_files[user.username] %}
88
- <li>
89
- 📄 <a href="/download/{{ user.username }}/{{ f }}" class="file-link"
90
- title="Download {{ f }}">{{ f }}</a>
91
- </li>
92
- {% endfor %}
93
- </ul>
94
- {% else %}
95
- <span style="color: var(--text-muted);">No files</span>
96
- {% endif %}
97
- </td>
98
- </tr>
99
- {% endfor %}
100
- </tbody>
101
- </table>
102
- </div>
103
- </div>
104
- </body>
105
-
106
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/chat.html DELETED
@@ -1,162 +0,0 @@
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>RAG Chat</title>
8
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
- <script src="{{ url_for('static', filename='script.js') }}"></script>
10
- </head>
11
-
12
- <body>
13
-
14
- <!-- ── Header ── -->
15
- <header>
16
- <h1>💬 RAG PDF Assistant</h1>
17
- <div class="header-right">
18
- <button class="logout-btn" style="background-color: var(--primary-color);"
19
- onclick="toggleLightMode()">🌗</button>
20
- <a href="/profile">👤 Profile</a>
21
- <a href="/">← Dashboard</a>
22
- </div>
23
- </header>
24
-
25
- <!-- ── Chat Box ── -->
26
- <div class="container"
27
- style="max-width: 1000px; display: flex; flex-direction: column; height: 85vh; margin-top: 20px;">
28
-
29
- <!-- ── Current File Badge ── -->
30
- <div id="currentFileBadge" class="file-badge" style="align-self: flex-start; margin-bottom: 10px;">
31
- 📄 No file selected
32
- </div>
33
-
34
- <div class="chat-box" id="chatBox"
35
- style="flex-grow: 1; height: auto; border-radius: 16px 16px 0 0; border-bottom: none; margin-bottom: 0;">
36
- <!-- Messages appear here -->
37
- </div>
38
-
39
- <!-- ── Input Area ── -->
40
- <div class="input-area"
41
- style="border-radius: 0 0 16px 16px; border-top: 1px solid var(--glass-border); padding: 20px;">
42
- <input type="text" id="questionInput" placeholder="Ask a question about your document..."
43
- style="font-size: 16px; padding: 15px;" />
44
- <button id="askBtn" style="padding: 15px 30px; font-size: 16px; min-width: 100px;">Ask</button>
45
- <button id="clearBtn" style="padding: 15px 20px;">Clear</button>
46
- </div>
47
-
48
- <!-- ── Loading Spinner ── -->
49
- <div id="loader" style="display:none; margin-top: 15px;">
50
- Generating response...
51
- </div>
52
- </div>
53
-
54
- <script>
55
- // ── Store Current Filename ───────────────────────
56
- let currentFile = localStorage.getItem("currentFile") || ""
57
-
58
- // ── Show Current File Badge ──────────────────────
59
- const badge = document.getElementById("currentFileBadge")
60
- if (currentFile) {
61
- badge.innerText = `📄 Asking from: ${currentFile}`
62
- badge.style.backgroundColor = "#eafaf1"
63
- badge.style.color = "green"
64
- } else {
65
- badge.innerText = "⚠️ No file selected - Go back and upload a PDF"
66
- badge.style.backgroundColor = "#fadbd8"
67
- badge.style.color = "red"
68
- }
69
-
70
- // ── Ask Question ─────────────────────────────────
71
- document.getElementById("askBtn").addEventListener("click", async () => {
72
- const question = document.getElementById("questionInput").value.trim()
73
- const chatBox = document.getElementById("chatBox")
74
- const loader = document.getElementById("loader")
75
-
76
- if (!question) {
77
- alert("Please enter a question!")
78
- return
79
- }
80
-
81
- if (!currentFile) {
82
- chatBox.innerHTML += `
83
- <div class="message error">
84
- ❌ Please go back and upload a PDF first!
85
- </div>
86
- `
87
- document.getElementById("questionInput").value = ""
88
- chatBox.scrollTop = chatBox.scrollHeight
89
- return
90
- }
91
-
92
- // ── Show user message ──
93
- chatBox.innerHTML += `
94
- <div class="message user">
95
- <strong>You:</strong> ${question}
96
- </div>
97
- `
98
-
99
- document.getElementById("questionInput").value = ""
100
- loader.style.display = "block"
101
- chatBox.scrollTop = chatBox.scrollHeight
102
-
103
- try {
104
- const response = await fetch("/ask", {
105
- method: "POST",
106
- headers: { "Content-Type": "application/json" },
107
- body: JSON.stringify({
108
- question: question,
109
- filename: currentFile
110
- })
111
- })
112
-
113
- const data = await response.json()
114
- loader.style.display = "none"
115
-
116
- if (response.ok) {
117
- // ── Show answer ──
118
- chatBox.innerHTML += `
119
- <div class="message bot">
120
- <strong>Assistant:</strong> ${data.answer}
121
- </div>
122
- `
123
- } else {
124
- chatBox.innerHTML += `
125
- <div class="message error">
126
- ❌ Error: ${data.error || "Unknown error"}
127
- </div>
128
- `
129
- }
130
- } catch (error) {
131
- loader.style.display = "none"
132
- chatBox.innerHTML += `
133
- <div class="message error">
134
- ❌ Something went wrong.
135
- </div>
136
- `
137
- }
138
-
139
- // ── Auto scroll to bottom ──
140
- chatBox.scrollTop = chatBox.scrollHeight
141
- })
142
-
143
-
144
- // ── Clear History ─────────────────────────────────
145
- document.getElementById("clearBtn").addEventListener("click", async () => {
146
- if (!confirm("Are you sure you want to clear chat history? think again")) return
147
- await fetch("/clear", { method: "POST" })
148
- document.getElementById("chatBox").innerHTML = ""
149
- })
150
-
151
-
152
- // ── Enter Key Support ─────────────────────────────
153
- document.getElementById("questionInput").addEventListener("keypress", (e) => {
154
- if (e.key === "Enter") {
155
- document.getElementById("askBtn").click()
156
- }
157
- })
158
- </script>
159
-
160
- </body>
161
-
162
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/index.html DELETED
@@ -1,243 +0,0 @@
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>RAG Application</title>
8
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
- <script src="{{ url_for('static', filename='script.js') }}"></script>
10
- </head>
11
-
12
- <body>
13
-
14
- <!-- ── Header ── -->
15
- <header>
16
- <h1>📄 RAG Assistant</h1>
17
- <div class="header-right">
18
-
19
- <!-- ── Profile Pic ── -->
20
- <div class="profile-container">
21
- <img id="profilePic"
22
- src="{{ current_user.profile_pic or url_for('static', filename='default_avatar.png') }}"
23
- alt="Profile" class="profile-pic" onclick="document.getElementById('picInput').click()"
24
- title="Click to change profile picture" />
25
- <input type="file" id="picInput" accept="image/*" style="display:none"
26
- onchange="uploadProfilePic(this)" />
27
- </div>
28
-
29
- <!-- ── Username ── -->
30
- <a href="/profile" style="text-decoration: none; display: flex; align-items: center;">
31
- <span class="username-text" style="cursor: pointer;" title="Go to Profile">{{ current_user.username
32
- }}</span>
33
- </a>
34
-
35
- <!-- ── Admin Button ── -->
36
- {% if current_user.is_admin %}
37
- <a href="/admin">
38
- <button class="logout-btn" style="background-color: var(--danger-color); margin-right: 10px;">🛡️ Admin
39
- Node</button>
40
- </a>
41
- {% endif %}
42
-
43
- <!-- ── Light Mode Toggle ── -->
44
- <button class="logout-btn" style="background-color: var(--primary-color);"
45
- onclick="toggleLightMode()">🌗</button>
46
-
47
- <!-- ── Logout ── -->
48
- <a href="/logout">
49
- <button class="logout-btn">Logout</button>
50
- </a>
51
-
52
- </div>
53
- </header>
54
-
55
- <div class="container" style="max-width: 1100px;">
56
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px;">
57
- <div class="left-panel">
58
- <!-- ── Upload Section ── -->
59
- <div class="upload-box">
60
- <h2>Upload PDF</h2>
61
- <form id="uploadForm">
62
- <input type="file" id="pdfFile" accept=".pdf,.docx,.txt,.md" required />
63
- <button style="width: 100%;" type="submit">Upload & Index</button>
64
- </form>
65
- <div id="uploadStatus"></div>
66
- </div>
67
-
68
- <!-- ── Navigate to Chat ── -->
69
- <div class="nav-btn" style="text-align: left; margin-top: 20px;">
70
- <a href="/chat" style="display: block;">
71
- <button style="width: 100%; background: var(--accent-color); color: white; padding: 18px; font-size: 18px;">Go to Chat →</button>
72
- </a>
73
- </div>
74
- </div>
75
-
76
- <div class="right-panel">
77
- <!-- ── Uploaded Files List ── -->
78
- <div class="files-box" style="height: 100%; display: flex; flex-direction: column;">
79
- <h2>📋 Uploaded Files</h2>
80
- <ul id="filesList" style="flex-grow: 1; overflow-y: auto; max-height: 400px; padding-right: 10px;">
81
- <!-- Files appear here -->
82
- </ul>
83
-
84
- <div class="clear-box" style="margin-top: 20px; margin-bottom: 0;">
85
- <h2 style="font-size: 16px;">🔄 Reset Vector Store</h2>
86
- <button id="clearVectorBtn" class="clear-vector-btn" style="width: 100%;">
87
- 🔄 Clear Vector Store
88
- </button>
89
- <div id="clearStatus"></div>
90
- </div>
91
- </div>
92
- </div>
93
- </div>
94
- </div>
95
-
96
- <script>
97
- // ── Load Files on Page Load ───────────────────────
98
- async function loadFiles() {
99
- const response = await fetch("/files")
100
- const data = await response.json()
101
- const filesList = document.getElementById("filesList")
102
-
103
- filesList.innerHTML = ""
104
-
105
- if (data.files.length === 0) {
106
- filesList.innerHTML = "<li>No files uploaded yet</li>"
107
- return
108
- }
109
-
110
- data.files.forEach(file => {
111
- let icon = "📄"
112
- if (file.endsWith(".docx")) icon = "📝"
113
- if (file.endsWith(".txt")) icon = "📃"
114
-
115
- const isSelected = localStorage.getItem("currentFile") === file ? "checked" : ""
116
- const activeClass = localStorage.getItem("currentFile") === file ? "active-file" : ""
117
-
118
- filesList.innerHTML += `
119
- <li class="file-item ${activeClass}" style="cursor: pointer;" onclick="selectFile('${file}')">
120
- <span>
121
- <input type="radio" name="selectedFile" value="${file}" ${isSelected} class="file-radio" onclick="event.stopPropagation(); selectFile('${file}')">
122
- ${icon} ${file}
123
- </span>
124
- <button
125
- class="delete-btn"
126
- onclick="event.stopPropagation(); deleteFile('${file}')">
127
- 🗑️ Delete
128
- </button>
129
- </li>
130
- `
131
- })
132
- }
133
-
134
- function selectFile(filename) {
135
- localStorage.setItem("currentFile", filename)
136
- loadFiles() // Refresh list to show active selection
137
- }
138
-
139
- async function uploadProfilePic(input) {
140
- const file = input.files[0]
141
- if (!file) return
142
-
143
- const formData = new FormData()
144
- formData.append("profile_pic", file)
145
-
146
- const response = await fetch("/upload_profile_pic", {
147
- method: "POST",
148
- body: formData
149
- })
150
-
151
- const data = await response.json()
152
-
153
- if (response.ok) {
154
- // ── Update pic instantly ──
155
- document.getElementById("profilePic").src = data.profile_pic + "?t=" + Date.now()
156
- } else {
157
- alert("❌ " + data.error)
158
- }
159
- }
160
-
161
- // ── Upload PDF ────────────────────────────────────
162
- document.getElementById("uploadForm").addEventListener("submit", async (e) => {
163
- e.preventDefault()
164
-
165
- const fileInput = document.getElementById("pdfFile")
166
- const statusDiv = document.getElementById("uploadStatus")
167
- const formData = new FormData()
168
-
169
- formData.append("pdf", fileInput.files[0])
170
- statusDiv.innerText = "Uploading..."
171
-
172
- const response = await fetch("/upload", {
173
- method: "POST",
174
- body: formData
175
- })
176
-
177
- const data = await response.json()
178
-
179
- if (response.ok) {
180
- localStorage.setItem("currentFile", fileInput.files[0].name)
181
- statusDiv.innerText = "✅ " + data.message
182
- statusDiv.style.color = "green"
183
- loadFiles() // Refresh files list
184
- } else {
185
- statusDiv.innerText = "❌ " + data.error
186
- statusDiv.style.color = "red"
187
- }
188
- })
189
-
190
- document.getElementById("clearVectorBtn").addEventListener("click", async () => {
191
- if (!confirm("Are you sure? This will delete ALL embeddings!")) return
192
-
193
- const clearStatus = document.getElementById("clearStatus")
194
- clearStatus.innerText = "Clearing..."
195
-
196
- const response = await fetch("/clear_vectorstore", {
197
- method: "POST"
198
- })
199
-
200
- const data = await response.json()
201
-
202
- if (response.ok) {
203
- clearStatus.innerText = "✅ " + data.message
204
- clearStatus.style.color = "green"
205
- localStorage.removeItem("currentFile")
206
- loadFiles()
207
- } else {
208
- clearStatus.innerText = "❌ " + data.error
209
- clearStatus.style.color = "red"
210
- }
211
- })
212
-
213
- // ── Load Files on Page Load ───────────────────────
214
- async function deleteFile(filename) {
215
- if (!confirm(`Are you sure you want to delete ${filename}?`)) return
216
-
217
- const response = await fetch("/delete", {
218
- method: "POST",
219
- headers: { "Content-Type": "application/json" },
220
- body: JSON.stringify({ filename: filename })
221
- })
222
-
223
- const data = await response.json()
224
-
225
- if (response.ok) {
226
- alert("✅ " + data.message)
227
-
228
- // Clear localStorage if deleted file was selected
229
- if (localStorage.getItem("currentFile") === filename) {
230
- localStorage.removeItem("currentFile")
231
- }
232
-
233
- loadFiles() // Refresh list
234
- } else {
235
- alert("❌ " + data.error)
236
- }
237
- }
238
- loadFiles()
239
- </script>
240
-
241
- </body>
242
-
243
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/login.html DELETED
@@ -1,52 +0,0 @@
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>Login</title>
8
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
- </head>
10
-
11
- <body>
12
- <header>
13
- <h1>📄 PDF Assistant RAG</h1>
14
- </header>
15
-
16
- <div class="container">
17
- <div class="auth-box">
18
- <h2>Welcome Back!</h2>
19
-
20
- {% if error %}
21
- <div class="error-msg">❌ {{ error }}</div>
22
- {% endif %}
23
-
24
- <form method="POST" action="/login">
25
- <input type="text" name="username" placeholder="Username" required />
26
- <input type="password" name="password" placeholder="Password" required />
27
- <button type="submit">Login</button>
28
- </form>
29
-
30
- <div class="divider">
31
- <span>
32
- OR
33
- </span>
34
- </div>
35
-
36
- <a href="{{ url_for('google.login') }}">
37
- <button class="google-btn">
38
- 🌐 Login with Google
39
- </button>
40
- </a>
41
-
42
- <p>Don't have an account? <a href="/register">Register</a></p>
43
- </div>
44
- </div>
45
-
46
- <!-- ── Footer ── -->
47
- <footer style="text-align: center; padding: 20px; color: var(--text-muted); margin-top: 40px; font-size: 14px;">
48
- <p>&copy; 2026 RAG Assistant. Param20h</p>
49
- </footer>
50
- </body>
51
-
52
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/profile.html DELETED
@@ -1,208 +0,0 @@
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>User Profile</title>
8
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
- <script src="{{ url_for('static', filename='script.js') }}"></script>
10
- </head>
11
-
12
- <body>
13
-
14
- <!-- ── Header ── -->
15
- <header>
16
- <h1>👤 My Profile</h1>
17
- <div class="header-right">
18
- <button class="logout-btn" style="background-color: var(--primary-color);"
19
- onclick="toggleLightMode()">🌗</button>
20
- <a href="/">← Back to Dashboard</a>
21
- <a href="/logout"><button class="logout-btn">Logout</button></a>
22
- </div>
23
- </header>
24
-
25
- <div class="container">
26
-
27
- <!-- ── User Profile ── -->
28
- <div class="upload-box" style="display: flex; align-items: center; gap: 20px; text-align: left;">
29
- <div class="profile-container" style="display:inline-block;">
30
- <img id="profilePic"
31
- src="{{ current_user.profile_pic or url_for('static', filename='default_avatar.png') }}"
32
- alt="Profile" class="profile-pic" style="width: 80px; height: 80px;"
33
- onclick="document.getElementById('picInput').click()" title="Click to change profile picture" />
34
- <input type="file" id="picInput" accept="image/*" style="display:none"
35
- onchange="uploadProfilePic(this)" />
36
- </div>
37
- <div>
38
- <h2 style="margin-bottom: 5px;">{{ current_user.username }}</h2>
39
- <p style="color: var(--text-muted);">{{ current_user.email }}</p>
40
- </div>
41
- </div>
42
-
43
- <!-- ── Settings / Custom APIs ── -->
44
- <div class="upload-box" style="text-align: left;">
45
- <h2>⚙️ API Settings</h2>
46
- <form id="settingsForm">
47
- <label style="color: var(--text-main); font-weight: 500; font-size: 14px;">Select Preferred
48
- Model</label>
49
- <select id="preferredModel" name="preferred_model"
50
- style="width: 100%; padding: 12px; margin: 10px 0 20px; border-radius: 8px; border: 1px solid var(--glass-border); background: var(--input-bg); color: var(--text-main); outline: none;">
51
- <option value="groq" {% if current_user.preferred_model=='groq' %}selected{% endif %}>Groq (Llama 3)
52
- </option>
53
- <option value="gemini" {% if current_user.preferred_model=='gemini' %}selected{% endif %}>Gemini
54
- (Google)</option>
55
- </select>
56
-
57
- <label style="color: var(--text-main); font-weight: 500; font-size: 14px;">Groq API Key
58
- (Optional — for chat generation)</label>
59
- <input type="password" id="groqKey" name="groq_key" placeholder="Enter your Groq API Key..."
60
- value="" data-has-key="{{ 'true' if current_user.groq_api_key else 'false' }}" style="margin-top: 10px;">
61
-
62
- <label style="color: var(--text-main); font-weight: 500; font-size: 14px;">Gemini API Key
63
- (Required — used for embeddings & chat)</label>
64
- <input type="password" id="geminiKey" name="gemini_key" placeholder="Enter your Gemini API Key..."
65
- value="" data-has-key="{{ 'true' if current_user.gemini_api_key else 'false' }}" style="margin-top: 10px;">
66
-
67
- <hr style="border: 1px solid var(--glass-border); margin: 20px 0;">
68
-
69
- <h3 style="margin-bottom: 10px; color: var(--text-main);">🌲 Pinecone Vector Database</h3>
70
- <p style="color: var(--text-muted); font-size: 13px; margin-bottom: 15px;">
71
- Create a free index at <a href="https://app.pinecone.io" target="_blank" style="color: var(--primary-color);">pinecone.io</a>
72
- (Serverless, Dimension: 768, Metric: Cosine)
73
- </p>
74
-
75
- <label style="color: var(--text-main); font-weight: 500; font-size: 14px;">Pinecone API Key
76
- (Required)</label>
77
- <input type="password" id="pineconeKey" name="pinecone_key" placeholder="Enter your Pinecone API Key..."
78
- value="" data-has-key="{{ 'true' if current_user.pinecone_api_key else 'false' }}" style="margin-top: 10px;">
79
-
80
- <label style="color: var(--text-main); font-weight: 500; font-size: 14px;">Pinecone Index Name
81
- (Required)</label>
82
- <input type="text" id="pineconeIndex" name="pinecone_index" placeholder="e.g. rag-app"
83
- value="{{ current_user.pinecone_index_name or '' }}" style="margin-top: 10px;">
84
-
85
- <button type="submit" style="width: 100%; margin-top: 15px;">Save Settings</button>
86
- </form>
87
- <div id="settingsStatus" style="margin-top: 15px; font-weight: 500;"></div>
88
- </div>
89
-
90
- <!-- ── Uploaded Files List ── -->
91
- <div class="files-box">
92
- <h2>📋 My Uploaded Files</h2>
93
- <ul id="filesList">
94
- <!-- Files appear here -->
95
- </ul>
96
- </div>
97
- </div>
98
-
99
- <script>
100
- async function uploadProfilePic(input) {
101
- const file = input.files[0]
102
- if (!file) return
103
-
104
- const formData = new FormData()
105
- formData.append("profile_pic", file)
106
-
107
- const response = await fetch("/upload_profile_pic", {
108
- method: "POST",
109
- body: formData
110
- })
111
-
112
- const data = await response.json()
113
-
114
- if (response.ok) {
115
- document.getElementById("profilePic").src = data.profile_pic + "?t=" + Date.now()
116
- } else {
117
- alert("❌ " + data.error)
118
- }
119
- }
120
-
121
- // Show placeholder for existing keys
122
- document.querySelectorAll('[data-has-key="true"]').forEach(input => {
123
- input.placeholder = "•••••••• (key saved — leave blank to keep)"
124
- })
125
-
126
- document.getElementById("settingsForm").addEventListener("submit", async (e) => {
127
- e.preventDefault()
128
- const statusDiv = document.getElementById("settingsStatus")
129
- statusDiv.innerText = "Saving..."
130
- statusDiv.style.color = "var(--primary-color)"
131
-
132
- const preferredModel = document.getElementById("preferredModel").value
133
- const groqKey = document.getElementById("groqKey").value.trim()
134
- const geminiKey = document.getElementById("geminiKey").value.trim()
135
- const pineconeKey = document.getElementById("pineconeKey").value.trim()
136
- const pineconeIndex = document.getElementById("pineconeIndex").value.trim()
137
-
138
- // Only send keys if user actually typed a new value
139
- const payload = { preferred_model: preferredModel, pinecone_index: pineconeIndex }
140
- if (groqKey) payload.groq_key = groqKey
141
- if (geminiKey) payload.gemini_key = geminiKey
142
- if (pineconeKey) payload.pinecone_key = pineconeKey
143
-
144
- const response = await fetch("/update_settings", {
145
- method: "POST",
146
- headers: { "Content-Type": "application/json" },
147
- body: JSON.stringify(payload)
148
- })
149
-
150
- const data = await response.json()
151
- if (response.ok) {
152
- statusDiv.innerText = "✅ " + data.message
153
- statusDiv.style.color = "var(--success-color)"
154
- } else {
155
- statusDiv.innerText = "❌ " + data.error
156
- statusDiv.style.color = "var(--danger-color)"
157
- }
158
- })
159
-
160
- async function loadFiles() {
161
- const response = await fetch("/files")
162
- const data = await response.json()
163
- const filesList = document.getElementById("filesList")
164
- filesList.innerHTML = ""
165
-
166
- if (data.files.length === 0) {
167
- filesList.innerHTML = "<li>No files uploaded yet</li>"
168
- return
169
- }
170
-
171
- data.files.forEach(file => {
172
- let icon = "📄"
173
- if (file.endsWith(".docx")) icon = "📝"
174
- if (file.endsWith(".txt")) icon = "📃"
175
- if (file.endsWith(".md")) icon = "📑"
176
-
177
- filesList.innerHTML += `
178
- <li class="file-item">
179
- <span>${icon} ${file}</span>
180
- <button class="delete-btn" onclick="deleteFile('${file}')">🗑️ Delete</button>
181
- </li>
182
- `
183
- })
184
- }
185
-
186
- async function deleteFile(filename) {
187
- if (!confirm(`Are you sure you want to delete ${filename}?`)) return
188
- const response = await fetch("/delete", {
189
- method: "POST",
190
- headers: { "Content-Type": "application/json" },
191
- body: JSON.stringify({ filename: filename })
192
- })
193
- const data = await response.json()
194
- if (response.ok) {
195
- if (localStorage.getItem("currentFile") === filename) {
196
- localStorage.removeItem("currentFile")
197
- }
198
- loadFiles()
199
- } else {
200
- alert("❌ " + data.error)
201
- }
202
- }
203
-
204
- loadFiles()
205
- </script>
206
- </body>
207
-
208
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/register.html DELETED
@@ -1,41 +0,0 @@
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>Register</title>
8
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
- </head>
10
-
11
- <body>
12
- <header>
13
- <h1>📄 PDF Assistant RAG</h1>
14
- </header>
15
-
16
- <div class="container">
17
- <div class="auth-box">
18
- <h2>Create Account</h2>
19
-
20
- {% if error %}
21
- <div class="error-msg">❌ {{ error }}</div>
22
- {% endif %}
23
-
24
- <form method="POST" action="/register">
25
- <input type="text" name="username" placeholder="Username" required />
26
- <input type="email" name="email" placeholder="Email" required />
27
- <input type="password" name="password" placeholder="Password" required />
28
- <button type="submit">Register</button>
29
- </form>
30
-
31
- <p>Already have an account? <a href="/login">Login</a></p>
32
- </div>
33
- </div>
34
-
35
- <!-- ── Footer ── -->
36
- <footer style="text-align: center; padding: 20px; color: var(--text-muted); margin-top: 40px; font-size: 14px;">
37
- <p>&copy; 2026 RAG Assistant. Param20h</p>
38
- </footer>
39
- </body>
40
-
41
- </html>