azlaan428 commited on
Commit
9083f85
·
1 Parent(s): a7f8aab

feat: selective literature review with paper checkboxes

Browse files
Files changed (3) hide show
  1. agent/agent.py +23 -0
  2. app.py +15 -0
  3. templates/index.html +130 -6
agent/agent.py CHANGED
@@ -128,6 +128,29 @@ def run_confidence_scorer(synthesis):
128
  text = text.replace("```json", "").replace("```", "").strip()
129
  return json.loads(text)
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  def run_pipeline(user_question):
132
  print("[1/4] Query Architect: generating search queries...")
133
  queries = run_query_architect(user_question)
 
128
  text = text.replace("```json", "").replace("```", "").strip()
129
  return json.loads(text)
130
 
131
+
132
+ def run_selective_review(user_question, selected_papers):
133
+ llm = get_llm()
134
+ parts = []
135
+ for pmid, p in selected_papers.items():
136
+ title = p.get("title", "N/A")
137
+ abstract = p.get("abstract", "")[:400]
138
+ authors = p.get("authors", "")
139
+ year = p.get("year", "")
140
+ parts.append("[PMID " + pmid + "] " + authors + " (" + year + "). " + title + "\n" + abstract)
141
+ corpus = "\n\n".join(parts)
142
+ prompt = (
143
+ "You are an academic writer producing a literature review paragraph.\n"
144
+ "Write a single cohesive academic paragraph (200-300 words) that synthesises "
145
+ "the following selected papers in relation to this question.\n"
146
+ "Cite papers inline by PMID in parentheses e.g. (PMID: 12345678).\n"
147
+ "Write in formal academic prose. No bullet points. No headings.\n\n"
148
+ "Question: " + user_question + "\n\n"
149
+ "Selected Papers:\n" + corpus
150
+ )
151
+ response = llm.invoke(prompt)
152
+ return response.content
153
+
154
  def run_pipeline(user_question):
155
  print("[1/4] Query Architect: generating search queries...")
156
  queries = run_query_architect(user_question)
app.py CHANGED
@@ -189,5 +189,20 @@ def score():
189
  except Exception as e:
190
  return jsonify({"error": str(e)}), 500
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  if __name__ == "__main__":
193
  app.run(debug=True, port=5000, threaded=True)
 
189
  except Exception as e:
190
  return jsonify({"error": str(e)}), 500
191
 
192
+
193
+ @app.route("/selective-review", methods=["POST"])
194
+ def selective_review():
195
+ data = request.get_json()
196
+ question = data.get("question", "")
197
+ selected_papers = data.get("papers", {})
198
+ if not selected_papers:
199
+ return jsonify({"error": "No papers selected"}), 400
200
+ try:
201
+ from agent.agent import run_selective_review
202
+ review = run_selective_review(question, selected_papers)
203
+ return jsonify({"review": review})
204
+ except Exception as e:
205
+ return jsonify({"error": str(e)}), 500
206
+
207
  if __name__ == "__main__":
208
  app.run(debug=True, port=5000, threaded=True)
templates/index.html CHANGED
@@ -459,6 +459,66 @@
459
  font-size: 10px;
460
  margin-left: 6px;
461
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  </style>
463
  </head>
464
  <body>
@@ -512,6 +572,14 @@
512
  <div class="citations-list" id="citationsList"></div>
513
  </div>
514
  </div>
 
 
 
 
 
 
 
 
515
 
516
  <footer>
517
  <span>Powered by LangGraph &middot; Groq LLaMA-3.1 &middot; PubMed NCBI</span>
