azlaan428 commited on
Commit
20b4913
·
1 Parent(s): 58d2397

feat: SSE streaming pipeline, progress bar, PDF export

Browse files
Files changed (2) hide show
  1. app.py +60 -6
  2. templates/index.html +70 -72
app.py CHANGED
@@ -1,7 +1,7 @@
1
- import sys, os
2
  sys.path.append(os.path.dirname(os.path.abspath(__file__)))
3
- from flask import Flask, render_template, request, jsonify, send_file
4
- from agent.agent import run_pipeline
5
  from reportlab.lib.pagesizes import A4
6
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
7
  from reportlab.lib.units import mm
@@ -12,10 +12,12 @@ import io
12
 
13
  app = Flask(__name__)
14
 
 
15
  @app.route("/")
16
  def index():
17
  return render_template("index.html")
18
 
 
19
  @app.route("/query", methods=["POST"])
20
  def query():
21
  data = request.get_json()
@@ -33,6 +35,57 @@ def query():
33
  except Exception as e:
34
  return jsonify({"error": str(e)}), 500
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  @app.route("/export-pdf", methods=["POST"])
37
  def export_pdf():
38
  data = request.get_json()
@@ -69,7 +122,7 @@ def export_pdf():
69
  story = []
70
  story.append(Paragraph("ARIA — Autonomous Research Intelligence Agent", title_style))
71
  story.append(Paragraph(
72
- f"Query: {query} | {paper_count} papers retrieved | Groq LLaMA-3.1",
73
  meta_style))
74
  story.append(HRFlowable(width="100%", thickness=1,
75
  color=colors.HexColor("#1e2936"), spaceAfter=16))
@@ -116,9 +169,10 @@ def export_pdf():
116
  doc.build(story)
117
  buf.seek(0)
118
  safe_query = "".join(c for c in query[:40] if c.isalnum() or c in " -_").strip()
119
- filename = f"ARIA_{safe_query}.pdf".replace(" ", "_")
120
  return send_file(buf, mimetype="application/pdf",
121
  as_attachment=True, download_name=filename)
122
 
 
123
  if __name__ == "__main__":
124
- app.run(debug=True, port=5000)
 
1
+ import sys, os, json
2
  sys.path.append(os.path.dirname(os.path.abspath(__file__)))
3
+ from flask import Flask, render_template, request, jsonify, send_file, Response, stream_with_context
4
+ from agent.agent import run_pipeline, run_query_architect, run_literature_scout, run_evidence_synthesiser, run_citation_builder
5
  from reportlab.lib.pagesizes import A4
6
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
7
  from reportlab.lib.units import mm
 
12
 
13
  app = Flask(__name__)
14
 
15
+
16
  @app.route("/")
17
  def index():
18
  return render_template("index.html")
19
 
20
+
21
  @app.route("/query", methods=["POST"])
22
  def query():
23
  data = request.get_json()
 
35
  except Exception as e:
36
  return jsonify({"error": str(e)}), 500
37
 
38
+
39
+ @app.route("/stream", methods=["GET"])
40
+ def stream():
41
+ user_query = request.args.get("query", "").strip()
42
+ if not user_query:
43
+ return jsonify({"error": "Empty query"}), 400
44
+
45
+ def generate():
46
+ def emit(event, data):
47
+ return "event: " + event + "\ndata: " + json.dumps(data) + "\n\n"
48
+
49
+ try:
50
+ # Stage 1
51
+ yield emit("stage", {"stage": 1, "pct": 10})
52
+ queries = run_query_architect(user_query)
53
+ yield emit("queries", {"queries": queries, "pct": 25})
54
+
55
+ # Stage 2
56
+ yield emit("stage", {"stage": 2, "pct": 35})
57
+ papers = run_literature_scout(queries)
58
+ yield emit("papers", {"paper_count": len(papers), "pct": 55})
59
+
60
+ # Stage 3
61
+ yield emit("stage", {"stage": 3, "pct": 70})
62
+ synthesis = run_evidence_synthesiser(user_query, papers)
63
+ yield emit("synthesis", {"synthesis": synthesis, "pct": 88})
64
+
65
+ # Stage 4
66
+ yield emit("stage", {"stage": 4, "pct": 90})
67
+ citations = run_citation_builder(papers)
68
+ yield emit("done", {
69
+ "synthesis": synthesis,
70
+ "citations": citations,
71
+ "paper_count": len(papers),
72
+ "queries": queries,
73
+ "pct": 100
74
+ })
75
+
76
+ except Exception as e:
77
+ yield emit("error", {"message": str(e)})
78
+
79
+ return Response(
80
+ stream_with_context(generate()),
81
+ mimetype="text/event-stream",
82
+ headers={
83
+ "Cache-Control": "no-cache",
84
+ "X-Accel-Buffering": "no"
85
+ }
86
+ )
87
+
88
+
89
  @app.route("/export-pdf", methods=["POST"])
