aashutoshsaini commited on
Commit
e7443ec
Β·
verified Β·
1 Parent(s): 441c447

Update dvnc_connectome_v3/src/dvnc_connectome/apps/gradio_app.py

Browse files
dvnc_connectome_v3/src/dvnc_connectome/apps/gradio_app.py CHANGED
@@ -39,7 +39,7 @@ MODELS = [
39
  {"name": "DVNC Curie", "tag": "lab", "desc": "Experimental hypothesis generation for anomalous signals"},
40
  ]
41
 
42
- CANDIDATES = [
43
  {
44
  "title": "Piezoelectric Scaffold Cascade",
45
  "front": "Use mechano-electric scaffolds to convert cardiac strain into micro-current signalling.",
@@ -60,6 +60,116 @@ CANDIDATES = [
60
  },
61
  ]
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  JOURNALS = [
64
  {"name": "Nature", "url": "https://www.nature.com/search", "desc": "Flagship multidisciplinary research journal."},
65
  {"name": "Science", "url": "https://www.science.org/search", "desc": "High-impact science journal and family."},
@@ -130,36 +240,83 @@ def build_agent_timeline(reasoning: list) -> str:
130
 
131
 
132
  def build_cards_html(cards: list) -> str:
 
 
 
 
 
 
 
133
  items = []
134
  for c in cards:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  items.append(f"""
136
- <article class="candidate-card" tabindex="0">
137
- <div class="candidate-card-inner">
138
- <div class="candidate-face candidate-front">
139
- <div class="candidate-top">
140
- <span class="chip">{c['agent']}</span>
141
- <span class="score">{c['score']}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  </div>
143
- <h4>{c['title']}</h4>
144
- <p>{c['front']}</p>
145
- <div class="meta-row"><span>Novelty</span><strong>{c['novelty']}</strong></div>
146
- <button class="mini" type="button">Flip insight</button>
147
  </div>
148
- <div class="candidate-face candidate-back">
149
- <div class="candidate-top">
150
- <span class="chip alt">Alternative path</span>
151
- <span class="score">{c['score']}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  </div>
153
- <h4>{c['title']}</h4>
154
- <p>{c['back']}</p>
155
- <div class="meta-row"><span>Swap into route</span><strong>Enabled</strong></div>
156
- <button class="mini" type="button">Return</button>
157
  </div>
 
158
  </div>
159
  </article>""")
 
160
  return (
161
- '<div class="panel" style="padding:20px;">'
162
- '<div class="candidate-grid">' + "".join(items) + "</div></div>"
 
163
  )
164
 
165
 
@@ -493,35 +650,185 @@ html, body, .gradio-container {
493
  .agent-head span { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
494
  .agent-copy { padding: 0 14px 16px 66px; }
495
  .agent-copy p { margin: 0; color: #2d2d2d; font-size: .93rem; line-height: 1.6; }
496
- /* candidate cards */
497
- .candidate-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
498
- .candidate-card { background: none; perspective: 1400px; min-height: 330px; }
499
- .candidate-card-inner {
500
- position: relative; width: 100%; min-height: 330px;
501
- transition: transform .8s cubic-bezier(.2,.7,.1,1); transform-style: preserve-3d;
502
- }
503
- .candidate-card:hover .candidate-card-inner,
504
- .candidate-card:focus .candidate-card-inner,
505
- .candidate-card:focus-within .candidate-card-inner { transform: rotateY(180deg); }
506
- .candidate-face {
507
- position: absolute; inset: 0; padding: 20px; border-radius: 22px;
508
- border: 1px solid var(--line); background: #ffffff; color: var(--text);
509
- backface-visibility: hidden; box-shadow: 0 12px 24px rgba(0,0,0,0.06);
510
- display: flex; flex-direction: column; gap: 14px;
511
- }
512
- .candidate-back { transform: rotateY(180deg); }
513
- .candidate-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
514
- .chip {
515
- font-size: .72rem; text-transform: uppercase; letter-spacing: .12em; color: #0b6f66;
516
- padding: 7px 10px; border-radius: 999px;
517
- background: rgba(23,184,166,0.08); border: 1px solid rgba(23,184,166,0.18);
518
- }
519
- .chip.alt { color: var(--gold); background: rgba(255,122,26,0.08); border-color: rgba(255,122,26,0.18); }
520
- .score { font-weight: 700; color: var(--gold); }
521
- .candidate-face h4 { margin: 0; font-size: 1.08rem; line-height: 1.35; }
522
- .candidate-face p { margin: 0; color: #1e1e1e; line-height: 1.65; font-size: .96rem; overflow-wrap: anywhere; }
523
- .meta-row { margin-top: auto; display: flex; justify-content: space-between; color: var(--muted); font-size: .88rem; gap: 14px; }
524
- .mini { margin-top: 8px; align-self: flex-start; color: var(--text); padding: 10px 12px; border-radius: 14px; border: 1px solid var(--line); background: #ffffff; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  /* self-learning graph */
526
  .brain-shell { padding: 18px; }
527
  .brain-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; margin-bottom: 10px; }
@@ -577,8 +884,9 @@ html, body, .gradio-container {
577
  footer { display: none !important; }
578
  @keyframes pulseEdge { to { stroke-dashoffset: -40; } }
579
  @media (max-width: 1180px) {
580
- .model-switcher, .candidate-grid, .papers-grid, .journal-grid { grid-template-columns: 1fr; }
581
  .brain-svg { height: 460px; }
 
582
  }
583
  """
584
 
@@ -690,12 +998,11 @@ def make_app(db_path: str) -> gr.Blocks:
690
 
691
  # ── Right column ──────────────────────────────────────
692
  with gr.Column(scale=3):
693
- # v5 animated route graph (replaces v4 static SVG)
694
- connectome = gr.HTML(route_graph_placeholder())
695
- cards = gr.HTML(
696
- '<div class="panel" style="padding:20px">'
697
- '<div class="candidate-grid"></div></div>'
698
  )
 
699
 
700
  # ── Below columns (v4 exact) ──────────────────────────────
701
  output = gr.Markdown("# Discovery Output\n\nAwaiting query.")
@@ -766,8 +1073,8 @@ def make_app(db_path: str) -> gr.Blocks:
766
  <p>Enter a query to activate the 6-agent discovery stack.</p>
767
  </div></div></div>""",
768
  '<div class="panel" style="padding:18px"><div class="timeline"></div></div>',
769
- route_graph_placeholder(),
770
- '<div class="panel" style="padding:20px"><div class="candidate-grid"></div></div>',
771
  "# Discovery Output\n\nAwaiting query.",
772
  )
773
 
@@ -817,15 +1124,18 @@ def make_app(db_path: str) -> gr.Blocks:
817
 
818
  # ── v5 route graph in the right column ───────────────────────
819
  try:
820
- connectome_html = (
821
  build_route_graph_html(route_result)
822
  if route_result else route_graph_placeholder()
823
  )
824
  except Exception:
825
- connectome_html = route_graph_placeholder()
 
826
 
827
- # ── v4 candidate cards (static) ───────────────────────────────
828
- cards_html = build_cards_html(CANDIDATES)
 
 
829
 
830
  # ── v4 output Markdown ────────────────────────────────────────
831
  metrics_text = ""
 
39
  {"name": "DVNC Curie", "tag": "lab", "desc": "Experimental hypothesis generation for anomalous signals"},
40
  ]
41
 
42
+ CANDIDATES_FALLBACK = [
43
  {
44
  "title": "Piezoelectric Scaffold Cascade",
45
  "front": "Use mechano-electric scaffolds to convert cardiac strain into micro-current signalling.",
 
60
  },
61
  ]
62
 
63
+
64
+ def _parse_cards_from_pipeline(final_card: str, agent_log: list) -> list:
65
+ """
66
+ Extract innovation card content from the pipeline's final output.
67
+ Returns a list of card dicts with: title, front, back, score, novelty, agent.
68
+ Falls back to CANDIDATES_FALLBACK if parsing fails or card is empty.
69
+ """
70
+ if not final_card or len(final_card.strip()) < 50:
71
+ return CANDIDATES_FALLBACK
72
+
73
+ cards = []
74
+
75
+ # ── Card 1: Primary hypothesis (from final Innovation Card) ─────────
76
+ # Extract title from BRIEF line or first meaningful heading
77
+ title1 = "Primary Innovation"
78
+ m_brief = re.search(r'BRIEF[:\s]+(.+?)(?:\n|ROUTE)', final_card, re.IGNORECASE)
79
+ if m_brief:
80
+ title1 = m_brief.group(1).strip()[:60]
81
+
82
+ # Front: the refined hypothesis section
83
+ front1 = ""
84
+ m_hyp = re.search(
85
+ r'REFINED HYPOTHESIS\s*[-━═]+\s*(.*?)(?:━|═|EXPERIMENTAL|$)',
86
+ final_card, re.DOTALL | re.IGNORECASE
87
+ )
88
+ if m_hyp:
89
+ front1 = m_hyp.group(1).strip()[:300]
90
+ if not front1:
91
+ # fallback: first substantive paragraph
92
+ paras = [p.strip() for p in final_card.split("\n\n") if len(p.strip()) > 60]
93
+ front1 = paras[0][:300] if paras else final_card[:300]
94
+
95
+ # Back: cross-domain leap
96
+ back1 = ""
97
+ m_leap = re.search(
98
+ r'CROSS.DOMAIN LEAP\s*[-━═]+\s*(.*?)(?:━|═|REFINED|$)',
99
+ final_card, re.DOTALL | re.IGNORECASE
100
+ )
101
+ if m_leap:
102
+ back1 = m_leap.group(1).strip()[:300]
103
+ if not back1:
104
+ back1 = "Cross-domain leap synthesised from connectome routing."
105
+
106
+ # Score: from OVERALL line
107
+ score1 = 0
108
+ m_score = re.search(r'OVERALL[:\s]+(\d+)', final_card)
109
+ if m_score:
110
+ score1 = int(m_score.group(1))
111
+
112
+ # Novelty derived from score
113
+ novelty1 = "High" if score1 >= 75 else ("Medium-High" if score1 >= 60 else "Medium")
114
+
115
+ cards.append({
116
+ "title": title1,
117
+ "front": front1,
118
+ "back": back1,
119
+ "score": score1 or 88,
120
+ "novelty": novelty1,
121
+ "agent": "Orchestrator",
122
+ })
123
+
124
+ # ── Card 2: Experimental Programme ───────────────────────────────────
125
+ exp_text = ""
126
+ m_exp = re.search(
127
+ r'EXPERIMENTAL PROGRAMME\s*[-━═]+\s*(.*?)(?:━|═|TRANSLATION|CRITIQUE|$)',
128
+ final_card, re.DOTALL | re.IGNORECASE
129
+ )
130
+ if m_exp:
131
+ exp_text = m_exp.group(1).strip()[:300]
132
+
133
+ if exp_text:
134
+ cards.append({
135
+ "title": "Experimental Programme",
136
+ "front": exp_text[:300],
137
+ "back": "Conditionally-gated 3–5 step validation pathway derived from the evidence pack via the Da Vinci routing algorithm.",
138
+ "score": max(score1 - 4, 80),
139
+ "novelty": "High",
140
+ "agent": "Hypothesis Composer",
141
+ })
142
+ else:
143
+ cards.append(CANDIDATES_FALLBACK[1])
144
+
145
+ # ── Card 3: Translation Lens ──────────────────────────────────────────
146
+ trans_text = ""
147
+ m_trans = re.search(
148
+ r'TRANSLATION LENS\s*[-━═]+\s*(.*?)(?:━|═|CRITIQUE|OUTPUT|$)',
149
+ final_card, re.DOTALL | re.IGNORECASE
150
+ )
151
+ if m_trans:
152
+ trans_text = m_trans.group(1).strip()[:300]
153
+
154
+ if trans_text:
155
+ # Pull adversarial critique score for this card
156
+ adv_score = max(score1 - 8, 75)
157
+ m_feas = re.search(r'Feasibility[:\s]+(\d+)', final_card)
158
+ if m_feas:
159
+ adv_score = int(m_feas.group(1))
160
+ cards.append({
161
+ "title": "Translation Lens",
162
+ "front": trans_text[:300],
163
+ "back": "IP white space, manufacturability constraints, and commercial hook surface by the 6-agent debate pipeline.",
164
+ "score": adv_score,
165
+ "novelty": "Medium-High",
166
+ "agent": "Adversarial Reviewer",
167
+ })
168
+ else:
169
+ cards.append(CANDIDATES_FALLBACK[2])
170
+
171
+ return cards
172
+
173
  JOURNALS = [
174
  {"name": "Nature", "url": "https://www.nature.com/search", "desc": "Flagship multidisciplinary research journal."},
175
  {"name": "Science", "url": "https://www.science.org/search", "desc": "High-impact science journal and family."},
 
240
 
241
 
242
  def build_cards_html(cards: list) -> str:
243
+ """
244
+ Premium flippable innovation cards.
245
+ - Fixed height, no scroll
246
+ - Gold score ring, subtle gradient front face
247
+ - Back face: dark ink with ivory text for contrast
248
+ - Hover triggers 3D flip
249
+ """
250
  items = []
251
  for c in cards:
252
+ score = c.get("score", 0)
253
+ # Score ring colour: gold β‰₯80, teal 60-79, muted <60
254
+ if score >= 80:
255
+ ring_color = "#ff7a1a"
256
+ ring_bg = "rgba(255,122,26,0.10)"
257
+ elif score >= 60:
258
+ ring_color = "#17b8a6"
259
+ ring_bg = "rgba(23,184,166,0.10)"
260
+ else:
261
+ ring_color = "#9b9b9b"
262
+ ring_bg = "rgba(0,0,0,0.06)"
263
+
264
+ # Truncate to prevent overflow
265
+ front_text = (c.get("front") or "")[:220]
266
+ back_text = (c.get("back") or "")[:220]
267
+ title = (c.get("title") or "")[:55]
268
+ agent = (c.get("agent") or "")[:28].upper()
269
+ novelty = c.get("novelty", "")
270
+
271
  items.append(f"""
272
+ <article class="icard" tabindex="0" role="button" aria-label="Innovation card: {html_lib.escape(title)}">
273
+ <div class="icard-inner">
274
+
275
+ <!-- FRONT -->
276
+ <div class="icard-face icard-front">
277
+ <div class="icard-eyebrow">
278
+ <span class="icard-agent">{html_lib.escape(agent)}</span>
279
+ <div class="icard-score-ring" style="--rc:{ring_color};--rb:{ring_bg};">
280
+ <span class="icard-score-num">{score}</span>
281
+ </div>
282
+ </div>
283
+ <h3 class="icard-title">{html_lib.escape(title)}</h3>
284
+ <p class="icard-body">{html_lib.escape(front_text)}{"…" if len(c.get("front","")) > 220 else ""}</p>
285
+ <div class="icard-footer">
286
+ <div class="icard-novelty-row">
287
+ <span class="icard-novelty-label">Novelty</span>
288
+ <span class="icard-novelty-val">{html_lib.escape(novelty)}</span>
289
+ </div>
290
+ <span class="icard-hint">Hover to flip ↻</span>
291
  </div>
 
 
 
 
292
  </div>
293
+
294
+ <!-- BACK -->
295
+ <div class="icard-face icard-back">
296
+ <div class="icard-eyebrow">
297
+ <span class="icard-agent icard-agent-back">Insight Path</span>
298
+ <div class="icard-score-ring icard-score-ring-back" style="--rc:#fffdf7;--rb:rgba(255,253,247,0.12);">
299
+ <span class="icard-score-num icard-score-num-back">{score}</span>
300
+ </div>
301
+ </div>
302
+ <h3 class="icard-title icard-title-back">{html_lib.escape(title)}</h3>
303
+ <p class="icard-body icard-body-back">{html_lib.escape(back_text)}{"…" if len(c.get("back","")) > 220 else ""}</p>
304
+ <div class="icard-footer">
305
+ <div class="icard-novelty-row">
306
+ <span class="icard-novelty-label icard-novelty-label-back">Agent</span>
307
+ <span class="icard-novelty-val icard-novelty-val-back">{html_lib.escape(c.get("agent",""))}</span>
308
+ </div>
309
+ <span class="icard-hint icard-hint-back">↻ Flip back</span>
310
  </div>
 
 
 
 
311
  </div>
312
+
313
  </div>
314
  </article>""")
315
+
316
  return (
317
+ '<div class="icard-grid">'
318
+ + "".join(items)
319
+ + "</div>"
320
  )
321
 
322
 
 
650
  .agent-head span { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
651
  .agent-copy { padding: 0 14px 16px 66px; }
652
  .agent-copy p { margin: 0; color: #2d2d2d; font-size: .93rem; line-height: 1.6; }
653
+ /* ── Premium Innovation Cards ─────────────────────────────────────────── */
654
+ /* Grid wrapper β€” no panel wrapper needed, cards are self-contained */
655
+ .icard-grid {
656
+ display: grid;
657
+ grid-template-columns: repeat(3, minmax(0, 1fr));
658
+ gap: 20px;
659
+ padding: 4px 2px;
660
+ }
661
+ .icard {
662
+ height: 300px;
663
+ perspective: 1600px;
664
+ cursor: pointer;
665
+ outline: none;
666
+ }
667
+ .icard-inner {
668
+ position: relative;
669
+ width: 100%;
670
+ height: 100%;
671
+ transform-style: preserve-3d;
672
+ transition: transform 0.75s cubic-bezier(0.23, 1, 0.32, 1);
673
+ border-radius: 20px;
674
+ }
675
+ .icard:hover .icard-inner,
676
+ .icard:focus .icard-inner,
677
+ .icard:focus-within .icard-inner {
678
+ transform: rotateY(180deg);
679
+ }
680
+ /* Shared face styles */
681
+ .icard-face {
682
+ position: absolute;
683
+ inset: 0;
684
+ border-radius: 20px;
685
+ padding: 22px 22px 18px;
686
+ backface-visibility: hidden;
687
+ display: flex;
688
+ flex-direction: column;
689
+ gap: 12px;
690
+ overflow: hidden;
691
+ }
692
+ /* FRONT β€” clean white with subtle top accent */
693
+ .icard-front {
694
+ background: #ffffff;
695
+ border: 1px solid rgba(0,0,0,0.09);
696
+ box-shadow: 0 4px 24px rgba(0,0,0,0.07), 0 1px 3px rgba(0,0,0,0.05);
697
+ }
698
+ .icard-front::before {
699
+ content: '';
700
+ position: absolute;
701
+ top: 0; left: 0; right: 0;
702
+ height: 3px;
703
+ border-radius: 20px 20px 0 0;
704
+ background: linear-gradient(90deg, #ff7a1a, #17b8a6);
705
+ }
706
+ /* BACK β€” deep ink */
707
+ .icard-back {
708
+ background: linear-gradient(145deg, #1a1a2e 0%, #16213e 60%, #0f3460 100%);
709
+ border: 1px solid rgba(255,255,255,0.08);
710
+ box-shadow: 0 4px 24px rgba(0,0,0,0.28);
711
+ transform: rotateY(180deg);
712
+ }
713
+ .icard-back::before {
714
+ content: '';
715
+ position: absolute;
716
+ top: 0; left: 0; right: 0;
717
+ height: 3px;
718
+ border-radius: 20px 20px 0 0;
719
+ background: linear-gradient(90deg, #ff7a1a, #628dff);
720
+ }
721
+ /* Eyebrow row: agent chip + score ring */
722
+ .icard-eyebrow {
723
+ display: flex;
724
+ align-items: center;
725
+ justify-content: space-between;
726
+ gap: 8px;
727
+ }
728
+ .icard-agent {
729
+ font-size: 0.62rem;
730
+ font-weight: 700;
731
+ letter-spacing: 0.13em;
732
+ text-transform: uppercase;
733
+ color: #17b8a6;
734
+ background: rgba(23,184,166,0.08);
735
+ border: 1px solid rgba(23,184,166,0.22);
736
+ border-radius: 999px;
737
+ padding: 5px 10px;
738
+ white-space: nowrap;
739
+ }
740
+ .icard-agent-back {
741
+ color: rgba(255,253,247,0.7);
742
+ background: rgba(255,253,247,0.08);
743
+ border-color: rgba(255,253,247,0.15);
744
+ }
745
+ .icard-score-ring {
746
+ width: 42px;
747
+ height: 42px;
748
+ border-radius: 50%;
749
+ background: var(--rb);
750
+ border: 2px solid var(--rc);
751
+ display: flex;
752
+ align-items: center;
753
+ justify-content: center;
754
+ flex-shrink: 0;
755
+ }
756
+ .icard-score-num {
757
+ font-size: 0.82rem;
758
+ font-weight: 800;
759
+ color: var(--rc, #ff7a1a);
760
+ line-height: 1;
761
+ }
762
+ .icard-score-ring-back { border-color: rgba(255,253,247,0.35); }
763
+ .icard-score-num-back { color: rgba(255,253,247,0.9); }
764
+ /* Title */
765
+ .icard-title {
766
+ margin: 0;
767
+ font-size: 1.05rem;
768
+ font-weight: 700;
769
+ color: #111111;
770
+ line-height: 1.3;
771
+ letter-spacing: -0.01em;
772
+ }
773
+ .icard-title-back { color: #fffdf7; }
774
+ /* Body copy */
775
+ .icard-body {
776
+ margin: 0;
777
+ font-size: 0.875rem;
778
+ color: #3a3a3a;
779
+ line-height: 1.62;
780
+ flex: 1;
781
+ overflow: hidden;
782
+ display: -webkit-box;
783
+ -webkit-line-clamp: 5;
784
+ -webkit-box-orient: vertical;
785
+ }
786
+ .icard-body-back { color: rgba(255,253,247,0.82); }
787
+ /* Footer */
788
+ .icard-footer {
789
+ display: flex;
790
+ align-items: center;
791
+ justify-content: space-between;
792
+ gap: 8px;
793
+ margin-top: auto;
794
+ padding-top: 10px;
795
+ border-top: 1px solid rgba(0,0,0,0.06);
796
+ }
797
+ .icard-back .icard-footer { border-top-color: rgba(255,255,255,0.10); }
798
+ .icard-novelty-row { display: flex; align-items: center; gap: 6px; }
799
+ .icard-novelty-label {
800
+ font-size: 0.70rem;
801
+ text-transform: uppercase;
802
+ letter-spacing: 0.1em;
803
+ color: #888;
804
+ }
805
+ .icard-novelty-label-back { color: rgba(255,253,247,0.45); }
806
+ .icard-novelty-val {
807
+ font-size: 0.78rem;
808
+ font-weight: 700;
809
+ color: #ff7a1a;
810
+ }
811
+ .icard-novelty-val-back { color: rgba(255,253,247,0.9); }
812
+ .icard-hint {
813
+ font-size: 0.66rem;
814
+ color: #bbb;
815
+ letter-spacing: 0.06em;
816
+ white-space: nowrap;
817
+ }
818
+ .icard-hint-back { color: rgba(255,253,247,0.35); }
819
+
820
+ /* ── Route Graph β€” make nodes/labels bigger & more readable ───────────── */
821
+ /* Wrapper that gives the graph more vertical room */
822
+ .rg-wrapper {
823
+ min-height: 560px;
824
+ }
825
+ /* Override font sizes inside the SVG-containing div via CSS font scaling */
826
+ .rg-wrapper text,
827
+ .rg-wrapper svg text {
828
+ font-size: 115% !important;
829
+ }
830
+ /* The graph container div rendered by route_graph.py uses inline styles.
831
+ We inject extra size via the gradio HTML wrapper below. */
832
  /* self-learning graph */
833
  .brain-shell { padding: 18px; }
834
  .brain-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; margin-bottom: 10px; }
 
884
  footer { display: none !important; }
885
  @keyframes pulseEdge { to { stroke-dashoffset: -40; } }
886
  @media (max-width: 1180px) {
887
+ .model-switcher, .icard-grid, .papers-grid, .journal-grid { grid-template-columns: 1fr; }
888
  .brain-svg { height: 460px; }
889
+ .icard { height: auto; min-height: 300px; }
890
  }
891
  """
892
 
 
998
 
999
  # ── Right column ──────────────────────────────────────
1000
  with gr.Column(scale=3):
1001
+ # v5 animated route graph β€” wrapped in rg-wrapper for bigger rendering
1002
+ connectome = gr.HTML(
1003
+ '<div class="rg-wrapper">' + route_graph_placeholder() + '</div>'
 
 
1004
  )
1005
+ cards = gr.HTML('<div class="icard-grid"></div>')
1006
 
1007
  # ── Below columns (v4 exact) ──────────────────────────────
1008
  output = gr.Markdown("# Discovery Output\n\nAwaiting query.")
 
1073
  <p>Enter a query to activate the 6-agent discovery stack.</p>
1074
  </div></div></div>""",
1075
  '<div class="panel" style="padding:18px"><div class="timeline"></div></div>',
1076
+ '<div class="rg-wrapper">' + route_graph_placeholder() + '</div>',
1077
+ '<div class="icard-grid"></div>',
1078
  "# Discovery Output\n\nAwaiting query.",
1079
  )
1080
 
 
1124
 
1125
  # ── v5 route graph in the right column ───────────────────────
1126
  try:
1127
+ rg_inner = (
1128
  build_route_graph_html(route_result)
1129
  if route_result else route_graph_placeholder()
1130
  )
1131
  except Exception:
1132
+ rg_inner = route_graph_placeholder()
1133
+ connectome_html = '<div class="rg-wrapper">' + rg_inner + '</div>'
1134
 
1135
+ # ── Dynamic cards parsed from pipeline output ─────────────────
1136
+ cards_html = build_cards_html(
1137
+ _parse_cards_from_pipeline(dvnc_card, agent_log_data)
1138
+ )
1139
 
1140
  # ── v4 output Markdown ────────────────────────────────────────
1141
  metrics_text = ""