AI Agent commited on
Commit
8aa24f9
Β·
1 Parent(s): 130f6cd

Add feedback UI and capture to nitdaa_summary.json

Browse files
Files changed (2) hide show
  1. app.py +40 -5
  2. templates/index.html +45 -0
app.py CHANGED
@@ -193,7 +193,7 @@ def log_session(event_type: str, token: str, ip: str):
193
 
194
  _known_sessions = {}
195
 
196
- def log_query_summary(token: str, ip: str, query: str, chunks_retrieved: int, gen_time: float, success: bool, error: str = ""):
197
  try:
198
  log_dir = Path(__file__).parent / "app" / "logs"
199
  log_dir.mkdir(parents=True, exist_ok=True)
@@ -205,6 +205,7 @@ def log_query_summary(token: str, ip: str, query: str, chunks_retrieved: int, ge
205
 
206
  entry = {
207
  "timestamp": ts,
 
208
  "ip_address": ip,
209
  "session_token": token,
210
  "question": query,
@@ -857,13 +858,12 @@ def query_start():
857
  _query_jobs[job_id]["done"] = True
858
 
859
  gen_time = metrics.get("time_seconds", 0)
860
- log_query_summary(token, remote_addr, q, top_k or 10, gen_time, True)
861
  except Exception as e:
862
- log.exception("Query pipeline error")
863
  _query_jobs[job_id]["events"].append({"error": str(e)})
864
- _query_jobs[job_id]["error"] = str(e)
865
  _query_jobs[job_id]["done"] = True
866
- log_query_summary(token, remote_addr, q, top_k or 10, 0, False, str(e))
867
 
868
  _query_executor.submit(_run)
869
 
@@ -901,6 +901,41 @@ def query_stream(job_id):
901
  headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
902
  )
903
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
  # ── Headless API v1 ───────────────────────────────────────────────────────────
906
 
 
193
 
194
  _known_sessions = {}
195
 
196
+ def log_query_summary(token: str, ip: str, query: str, chunks_retrieved: int, gen_time: float, success: bool, error: str = "", job_id: str = ""):
197
  try:
198
  log_dir = Path(__file__).parent / "app" / "logs"
199
  log_dir.mkdir(parents=True, exist_ok=True)
 
205
 
206
  entry = {
207
  "timestamp": ts,
208
+ "job_id": job_id,
209
  "ip_address": ip,
210
  "session_token": token,
211
  "question": query,
 
858
  _query_jobs[job_id]["done"] = True
859
 
860
  gen_time = metrics.get("time_seconds", 0)
861
+ log_query_summary(token, remote_addr, q, top_k or 10, gen_time, True, "", job_id)
862
  except Exception as e:
863
+ log.exception("Query failed")
864
  _query_jobs[job_id]["events"].append({"error": str(e)})
 
865
  _query_jobs[job_id]["done"] = True
866
+ log_query_summary(token, remote_addr, q, top_k or 10, 0, False, str(e), job_id)
867
 
868
  _query_executor.submit(_run)
869
 
 
901
  headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
902
  )
903
 
904
+ @app.route("/api/feedback", methods=["POST"])
905
+ @limiter.limit("20 per minute")
906
+ def api_feedback():
907
+ data = request.get_json() or {}
908
+ job_id = data.get("job_id", "")
909
+ rating = data.get("rating", "neutral")
910
+ text = data.get("text", "")
911
+ token = get_session_token()
912
+
913
+ try:
914
+ log_dir = Path(__file__).parent / "app" / "logs"
915
+ log_dir.mkdir(parents=True, exist_ok=True)
916
+ summary_file = log_dir / "nitdaa_summary.json"
917
+
918
+ from datetime import datetime, timezone, timedelta
919
+ ist = timezone(timedelta(hours=5, minutes=30))
920
+ ts = datetime.now(ist).strftime("%Y-%m-%d %H:%M:%S")
921
+
922
+ entry = {
923
+ "timestamp": ts,
924
+ "session_token": token,
925
+ "job_id": job_id,
926
+ "type": "feedback",
927
+ "feedback_rating": rating,
928
+ "feedback_text": text
929
+ }
930
+ with open(summary_file, "a") as f:
931
+ f.write(json.dumps(entry) + "\n")
932
+
933
+ async_sync_log(str(summary_file), "nitdaa_summary.json")
934
+ return jsonify({"ok": True})
935
+ except Exception as e:
936
+ log.error(f"Failed to save feedback: {e}")
937
+ return jsonify({"error": str(e)}), 500
938
+
939
 
940
  # ── Headless API v1 ───────────────────────────────────────────────────────────
941
 
templates/index.html CHANGED
@@ -507,6 +507,12 @@
507
  Analysis took ${data.metrics.time_seconds?.toFixed(1) || '?'} seconds.
508
  Tokens: ${data.metrics.tokens_in || '?'} in, ${data.metrics.tokens_out || '?'} out.
509
  </div>
 
 
 
 
 
 
510
  `;
511
  answerContent.innerHTML += metricsHtml;
512
  window.scrollTo(0, document.body.scrollHeight);
@@ -550,6 +556,45 @@
550
  }
551
  });
552
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  </script>
554
  </body>
555
  </html>
 
507
  Analysis took ${data.metrics.time_seconds?.toFixed(1) || '?'} seconds.
508
  Tokens: ${data.metrics.tokens_in || '?'} in, ${data.metrics.tokens_out || '?'} out.
509
  </div>
510
+ <div id="feedback-container-${jobId}" style="margin-top: 15px; display: flex; align-items: center; gap: 10px; background: rgba(0,0,0,0.03); padding: 10px; border-radius: 8px;">
511
+ <button class="fb-btn fb-up" onclick="submitFeedback('${jobId}', 'positive')" style="background:none; border:none; cursor:pointer; font-size:1.5rem; transition: transform 0.2s;">πŸ‘</button>
512
+ <button class="fb-btn fb-down" onclick="submitFeedback('${jobId}', 'negative')" style="background:none; border:none; cursor:pointer; font-size:1.5rem; transition: transform 0.2s;">πŸ‘Ž</button>
513
+ <input type="text" id="fb-text-${jobId}" placeholder="Optional feedback..." style="flex-grow:1; padding:8px; border:1px solid #ddd; border-radius:4px; font-size: 0.9rem;">
514
+ <button onclick="submitFeedback('${jobId}', null)" style="padding:8px 15px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor:pointer; font-weight: bold;">Submit</button>
515
+ </div>
516
  `;
517
  answerContent.innerHTML += metricsHtml;
518
  window.scrollTo(0, document.body.scrollHeight);
 
556
  }