@@ -584,22 +652,39 @@ function renderResults(data) {
584
  citList.innerHTML = "";
585
  citList.classList.add("open");
586
  const papersMap = data.papers || {};
 
 
587
  data.citations.split("\n").forEach(line => {
588
  if (!line.trim()) return;
589
  const pmidMatch = line.match(/PMID:\s*(\d+)/);
590
  const pmid = pmidMatch ? pmidMatch[1] : null;
 
591
  const div = document.createElement("div");
592
  div.className = "citation-item";
593
- const paper = pmid ? papersMap[pmid] : null;
594
  const citText = pmid ? line.replace(/PMID:\s*\d+/, `<span class="citation-pmid">PMID: ${pmid}</span>`) : line;
595
- div.innerHTML = citText + (paper ? `<span class="citation-toggle">▶ abstract</span><div class="abstract-body">${paper.abstract ? paper.abstract.replace(/\n/g,"<br>") : "Abstract not available"}</div>` : "");
 
 
596
  if (paper) {
597
- div.addEventListener("click", () => {
598
- div.classList.toggle("expanded");
599
- const tog = div.querySelector(".citation-toggle");
600
- if (tog) tog.textContent = div.classList.contains("expanded") ? "▼ abstract" : "▶ abstract";
 
601
  });
602
  }
 
 
 
 
 
 
 
 
 
 
 
 
603
  citList.appendChild(div);
604
  });
605
  document.getElementById("results").classList.add("visible");
@@ -721,6 +806,45 @@ async function scoreResults(synthesis) {
721
  });
722
  } catch(e) { console.warn('Scoring failed:', e); }
723
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  </script>
725
  </body>
726
  </html>
 
459
  font-size: 10px;
460
  margin-left: 6px;
461
  }
462
+
463
+ .citation-checkbox {
464
+ width: 14px;
465
+ height: 14px;
466
+ margin-right: 8px;
467
+ accent-color: var(--accent);
468
+ cursor: pointer;
469
+ flex-shrink: 0;
470
+ }
471
+ .citation-item { display: flex; align-items: flex-start; gap: 4px; }
472
+ .citation-text { flex: 1; }
473
+ .review-bar {
474
+ display: none;
475
+ align-items: center;
476
+ justify-content: space-between;
477
+ padding: 10px 16px;
478
+ background: var(--accent-dim);
479
+ border: 1px solid var(--accent);
480
+ border-radius: 4px;
481
+ margin-top: 12px;
482
+ font-family: var(--mono);
483
+ font-size: 12px;
484
+ color: var(--accent);
485
+ }
486
+ .review-bar.visible { display: flex; }
487
+ .review-btn {
488
+ background: var(--accent);
489
+ color: #000;
490
+ border: none;
491
+ padding: 6px 16px;
492
+ border-radius: 3px;
493
+ font-family: var(--mono);
494
+ font-size: 11px;
495
+ font-weight: 600;
496
+ cursor: pointer;
497
+ }
498
+ .review-btn:disabled { opacity: 0.5; cursor: not-allowed; }
499
+ .review-output {
500
+ display: none;
501
+ margin-top: 16px;
502
+ border: 1px solid var(--accent);
503
+ border-radius: 4px;
504
+ overflow: hidden;
505
+ }
506
+ .review-output.visible { display: block; }
507
+ .review-output-header {
508
+ background: var(--accent-dim);
509
+ padding: 10px 16px;
510
+ font-family: var(--mono);
511
+ font-size: 11px;
512
+ color: var(--accent);
513
+ letter-spacing: 0.1em;
514
+ text-transform: uppercase;
515
+ }
516
+ .review-output-body {
517
+ padding: 16px;
518
+ font-size: 14px;
519
+ line-height: 1.8;
520
+ color: var(--text);
521
+ }
522
  </style>
523
  </head>
524
  <body>
 
572
  <div class="citations-list" id="citationsList"></div>
573
  </div>
574
  </div>
575
+ <div class="review-bar" id="reviewBar">
576
+ <span id="reviewBarText">0 papers selected</span>
577
+ <button class="review-btn" id="reviewBtn" onclick="generateReview()" disabled>Generate Literature Review</button>
578
+ </div>
579
+ <div class="review-output" id="reviewOutput">
580
+ <div class="review-output-header">Generated Literature Review</div>
581
+ <div class="review-output-body" id="reviewText"></div>
582
+ </div>
583
 