90
  def export_pdf():
91
  data = request.get_json()
 
122
  story = []
123
  story.append(Paragraph("ARIA — Autonomous Research Intelligence Agent", title_style))
124
  story.append(Paragraph(
125
+ "Query: " + query + " | " + str(paper_count) + " papers retrieved | Groq LLaMA-3.1",
126
  meta_style))
127
  story.append(HRFlowable(width="100%", thickness=1,
128
  color=colors.HexColor("#1e2936"), spaceAfter=16))
 
169
  doc.build(story)
170
  buf.seek(0)
171
  safe_query = "".join(c for c in query[:40] if c.isalnum() or c in " -_").strip()
172
+ filename = "ARIA_" + safe_query.replace(" ", "_") + ".pdf"
173
  return send_file(buf, mimetype="application/pdf",
174
  as_attachment=True, download_name=filename)
175
 
176
+
177
  if __name__ == "__main__":
178
+ app.run(debug=True, port=5000, threaded=True)
templates/index.html CHANGED
@@ -502,92 +502,90 @@ function toggleCitations() {
502
  document.getElementById("citationsList").classList.contains("open") ? "▲" : "▼";
503
  }
504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  async function submitQuery() {
506
  const q = document.getElementById("queryInput").value.trim();
507
  if (!q) return;
508
-
509
  const btn = document.getElementById("submitBtn");
510
  btn.disabled = true;
511
-
512
  document.getElementById("results").classList.remove("visible");
513
  document.getElementById("errorBox").classList.remove("visible");
514
  document.getElementById("pipeline").classList.add("visible");
515
  setStage(1);
516
-
517
- // Simulate stage progression while waiting
518
- const delays = [0, 3000, 8000, 14000];
519
- delays.forEach((d, i) => setTimeout(() => setStage(i + 1), d));
520
-
521
- try {
522
- const res = await fetch("/query", {
523
- method: "POST",
524
- headers: { "Content-Type": "application/json" },
525
- body: JSON.stringify({ query: q })
526
- });
527
- const data = await res.json();
 
 
 
 
 
 
 
 
 
 
 
 
528
  lastResult = data;
529
- lastQuery = q;
530
-
531
- setStage(5); // all done
532
-
533
- if (data.error) {
534
- document.getElementById("errorBox").textContent = "Error: " + data.error;
535
- document.getElementById("errorBox").classList.add("visible");
536
- return;
537
- }
538
-
539
- // Meta bar
540
- const meta = document.getElementById("metaBar");
541
- meta.innerHTML = `
542
- <div class="badge green">${data.paper_count} papers retrieved</div>
543
- <div class="badge">${data.queries ? data.queries.length : 0} PubMed queries</div>
544
- <div class="badge">LLaMA-3.1-8B via Groq</div>
545
- `;
546
-
547
- // Synthesis sections
548
- const sections = parseSynthesis(data.synthesis);
549
- const synthEl = document.getElementById("synthesis");
550
- synthEl.innerHTML = "";
551
- SECTIONS.forEach(s => {
552
- if (!sections[s.label]) return;
553
- const div = document.createElement("div");
554
- div.className = "section open";
555
- div.innerHTML = `
556
- <div class="section-header" onclick="toggleSection(this.parentElement)">
557
- <span class="section-tag">${s.label}</span>
558
- <span class="section-chevron">▲</span>
559
- </div>
560
- <div class="section-body">${sections[s.label].replace(/\n/g, '<br>')}</div>
561
- `;
562
- synthEl.appendChild(div);
563
- });
564
-
565
- // Citations
566
- const citList = document.getElementById("citationsList");
567
- citList.innerHTML = "";
568
- citList.classList.add("open");
569
- data.citations.split("\n").forEach(line => {
570
- if (!line.trim()) return;
571
- const pmidMatch = line.match(/PMID:\s*(\d+)/);
572
- const pmid = pmidMatch ? pmidMatch[1] : null;
573
- const div = document.createElement("div");
574
- div.className = "citation-item";
575
- div.innerHTML = pmid
576
- ? line.replace(/PMID:\s*\d+/, `<span class="citation-pmid">PMID: ${pmid}</span>`)
577
- : line;
578
- citList.appendChild(div);
579
- });
580
-
581
- document.getElementById("results").classList.add("visible");
582
-
583
- } catch (err) {
584
- document.getElementById("errorBox").textContent = "Network error: " + err.message;
585
  document.getElementById("errorBox").classList.add("visible");
586
- } finally {
587
  btn.disabled = false;
588
- }
589
  }