557
  });
558
  });
559
+
560
+ // Feedback Logic
561
+ window.submitFeedback = async function(jobId, explicitRating) {
562
+ const container = document.getElementById(`feedback-container-${jobId}`);
563
+ if (!container) return;
564
+
565
+ let rating = container.getAttribute('data-rating') || 'neutral';
566
+
567
+ if (explicitRating) {
568
+ rating = explicitRating;
569
+ container.setAttribute('data-rating', rating);
570
+ // UI updates for selected icon
571
+ const upBtn = container.querySelector('.fb-up');
572
+ const downBtn = container.querySelector('.fb-down');
573
+ upBtn.style.transform = rating === 'positive' ? 'scale(1.2)' : 'scale(1)';
574
+ upBtn.style.filter = rating === 'positive' ? 'drop-shadow(0 0 5px #4caf50)' : 'none';
575
+
576
+ downBtn.style.transform = rating === 'negative' ? 'scale(1.2)' : 'scale(1)';
577
+ downBtn.style.filter = rating === 'negative' ? 'drop-shadow(0 0 5px #f44336)' : 'none';
578
+ }
579
+
580
+ const textInput = document.getElementById(`fb-text-${jobId}`);
581
+ const text = textInput ? textInput.value : "";
582
+
583
+ try {
584
+ const res = await fetch('/api/feedback', {
585
+ method: 'POST',
586
+ headers: { 'Content-Type': 'application/json', 'X-Session-Token': SESSION_TOKEN },
587
+ body: JSON.stringify({ job_id: jobId, rating: rating, text: text })
588
+ });
589
+
590
+ if (res.ok && !explicitRating) {
591
+ // If explicitRating is null, user clicked the Submit button
592
+ container.innerHTML = '<span style="color: #4caf50; font-weight: bold; margin: 0 auto;">Feedback submitted! Thank you.</span>';
593
+ }
594
+ } catch (e) {
595
+ console.error("Failed to submit feedback", e);
596
+ }
597
+ };
598
  </script>
599
  </body>
600
  </html>