Aditya commited on
Commit
e2b3faf
Β·
1 Parent(s): 4a3f117

Improve UI: animated 3D background, spinning logo, and interactive quick questions

Browse files

- Add canvas-based 3D particle network background via components.html
- Add spinning conic-gradient ring logo with float + glow animation
- Add hero title shimmer animation and animated gradient background orbs
- Fix quick question buttons to persist via session state and auto-trigger all 3 systems
- Restyle answer cards with icon headers, time chips, and per-system color themes
- Style buttons as pill chips, improve text input, hide Streamlit chrome

Files changed (1) hide show
  1. demo.py +543 -226
demo.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import streamlit as st
 
3
  from system1_baseline import ask_baseline
4
  from system2_rag import ask_rag, load_vectorstore, build_vectorstore
5
 
@@ -9,123 +10,306 @@ st.set_page_config(
9
  layout="wide"
10
  )
11
 
12
- # ── Session state init ────────────────────────────────────────────────────────
13
- if "ratings" not in st.session_state:
14
- st.session_state.ratings = [] # list of rating dicts
15
- if "last_results" not in st.session_state:
16
- st.session_state.last_results = None # most recent answers
17
- if "rating_submitted" not in st.session_state:
18
- st.session_state.rating_submitted = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- # ── Custom CSS ────────────────────────────────────────────────────────────────
21
- st.markdown("""
22
- <style>
23
- /* System 1 β€” Blue */
24
- .card-blue {
25
- background: linear-gradient(135deg, #0d1f3c, #102a4c);
26
- border: 1px solid #2563eb;
27
- border-top: 4px solid #3b82f6;
28
- border-radius: 12px;
29
- padding: 20px;
30
- margin-top: 10px;
31
- min-height: 180px;
32
- }
33
- .card-blue .answer { color: #93c5fd; font-size: 15px; line-height: 1.7; }
34
- .card-blue .time { color: #60a5fa; font-size: 13px; margin-top: 12px; }
35
-
36
- /* System 2 β€” Teal/Green */
37
- .card-teal {
38
- background: linear-gradient(135deg, #052e1c, #063d24);
39
- border: 1px solid #059669;
40
- border-top: 4px solid #10b981;
41
- border-radius: 12px;
42
- padding: 20px;
43
- margin-top: 10px;
44
- min-height: 180px;
45
- }
46
- .card-teal .answer { color: #6ee7b7; font-size: 15px; line-height: 1.7; }
47
- .card-teal .time { color: #34d399; font-size: 13px; margin-top: 12px; }
48
- .card-teal .context { background: #063d24; border: 1px solid #065f46; border-radius: 8px;
49
- padding: 12px; margin-top: 14px; color: #a7f3d0;
50
- font-size: 13px; line-height: 1.6; }
51
- .card-teal .ctx-label { color: #6ee7b7; font-size: 12px; font-weight: 600;
52
- margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px; }
53
-
54
- /* System 3 β€” Purple */
55
- .card-purple {
56
- background: linear-gradient(135deg, #1a0a2e, #220d3b);
57
- border: 1px solid #7c3aed;
58
- border-top: 4px solid #8b5cf6;
59
- border-radius: 12px;
60
- padding: 20px;
61
- margin-top: 10px;
62
- min-height: 180px;
63
- }
64
- .card-purple .answer { color: #c4b5fd; font-size: 15px; line-height: 1.7; }
65
- .card-purple .time { color: #a78bfa; font-size: 13px; margin-top: 12px; }
66
-
67
- /* Section headers */
68
- .sys-header { font-size: 18px; font-weight: 700; margin-bottom: 2px; }
69
- .sys-blue { color: #3b82f6; }
70
- .sys-teal { color: #10b981; }
71
- .sys-purple { color: #8b5cf6; }
72
-
73
- /* Rating box */
74
- .rating-box {
75
- background: #111827;
76
- border: 1px solid #374151;
77
- border-radius: 12px;
78
- padding: 18px 20px;
79
- margin-top: 8px;
80
- }
81
-
82
- /* Eval table */
83
- .eval-table {
84
- width: 100%;
85
- border-collapse: collapse;
86
- font-size: 14px;
87
- margin-top: 10px;
88
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  .eval-table th {
90
- padding: 12px 16px;
91
- text-align: center;
92
- font-weight: 700;
93
- font-size: 15px;
94
- border-bottom: 2px solid #374151;
95
- }
96
- .eval-table th:first-child { text-align: left; color: #9ca3af; }
97
- .eval-table th.col-blue { color: #3b82f6; }
98
- .eval-table th.col-teal { color: #10b981; }
99
- .eval-table th.col-purple { color: #8b5cf6; }
100
  .eval-table td {
101
- padding: 11px 16px;
102
- text-align: center;
103
- border-bottom: 1px solid #1f2937;
104
- color: #d1d5db;
105
  }
106
- .eval-table td:first-child { text-align: left; color: #9ca3af; font-weight: 600; }
107
- .eval-table tr:hover td { background: #111827; }
 
108
  .badge {
109
- display: inline-block;
110
- padding: 3px 10px;
111
- border-radius: 12px;
112
- font-weight: 700;
113
- font-size: 13px;
114
- }
115
- .badge-green { background: #052e1c; color: #10b981; border: 1px solid #059669; }
116
- .badge-yellow { background: #1c1a05; color: #f59e0b; border: 1px solid #d97706; }
117
- .badge-red { background: #2e0505; color: #ef4444; border: 1px solid #dc2626; }
118
- .badge-grey { background: #1f2937; color: #6b7280; border: 1px solid #374151; }
119
- .metric-note { color: #6b7280; font-size: 12px; font-weight: 400; }
120
- </style>
121
- """, unsafe_allow_html=True)
122
 