584
  <footer>
585
  <span>Powered by LangGraph &middot; Groq LLaMA-3.1 &middot; PubMed NCBI</span>
 
652
  citList.innerHTML = "";
653
  citList.classList.add("open");
654
  const papersMap = data.papers || {};
655
+ selectedPapers = {};
656
+ updateReviewBar();
657
  data.citations.split("\n").forEach(line => {
658
  if (!line.trim()) return;
659
  const pmidMatch = line.match(/PMID:\s*(\d+)/);
660
  const pmid = pmidMatch ? pmidMatch[1] : null;
661
+ const paper = pmid ? papersMap[pmid] : null;
662
  const div = document.createElement("div");
663
  div.className = "citation-item";
 
664
  const citText = pmid ? line.replace(/PMID:\s*\d+/, `<span class="citation-pmid">PMID: ${pmid}</span>`) : line;
665
+ const inner = document.createElement("div");
666
+ inner.className = "citation-text";
667
+ inner.innerHTML = citText + (paper ? `<span class="citation-toggle">▶ abstract</span><div class="abstract-body">${paper.abstract ? paper.abstract.replace(/\n/g,"<br>") : "Abstract not available"}</div>` : "");
668
  if (paper) {
669
+ inner.addEventListener("click", (e) => {
670
+ if (e.target.type === "checkbox") return;
671
+ inner.parentElement.classList.toggle("expanded");
672
+ const tog = inner.querySelector(".citation-toggle");
673
+ if (tog) tog.textContent = inner.parentElement.classList.contains("expanded") ? "▼ abstract" : "▶ abstract";
674
  });
675
  }
676
+ if (pmid && paper) {
677
+ const cb = document.createElement("input");
678
+ cb.type = "checkbox";
679
+ cb.className = "citation-checkbox";
680
+ cb.addEventListener("change", () => {
681
+ if (cb.checked) selectedPapers[pmid] = paper;
682
+ else delete selectedPapers[pmid];
683
+ updateReviewBar();
684
+ });
685
+ div.appendChild(cb);
686
+ }
687
+ div.appendChild(inner);
688
  citList.appendChild(div);
689
  });
690
  document.getElementById("results").classList.add("visible");
 
806
  });
807
  } catch(e) { console.warn('Scoring failed:', e); }
808
  }
809
+
810
+ let selectedPapers = {};
811
+
812
+ function updateReviewBar() {
813
+ const count = Object.keys(selectedPapers).length;
814
+ const bar = document.getElementById('reviewBar');
815
+ const btn = document.getElementById('reviewBtn');
816
+ const txt = document.getElementById('reviewBarText');
817
+ bar.classList.toggle('visible', count > 0);
818
+ txt.textContent = count + ' paper' + (count !== 1 ? 's' : '') + ' selected';
819
+ btn.disabled = count < 2;
820
+ }
821
+
822
+ async function generateReview() {
823
+ const btn = document.getElementById('reviewBtn');
824
+ btn.disabled = true;
825
+ btn.textContent = 'Generating...';
826
+ try {
827
+ const res = await fetch('/selective-review', {
828
+ method: 'POST',
829
+ headers: {'Content-Type': 'application/json'},
830
+ body: JSON.stringify({
831
+ question: lastQuery,
832
+ papers: selectedPapers
833
+ })
834
+ });
835
+ const data = await res.json();
836
+ if (data.error) throw new Error(data.error);
837
+ document.getElementById('reviewText').textContent = data.review;
838
+ document.getElementById('reviewOutput').classList.add('visible');
839
+ document.getElementById('reviewOutput').scrollIntoView({behavior: 'smooth'});
840
+ } catch(e) {
841
+ alert('Error: ' + e.message);
842
+ } finally {
843
+ btn.disabled = false;
844
+ btn.textContent = 'Generate Literature Review';
845
+ updateReviewBar();
846
+ }
847
+ }
848
  </script>
849
  </body>
850
  </html>