590
 
 
591
  document.getElementById("queryInput").addEventListener("keydown", e => {
592
  if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitQuery(); }
593
  });
 
502
  document.getElementById("citationsList").classList.contains("open") ? "▲" : "▼";
503
  }
504
 
505
+ function renderResults(data) {
506
+ const meta = document.getElementById("metaBar");
507
+ meta.innerHTML = `<div class="badge green">${data.paper_count} papers retrieved</div><div class="badge">${data.queries ? data.queries.length : 0} PubMed queries</div><div class="badge">LLaMA-3.1-8B via Groq</div>`;
508
+ const sections = parseSynthesis(data.synthesis);
509
+ const synthEl = document.getElementById("synthesis");
510
+ synthEl.innerHTML = "";
511
+ SECTIONS.forEach(s => {
512
+ if (!sections[s.label]) return;
513
+ const div = document.createElement("div");
514
+ div.className = "section open";
515
+ div.innerHTML = `<div class="section-header" onclick="toggleSection(this.parentElement)"><span class="section-tag">${s.label}</span><span class="section-chevron">▲</span></div><div class="section-body">${sections[s.label].replace(/\n/g, "<br>")}</div>`;
516
+ synthEl.appendChild(div);
517
+ });
518
+ const citList = document.getElementById("citationsList");
519
+ citList.innerHTML = "";
520
+ citList.classList.add("open");
521
+ data.citations.split("\n").forEach(line => {
522
+ if (!line.trim()) return;
523
+ const pmidMatch = line.match(/PMID:\s*(\d+)/);
524
+ const pmid = pmidMatch ? pmidMatch[1] : null;
525
+ const div = document.createElement("div");
526
+ div.className = "citation-item";
527
+ div.innerHTML = pmid ? line.replace(/PMID:\s*\d+/, `<span class="citation-pmid">PMID: ${pmid}</span>`) : line;
528
+ citList.appendChild(div);
529
+ });
530
+ document.getElementById("results").classList.add("visible");
531
+ }
532
+
533
+ function updateProgress(pct) {
534
+ document.getElementById("progressFill").style.width = pct + "%";
535
+ document.getElementById("progressPct").textContent = pct + "%";
536
+ }
537
+
538
  async function submitQuery() {
539
  const q = document.getElementById("queryInput").value.trim();
540
  if (!q) return;
 
541
  const btn = document.getElementById("submitBtn");
542
  btn.disabled = true;
 
543
  document.getElementById("results").classList.remove("visible");
544
  document.getElementById("errorBox").classList.remove("visible");
545
  document.getElementById("pipeline").classList.add("visible");
546
  setStage(1);
547
+ updateProgress(5);
548
+ lastQuery = q;
549
+
550
+ const es = new EventSource("/stream?query=" + encodeURIComponent(q));
551
+
552
+ es.addEventListener("stage", e => {
553
+ const d = JSON.parse(e.data);
554
+ setStage(d.stage);
555
+ updateProgress(d.pct);
556
+ });
557
+ es.addEventListener("queries", e => {
558
+ const d = JSON.parse(e.data);
559
+ updateProgress(d.pct);
560
+ });
561
+ es.addEventListener("papers", e => {
562
+ const d = JSON.parse(e.data);
563
+ updateProgress(d.pct);
564
+ });
565
+ es.addEventListener("synthesis", e => {
566
+ const d = JSON.parse(e.data);
567
+ updateProgress(d.pct);
568
+ });
569
+ es.addEventListener("done", e => {
570
+ const data = JSON.parse(e.data);
571
  lastResult = data;
572
+ updateProgress(100);
573
+ setStage(5);
574
+ es.close();
575
+ renderResults(data);
576
+ btn.disabled = false;
577
+ });
578
+ es.addEventListener("error", e => {
579
+ es.close();
580
+ let msg = "Pipeline error";
581
+ try { msg = JSON.parse(e.data).message; } catch(err) {}
582
+ document.getElementById("errorBox").textContent = "Error: " + msg;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  document.getElementById("errorBox").classList.add("visible");
 
584
  btn.disabled = false;
585
+ });
586
  }
587
 
588
+
589
  document.getElementById("queryInput").addEventListener("keydown", e => {
590
  if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submitQuery(); }
591
  });