123
- # ── Title ─────────────────────────────────────────────────────────────────────
124
- st.title("πŸ§™ CODESAGE")
125
- st.markdown(
126
- "**Comparative Study β€” RAG vs Fine-Tuning for Domain-Specific QA** \n"
127
- "Ask the same question to all 3 systems and compare their answers."
128
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  # ── Load vector store once ────────────────────────────────────────────────────
131
  @st.cache_resource(show_spinner="Building knowledge base...")
@@ -136,153 +320,302 @@ def get_vectorstore():
136
 
137
  vs = get_vectorstore()
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  # ── Input ─────────────────────────────────────────────────────────────────────
140
  st.divider()
141
- question = st.text_input(
142
- "Enter your question:",
143
- placeholder="e.g. What is binary search? / What are React hooks? / Explain merge sort."
144
- )
145
 
146
  sample_questions = [
147
  "What is binary search?",
148
- "What is the difference between stack and queue?",
149
  "Explain merge sort.",
150
  "What are React hooks?",
151
  "What is a REST API?",
152
  "What is dynamic programming?",
153
  ]
154
 
155
- st.markdown("**Quick questions:**")
156
  cols = st.columns(len(sample_questions))
157
  for i, sq in enumerate(sample_questions):
158
- if cols[i].button(sq, key=f"sq_{i}"):
159
- question = sq
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  # ── Run systems ───────────────────────────────────────────────────────────────
162
- if st.button("Ask All 3 Systems", type="primary") and question:
163
- st.session_state.rating_submitted = False # reset for new question
 
 
164
  col1, col2, col3 = st.columns(3)
165
 
166
  with col1:
167
- st.markdown('<p class="sys-header sys-blue">System 1 β€” Baseline LLM</p>', unsafe_allow_html=True)
168
- st.caption("Prompt only. No extra knowledge.")
169
- with st.spinner("Generating..."):
170
  r1 = ask_baseline(question)
171
-
172
  with col2:
173
- st.markdown('<p class="sys-header sys-teal">System 2 β€” RAG Chatbot</p>', unsafe_allow_html=True)
174
- st.caption("Retrieves from knowledge base, then generates.")
175
- with st.spinner("Retrieving + generating..."):
176
  r2 = ask_rag(question, vs)
177
-
178
  with col3:
179
- st.markdown('<p class="sys-header sys-purple">System 3 β€” Fine-Tuned Model</p>', unsafe_allow_html=True)
180
- st.caption("TinyLlama fine-tuned on programming Q&A via LoRA.")
181
  if os.path.exists("./finetuned_model"):
182
- with st.spinner("Generating..."):
183
  from system3_inference import ask_finetuned
184
  r3 = ask_finetuned(question)
185
  else:
186
  r3 = {"answer": "Model not available.", "response_time": 0}
187
 
188
- # persist results so they survive reruns
189
  st.session_state.last_results = {
190
  "question": question,
191
  "r1": r1, "r2": r2, "r3": r3,
192
  }
193
 
194
- # ── Always render answers if they exist ───────────────────────────────────────
195
  if st.session_state.last_results:
196
  res = st.session_state.last_results
197
  r1, r2, r3 = res["r1"], res["r2"], res["r3"]
198
 
199
  st.divider()
 
 
 
 
 
 
200
  col1, col2, col3 = st.columns(3)
201
 
202
  with col1:
203
- st.markdown('<p class="sys-header sys-blue">System 1 β€” Baseline LLM</p>', unsafe_allow_html=True)
204
- st.caption("Prompt only. No extra knowledge.")
205
  st.markdown(f"""
206
- <div class="card-blue">
207
- <div class="answer">{r1['answer']}</div>
208
- <div class="time">⏱️ Response Time: {r1['response_time']}s</div>
 
 
 
 
 
 
 
 
 
 
 
209
  </div>
210
  """, unsafe_allow_html=True)
211
 
212
  with col2:
213
- st.markdown('<p class="sys-header sys-teal">System 2 β€” RAG Chatbot</p>', unsafe_allow_html=True)
214
- st.caption("Retrieves from knowledge base, then generates.")
215
  st.markdown(f"""
216
- <div class="card-teal">
217
- <div class="answer">{r2['answer']}</div>
218
- <div class="time">⏱️ Response Time: {r2['response_time']}s</div>
219
- <div class="context">
220
- <div class="ctx-label">πŸ“„ Context retrieved from knowledge base</div>
221
- {r2['context_used']}
 
 
 
 
 
 
 
 
 
 
 
222
  </div>
223
  </div>
224
  """, unsafe_allow_html=True)
225
 
226
  with col3:
227
- st.markdown('<p class="sys-header sys-purple">System 3 β€” Fine-Tuned Model</p>', unsafe_allow_html=True)
228
- st.caption("TinyLlama fine-tuned on programming Q&A via LoRA.")
229
  if r3["answer"] == "Model not available.":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  st.warning(
231
- "Fine-tuned model not ready yet. \n"
232
  "Run `system3_finetune_colab.ipynb` on Google Colab, "
233
  "download `finetuned_model.zip`, and unzip it here."
234
  )
235
  else:
236
  st.markdown(f"""
237
- <div class="card-purple">
238
- <div class="answer">{r3['answer']}</div>
239
- <div class="time">⏱️ Response Time: {r3['response_time']}s</div>
 
 
 
 
 
 
 
 
 
 
 
240
  </div>
241
  """, unsafe_allow_html=True)
242
 
243
  # ── Rating section ─────────────────────────────────────────────────────────────
244
  if st.session_state.last_results and not st.session_state.rating_submitted:
245
  st.divider()
246
- st.subheader("Rate the Answers")
247
- st.caption("Your ratings are used to build the live evaluation table below.")
 
 
 
 
 
248
 
249
  rc1, rc2, rc3 = st.columns(3)
250
 
251
  with rc1:
252
- st.markdown("**System 1 β€” Baseline LLM**")
253
- c1_correct = st.slider("Correctness (1–5)", 1, 5, 3, key="c1_correct")
254
- c1_halluc = st.checkbox("Hallucination detected?", key="c1_halluc")
255
- c1_complete= st.slider("Completeness (1–5)", 1, 5, 3, key="c1_complete")
256
 
257
  with rc2:
258
- st.markdown("**System 2 β€” RAG Chatbot**")
259
- c2_correct = st.slider("Correctness (1–5)", 1, 5, 3, key="c2_correct")
260
- c2_halluc = st.checkbox("Hallucination detected?", key="c2_halluc")
261
- c2_complete= st.slider("Completeness (1–5)", 1, 5, 3, key="c2_complete")
262
 
263
  with rc3:
264
- st.markdown("**System 3 β€” Fine-Tuned**")
265
- c3_correct = st.slider("Correctness (1–5)", 1, 5, 3, key="c3_correct")
266
- c3_halluc = st.checkbox("Hallucination detected?", key="c3_halluc")
267
- c3_complete= st.slider("Completeness (1–5)", 1, 5, 3, key="c3_complete")
268
 
269
  if st.button("Submit Ratings", type="primary"):
270
  res = st.session_state.last_results
271
  st.session_state.ratings.append({
272
  "question": res["question"],
273
- # correctness
274
- "c1_correct": c1_correct,
275
- "c2_correct": c2_correct,
276
- "c3_correct": c3_correct,
277
- # hallucination (1 = yes, 0 = no)
278
  "c1_halluc": 1 if c1_halluc else 0,
279
  "c2_halluc": 1 if c2_halluc else 0,
280
  "c3_halluc": 1 if c3_halluc else 0,
281
- # completeness
282
- "c1_complete": c1_complete,
283
- "c2_complete": c2_complete,
284
- "c3_complete": c3_complete,
285
- # response times (auto)
286
  "c1_time": res["r1"]["response_time"],
287
  "c2_time": res["r2"]["response_time"],
288
  "c3_time": res["r3"]["response_time"],
@@ -291,81 +624,63 @@ if st.session_state.last_results and not st.session_state.rating_submitted:
291
  st.success(f"Rating saved! Total questions rated: {len(st.session_state.ratings)}")
292
  st.rerun()
293
 
294
- # ── Metrics table (live) ───────────────────────────────────────────────────────
295
  st.divider()
296
- st.subheader("Evaluation Metrics Comparison")
 
 
 
 
 
297
 
298
  ratings = st.session_state.ratings
299
  n = len(ratings)
300
 
301
  if n == 0:
302
  st.caption("No ratings yet. Ask a question and rate the answers to populate this table.")
303
-
304
- # show placeholder dashes
305
  def badge(val, _n):
306
- return f'<span class="badge badge-grey">β€”</span>'
307
  else:
308
  st.caption(f"Based on **{n} question(s)** rated by you. Response time is measured automatically.")
309
 
310
- def avg(key):
311
- return round(sum(r[key] for r in ratings) / n, 2)
312
-
313
- def pct(key):
314
- return round(sum(r[key] for r in ratings) / n * 100)
315
 
316
  def color_correct(v):
317
- if v >= 4.0: return "badge-green"
318
- if v >= 3.0: return "badge-yellow"
319
- return "badge-red"
320
-
321
- def color_halluc(v): # lower = better
322
- if v <= 15: return "badge-green"
323
- if v <= 30: return "badge-yellow"
324
- return "badge-red"
325
-
326
  def color_complete(v):
327
- if v >= 4.0: return "badge-green"
328
- if v >= 3.0: return "badge-yellow"
329
- return "badge-red"
330
-
331
- def color_time(v): # lower = better
332
- if v <= 1.0: return "badge-green"
333
- if v <= 2.0: return "badge-yellow"
334
- return "badge-red"
335
-
336
- v_c1 = avg("c1_correct"); v_c2 = avg("c2_correct"); v_c3 = avg("c3_correct")
337
- v_h1 = pct("c1_halluc"); v_h2 = pct("c2_halluc"); v_h3 = pct("c3_halluc")
338
- v_p1 = avg("c1_complete"); v_p2 = avg("c2_complete"); v_p3 = avg("c3_complete")
339
- v_t1 = avg("c1_time"); v_t2 = avg("c2_time"); v_t3 = avg("c3_time")
340
 
341
  def badge(val, cls):
342
  return f'<span class="badge {cls}">{val}</span>'
343
 
344
- row_correct = (badge(f"{v_c1}/5", color_correct(v_c1)),
345
- badge(f"{v_c2}/5", color_correct(v_c2)),
346
- badge(f"{v_c3}/5", color_correct(v_c3)))
347
- row_halluc = (badge(f"{v_h1}%", color_halluc(v_h1)),
348
- badge(f"{v_h2}%", color_halluc(v_h2)),
349
- badge(f"{v_h3}%", color_halluc(v_h3)))
350
- row_complete = (badge(f"{v_p1}/5", color_complete(v_p1)),
351
- badge(f"{v_p2}/5", color_complete(v_p2)),
352
- badge(f"{v_p3}/5", color_complete(v_p3)))
353
- row_time = (badge(f"{v_t1}s", color_time(v_t1)),
354
- badge(f"{v_t2}s", color_time(v_t2)),
355
- badge(f"{v_t3}s", color_time(v_t3)))
356
 
357
  if n == 0:
358
  dash = '<span class="badge badge-grey">β€”</span>'
359
  row_correct = row_halluc = row_complete = row_time = (dash, dash, dash)
360
 
361
  st.markdown(f"""
 
362
  <table class="eval-table">
363
  <thead>
364
  <tr>
365
  <th>Metric</th>
366
- <th class="col-blue">System 1<br><span style="font-weight:400;font-size:12px">Baseline LLM</span></th>
367
- <th class="col-teal">System 2<br><span style="font-weight:400;font-size:12px">RAG Chatbot</span></th>
368
- <th class="col-purple">System 3<br><span style="font-weight:400;font-size:12px">Fine-Tuned</span></th>
369
  </tr>
370
  </thead>
371
  <tbody>
@@ -387,9 +702,11 @@ st.markdown(f"""
387
  </tr>
388
  </tbody>
389
  </table>
 
390
  """, unsafe_allow_html=True)
391
 
392
  if n > 0:
 
393
  if st.button("Clear All Ratings"):
394
  st.session_state.ratings = []
395
  st.rerun()
@@ -398,12 +715,12 @@ if n > 0:
398
  st.divider()
399
  with st.expander("About CodeSage"):
400
  st.markdown("""
401
- **Project:** CodeSage β€” Comparative Study of RAG and Fine-Tuning for Domain-Specific Question Answering
402
  **Domain:** Programming Tutor (DSA + Web Development)
403
 
404
  | System | Description |
405
  |--------|-------------|
406
- | Baseline | Llama 3.1 8B (via Groq) with only a system prompt |
407
- | RAG | FAISS vector store + HuggingFace embeddings + Llama 3.1 8B (via Groq) |
408
  | Fine-tuned | TinyLlama 1.1B fine-tuned via LoRA on 20 programming Q&A pairs |
409
  """)
 
1
  import os
2
  import streamlit as st
3
+ import streamlit.components.v1 as components
4
  from system1_baseline import ask_baseline
5
  from system2_rag import ask_rag, load_vectorstore, build_vectorstore
6
 
 
10
  layout="wide"
11
  )
12
 
13
+ # ── Session state ─────────────────────────────────────────────────────────────
14
+ if "ratings" not in st.session_state: st.session_state.ratings = []
15
+ if "last_results" not in st.session_state: st.session_state.last_results = None
16
+ if "rating_submitted" not in st.session_state: st.session_state.rating_submitted = False
17
+ if "input_question" not in st.session_state: st.session_state.input_question = ""
18
+ if "auto_run" not in st.session_state: st.session_state.auto_run = False
19
+
20
+ # ── CSS ───────────────────────────────────────────────────────────────────────
21
+ st.markdown("""<style>
22
+
23
+ /* ── Global & chrome ────────────────────────────────────────── */
24
+ #MainMenu, footer, header { visibility: hidden; }
25
+ .stDeployButton { display: none !important; }
26
+ .stApp {
27
+ background: #060b18;
28
+ background-image:
29
+ radial-gradient(ellipse 60% 50% at 15% 25%, rgba(59,130,246,.07) 0%, transparent 70%),
30
+ radial-gradient(ellipse 50% 60% at 85% 75%, rgba(139,92,246,.07) 0%, transparent 70%),
31
+ radial-gradient(ellipse 40% 40% at 50% 50%, rgba(16,185,129,.04) 0%, transparent 70%);
32
+ animation: bgPulse 12s ease-in-out infinite alternate;
33
+ }
34
+ @keyframes bgPulse {
35
+ 0% { background-size: 100% 100%, 100% 100%, 100% 100%; }
36
+ 100% { background-size: 120% 120%, 115% 115%, 110% 110%; }
37
+ }
38
+ .block-container {
39
+ padding-top: 1.5rem !important;
40
+ padding-bottom: 3rem !important;
41
+ max-width: 1400px;
42
+ }
43
 
44
+ /* ── Hero ───────────────────────────────────────────────────── */
45
+ .hero { text-align: center; padding: 2rem 1rem 0.8rem; }
46
+ .hero-eyebrow {
47
+ font-size: 10px; font-weight: 700; letter-spacing: 3.5px;
48
+ text-transform: uppercase; color: #334155; margin-bottom: 20px;
49
+ }
50
+
51
+ /* Animated logo icon */
52
+ .logo-wrap {
53
+ position: relative; width: 92px; height: 92px;
54
+ margin: 0 auto 22px;
55
+ animation: logoFloat 4s ease-in-out infinite;
56
+ }
57
+ @keyframes logoFloat {
58
+ 0%, 100% { transform: translateY(0px); filter: drop-shadow(0 0 18px rgba(99,102,241,.35)); }
59
+ 50% { transform: translateY(-9px); filter: drop-shadow(0 0 28px rgba(99,102,241,.55)); }
60
+ }
61
+ .logo-ring {
62
+ position: absolute; inset: 0; border-radius: 50%;
63
+ background: conic-gradient(from 0deg, #3b82f6, #7c3aed, #10b981, #60a5fa, #3b82f6);
64
+ animation: spinRing 3.5s linear infinite;
65
+ }
66
+ @keyframes spinRing { to { transform: rotate(360deg); } }
67
+ .logo-inner {
68
+ position: absolute; inset: 3px; border-radius: 50%;
69
+ background: #0a0f1e;
70
+ display: flex; align-items: center; justify-content: center;
71
+ font-size: 38px; line-height: 1;
72
+ }
73
+ .logo-glow {
74
+ position: absolute; inset: -8px; border-radius: 50%;
75
+ background: conic-gradient(from 0deg, rgba(59,130,246,.15), rgba(124,58,237,.15), rgba(16,185,129,.1), rgba(59,130,246,.15));
76
+ animation: spinRing 3.5s linear infinite reverse;
77
+ filter: blur(8px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
+
80
+ .hero-title {
81
+ font-size: clamp(2.4rem, 5vw, 3.6rem);
82
+ font-weight: 900; letter-spacing: -2.5px; line-height: 1.05;
83
+ background: linear-gradient(130deg, #60a5fa 0%, #a78bfa 55%, #34d399 100%);
84
+ background-size: 200% 200%;
85
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
86
+ background-clip: text; margin-bottom: 14px;
87
+ animation: titleShimmer 5s ease-in-out infinite alternate;
88
+ }
89
+ @keyframes titleShimmer {
90
+ 0% { background-position: 0% 50%; }
91
+ 100% { background-position: 100% 50%; }
92
+ }
93
+ .hero-sub {
94
+ font-size: 14.5px; color: #475569; max-width: 480px;
95
+ margin: 0 auto 22px; line-height: 1.65;
96
+ }
97
+ .sys-pills { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; }
98
+ .sys-pill {
99
+ padding: 5px 16px; border-radius: 99px;
100
+ font-size: 12px; font-weight: 600; letter-spacing: 0.2px;
101
+ transition: transform .2s, box-shadow .2s;
102
+ }
103
+ .sys-pill:hover { transform: translateY(-2px); }
104
+ .pill-blue { background: rgba(59,130,246,.1); color: #60a5fa; border: 1px solid rgba(59,130,246,.25); }
105
+ .pill-teal { background: rgba(16,185,129,.1); color: #34d399; border: 1px solid rgba(16,185,129,.25); }
106
+ .pill-purple { background: rgba(139,92,246,.1); color: #a78bfa; border: 1px solid rgba(139,92,246,.25); }
107
+
108
+ /* ── Divider ─────────────────────────────────────────────────── */
109
+ hr { border-color: #0f172a !important; margin: 1.2rem 0 !important; }
110
+
111
+ /* ── Section labels ─────────────────────────────────────────── */
112
+ .section-label {
113
+ font-size: 10px; font-weight: 700; letter-spacing: 2.5px;
114
+ text-transform: uppercase; color: #334155; margin-bottom: 10px;
115
+ }
116
+ .section-header {
117
+ display: flex; align-items: center; gap: 12px; margin-bottom: 1.2rem;
118
+ }
119
+ .section-header-text {
120
+ font-size: 10px; font-weight: 700; letter-spacing: 2.5px;
121
+ text-transform: uppercase; color: #334155; white-space: nowrap;
122
+ }
123
+ .section-header-line { flex: 1; height: 1px; background: #0f172a; }
124
+
125
+ /* ── Chip buttons (secondary) ───────────────────────────────── */
126
+ button[kind="secondary"] {
127
+ background: rgba(255,255,255,.03) !important;
128
+ border: 1px solid #1a2540 !important;
129
+ border-radius: 99px !important;
130
+ color: #4f6282 !important;
131
+ font-size: 12px !important;
132
+ padding: 5px 14px !important;
133
+ font-weight: 500 !important;
134
+ transition: all 0.18s ease !important;
135
+ white-space: nowrap !important;
136
+ }
137
+ button[kind="secondary"]:hover {
138
+ background: rgba(96,165,250,.08) !important;
139
+ border-color: rgba(96,165,250,.35) !important;
140
+ color: #93c5fd !important;
141
+ transform: translateY(-1px) !important;
142
+ box-shadow: 0 4px 14px rgba(96,165,250,.08) !important;
143
+ }
144
+
145
+ /* ── Primary button ─────────────────────────────────────────── */
146
+ button[kind="primary"] {
147
+ background: linear-gradient(135deg, #2563eb, #7c3aed) !important;
148
+ border: none !important;
149
+ border-radius: 10px !important;
150
+ font-weight: 700 !important;
151
+ letter-spacing: 0.3px !important;
152
+ font-size: 14px !important;
153
+ box-shadow: 0 4px 20px rgba(99,102,241,.25) !important;
154
+ transition: all 0.2s !important;
155
+ }
156
+ button[kind="primary"]:hover {
157
+ opacity: .9 !important;
158
+ transform: translateY(-1px) !important;
159
+ box-shadow: 0 8px 28px rgba(99,102,241,.35) !important;
160
+ }
161
+
162
+ /* ── Text input ─────────────────────────────────────────────── */
163
+ div[data-testid="stTextInput"] > div > div > input {
164
+ background: #0c1220 !important;
165
+ border: 1px solid #1a2540 !important;
166
+ border-radius: 12px !important;
167
+ color: #e2e8f0 !important;
168
+ font-size: 15px !important;
169
+ padding: 14px 18px !important;
170
+ transition: border-color .2s, box-shadow .2s !important;
171
+ }
172
+ div[data-testid="stTextInput"] > div > div > input:focus {
173
+ border-color: #3b82f6 !important;
174
+ box-shadow: 0 0 0 3px rgba(59,130,246,.1) !important;
175
+ outline: none !important;
176
+ }
177
+ div[data-testid="stTextInput"] label {
178
+ color: #64748b !important; font-size: 12px !important;
179
+ letter-spacing: .5px !important; text-transform: uppercase !important;
180
+ font-weight: 600 !important;
181
+ }
182
+
183
+ /* ── Answer cards ───────────────────────────────────────────── */
184
+ .card { border-radius: 14px; overflow: hidden; margin-top: 6px; }
185
+
186
+ .card-header {
187
+ display: flex; align-items: center; gap: 10px;
188
+ padding: 13px 18px 11px;
189
+ border-bottom: 1px solid rgba(255,255,255,.05);
190
+ }
191
+ .card-icon { font-size: 17px; line-height: 1; }
192
+ .card-title { font-size: 13px; font-weight: 700; line-height: 1.2; }
193
+ .card-subtitle { font-size: 10.5px; opacity: .5; margin-top: 2px; }
194
+ .card-body { padding: 16px 18px; }
195
+ .card-answer { font-size: 14px; line-height: 1.8; }
196
+ .card-meta {
197
+ display: flex; align-items: center; gap: 8px;
198
+ margin-top: 14px; padding-top: 12px;
199
+ border-top: 1px solid rgba(255,255,255,.05);
200
+ }
201
+ .time-chip {
202
+ font-size: 11px; font-weight: 600;
203
+ padding: 3px 10px; border-radius: 99px;
204
+ }
205
+
206
+ /* Blue */
207
+ .card-blue { background: linear-gradient(160deg,#091b38,#0c2045); border: 1px solid rgba(59,130,246,.2); }
208
+ .card-blue .card-header { background: rgba(59,130,246,.07); }
209
+ .card-blue .card-title { color: #60a5fa; }
210
+ .card-blue .card-answer { color: #93c5fd; }
211
+ .card-blue .time-chip { background: rgba(59,130,246,.1); color: #60a5fa; border: 1px solid rgba(59,130,246,.2); }
212
+
213
+ /* Teal */
214
+ .card-teal { background: linear-gradient(160deg,#041a0f,#052516); border: 1px solid rgba(16,185,129,.2); }
215
+ .card-teal .card-header { background: rgba(16,185,129,.07); }
216
+ .card-teal .card-title { color: #34d399; }
217
+ .card-teal .card-answer { color: #6ee7b7; }
218
+ .card-teal .time-chip { background: rgba(16,185,129,.1); color: #34d399; border: 1px solid rgba(16,185,129,.2); }
219
+ .rag-ctx {
220
+ background: rgba(16,185,129,.04); border: 1px solid rgba(16,185,129,.15);
221
+ border-radius: 8px; padding: 10px 14px; margin-top: 14px;
222
+ font-size: 12px; color: #86efac; line-height: 1.65;
223
+ }
224
+ .rag-ctx-label {
225
+ font-size: 10px; font-weight: 700; text-transform: uppercase;
226
+ letter-spacing: 1.5px; color: #34d399; margin-bottom: 7px;
227
+ }
228
+
229
+ /* Purple */
230
+ .card-purple { background: linear-gradient(160deg,#100825,#16092e); border: 1px solid rgba(139,92,246,.2); }
231
+ .card-purple .card-header { background: rgba(139,92,246,.07); }
232
+ .card-purple .card-title { color: #a78bfa; }
233
+ .card-purple .card-answer { color: #c4b5fd; }
234
+ .card-purple .time-chip { background: rgba(139,92,246,.1); color: #a78bfa; border: 1px solid rgba(139,92,246,.2); }
235
+
236
+ /* ── Question banner ─────────────────────────────────────────── */
237
+ .q-banner {
238
+ background: rgba(255,255,255,.02); border: 1px solid #1a2540;
239
+ border-left: 3px solid #6366f1; border-radius: 0 10px 10px 0;
240
+ padding: 10px 16px; margin-bottom: 18px;
241
+ font-size: 13px; color: #64748b; line-height: 1.5;
242
+ }
243
+ .q-banner strong { color: #e2e8f0; font-weight: 600; }
244
+
245
+ /* ── Rating section ──────────────────────────────────────────── */
246
+ .rate-header { font-size: 13px; font-weight: 700; margin-bottom: 2px; }
247
+ .rate-blue { color: #60a5fa; }
248
+ .rate-teal { color: #34d399; }
249
+ .rate-purple { color: #a78bfa; }
250
+
251
+ /* ── Eval table ──────────────────────────────────────────────── */
252
+ .eval-wrap {
253
+ background: #0b1020; border: 1px solid #1a2540;
254
+ border-radius: 14px; overflow: hidden; margin-top: 10px;
255
+ }
256
+ .eval-table { width: 100%; border-collapse: collapse; font-size: 13px; }
257
  .eval-table th {
258
+ padding: 13px 18px; text-align: center;
259
+ font-weight: 700; font-size: 12px;
260
+ border-bottom: 1px solid #1a2540;
261
+ background: #090e1c; letter-spacing: .3px;
262
+ }
263
+ .eval-table th:first-child { text-align: left; color: #475569; }
264
+ .eval-table th.col-blue { color: #60a5fa; }
265
+ .eval-table th.col-teal { color: #34d399; }
266
+ .eval-table th.col-purple { color: #a78bfa; }
 
267
  .eval-table td {
268
+ padding: 12px 18px; text-align: center;
269
+ border-bottom: 1px solid #0d1424; color: #cbd5e1;
 
 
270
  }
271
+ .eval-table td:first-child { text-align: left; color: #475569; font-weight: 600; }
272
+ .eval-table tr:last-child td { border-bottom: none; }
273
+ .eval-table tr:hover td { background: rgba(255,255,255,.015); }
274
  .badge {
275
+ display: inline-block; padding: 3px 10px;
276
+ border-radius: 99px; font-weight: 700; font-size: 12px;
277
+ }
278
+ .badge-green { background: rgba(16,185,129,.1); color: #34d399; border: 1px solid rgba(16,185,129,.25); }
279
+ .badge-yellow { background: rgba(245,158,11,.1); color: #fbbf24; border: 1px solid rgba(245,158,11,.25); }
280
+ .badge-red { background: rgba(239,68,68,.1); color: #f87171; border: 1px solid rgba(239,68,68,.25); }
281
+ .badge-grey { background: rgba(100,116,139,.1); color: #64748b; border: 1px solid rgba(100,116,139,.25); }
282
+ .metric-note { color: #334155; font-size: 11px; font-weight: 400; }
 
 
 
 
 
283
 
284
+ /* ── Scrollbar ───────────────────────────────────────────────── */
285
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
286
+ ::-webkit-scrollbar-track { background: #080d1a; }
287
+ ::-webkit-scrollbar-thumb { background: #1a2540; border-radius: 3px; }
288
+ ::-webkit-scrollbar-thumb:hover { background: #2d3f5e; }
289
+
290
+ </style>""", unsafe_allow_html=True)
291
+
292
+ # ── Hero ──────────────────────────────────────────────────────────────────────
293
+ st.markdown("""
294
+ <div class="hero">
295
+ <div class="hero-eyebrow">Research Project &nbsp;Β·&nbsp; NLP &nbsp;Β·&nbsp; LLM Comparison</div>
296
+ <div class="logo-wrap">
297
+ <div class="logo-glow"></div>
298
+ <div class="logo-ring"></div>
299
+ <div class="logo-inner">πŸ§™</div>
300
+ </div>
301
+ <div class="hero-title">CodeSage</div>
302
+ <div class="hero-sub">
303
+ Comparative study of RAG vs Fine-Tuning for domain-specific QA.<br>
304
+ Ask once &mdash; get answers from all three systems simultaneously.
305
+ </div>
306
+ <div class="sys-pills">
307
+ <span class="sys-pill pill-blue">⚑ Baseline LLM</span>
308
+ <span class="sys-pill pill-teal">πŸ” RAG Chatbot</span>
309
+ <span class="sys-pill pill-purple">🧠 Fine-Tuned Model</span>
310
+ </div>
311
+ </div>
312
+ """, unsafe_allow_html=True)
313
 
314
  # ── Load vector store once ────────────────────────────────────────────────────
315
  @st.cache_resource(show_spinner="Building knowledge base...")
 
320
 
321
  vs = get_vectorstore()
322
 
323
+ # ── 3D Particle background ────────────────────────────────────────────────────
324
+ components.html("""
325
+ <script>
326
+ (function() {
327
+ const doc = window.parent.document;
328
+ if (doc.getElementById('cs-canvas')) return; // already injected
329
+
330
+ const canvas = doc.createElement('canvas');
331
+ canvas.id = 'cs-canvas';
332
+ Object.assign(canvas.style, {
333
+ position: 'fixed', top: '0', left: '0',
334
+ width: '100%', height: '100%',
335
+ pointerEvents: 'none', zIndex: '0',
336
+ });
337
+ doc.body.prepend(canvas);
338
+
339
+ const ctx = canvas.getContext('2d');
340
+ const COLORS = ['96,165,250', '167,139,250', '52,211,153'];
341
+ let W, H, pts = [];
342
+
343
+ function resize() {
344
+ W = canvas.width = doc.documentElement.clientWidth;
345
+ H = canvas.height = doc.documentElement.clientHeight;
346
+ }
347
+
348
+ function Pt() {
349
+ return {
350
+ x: Math.random() * W,
351
+ y: Math.random() * H,
352
+ z: Math.random() * 800 + 200, // depth 200–1000
353
+ vx: (Math.random() - .5) * .4,
354
+ vy: (Math.random() - .5) * .4,
355
+ vz: (Math.random() - .5) * .8,
356
+ c: COLORS[Math.floor(Math.random() * COLORS.length)],
357
+ };
358
+ }
359
+
360
+ function project(p) {
361
+ const fov = 600;
362
+ const scale = fov / (fov + p.z);
363
+ return { sx: p.x * scale + W * (1 - scale) / 2,
364
+ sy: p.y * scale + H * (1 - scale) / 2,
365
+ scale };
366
+ }
367
+
368
+ function init() {
369
+ resize();
370
+ pts = Array.from({ length: 90 }, Pt);
371
+ }
372
+
373
+ function draw() {
374
+ ctx.clearRect(0, 0, W, H);
375
+
376
+ // Draw connections
377
+ for (let i = 0; i < pts.length; i++) {
378
+ const a = project(pts[i]);
379
+ for (let j = i + 1; j < pts.length; j++) {
380
+ const b = project(pts[j]);
381
+ const dx = a.sx - b.sx, dy = a.sy - b.sy;
382
+ const dist = Math.sqrt(dx*dx + dy*dy);
383
+ if (dist < 140) {
384
+ const alpha = (1 - dist / 140) * 0.12 * a.scale;
385
+ ctx.strokeStyle = `rgba(99,102,241,${alpha})`;
386
+ ctx.lineWidth = 0.6 * a.scale;
387
+ ctx.beginPath();
388
+ ctx.moveTo(a.sx, a.sy);
389
+ ctx.lineTo(b.sx, b.sy);
390
+ ctx.stroke();
391
+ }
392
+ }
393
+ }
394
+
395
+ // Draw points
396
+ pts.forEach(p => {
397
+ const { sx, sy, scale } = project(p);
398
+ const r = Math.max(0.6, 2.2 * scale);
399
+ const grd = ctx.createRadialGradient(sx, sy, 0, sx, sy, r * 3);
400
+ grd.addColorStop(0, `rgba(${p.c},${.85 * scale})`);
401
+ grd.addColorStop(0.4, `rgba(${p.c},${.35 * scale})`);
402
+ grd.addColorStop(1, `rgba(${p.c},0)`);
403
+ ctx.beginPath();
404
+ ctx.arc(sx, sy, r * 3, 0, Math.PI * 2);
405
+ ctx.fillStyle = grd;
406
+ ctx.fill();
407
+ });
408
+
409
+ // Move points
410
+ pts.forEach(p => {
411
+ p.x += p.vx; p.y += p.vy; p.z += p.vz;
412
+ if (p.x < 0 || p.x > W) p.vx *= -1;
413
+ if (p.y < 0 || p.y > H) p.vy *= -1;
414
+ if (p.z < 200 || p.z > 1000) p.vz *= -1;
415
+ });
416
+
417
+ requestAnimationFrame(draw);
418
+ }
419
+
420
+ window.addEventListener('resize', resize);
421
+ init();
422
+ draw();
423
+ })();
424
+ </script>
425
+ """, height=0)
426
+
427
  # ── Input ─────────────────────────────────────────────────────────────────────
428
  st.divider()
 
 
 
 
429
 
430
  sample_questions = [
431
  "What is binary search?",
432
+ "Stack vs Queue?",
433
  "Explain merge sort.",
434
  "What are React hooks?",
435
  "What is a REST API?",
436
  "What is dynamic programming?",
437
  ]
438
 
439
+ st.markdown('<div class="section-label">Quick questions</div>', unsafe_allow_html=True)
440
  cols = st.columns(len(sample_questions))
441
  for i, sq in enumerate(sample_questions):
442
+ if cols[i].button(sq, key=f"sq_{i}", use_container_width=True):
443
+ st.session_state.input_question = sq
444
+ st.session_state.auto_run = True
445
+ st.rerun()
446
+
447
+ st.markdown("<div style='margin-top:14px'></div>", unsafe_allow_html=True)
448
+
449
+ question = st.text_input(
450
+ "Your question",
451
+ placeholder="e.g. What is binary search? / What are React hooks? / Explain merge sort.",
452
+ key="input_question"
453
+ )
454
+
455
+ st.markdown("<div style='margin-top:8px'></div>", unsafe_allow_html=True)
456
+ run_clicked = st.button("⚑ Ask All 3 Systems", type="primary")
457
 
458
  # ── Run systems ───────────────────────────────────────────────────────────────
459
+ if (run_clicked or st.session_state.auto_run) and question:
460
+ st.session_state.auto_run = False
461
+ st.session_state.rating_submitted = False
462
+
463
  col1, col2, col3 = st.columns(3)
464
 
465
  with col1:
466
+ with st.spinner("Baseline LLM generating..."):
 
 
467
  r1 = ask_baseline(question)
 
468
  with col2:
469
+ with st.spinner("RAG retrieving + generating..."):
 
 
470
  r2 = ask_rag(question, vs)
 
471
  with col3:
 
 
472
  if os.path.exists("./finetuned_model"):
473
+ with st.spinner("Fine-tuned model generating..."):
474
  from system3_inference import ask_finetuned
475
  r3 = ask_finetuned(question)
476
  else:
477
  r3 = {"answer": "Model not available.", "response_time": 0}
478
 
 
479
  st.session_state.last_results = {
480
  "question": question,
481
  "r1": r1, "r2": r2, "r3": r3,
482
  }
483
 
484
+ # ── Render answers ────────────────────────────────────────────────────────────
485
  if st.session_state.last_results:
486
  res = st.session_state.last_results
487
  r1, r2, r3 = res["r1"], res["r2"], res["r3"]
488
 
489
  st.divider()
490
+ st.markdown(f"""
491
+ <div class="q-banner">
492
+ Showing answers for: <strong>"{res['question']}"</strong>
493
+ </div>
494
+ """, unsafe_allow_html=True)
495
+
496
  col1, col2, col3 = st.columns(3)
497
 
498
  with col1:
 
 
499
  st.markdown(f"""
500
+ <div class="card card-blue">
501
+ <div class="card-header">
502
+ <span class="card-icon">⚑</span>
503
+ <div>
504
+ <div class="card-title">System 1 β€” Baseline LLM</div>
505
+ <div class="card-subtitle">Prompt only Β· No extra knowledge</div>
506
+ </div>
507
+ </div>
508
+ <div class="card-body">
509
+ <div class="card-answer">{r1['answer']}</div>
510
+ <div class="card-meta">
511
+ <span class="time-chip">⏱ {r1['response_time']}s</span>
512
+ </div>
513
+ </div>
514
  </div>
515
  """, unsafe_allow_html=True)
516
 
517
  with col2:
 
 
518
  st.markdown(f"""
519
+ <div class="card card-teal">
520
+ <div class="card-header">
521
+ <span class="card-icon">πŸ”</span>
522
+ <div>
523
+ <div class="card-title">System 2 β€” RAG Chatbot</div>
524
+ <div class="card-subtitle">Retrieves from knowledge base Β· Then generates</div>
525
+ </div>
526
+ </div>
527
+ <div class="card-body">
528
+ <div class="card-answer">{r2['answer']}</div>
529
+ <div class="rag-ctx">
530
+ <div class="rag-ctx-label">πŸ“„ Retrieved context</div>
531
+ {r2['context_used']}
532
+ </div>
533
+ <div class="card-meta">
534
+ <span class="time-chip">⏱ {r2['response_time']}s</span>
535
+ </div>
536
  </div>
537
  </div>
538
  """, unsafe_allow_html=True)
539
 
540
  with col3:
 
 
541
  if r3["answer"] == "Model not available.":
542
+ st.markdown("""
543
+ <div class="card card-purple">
544
+ <div class="card-header">
545
+ <span class="card-icon">🧠</span>
546
+ <div>
547
+ <div class="card-title">System 3 β€” Fine-Tuned Model</div>
548
+ <div class="card-subtitle">TinyLlama Β· LoRA fine-tuning</div>
549
+ </div>
550
+ </div>
551
+ <div class="card-body">
552
+ <div class="card-answer" style="opacity:.5">Model not loaded yet.</div>
553
+ </div>
554
+ </div>
555
+ """, unsafe_allow_html=True)
556
  st.warning(
 
557
  "Run `system3_finetune_colab.ipynb` on Google Colab, "
558
  "download `finetuned_model.zip`, and unzip it here."
559
  )
560
  else:
561
  st.markdown(f"""
562
+ <div class="card card-purple">
563
+ <div class="card-header">
564
+ <span class="card-icon">🧠</span>
565
+ <div>
566
+ <div class="card-title">System 3 β€” Fine-Tuned Model</div>
567
+ <div class="card-subtitle">TinyLlama 1.1B Β· LoRA on programming Q&A</div>
568
+ </div>
569
+ </div>
570
+ <div class="card-body">
571
+ <div class="card-answer">{r3['answer']}</div>
572
+ <div class="card-meta">
573
+ <span class="time-chip">⏱ {r3['response_time']}s</span>
574
+ </div>
575
+ </div>
576
  </div>
577
  """, unsafe_allow_html=True)
578
 
579
  # ── Rating section ─────────────────────────────────────────────────────────────
580
  if st.session_state.last_results and not st.session_state.rating_submitted:
581
  st.divider()
582
+ st.markdown("""
583
+ <div class="section-header">
584
+ <span class="section-header-text">Rate the answers</span>
585
+ <div class="section-header-line"></div>
586
+ </div>
587
+ """, unsafe_allow_html=True)
588
+ st.caption("Your ratings populate the live evaluation table below.")
589
 
590
  rc1, rc2, rc3 = st.columns(3)
591
 
592
  with rc1:
593
+ st.markdown('<div class="rate-header rate-blue">System 1 β€” Baseline LLM</div>', unsafe_allow_html=True)
594
+ c1_correct = st.slider("Correctness (1–5)", 1, 5, 3, key="c1_correct")
595
+ c1_halluc = st.checkbox("Hallucination detected?", key="c1_halluc")
596
+ c1_complete = st.slider("Completeness (1–5)", 1, 5, 3, key="c1_complete")
597
 
598
  with rc2:
599
+ st.markdown('<div class="rate-header rate-teal">System 2 β€” RAG Chatbot</div>', unsafe_allow_html=True)
600
+ c2_correct = st.slider("Correctness (1–5)", 1, 5, 3, key="c2_correct")
601
+ c2_halluc = st.checkbox("Hallucination detected?", key="c2_halluc")
602
+ c2_complete = st.slider("Completeness (1–5)", 1, 5, 3, key="c2_complete")
603
 
604
  with rc3:
605
+ st.markdown('<div class="rate-header rate-purple">System 3 β€” Fine-Tuned</div>', unsafe_allow_html=True)
606
+ c3_correct = st.slider("Correctness (1–5)", 1, 5, 3, key="c3_correct")
607
+ c3_halluc = st.checkbox("Hallucination detected?", key="c3_halluc")
608
+ c3_complete = st.slider("Completeness (1–5)", 1, 5, 3, key="c3_complete")
609
 
610
  if st.button("Submit Ratings", type="primary"):
611
  res = st.session_state.last_results
612
  st.session_state.ratings.append({
613
  "question": res["question"],
614
+ "c1_correct": c1_correct, "c2_correct": c2_correct, "c3_correct": c3_correct,
 
 
 
 
615
  "c1_halluc": 1 if c1_halluc else 0,
616
  "c2_halluc": 1 if c2_halluc else 0,
617
  "c3_halluc": 1 if c3_halluc else 0,
618
+ "c1_complete": c1_complete, "c2_complete": c2_complete, "c3_complete": c3_complete,
 
 
 
 
619
  "c1_time": res["r1"]["response_time"],
620
  "c2_time": res["r2"]["response_time"],
621
  "c3_time": res["r3"]["response_time"],
 
624
  st.success(f"Rating saved! Total questions rated: {len(st.session_state.ratings)}")
625
  st.rerun()
626
 
627
+ # ── Metrics table ──────────────────────────────────────────────────────────────
628
  st.divider()
629
+ st.markdown("""
630
+ <div class="section-header">
631
+ <span class="section-header-text">Evaluation metrics</span>
632
+ <div class="section-header-line"></div>
633
+ </div>
634
+ """, unsafe_allow_html=True)
635
 
636
  ratings = st.session_state.ratings
637
  n = len(ratings)
638
 
639
  if n == 0:
640
  st.caption("No ratings yet. Ask a question and rate the answers to populate this table.")
 
 
641
  def badge(val, _n):
642
+ return '<span class="badge badge-grey">β€”</span>'
643
  else:
644
  st.caption(f"Based on **{n} question(s)** rated by you. Response time is measured automatically.")
645
 
646
+ def avg(key): return round(sum(r[key] for r in ratings) / n, 2)
647
+ def pct(key): return round(sum(r[key] for r in ratings) / n * 100)
 
 
 
648
 
649
  def color_correct(v):
650
+ return "badge-green" if v >= 4.0 else ("badge-yellow" if v >= 3.0 else "badge-red")
651
+ def color_halluc(v):
652
+ return "badge-green" if v <= 15 else ("badge-yellow" if v <= 30 else "badge-red")
 
 
 
 
 
 
653
  def color_complete(v):
654
+ return "badge-green" if v >= 4.0 else ("badge-yellow" if v >= 3.0 else "badge-red")
655
+ def color_time(v):
656
+ return "badge-green" if v <= 1.0 else ("badge-yellow" if v <= 2.0 else "badge-red")
 
 
 
 
 
 
 
 
 
 
657
 
658
  def badge(val, cls):
659
  return f'<span class="badge {cls}">{val}</span>'
660
 
661
+ v_c1,v_c2,v_c3 = avg("c1_correct"), avg("c2_correct"), avg("c3_correct")
662
+ v_h1,v_h2,v_h3 = pct("c1_halluc"), pct("c2_halluc"), pct("c3_halluc")
663
+ v_p1,v_p2,v_p3 = avg("c1_complete"),avg("c2_complete"),avg("c3_complete")
664
+ v_t1,v_t2,v_t3 = avg("c1_time"), avg("c2_time"), avg("c3_time")
665
+
666
+ row_correct = (badge(f"{v_c1}/5", color_correct(v_c1)), badge(f"{v_c2}/5", color_correct(v_c2)), badge(f"{v_c3}/5", color_correct(v_c3)))
667
+ row_halluc = (badge(f"{v_h1}%", color_halluc(v_h1)), badge(f"{v_h2}%", color_halluc(v_h2)), badge(f"{v_h3}%", color_halluc(v_h3)))
668
+ row_complete = (badge(f"{v_p1}/5", color_complete(v_p1)), badge(f"{v_p2}/5", color_complete(v_p2)), badge(f"{v_p3}/5", color_complete(v_p3)))
669
+ row_time = (badge(f"{v_t1}s", color_time(v_t1)), badge(f"{v_t2}s", color_time(v_t2)), badge(f"{v_t3}s", color_time(v_t3)))
 
 
 
670
 
671
  if n == 0:
672
  dash = '<span class="badge badge-grey">β€”</span>'
673
  row_correct = row_halluc = row_complete = row_time = (dash, dash, dash)
674
 
675
  st.markdown(f"""
676
+ <div class="eval-wrap">
677
  <table class="eval-table">
678
  <thead>
679
  <tr>
680
  <th>Metric</th>
681
+ <th class="col-blue">⚑ System 1<br><span style="font-weight:500;font-size:11px;opacity:.6">Baseline LLM</span></th>
682
+ <th class="col-teal">πŸ” System 2<br><span style="font-weight:500;font-size:11px;opacity:.6">RAG Chatbot</span></th>
683
+ <th class="col-purple">🧠 System 3<br><span style="font-weight:500;font-size:11px;opacity:.6">Fine-Tuned</span></th>
684
  </tr>
685
  </thead>
686
  <tbody>
 
702
  </tr>
703
  </tbody>
704
  </table>
705
+ </div>
706
  """, unsafe_allow_html=True)
707
 
708
  if n > 0:
709
+ st.markdown("<div style='margin-top:12px'></div>", unsafe_allow_html=True)
710
  if st.button("Clear All Ratings"):
711
  st.session_state.ratings = []
712
  st.rerun()
 
715
  st.divider()
716
  with st.expander("About CodeSage"):
717
  st.markdown("""
718
+ **Project:** CodeSage β€” Comparative Study of RAG and Fine-Tuning for Domain-Specific QA
719
  **Domain:** Programming Tutor (DSA + Web Development)
720
 
721
  | System | Description |
722
  |--------|-------------|
723
+ | Baseline | Llama 3.1 8B via Groq Β· system prompt only |
724
+ | RAG | FAISS vector store + HuggingFace embeddings + Llama 3.1 8B via Groq |
725
  | Fine-tuned | TinyLlama 1.1B fine-tuned via LoRA on 20 programming Q&A pairs |
726
  """)