Adi12345 commited on
Commit
4e74ccb
Β·
verified Β·
1 Parent(s): e65b64a

Create app.py

Browse files
Files changed (1) hide show
  1. dvnc_ai_v2_hf/app.py +1447 -0
dvnc_ai_v2_hf/app.py ADDED
@@ -0,0 +1,1447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DVNC.AI β€” app.py
3
+ Production Gradio entrypoint for the DVNC discovery workspace.
4
+ Aligned to the current dvnc_ai_v2_hf.self_learning_graph interface.
5
+ """
6
+
7
+ # ── Standard library ────────────────────────────────────────────────────────
8
+ import random
9
+ import re
10
+ from typing import Dict, List, Optional
11
+
12
+ # ── Third-party ─────────────────────────────────────────────────────────────
13
+ import gradio as gr
14
+
15
+ # ── Internal modules ────────────────────────────────────────────────────────
16
+ from dvnc_ai_v2_hf.agent_route_cards import build_agent_route_cards_html
17
+ from dvnc_ai_v2_hf.discovery_app_bridge import (
18
+ get_default_route_state,
19
+ get_discovery_css,
20
+ get_initial_discovery_timeline_html,
21
+ )
22
+ from dvnc_ai_v2_hf.dvnc_ui_layout import get_dvnc_layout_css
23
+ from dvnc_ai_v2_hf import self_learning_graph as slg
24
+ from dvnc_ai_v2_hf.self_learning_graph import (
25
+ DEFAULT_SOURCES,
26
+ SEARCH_MODES,
27
+ SOURCE_OPTIONS,
28
+ build_journal_html,
29
+ build_learning_graph_html,
30
+ ingest_selected_papers,
31
+ parse_uploaded_pdf,
32
+ render_parse_result,
33
+ run_paper_discovery,
34
+ safe_text,
35
+ )
36
+
37
+ # ── Constants ───────────────────────────────────────────────────────────────
38
+ MODELS = [
39
+ {
40
+ "name": "DVNC Sovereign",
41
+ "tag": "flagship",
42
+ "desc": "Maximum depth orchestration for frontier discovery",
43
+ },
44
+ {
45
+ "name": "DVNC Atlas",
46
+ "tag": "research",
47
+ "desc": "Balanced reasoning, graph traversal, and synthesis",
48
+ },
49
+ {
50
+ "name": "DVNC Curie",
51
+ "tag": "lab",
52
+ "desc": "Experimental hypothesis generation for anomalous signals",
53
+ },
54
+ ]
55
+
56
+ AGENTS = [
57
+ "Query Interpreter",
58
+ "Graph Divergence Mapper",
59
+ "Evidence Harvester",
60
+ "Analogy Engine",
61
+ "Hypothesis Composer",
62
+ "Adversarial Critic",
63
+ "Experimental Program Designer",
64
+ ]
65
+
66
+ NODES = [
67
+ {"id": "seed", "label": "Seed Query", "group": "core", "x": 10, "y": 0, "z": 0},
68
+ {"id": "bio", "label": "Biomaterials", "group": "domain", "x": 24, "y": 12, "z": -8},
69
+ {"id": "card", "label": "Cardiac Repair", "group": "domain", "x": 38, "y": 3, "z": 14},
70
+ {"id": "nano", "label": "Nanostructure", "group": "bridge", "x": 24, "y": -18, "z": 16},
71
+ {"id": "selfasm", "label": "Self-Assembly", "group": "bridge", "x": 40, "y": -16, "z": -16},
72
+ {"id": "electro", "label": "Electro-signalling", "group": "mechanism", "x": 58, "y": 10, "z": -10},
73
+ {"id": "immune", "label": "Immune Modulation", "group": "mechanism", "x": 64, "y": -8, "z": 10},
74
+ {"id": "trial", "label": "Validation Path", "group": "outcome", "x": 80, "y": 0, "z": 0},
75
+ {"id": "alt1", "label": "Piezoelectric Scaffold", "group": "candidate", "x": 56, "y": 26, "z": 14},
76
+ {"id": "alt2", "label": "Peptide Mesh", "group": "candidate", "x": 54, "y": -27, "z": -14},
77
+ ]
78
+
79
+ EDGES = [
80
+ ("seed", "bio"),
81
+ ("seed", "nano"),
82
+ ("bio", "card"),
83
+ ("nano", "selfasm"),
84
+ ("selfasm", "electro"),
85
+ ("card", "immune"),
86
+ ("electro", "trial"),
87
+ ("immune", "trial"),
88
+ ("card", "alt1"),
89
+ ("selfasm", "alt2"),
90
+ ("alt1", "trial"),
91
+ ("alt2", "trial"),
92
+ ]
93
+
94
+ DEFAULT_PATH = ["seed", "nano", "selfasm", "electro", "trial"]
95
+
96
+ CANDIDATES = [
97
+ {
98
+ "title": "Piezoelectric Scaffold Cascade",
99
+ "front": "Use mechano-electric scaffolds to convert cardiac strain into micro-current signalling.",
100
+ "back": "Discovery path: anomalous healing signal -> piezoelectric analog -> ion-channel entrainment -> tissue regeneration. Risk: power density and fibrosis coupling.",
101
+ "score": 92,
102
+ "novelty": "High",
103
+ "agent": "Hypothesis Composer",
104
+ },
105
+ {
106
+ "title": "Peptide Self-Assembly Mesh",
107
+ "front": "Deploy dynamic peptide meshes that self-assemble around damaged myocardium and guide repair.",
108
+ "back": "Discovery path: self-assembly -> local immune choreography -> regenerative substrate formation. Risk: degradation timing and targeting specificity.",
109
+ "score": 88,
110
+ "novelty": "High",
111
+ "agent": "Analogy Engine",
112
+ },
113
+ {
114
+ "title": "Immune-Tuned Conductive Hydrogel",
115
+ "front": "Blend conductivity with macrophage-state modulation to reduce scarring and restore conduction.",
116
+ "back": "Discovery path: inflammation mismatch -> conductive medium -> macrophage polarization -> synchronized healing. Risk: persistence and biocompatibility.",
117
+ "score": 85,
118
+ "novelty": "Medium-High",
119
+ "agent": "Adversarial Critic",
120
+ },
121
+ ]
122
+
123
+ ACADEMIC_INSIGHTS = [
124
+ {
125
+ "hypothesis": "Implementation of mechano-electric scaffolds to transduce cardiac strain into localized micro-current signalling for myocardial regeneration.",
126
+ "metrics": {
127
+ "Novelty": 92,
128
+ "Mechanistic clarity": 85,
129
+ "Experimental tractability": 78,
130
+ "Cross-domain distance": 94,
131
+ },
132
+ "outline": (
133
+ "1. Synthesize candidate piezoelectric biomaterial scaffolds with tunable strain-electric coupling.\n"
134
+ "2. Evaluate in vitro electromechanical transduction and subsequent ion-channel entrainment.\n"
135
+ "3. Conduct in vivo comparative models to assess regenerative efficacy against gold-standard substrates.\n"
136
+ "4. Rigorously validate to exclude pathological fibrosis and power-density toxicity."
137
+ ),
138
+ "path": ["seed", "bio", "card", "alt1", "trial"],
139
+ },
140
+ {
141
+ "hypothesis": "Deployment of dynamic peptide networks that self-assemble post-infarction to orchestrate local immunological responses and guide substrate regeneration.",
142
+ "metrics": {
143
+ "Novelty": 88,
144
+ "Mechanistic clarity": 82,
145
+ "Experimental tractability": 86,
146
+ "Cross-domain distance": 85,
147
+ },
148
+ "outline": (
149
+ "1. Formulate peptide sequences programmed for triggered in situ self-assembly within the myocardial infarct zone.\n"
150
+ "2. Quantify macrophage polarization and local immune choreography post-deployment.\n"
151
+ "3. Map the temporospatial degradation profile against de novo tissue formation.\n"
152
+ "4. Falsify against off-target aggregation and delayed clearance risks."
153
+ ),
154
+ "path": ["seed", "nano", "selfasm", "alt2", "trial"],
155
+ },
156
+ {
157
+ "hypothesis": "Integration of conductive hydrogels with immunomodulatory properties to simultaneously bridge electrical uncoupling and mitigate adverse fibrotic scarring.",
158
+ "metrics": {
159
+ "Novelty": 85,
160
+ "Mechanistic clarity": 90,
161
+ "Experimental tractability": 88,
162
+ "Cross-domain distance": 79,
163
+ },
164
+ "outline": (
165
+ "1. Fabricate biocompatible hydrogels featuring precisely tuned electrical conductivity and immunomodulatory motifs.\n"
166
+ "2. Monitor electrophysiological synchronization across the scaffold-tissue interface.\n"
167
+ "3. Assess macrophage state transitions and suppression of adverse fibrotic remodelling.\n"
168
+ "4. Validate long-term persistence, hemocompatibility, and mechanical integration."
169
+ ),
170
+ "path": ["seed", "bio", "card", "immune", "trial"],
171
+ },
172
+ ]
173
+
174
+ PDF_PARSER_OPTIONS = getattr(slg, "PDF_PARSERS", ["pymupdf"])
175
+
176
+
177
+ # ── Utility helpers ─────────────────────────────────────────────────────────
178
+ def norm_text(x: Optional[str]) -> str:
179
+ return re.sub(r"\s+", " ", (x or "")).strip()
180
+
181
+
182
+ def build_metrics_markdown(metrics: Dict[str, int]) -> str:
183
+ return "\n".join(f"- {key}: {value}/100" for key, value in metrics.items())
184
+
185
+
186
+ # ── HTML builders ────────────────────────────────────────────────────────────
187
+ def build_connectome_html(path_ids: List[str]) -> str:
188
+ active = set(path_ids)
189
+ node_map = {n["id"]: n for n in NODES}
190
+
191
+ path_pairs = {
192
+ pair
193
+ for i in range(len(path_ids) - 1)
194
+ for pair in [(path_ids[i], path_ids[i + 1]), (path_ids[i + 1], path_ids[i])]
195
+ }
196
+
197
+ base_lines, active_lines, circles, labels = [], [], [], []
198
+
199
+ for a, b in EDGES:
200
+ na, nb = node_map[a], node_map[b]
201
+ x1, y1 = na["x"] * 8 + 80, na["y"] * 6 + 280
202
+ x2, y2 = nb["x"] * 8 + 80, nb["y"] * 6 + 280
203
+ base_lines.append(
204
+ f'<line class="edge" x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" />'
205
+ )
206
+ if (a, b) in path_pairs:
207
+ active_lines.append(
208
+ f'<line class="edge active" x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" />'
209
+ )
210
+
211
+ for n in NODES:
212
+ cx, cy = n["x"] * 8 + 80, n["y"] * 6 + 280
213
+ is_active = n["id"] in active
214
+ state = "chosen" if is_active else "idle"
215
+ halo_cls = "halo active" if is_active else "halo"
216
+ lbl_cls = "label active" if is_active else "label"
217
+ radius = 18 if is_active else 13
218
+ halo_r = 30 if is_active else 0
219
+
220
+ circles.append(
221
+ f'<g class="node-wrap">'
222
+ f'<circle class="{halo_cls}" cx="{cx:.1f}" cy="{cy:.1f}" r="{halo_r}" />'
223
+ f'<circle class="node {n["group"]} {state}" cx="{cx:.1f}" cy="{cy:.1f}" r="{radius}" />'
224
+ f"</g>"
225
+ )
226
+ labels.append(
227
+ f'<text class="{lbl_cls}" x="{cx + 18:.1f}" y="{cy - 16:.1f}">{safe_text(n["label"])}</text>'
228
+ )
229
+
230
+ return f"""
231
+ <div class="panel brain-shell">
232
+ <div class="brain-header">
233
+ <div>
234
+ <p class="eyebrow">Connectome</p>
235
+ <h3>3D Connectome</h3>
236
+ </div>
237
+ <div class="brain-legend">
238
+ <span><i class="dot dot-live"></i> lit path</span>
239
+ <span><i class="dot dot-chosen"></i> chosen node</span>
240
+ <span><i class="dot dot-idle"></i> available node</span>
241
+ </div>
242
+ </div>
243
+ <div class="brain-stage">
244
+ <svg viewBox="0 0 780 560" class="brain-svg" role="img" aria-label="DVNC 3D connectome visualisation">
245
+ {''.join(base_lines)}
246
+ {''.join(active_lines)}
247
+ {''.join(circles)}
248
+ {''.join(labels)}
249
+ </svg>
250
+ </div>
251
+ </div>
252
+ """
253
+
254
+
255
+ def build_cards_html(cards: List[Dict]) -> str:
256
+ items = []
257
+ for i, c in enumerate(cards):
258
+ items.append(
259
+ f"""
260
+ <article class="candidate-card" tabindex="0">
261
+ <div class="candidate-card-inner">
262
+ <div class="candidate-face candidate-front">
263
+ <div class="candidate-top">
264
+ <span class="chip">{safe_text(c["agent"])}</span>
265
+ <span class="score">{safe_text(c["score"])}</span>
266
+ </div>
267
+ <h4>{safe_text(c["title"])}</h4>
268
+ <p>{safe_text(c["front"])}</p>
269
+ <div class="meta-row"><span>Novelty</span><strong>{safe_text(c["novelty"])}</strong></div>
270
+ <button class="mini" type="button" onclick="triggerRouteSwap('{i}')">Use as main insight</button>
271
+ </div>
272
+ <div class="candidate-face candidate-back">
273
+ <div class="candidate-top">
274
+ <span class="chip alt">Alternative path</span>
275
+ <span class="score">{safe_text(c["score"])}</span>
276
+ </div>
277
+ <h4>{safe_text(c["title"])}</h4>
278
+ <p>{safe_text(c["back"])}</p>
279
+ <div class="meta-row"><span>Swap into route</span><strong>Enabled</strong></div>
280
+ <button class="mini" type="button" onclick="triggerRouteSwap('{i}')">Use as main insight</button>
281
+ </div>
282
+ </div>
283
+ </article>
284
+ """
285
+ )
286
+ return '<div class="panel" style="padding:20px;"><div class="candidate-grid">' + "".join(items) + "</div></div>"
287
+
288
+
289
+ def build_chat_html(query: str, result: Dict) -> str:
290
+ return f"""
291
+ <div class="panel chat-panel">
292
+ <div class="chat-thread">
293
+ <div class="bubble bubble-user">
294
+ <span class="role">You</span>
295
+ <p>{safe_text(query)}</p>
296
+ </div>
297
+ <div class="bubble bubble-ai">
298
+ <span class="role">DVNC Sovereign</span>
299
+ <p>{safe_text(result["summary"])}</p>
300
+ </div>
301
+ <div class="bubble bubble-system">
302
+ <span class="role">Discovery Signal</span>
303
+ <p><strong>Primary hypothesis:</strong> {safe_text(result["primary_hypothesis"])}</p>
304
+ </div>
305
+ </div>
306
+ </div>
307
+ """
308
+
309
+
310
+ def build_models_html(selected: str) -> str:
311
+ items = []
312
+ for m in MODELS:
313
+ active = "active" if m["name"] == selected else ""
314
+ items.append(
315
+ f"""
316
+ <div class="model-pill {active}">
317
+ <span class="model-name">{safe_text(m["name"])}</span>
318
+ <span class="model-tag">{safe_text(m["tag"])}</span>
319
+ <small>{safe_text(m["desc"])}</small>
320
+ </div>
321
+ """
322
+ )
323
+ return '<div class="panel" style="padding:18px;"><div class="model-switcher">' + "".join(items) + "</div></div>"
324
+
325
+
326
+ # ── Discovery logic ──────────────────────────────────────────────────────────
327
+ def run_discovery(query: str, model_name: str):
328
+ query_text = norm_text(query)
329
+ model_text = norm_text(model_name) or "DVNC Sovereign"
330
+
331
+ random.seed(len(query_text) + len(model_text))
332
+
333
+ if "curie" in query_text.lower() or "einstein" in query_text.lower():
334
+ primary = "Map the anomaly first, then force a distant analogy before composing the experimental programme."
335
+ path = ["seed", "bio", "card", "immune", "trial"]
336
+ else:
337
+ primary = (
338
+ "Utilization of a self-assembling conductive scaffold to transduce mechanical strain "
339
+ "into localized regenerative signalling pathways."
340
+ )
341
+ path = DEFAULT_PATH
342
+
343
+ summaries = [
344
+ "Normalises the user prompt into a graph-searchable seed and isolates the tension inside the question.",
345
+ "Finds remote conceptual bridges instead of staying near the starting domain cluster.",
346
+ "Pulls evidence packets and conflict signals required for grounded hypothesis formation.",
347
+ "Generates cross-domain analogies with a bias toward mechanism transfer rather than keyword similarity.",
348
+ "Composes the lead hypothesis and two structurally different variants.",
349
+ "Attacks weak assumptions, hidden confounders, and feasibility gaps.",
350
+ "Produces a staged validation plan with measurable falsification criteria.",
351
+ ]
352
+ tags = ["input", "graph", "evidence", "analogy", "compose", "critique", "experiment"]
353
+
354
+ reasoning = [
355
+ {"step": i + 1, "agent": AGENTS[i], "tag": tags[i], "summary": summaries[i]}
356
+ for i in range(7)
357
+ ]
358
+
359
+ result = {
360
+ "summary": (
361
+ "A deeper route was chosen through the connectome, with live alternatives preserved "
362
+ "as swappable cards so the reasoning path can be inspected rather than hidden."
363
+ ),
364
+ "primary_hypothesis": primary,
365
+ "reasoning": reasoning,
366
+ "cards": CANDIDATES,
367
+ "path": path,
368
+ "metrics": {
369
+ "Novelty": 93,
370
+ "Mechanistic clarity": 89,
371
+ "Experimental tractability": 82,
372
+ "Cross-domain distance": 91,
373
+ },
374
+ }
375
+
376
+ chat_html = build_chat_html(query_text, result)
377
+ connectome_html = build_connectome_html(path)
378
+ timeline_html = build_agent_route_cards_html(reasoning)
379
+ metrics_md = build_metrics_markdown(result["metrics"])
380
+ cards_html = build_cards_html(CANDIDATES)
381
+ route_state = get_default_route_state()
382
+
383
+ hypothesis_md = (
384
+ "# Discovery Output\n\n"
385
+ f"**Model:** {model_text}\n\n"
386
+ f"**Primary hypothesis:** {result['primary_hypothesis']}\n\n"
387
+ "## Scoring\n"
388
+ f"{metrics_md}\n\n"
389
+ "## Experimental outline\n"
390
+ "1. Construct the candidate material or protocol.\n"
391
+ "2. Test mechanistic signal expression under controlled conditions.\n"
392
+ "3. Compare against baseline and nearest-neighbour alternatives.\n"
393
+ "4. Falsify using the adversarial risk criteria surfaced in the reasoning path.\n"
394
+ )
395
+
396
+ return (
397
+ chat_html,
398
+ connectome_html,
399
+ timeline_html,
400
+ cards_html,
401
+ hypothesis_md,
402
+ build_models_html(model_text),
403
+ route_state,
404
+ )
405
+
406
+
407
+ def apply_route_swap(query: str, model_name: str, route_swap_payload: str, route_state):
408
+ try:
409
+ idx = int((route_swap_payload or "").strip())
410
+ except Exception:
411
+ idx = 0
412
+
413
+ if not (0 <= idx < len(ACADEMIC_INSIGHTS)):
414
+ idx = 0
415
+
416
+ academic = ACADEMIC_INSIGHTS[idx]
417
+ connectome_html = build_connectome_html(academic["path"])
418
+
419
+ result = {
420
+ "summary": (
421
+ "Main insight formally adopted. The connectome pathway and validation protocol "
422
+ "have been realigned to the selected candidate methodology."
423
+ ),
424
+ "primary_hypothesis": academic["hypothesis"],
425
+ }
426
+ chat_html = build_chat_html(norm_text(query), result)
427
+ metrics_md = build_metrics_markdown(academic["metrics"])
428
+
429
+ hypothesis_md = (
430
+ "# Discovery Output\n\n"
431
+ f"**Model:** {norm_text(model_name) or 'DVNC Sovereign'}\n\n"
432
+ f"**Primary hypothesis:** {academic['hypothesis']}\n\n"
433
+ "## Scoring\n"
434
+ f"{metrics_md}\n\n"
435
+ "## Experimental outline\n"
436
+ f"{academic['outline']}\n"
437
+ )
438
+
439
+ return chat_html, connectome_html, gr.update(), hypothesis_md, route_state
440
+
441
+
442
+ # ── Example loaders ──────────────────────────────────────────────────────────
443
+ def load_example() -> str:
444
+ return (
445
+ "How could a self-assembling conductive biomaterial improve cardiac tissue "
446
+ "regeneration by converting mechanical strain into repair signalling?"
447
+ )
448
+
449
+
450
+ def load_paper_topic() -> str:
451
+ return "self-assembling conductive biomaterials for cardiac repair"
452
+
453
+
454
+ # ── CSS / HEAD ───────────────────────────────────────────────────────────────
455
+ BASE_CSS = r"""
456
+ :root {
457
+ --bg: #ffffff;
458
+ --panel: #ffffff;
459
+ --line: rgba(0,0,0,.12);
460
+ --text: #111111;
461
+ --muted: #5b5b5b;
462
+ --soft: rgba(0,0,0,.62);
463
+ --gold: #ff6600;
464
+ --teal: #17b8a6;
465
+ --blue: #628dff;
466
+ --chosen: #ff7a1a;
467
+ --idle: #b8d8ff;
468
+ --idle-stroke: #5e8fe6;
469
+ --query-node: #ffd8b3;
470
+ --paper-node: #d7f6f2;
471
+ --upload-node: #e7defe;
472
+ --shadow: 0 16px 40px rgba(0,0,0,.12);
473
+ }
474
+
475
+ html, body, .gradio-container {
476
+ background: #ffffff !important;
477
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
478
+ }
479
+
480
+ .gradio-container {
481
+ max-width: 1640px !important;
482
+ padding: 20px !important;
483
+ }
484
+
485
+ #dvnc-shell {
486
+ border: 1px solid var(--line);
487
+ border-radius: 28px;
488
+ overflow: hidden;
489
+ background: #ffffff;
490
+ box-shadow: var(--shadow);
491
+ padding: 20px 22px 22px;
492
+ }
493
+
494
+ .hero-bar {
495
+ display: flex;
496
+ justify-content: space-between;
497
+ align-items: center;
498
+ gap: 16px;
499
+ padding-bottom: 12px;
500
+ border-bottom: 1px solid rgba(0,0,0,.06);
501
+ margin-bottom: 16px;
502
+ }
503
+
504
+ .brand {
505
+ display: flex;
506
+ align-items: center;
507
+ gap: 14px;
508
+ }
509
+
510
+ .logo {
511
+ width: 42px;
512
+ height: 42px;
513
+ border-radius: 14px;
514
+ display: grid;
515
+ place-items: center;
516
+ color: var(--gold);
517
+ background: linear-gradient(135deg, rgba(255,122,26,.12), rgba(23,184,166,.10));
518
+ border: 1px solid rgba(0,0,0,.08);
519
+ }
520
+
521
+ .logo svg {
522
+ width: 24px;
523
+ height: 24px;
524
+ }
525
+
526
+ .brand h1 {
527
+ font-size: 1.05rem;
528
+ margin: 0;
529
+ font-weight: 700;
530
+ letter-spacing: .12em;
531
+ text-transform: uppercase;
532
+ }
533
+
534
+ .brand p {
535
+ margin: 3px 0 0;
536
+ color: var(--muted);
537
+ font-size: .84rem;
538
+ }
539
+
540
+ .status {
541
+ display: flex;
542
+ gap: 10px;
543
+ align-items: center;
544
+ color: var(--soft);
545
+ font-size: .85rem;
546
+ }
547
+
548
+ .status-dot {
549
+ width: 10px;
550
+ height: 10px;
551
+ border-radius: 50%;
552
+ background: var(--teal);
553
+ box-shadow: 0 0 0 6px rgba(23,184,166,.10), 0 0 14px rgba(23,184,166,.25);
554
+ }
555
+
556
+ .panel {
557
+ background: #ffffff;
558
+ border: 1px solid var(--line);
559
+ border-radius: 22px;
560
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.8);
561
+ }
562
+
563
+ .querybox textarea,
564
+ .querybox input {
565
+ background: transparent !important;
566
+ color: var(--text) !important;
567
+ }
568
+
569
+ .querybox,
570
+ .querybox > div {
571
+ background: #ffffff !important;
572
+ border-radius: 18px !important;
573
+ border-color: var(--line) !important;
574
+ }
575
+
576
+ .chat-panel {
577
+ padding: 18px;
578
+ min-height: 280px;
579
+ }
580
+
581
+ .chat-thread {
582
+ display: flex;
583
+ flex-direction: column;
584
+ gap: 14px;
585
+ }
586
+
587
+ .bubble {
588
+ max-width: 88%;
589
+ padding: 16px 18px;
590
+ border-radius: 22px;
591
+ border: 1px solid var(--line);
592
+ }
593
+
594
+ .bubble p {
595
+ margin: 8px 0 0;
596
+ line-height: 1.6;
597
+ font-size: .96rem;
598
+ color: var(--text);
599
+ }
600
+
601
+ .bubble .role {
602
+ font-size: .72rem;
603
+ letter-spacing: .12em;
604
+ text-transform: uppercase;
605
+ color: var(--muted);
606
+ }
607
+
608
+ .bubble-user {
609
+ align-self: flex-end;
610
+ background: linear-gradient(135deg, rgba(98,141,255,.16), rgba(98,141,255,.08));
611
+ }
612
+
613
+ .bubble-ai {
614
+ align-self: flex-start;
615
+ background: #ffffff;
616
+ }
617
+
618
+ .bubble-system {
619
+ align-self: flex-start;
620
+ background: linear-gradient(135deg, rgba(255,122,26,.10), rgba(255,122,26,.04));
621
+ }
622
+
623
+ .model-switcher {
624
+ display: grid;
625
+ grid-template-columns: repeat(3, 1fr);
626
+ gap: 12px;
627
+ }
628
+
629
+ .model-pill {
630
+ padding: 14px;
631
+ border: 1px solid var(--line);
632
+ border-radius: 18px;
633
+ display: flex;
634
+ flex-direction: column;
635
+ gap: 4px;
636
+ min-height: 98px;
637
+ background: #ffffff;
638
+ }
639
+
640
+ .model-pill.active {
641
+ border-color: rgba(255,122,26,.40);
642
+ background: linear-gradient(135deg, rgba(255,122,26,.10), rgba(255,255,255,.96));
643
+ }
644
+
645
+ .model-name {
646
+ font-weight: 650;
647
+ color: var(--text);
648
+ }
649
+
650
+ .model-tag {
651
+ font-size: .76rem;
652
+ text-transform: uppercase;
653
+ letter-spacing: .12em;
654
+ color: var(--gold);
655
+ }
656
+
657
+ .model-pill small {
658
+ color: var(--muted);
659
+ line-height: 1.45;
660
+ }
661
+
662
+ .brain-shell {
663
+ padding: 18px;
664
+ }
665
+
666
+ .brain-header {
667
+ display: flex;
668
+ justify-content: space-between;
669
+ align-items: flex-end;
670
+ gap: 16px;
671
+ margin-bottom: 10px;
672
+ }
673
+
674
+ .eyebrow {
675
+ font-size: .72rem;
676
+ letter-spacing: .16em;
677
+ text-transform: uppercase;
678
+ color: var(--gold);
679
+ margin: 0 0 4px;
680
+ }
681
+
682
+ .brain-header h3 {
683
+ margin: 0;
684
+ font-size: 1.12rem;
685
+ color: var(--text);
686
+ }
687
+
688
+ .brain-legend {
689
+ display: flex;
690
+ gap: 14px;
691
+ color: var(--muted);
692
+ font-size: .8rem;
693
+ flex-wrap: wrap;
694
+ }
695
+
696
+ .dot {
697
+ width: 10px;
698
+ height: 10px;
699
+ display: inline-block;
700
+ border-radius: 50%;
701
+ margin-right: 6px;
702
+ }
703
+
704
+ .dot-live {
705
+ background: var(--chosen);
706
+ box-shadow: 0 0 10px rgba(255,122,26,.35);
707
+ }
708
+
709
+ .dot-chosen {
710
+ background: var(--chosen);
711
+ }
712
+
713
+ .dot-idle {
714
+ background: var(--idle);
715
+ border: 1px solid var(--idle-stroke);
716
+ }
717
+
718
+ .dot-query {
719
+ background: var(--query-node);
720
+ border: 1px solid #de9e58;
721
+ }
722
+
723
+ .dot-paper {
724
+ background: var(--paper-node);
725
+ border: 1px solid #4fb3a5;
726
+ }
727
+
728
+ .dot-upload {
729
+ background: var(--upload-node);
730
+ border: 1px solid #8f73d9;
731
+ }
732
+
733
+ .brain-stage {
734
+ position: relative;
735
+ min-height: 420px;
736
+ overflow: hidden;
737
+ background: linear-gradient(180deg, rgba(250,250,250,1), rgba(255,255,255,1));
738
+ border: 1px solid rgba(0,0,0,.05);
739
+ border-radius: 20px;
740
+ }
741
+
742
+ .brain-svg {
743
+ width: 100%;
744
+ height: 520px;
745
+ display: block;
746
+ }
747
+
748
+ .edge {
749
+ stroke: rgba(0,0,0,.12);
750
+ stroke-width: 2.4;
751
+ }
752
+
753
+ .edge.active {
754
+ stroke: var(--chosen);
755
+ stroke-width: 4.2;
756
+ stroke-linecap: round;
757
+ filter: drop-shadow(0 0 6px rgba(255,122,26,.45));
758
+ stroke-dasharray: 8 12;
759
+ animation: pulseEdge 1.5s linear infinite;
760
+ }
761
+
762
+ .node {
763
+ stroke-width: 2.2;
764
+ transition: all .25s ease;
765
+ }
766
+
767
+ .node.idle {
768
+ fill: var(--idle);
769
+ stroke: var(--idle-stroke);
770
+ }
771
+
772
+ .node.chosen {
773
+ fill: var(--chosen);
774
+ stroke: #ffb16d;
775
+ }
776
+
777
+ .halo {
778
+ fill: none;
779
+ }
780
+
781
+ .halo.active {
782
+ stroke: rgba(255,122,26,.18);
783
+ stroke-width: 12;
784
+ }
785
+
786
+ .label {
787
+ fill: #2c2c2c;
788
+ font-size: 13px;
789
+ font-weight: 500;
790
+ letter-spacing: .01em;
791
+ }
792
+
793
+ .label.active {
794
+ fill: #111111;
795
+ font-weight: 700;
796
+ }
797
+
798
+ .learn-edge {
799
+ stroke: rgba(0,0,0,.18);
800
+ stroke-width: 2.2;
801
+ stroke-linecap: round;
802
+ }
803
+
804
+ .learn-node {
805
+ stroke-width: 2.2;
806
+ }
807
+
808
+ .learn-node.query {
809
+ fill: var(--query-node);
810
+ stroke: #de9e58;
811
+ }
812
+
813
+ .learn-node.paper {
814
+ fill: var(--paper-node);
815
+ stroke: #36a091;
816
+ }
817
+
818
+ .learn-node.upload {
819
+ fill: var(--upload-node);
820
+ stroke: #7e63cb;
821
+ }
822
+
823
+ .learn-label {
824
+ fill: #1e1e1e;
825
+ font-size: 12px;
826
+ font-weight: 600;
827
+ }
828
+
829
+ .learning-empty {
830
+ display: grid;
831
+ place-items: center;
832
+ }
833
+
834
+ .empty-graph-copy {
835
+ text-align: center;
836
+ max-width: 440px;
837
+ padding: 40px 20px;
838
+ }
839
+
840
+ .empty-graph-copy h4 {
841
+ margin: 0 0 10px;
842
+ font-size: 1.05rem;
843
+ }
844
+
845
+ .empty-graph-copy p {
846
+ margin: 0;
847
+ color: var(--muted);
848
+ line-height: 1.6;
849
+ }
850
+
851
+ .timeline {
852
+ display: flex;
853
+ flex-direction: column;
854
+ gap: 10px;
855
+ }
856
+
857
+ .agent-step {
858
+ border: 1px solid var(--line);
859
+ border-radius: 18px;
860
+ background: #ffffff;
861
+ overflow: hidden;
862
+ }
863
+
864
+ .agent-summary {
865
+ list-style: none;
866
+ display: grid;
867
+ grid-template-columns: 42px 1fr;
868
+ gap: 12px;
869
+ align-items: center;
870
+ padding: 12px;
871
+ cursor: pointer;
872
+ }
873
+
874
+ .agent-summary::-webkit-details-marker {
875
+ display: none;
876
+ }
877
+
878
+ .agent-index {
879
+ width: 42px;
880
+ height: 42px;
881
+ border-radius: 14px;
882
+ display: grid;
883
+ place-items: center;
884
+ font-weight: 700;
885
+ color: var(--gold);
886
+ background: rgba(255,122,26,.08);
887
+ border: 1px solid rgba(255,122,26,.18);
888
+ }
889
+
890
+ .agent-head {
891
+ display: flex;
892
+ justify-content: space-between;
893
+ gap: 12px;
894
+ align-items: center;
895
+ }
896
+
897
+ .agent-head h4 {
898
+ margin: 0;
899
+ font-size: .98rem;
900
+ color: var(--text);
901
+ }
902
+
903
+ .agent-head span {
904
+ font-size: .72rem;
905
+ letter-spacing: .12em;
906
+ text-transform: uppercase;
907
+ color: var(--muted);
908
+ }
909
+
910
+ .agent-copy {
911
+ padding: 0 14px 16px 66px;
912
+ }
913
+
914
+ .agent-copy p {
915
+ margin: 0;
916
+ color: #2d2d2d;
917
+ font-size: .93rem;
918
+ line-height: 1.6;
919
+ }
920
+
921
+ .candidate-grid {
922
+ display: grid;
923
+ grid-template-columns: repeat(3, minmax(0,1fr));
924
+ gap: 18px;
925
+ }
926
+
927
+ .candidate-card {
928
+ background: none;
929
+ perspective: 1400px;
930
+ min-height: 330px;
931
+ }
932
+
933
+ .candidate-card-inner {
934
+ position: relative;
935
+ width: 100%;
936
+ min-height: 330px;
937
+ transition: transform .8s cubic-bezier(.2,.7,.1,1);
938
+ transform-style: preserve-3d;
939
+ }
940
+
941
+ .candidate-card:hover .candidate-card-inner,
942
+ .candidate-card:focus .candidate-card-inner,
943
+ .candidate-card:focus-within .candidate-card-inner {
944
+ transform: rotateY(180deg);
945
+ }
946
+
947
+ .candidate-face {
948
+ position: absolute;
949
+ inset: 0;
950
+ padding: 20px;
951
+ border-radius: 22px;
952
+ border: 1px solid var(--line);
953
+ background: #ffffff;
954
+ color: var(--text);
955
+ backface-visibility: hidden;
956
+ box-shadow: 0 12px 24px rgba(0,0,0,.06);
957
+ display: flex;
958
+ flex-direction: column;
959
+ gap: 14px;
960
+ }
961
+
962
+ .candidate-back {
963
+ transform: rotateY(180deg);
964
+ }
965
+
966
+ .candidate-top {
967
+ display: flex;
968
+ justify-content: space-between;
969
+ align-items: center;
970
+ gap: 8px;
971
+ }
972
+
973
+ .chip {
974
+ font-size: .72rem;
975
+ text-transform: uppercase;
976
+ letter-spacing: .12em;
977
+ color: #0b6f66;
978
+ padding: 7px 10px;
979
+ border-radius: 999px;
980
+ background: rgba(23,184,166,.08);
981
+ border: 1px solid rgba(23,184,166,.18);
982
+ }
983
+
984
+ .chip.alt {
985
+ color: var(--gold);
986
+ background: rgba(255,122,26,.08);
987
+ border-color: rgba(255,122,26,.18);
988
+ }
989
+
990
+ .score {
991
+ font-weight: 700;
992
+ color: var(--gold);
993
+ }
994
+
995
+ .candidate-face h4 {
996
+ margin: 0;
997
+ font-size: 1.08rem;
998
+ line-height: 1.35;
999
+ }
1000
+
1001
+ .candidate-face p {
1002
+ margin: 0;
1003
+ color: #1e1e1e;
1004
+ line-height: 1.65;
1005
+ font-size: .96rem;
1006
+ overflow-wrap: anywhere;
1007
+ }
1008
+
1009
+ .meta-row {
1010
+ margin-top: auto;
1011
+ display: flex;
1012
+ justify-content: space-between;
1013
+ color: var(--muted);
1014
+ font-size: .88rem;
1015
+ gap: 14px;
1016
+ }
1017
+
1018
+ .mini {
1019
+ cursor: pointer;
1020
+ margin-top: 8px;
1021
+ align-self: flex-start;
1022
+ color: var(--text);
1023
+ padding: 10px 12px;
1024
+ border-radius: 14px;
1025
+ border: 1px solid var(--line);
1026
+ background: #ffffff;
1027
+ transition: all 0.2s;
1028
+ }
1029
+
1030
+ .mini:hover {
1031
+ background: #f5f5f5;
1032
+ border-color: var(--chosen);
1033
+ }
1034
+
1035
+ .papers-grid {
1036
+ display: grid;
1037
+ grid-template-columns: repeat(2, minmax(0,1fr));
1038
+ gap: 14px;
1039
+ }
1040
+
1041
+ .paper-card {
1042
+ border: 1px solid var(--line);
1043
+ border-radius: 18px;
1044
+ padding: 16px;
1045
+ background: #ffffff;
1046
+ }
1047
+
1048
+ .paper-topline {
1049
+ display: flex;
1050
+ gap: 8px;
1051
+ flex-wrap: wrap;
1052
+ margin-bottom: 10px;
1053
+ }
1054
+
1055
+ .paper-badge {
1056
+ font-size: .72rem;
1057
+ padding: 6px 10px;
1058
+ border-radius: 999px;
1059
+ background: rgba(98,141,255,.08);
1060
+ color: #3456b5;
1061
+ border: 1px solid rgba(98,141,255,.18);
1062
+ }
1063
+
1064
+ .paper-badge.alt {
1065
+ background: rgba(0,0,0,.04);
1066
+ color: #444;
1067
+ border-color: rgba(0,0,0,.08);
1068
+ }
1069
+
1070
+ .doi-badge {
1071
+ background: rgba(255,122,26,.08);
1072
+ color: #8a4105;
1073
+ border-color: rgba(255,122,26,.18);
1074
+ }
1075
+
1076
+ .paper-card h4 {
1077
+ margin: 0 0 10px;
1078
+ line-height: 1.35;
1079
+ font-size: 1rem;
1080
+ }
1081
+
1082
+ .paper-card p {
1083
+ margin: 0 0 12px;
1084
+ line-height: 1.6;
1085
+ color: #222;
1086
+ }
1087
+
1088
+ .paper-links {
1089
+ display: flex;
1090
+ gap: 12px;
1091
+ flex-wrap: wrap;
1092
+ }
1093
+
1094
+ .paper-meta-stack {
1095
+ display: flex;
1096
+ flex-direction: column;
1097
+ gap: 6px;
1098
+ color: #444;
1099
+ margin-bottom: 12px;
1100
+ font-size: .9rem;
1101
+ }
1102
+
1103
+ .paper-links a,
1104
+ .journal-card,
1105
+ .upload-note a {
1106
+ color: #0b63ce;
1107
+ text-decoration: none;
1108
+ }
1109
+
1110
+ .journal-grid {
1111
+ display: grid;
1112
+ grid-template-columns: repeat(2, minmax(0,1fr));
1113
+ gap: 14px;
1114
+ }
1115
+
1116
+ .journal-card {
1117
+ border: 1px solid var(--line);
1118
+ border-radius: 18px;
1119
+ padding: 16px;
1120
+ display: flex;
1121
+ justify-content: space-between;
1122
+ gap: 14px;
1123
+ align-items: center;
1124
+ background: #ffffff;
1125
+ }
1126
+
1127
+ .journal-card h4 {
1128
+ margin: 0 0 6px;
1129
+ }
1130
+
1131
+ .journal-card p {
1132
+ margin: 0;
1133
+ color: var(--muted);
1134
+ line-height: 1.5;
1135
+ }
1136
+
1137
+ .upload-note {
1138
+ border: 1px dashed rgba(0,0,0,.16);
1139
+ border-radius: 18px;
1140
+ padding: 16px;
1141
+ background: rgba(0,0,0,.015);
1142
+ color: #1f1f1f;
1143
+ line-height: 1.6;
1144
+ }
1145
+
1146
+ .selection-panel {
1147
+ padding: 18px;
1148
+ }
1149
+
1150
+ .parse-grid {
1151
+ display: grid;
1152
+ grid-template-columns: 1.2fr 1fr;
1153
+ gap: 14px;
1154
+ }
1155
+
1156
+ .parse-card {
1157
+ border: 1px solid var(--line);
1158
+ border-radius: 18px;
1159
+ padding: 16px;
1160
+ background: #ffffff;
1161
+ }
1162
+
1163
+ .ref-list {
1164
+ margin: 0;
1165
+ padding-left: 18px;
1166
+ }
1167
+
1168
+ .ref-list li {
1169
+ margin-bottom: 8px;
1170
+ line-height: 1.5;
1171
+ }
1172
+
1173
+ .prosebox {
1174
+ padding: 18px;
1175
+ white-space: pre-wrap;
1176
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1177
+ line-height: 1.55;
1178
+ color: #1b1b1b;
1179
+ }
1180
+
1181
+ .gr-button-primary {
1182
+ background: linear-gradient(135deg, rgba(255,122,26,.92), rgba(240,108,22,.92)) !important;
1183
+ color: #ffffff !important;
1184
+ border: none !important;
1185
+ }
1186
+
1187
+ .gr-button-secondary {
1188
+ background: #ffffff !important;
1189
+ color: var(--text) !important;
1190
+ border: 1px solid var(--line) !important;
1191
+ }
1192
+
1193
+ footer {
1194
+ display: none !important;
1195
+ }
1196
+
1197
+ @keyframes pulseEdge {
1198
+ to { stroke-dashoffset: -40; }
1199
+ }
1200
+
1201
+ @media (max-width: 1180px) {
1202
+ .model-switcher,
1203
+ .candidate-grid,
1204
+ .papers-grid,
1205
+ .journal-grid,
1206
+ .parse-grid {
1207
+ grid-template-columns: 1fr;
1208
+ }
1209
+
1210
+ .brain-svg {
1211
+ height: 460px;
1212
+ }
1213
+ }
1214
+ """
1215
+
1216
+ CSS = BASE_CSS + "\n" + get_dvnc_layout_css() + "\n" + get_discovery_css()
1217
+
1218
+ HEAD = """
1219
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1220
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1221
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
1222
+ <script>
1223
+ function triggerRouteSwap(idx) {
1224
+ const container = document.getElementById("route_swap_payload");
1225
+ if (!container) return;
1226
+ const input = container.querySelector("textarea, input");
1227
+ if (!input) return;
1228
+
1229
+ input.value = String(idx);
1230
+ input.dispatchEvent(new Event("input", { bubbles: true }));
1231
+ input.dispatchEvent(new Event("change", { bubbles: true }));
1232
+
1233
+ setTimeout(() => {
1234
+ const btn = document.getElementById("route_swap_apply");
1235
+ if (btn) btn.click();
1236
+ }, 120);
1237
+ }
1238
+ </script>
1239
+ """
1240
+
1241
+
1242
+ # ── Gradio layout ────────────────────────────────────────────────────────────
1243
+ with gr.Blocks(
1244
+ css=CSS,
1245
+ head=HEAD,
1246
+ theme=gr.themes.Base(),
1247
+ fill_height=True,
1248
+ title="DVNC.AI",
1249
+ ) as demo:
1250
+ # Shared state
1251
+ papers_state = gr.State([])
1252
+ parsed_pdf_state = gr.State({})
1253
+ ingest_payload_state = gr.State({})
1254
+ route_state = gr.State(get_default_route_state())
1255
+
1256
+ # Header
1257
+ gr.HTML(
1258
+ """
1259
+ <div id="dvnc-shell">
1260
+ <div class="hero-bar">
1261
+ <div class="brand">
1262
+ <div class="logo" aria-hidden="true">
1263
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7">
1264
+ <path d="M5 17L12 4l7 13"/>
1265
+ <path d="M8.5 12.5h7"/>
1266
+ <circle cx="12" cy="12" r="1.8" fill="currentColor" stroke="none"/>
1267
+ </svg>
1268
+ </div>
1269
+ <div>
1270
+ <h1>DVNC.AI</h1>
1271
+ <p>Sovereign discovery instrument Β· connectome-native reasoning</p>
1272
+ </div>
1273
+ </div>
1274
+ <div class="status"><span class="status-dot"></span><span>Live orchestration</span></div>
1275
+ </div>
1276
+ </div>
1277
+ """
1278
+ )
1279
+
1280
+ with gr.Tabs():
1281
+ # ── Tab 1: Discovery Engine ──────────────────────────────────────────
1282
+ with gr.Tab("Discovery Engine"):
1283
+ model_html = gr.HTML(build_models_html("DVNC Sovereign"))
1284
+
1285
+ with gr.Row():
1286
+ with gr.Column(scale=2):
1287
+ model = gr.Dropdown(
1288
+ choices=[m["name"] for m in MODELS],
1289
+ value="DVNC Sovereign",
1290
+ label="Model tier",
1291
+ )
1292
+ query = gr.Textbox(
1293
+ label="Discovery query",
1294
+ elem_classes=["querybox"],
1295
+ placeholder="Enter a scientific question, anomaly, or breakthrough direction…",
1296
+ lines=4,
1297
+ )
1298
+ with gr.Row():
1299
+ run_btn = gr.Button("Run discovery", variant="primary")
1300
+ example_btn = gr.Button("Load example", variant="secondary")
1301
+
1302
+ chat = gr.HTML(
1303
+ """
1304
+ <div class="panel chat-panel">
1305
+ <div class="chat-thread">
1306
+ <div class="bubble bubble-ai">
1307
+ <span class="role">DVNC</span>
1308
+ <p>Enter a query to activate the 7-agent discovery stack and illuminate the chosen path through the 3D connectome.</p>
1309
+ </div>
1310
+ </div>
1311
+ </div>
1312
+ """
1313
+ )
1314
+
1315
+ with gr.Column(scale=3):
1316
+ connectome = gr.HTML(build_connectome_html(DEFAULT_PATH))
1317
+ cards = gr.HTML("")
1318
+
1319
+ output = gr.Markdown("# Discovery Output\n\nAwaiting query.")
1320
+ timeline = gr.HTML(get_initial_discovery_timeline_html())
1321
+
1322
+ route_swap_payload = gr.Textbox(value="", visible=False, elem_id="route_swap_payload")
1323
+ route_swap_apply = gr.Button("Apply route swap", visible=False, elem_id="route_swap_apply")
1324
+
1325
+ # ── Tab 2: Self-Learning Graph ───────────────────────────────────────
1326
+ with gr.Tab("Self-Learning Graph"):
1327
+ with gr.Row():
1328
+ with gr.Column(scale=2):
1329
+ paper_query = gr.Textbox(
1330
+ label="Research topic / title / DOI / link",
1331
+ elem_classes=["querybox"],
1332
+ placeholder="e.g. self-assembling conductive biomaterials for cardiac repair",
1333
+ lines=3,
1334
+ )
1335
+ search_mode = gr.Dropdown(
1336
+ choices=SEARCH_MODES,
1337
+ value="topic",
1338
+ label="Search mode",
1339
+ )
1340
+ source_selector = gr.CheckboxGroup(
1341
+ choices=SOURCE_OPTIONS,
1342
+ value=DEFAULT_SOURCES,
1343
+ label="Sources",
1344
+ )
1345
+ pdf_upload = gr.File(
1346
+ label="Upload PDF papers",
1347
+ file_types=[".pdf"],
1348
+ file_count="single",
1349
+ )
1350
+
1351
+ with gr.Row():
1352
+ learn_btn = gr.Button("Discover papers", variant="primary")
1353
+ load_topic_btn = gr.Button("Load example topic", variant="secondary")
1354
+
1355
+ upload_status = gr.Markdown("No PDF uploaded yet.")
1356
+ discovery_status = gr.Markdown("### No discovery results yet.")
1357
+ journal_panel = gr.HTML(build_journal_html("biomaterials cardiac repair"))
1358
+
1359
+ gr.HTML(
1360
+ '<div class="panel selection-panel"><h3 style="margin:0 0 12px;">Select papers to ingest</h3></div>'
1361
+ )
1362
+
1363
+ selection_box = gr.CheckboxGroup(
1364
+ choices=[],
1365
+ value=[],
1366
+ label="Candidate papers",
1367
+ )
1368
+
1369
+ parser_order = gr.CheckboxGroup(
1370
+ choices=PDF_PARSER_OPTIONS,
1371
+ value=PDF_PARSER_OPTIONS,
1372
+ label="Parser routing order",
1373
+ )
1374
+
1375
+ with gr.Row():
1376
+ parse_btn = gr.Button("Parse uploaded PDF", variant="secondary")
1377
+ ingest_btn = gr.Button("Ingest selected into graph", variant="primary")
1378
+
1379
+ with gr.Column(scale=3):
1380
+ learning_graph = gr.HTML(build_learning_graph_html([], []))
1381
+ papers_panel = gr.HTML(
1382
+ '<div class="panel papers-panel" style="padding:18px"><p>Search by topic, title, DOI, or link, then select papers before graph ingestion.</p></div>'
1383
+ )
1384
+ parse_summary = gr.Markdown("### PDF parse status\n\nAwaiting upload.")
1385
+ parse_panel = gr.HTML(
1386
+ '<div class="panel" style="padding:18px"><p>No parsed document yet.</p></div>'
1387
+ )
1388
+ ingest_summary = gr.Markdown("### Graph ingest status\n\nAwaiting paper selection.")
1389
+ ingest_payload = gr.JSON(
1390
+ label="Graph ingest payload",
1391
+ value={"status": "empty", "nodes": [], "edges": []},
1392
+ )
1393
+
1394
+ # ── Event wiring ─────────────────────────────────────────────────────────
1395
+ example_btn.click(fn=load_example, outputs=query)
1396
+
1397
+ run_btn.click(
1398
+ fn=run_discovery,
1399
+ inputs=[query, model],
1400
+ outputs=[chat, connectome, timeline, cards, output, model_html, route_state],
1401
+ )
1402
+
1403
+ route_swap_apply.click(
1404
+ fn=apply_route_swap,
1405
+ inputs=[query, model, route_swap_payload, route_state],
1406
+ outputs=[chat, connectome, timeline, output, route_state],
1407
+ )
1408
+
1409
+ load_topic_btn.click(fn=load_paper_topic, outputs=paper_query)
1410
+
1411
+ learn_btn.click(
1412
+ fn=run_paper_discovery,
1413
+ inputs=[paper_query, search_mode, source_selector, pdf_upload],
1414
+ outputs=[
1415
+ learning_graph,
1416
+ papers_panel,
1417
+ journal_panel,
1418
+ upload_status,
1419
+ selection_box,
1420
+ papers_state,
1421
+ discovery_status,
1422
+ ],
1423
+ )
1424
+
1425
+ parse_btn.click(
1426
+ fn=parse_uploaded_pdf,
1427
+ inputs=[pdf_upload, parser_order],
1428
+ outputs=[parse_summary, parsed_pdf_state],
1429
+ ).then(
1430
+ fn=render_parse_result,
1431
+ inputs=[parsed_pdf_state],
1432
+ outputs=[parse_panel],
1433
+ )
1434
+
1435
+ ingest_btn.click(
1436
+ fn=ingest_selected_papers,
1437
+ inputs=[paper_query, selection_box, papers_state, pdf_upload, parsed_pdf_state],
1438
+ outputs=[learning_graph, ingest_summary, ingest_payload],
1439
+ ).then(
1440
+ fn=lambda payload: payload,
1441
+ inputs=[ingest_payload],
1442
+ outputs=[ingest_payload_state],
1443
+ )
1444
+
1445
+
1446
+ if __name__ == "__main__":
1447
+ demo.launch()