Adi12345 commited on
Commit
7f9f580
·
verified ·
1 Parent(s): 59db3e1

Upload app (2).py

Browse files
Files changed (1) hide show
  1. dvnc_ai_v2_hf/app (2).py +817 -0
dvnc_ai_v2_hf/app (2).py ADDED
@@ -0,0 +1,817 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import math
3
+ import random
4
+ import html
5
+ import urllib.parse
6
+ import xml.etree.ElementTree as ET
7
+ from pathlib import Path
8
+
9
+ import gradio as gr
10
+ import requests
11
+
12
+
13
+ MODELS = [
14
+ {"name": "DVNC Sovereign", "tag": "flagship", "desc": "Maximum depth orchestration for frontier discovery"},
15
+ {"name": "DVNC Atlas", "tag": "research", "desc": "Balanced reasoning, graph traversal, and synthesis"},
16
+ {"name": "DVNC Curie", "tag": "lab", "desc": "Experimental hypothesis generation for anomalous signals"},
17
+ ]
18
+
19
+
20
+ AGENTS = [
21
+ "Query Interpreter",
22
+ "Graph Divergence Mapper",
23
+ "Evidence Harvester",
24
+ "Analogy Engine",
25
+ "Hypothesis Composer",
26
+ "Adversarial Critic",
27
+ "Experimental Program Designer",
28
+ ]
29
+
30
+
31
+ NODES = [
32
+ {"id": "seed", "label": "Seed Query", "group": "core", "x": 10, "y": 0, "z": 0},
33
+ {"id": "bio", "label": "Biomaterials", "group": "domain", "x": 24, "y": 12, "z": -8},
34
+ {"id": "card", "label": "Cardiac Repair", "group": "domain", "x": 38, "y": 3, "z": 14},
35
+ {"id": "nano", "label": "Nanostructure", "group": "bridge", "x": 24, "y": -18, "z": 16},
36
+ {"id": "selfasm", "label": "Self-Assembly", "group": "bridge", "x": 40, "y": -16, "z": -16},
37
+ {"id": "electro", "label": "Electro-signalling", "group": "mechanism", "x": 58, "y": 10, "z": -10},
38
+ {"id": "immune", "label": "Immune Modulation", "group": "mechanism", "x": 64, "y": -8, "z": 10},
39
+ {"id": "trial", "label": "Validation Path", "group": "outcome", "x": 80, "y": 0, "z": 0},
40
+ {"id": "alt1", "label": "Piezoelectric Scaffold", "group": "candidate", "x": 56, "y": 26, "z": 14},
41
+ {"id": "alt2", "label": "Peptide Mesh", "group": "candidate", "x": 54, "y": -27, "z": -14},
42
+ ]
43
+
44
+
45
+ EDGES = [
46
+ ("seed", "bio"), ("seed", "nano"), ("bio", "card"), ("nano", "selfasm"),
47
+ ("selfasm", "electro"), ("card", "immune"), ("electro", "trial"), ("immune", "trial"),
48
+ ("card", "alt1"), ("selfasm", "alt2"), ("alt1", "trial"), ("alt2", "trial")
49
+ ]
50
+
51
+
52
+ DEFAULT_PATH = ["seed", "nano", "selfasm", "electro", "trial"]
53
+
54
+
55
+ CANDIDATES = [
56
+ {
57
+ "title": "Piezoelectric Scaffold Cascade",
58
+ "front": "Use mechano-electric scaffolds to convert cardiac strain into micro-current signalling.",
59
+ "back": "Discovery path: anomalous healing signal -> piezoelectric analog -> ion-channel entrainment -> tissue regeneration. Risk: power density and fibrosis coupling.",
60
+ "score": 92,
61
+ "novelty": "High",
62
+ "agent": "Hypothesis Composer"
63
+ },
64
+ {
65
+ "title": "Peptide Self-Assembly Mesh",
66
+ "front": "Deploy dynamic peptide meshes that self-assemble around damaged myocardium and guide repair.",
67
+ "back": "Discovery path: self-assembly -> local immune choreography -> regenerative substrate formation. Risk: degradation timing and targeting specificity.",
68
+ "score": 88,
69
+ "novelty": "High",
70
+ "agent": "Analogy Engine"
71
+ },
72
+ {
73
+ "title": "Immune-Tuned Conductive Hydrogel",
74
+ "front": "Blend conductivity with macrophage-state modulation to reduce scarring and restore conduction.",
75
+ "back": "Discovery path: inflammation mismatch -> conductive medium -> macrophage polarization -> synchronized healing. Risk: persistence and biocompatibility.",
76
+ "score": 85,
77
+ "novelty": "Medium-High",
78
+ "agent": "Adversarial Critic"
79
+ }
80
+ ]
81
+
82
+
83
+ JOURNALS = [
84
+ {"name": "Nature", "url": "https://www.nature.com/search", "desc": "Flagship multidisciplinary research journal."},
85
+ {"name": "Science", "url": "https://www.science.org/search", "desc": "High-impact science journal and family."},
86
+ {"name": "Cell", "url": "https://www.cell.com/search", "desc": "Life sciences and translational biology."},
87
+ {"name": "The Lancet", "url": "https://www.thelancet.com/search", "desc": "Clinical and medical research."},
88
+ {"name": "IEEE Xplore", "url": "https://ieeexplore.ieee.org/search/searchresult.jsp", "desc": "Engineering, AI, signal processing, and systems."},
89
+ ]
90
+
91
+
92
+ def build_connectome_html(path_ids):
93
+ active = set(path_ids)
94
+ node_map = {n["id"]: n for n in NODES}
95
+ path_pairs = set()
96
+ for i in range(len(path_ids) - 1):
97
+ path_pairs.add((path_ids[i], path_ids[i + 1]))
98
+ path_pairs.add((path_ids[i + 1], path_ids[i]))
99
+
100
+ lines = []
101
+ active_lines = []
102
+ for a, b in EDGES:
103
+ na, nb = node_map[a], node_map[b]
104
+ x1, y1 = na["x"] * 8 + 80, na["y"] * 6 + 280
105
+ x2, y2 = nb["x"] * 8 + 80, nb["y"] * 6 + 280
106
+ lines.append(f'<line class="edge" x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" />')
107
+ if (a, b) in path_pairs:
108
+ active_lines.append(f'<line class="edge active" x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" />')
109
+
110
+ circles = []
111
+ labels = []
112
+ for n in NODES:
113
+ cx = n["x"] * 8 + 80
114
+ cy = n["y"] * 6 + 280
115
+ is_active = n["id"] in active
116
+ cls = f'node {n["group"]} {'chosen' if is_active else 'idle'}'
117
+ halo_cls = f'halo {'active' if is_active else ''}'
118
+ circles.append(
119
+ f'<g class="node-wrap">'
120
+ f'<circle class="{halo_cls}" cx="{cx:.1f}" cy="{cy:.1f}" r="{30 if is_active else 0}" />'
121
+ f'<circle class="{cls}" cx="{cx:.1f}" cy="{cy:.1f}" r="{18 if is_active else 13}" />'
122
+ f'</g>'
123
+ )
124
+ labels.append(f'<text class="label {'active' if is_active else ''}" x="{cx + 18:.1f}" y="{cy - 16:.1f}">{n["label"]}</text>')
125
+
126
+ return f"""
127
+ <div class="panel brain-shell">
128
+ <div class="brain-header">
129
+ <div>
130
+ <p class="eyebrow">Connectome</p>
131
+ <h3>3D Connectome</h3>
132
+ </div>
133
+ <div class="brain-legend">
134
+ <span><i class="dot dot-live"></i> lit path</span>
135
+ <span><i class="dot dot-chosen"></i> chosen node</span>
136
+ <span><i class="dot dot-idle"></i> available node</span>
137
+ </div>
138
+ </div>
139
+ <div class="brain-stage">
140
+ <svg viewBox="0 0 780 560" class="brain-svg" role="img" aria-label="DVNC 3D connectome visualisation">
141
+ {''.join(lines)}
142
+ {''.join(active_lines)}
143
+ {''.join(circles)}
144
+ {''.join(labels)}
145
+ </svg>
146
+ </div>
147
+ </div>
148
+ """
149
+
150
+
151
+ def build_learning_graph_html(nodes, edges, title="Self-Learning Knowledge Graph"):
152
+ if not nodes:
153
+ return """
154
+ <div class="panel brain-shell">
155
+ <div class="brain-header">
156
+ <div>
157
+ <p class="eyebrow">Learning Graph</p>
158
+ <h3>Self-Learning Knowledge Graph</h3>
159
+ </div>
160
+ </div>
161
+ <div class="brain-stage learning-empty">
162
+ <div class="empty-graph-copy">
163
+ <h4>No papers mapped yet</h4>
164
+ <p>Search papers, pick a topic, or upload a PDF to grow the graph in real time.</p>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ """
169
+
170
+ node_items = []
171
+ label_items = []
172
+ edge_items = []
173
+ coords = [(110, 110), (320, 80), (540, 130), (660, 270), (550, 410), (300, 450), (110, 340), (380, 260)]
174
+
175
+ for i, node in enumerate(nodes[:8]):
176
+ x, y = coords[i]
177
+ node["sx"] = x
178
+ node["sy"] = y
179
+
180
+ node_map = {n["id"]: n for n in nodes[:8]}
181
+ for a, b in edges:
182
+ if a in node_map and b in node_map:
183
+ na = node_map[a]
184
+ nb = node_map[b]
185
+ edge_items.append(
186
+ f'<line class="learn-edge" x1="{na["sx"]}" y1="{na["sy"]}" x2="{nb["sx"]}" y2="{nb["sy"]}" />'
187
+ )
188
+
189
+ for node in nodes[:8]:
190
+ klass = f'learn-node {node.get("kind", "paper")}'
191
+ node_items.append(
192
+ f'<circle class="{klass}" cx="{node["sx"]}" cy="{node["sy"]}" r="{24 if node.get("kind") == "query" else 20}" />'
193
+ )
194
+ label_items.append(
195
+ f'<text class="learn-label" x="{node["sx"] + 28}" y="{node["sy"] - 10}">{html.escape(node["label"][:44])}</text>'
196
+ )
197
+
198
+ return f"""
199
+ <div class="panel brain-shell">
200
+ <div class="brain-header">
201
+ <div>
202
+ <p class="eyebrow">Learning Graph</p>
203
+ <h3>{html.escape(title)}</h3>
204
+ </div>
205
+ <div class="brain-legend">
206
+ <span><i class="dot dot-query"></i> query</span>
207
+ <span><i class="dot dot-paper"></i> paper</span>
208
+ <span><i class="dot dot-upload"></i> uploaded PDF</span>
209
+ </div>
210
+ </div>
211
+ <div class="brain-stage">
212
+ <svg viewBox="0 0 760 520" class="brain-svg" role="img" aria-label="Self-learning knowledge graph">
213
+ {''.join(edge_items)}
214
+ {''.join(node_items)}
215
+ {''.join(label_items)}
216
+ </svg>
217
+ </div>
218
+ </div>
219
+ """
220
+
221
+
222
+ def build_cards_html(cards):
223
+ items = []
224
+ for c in cards:
225
+ items.append(
226
+ f"""
227
+ <article class="candidate-card" tabindex="0">
228
+ <div class="candidate-card-inner">
229
+ <div class="candidate-face candidate-front">
230
+ <div class="candidate-top">
231
+ <span class="chip">{c['agent']}</span>
232
+ <span class="score">{c['score']}</span>
233
+ </div>
234
+ <h4>{c['title']}</h4>
235
+ <p>{c['front']}</p>
236
+ <div class="meta-row"><span>Novelty</span><strong>{c['novelty']}</strong></div>
237
+ <button class="mini" type="button">Flip insight</button>
238
+ </div>
239
+ <div class="candidate-face candidate-back">
240
+ <div class="candidate-top">
241
+ <span class="chip alt">Alternative path</span>
242
+ <span class="score">{c['score']}</span>
243
+ </div>
244
+ <h4>{c['title']}</h4>
245
+ <p>{c['back']}</p>
246
+ <div class="meta-row"><span>Swap into route</span><strong>Enabled</strong></div>
247
+ <button class="mini" type="button">Return</button>
248
+ </div>
249
+ </div>
250
+ </article>
251
+ """
252
+ )
253
+ return '<div class="panel" style="padding:20px;"><div class="candidate-grid">' + ''.join(items) + '</div></div>'
254
+
255
+
256
+ def build_agent_timeline(reasoning):
257
+ rows = []
258
+ for r in reasoning:
259
+ rows.append(
260
+ f"""
261
+ <details class="agent-step" {'open' if r['step'] == 1 else ''}>
262
+ <summary class="agent-summary">
263
+ <div class="agent-index">{r['step']}</div>
264
+ <div class="agent-head">
265
+ <h4>{r['agent']}</h4>
266
+ <span>{r['tag']}</span>
267
+ </div>
268
+ </summary>
269
+ <div class="agent-copy">
270
+ <p>{r['summary']}</p>
271
+ </div>
272
+ </details>
273
+ """
274
+ )
275
+ return '<div class="panel" style="padding:18px;"><div class="timeline">' + ''.join(rows) + '</div></div>'
276
+
277
+
278
+ def build_chat_html(query, result):
279
+ return f"""
280
+ <div class="panel chat-panel">
281
+ <div class="chat-thread">
282
+ <div class="bubble bubble-user">
283
+ <span class="role">You</span>
284
+ <p>{query}</p>
285
+ </div>
286
+ <div class="bubble bubble-ai">
287
+ <span class="role">DVNC Sovereign</span>
288
+ <p>{result['summary']}</p>
289
+ </div>
290
+ <div class="bubble bubble-system">
291
+ <span class="role">Discovery Signal</span>
292
+ <p><strong>Primary hypothesis:</strong> {result['primary_hypothesis']}</p>
293
+ </div>
294
+ </div>
295
+ </div>
296
+ """
297
+
298
+
299
+ def build_models_html(selected):
300
+ items = []
301
+ for m in MODELS:
302
+ active = "active" if m["name"] == selected else ""
303
+ items.append(
304
+ f"""
305
+ <div class="model-pill {active}">
306
+ <span class="model-name">{m['name']}</span>
307
+ <span class="model-tag">{m['tag']}</span>
308
+ <small>{m['desc']}</small>
309
+ </div>
310
+ """
311
+ )
312
+ return '<div class="panel" style="padding:18px;"><div class="model-switcher">' + ''.join(items) + '</div></div>'
313
+
314
+
315
+ def build_journal_html(query):
316
+ q = urllib.parse.quote_plus(query or "biomaterials cardiac repair")
317
+ rows = []
318
+ for journal in JOURNALS:
319
+ url = f"{journal['url']}?q={q}" if "?" not in journal["url"] else f"{journal['url']}&q={q}"
320
+ if "ieeexplore" in journal["url"]:
321
+ url = f"https://ieeexplore.ieee.org/search/searchresult.jsp?queryText={q}"
322
+ rows.append(
323
+ f"""
324
+ <a class="journal-card" href="{url}" target="_blank" rel="noopener noreferrer">
325
+ <div>
326
+ <h4>{journal['name']}</h4>
327
+ <p>{journal['desc']}</p>
328
+ </div>
329
+ <span>Open</span>
330
+ </a>
331
+ """
332
+ )
333
+ return '<div class="journal-grid">' + ''.join(rows) + '</div>'
334
+
335
+
336
+ def search_arxiv(query, max_results=5):
337
+ encoded = urllib.parse.quote(query)
338
+ url = (
339
+ "http://export.arxiv.org/api/query?search_query=all:"
340
+ f"{encoded}&start=0&max_results={max_results}&sortBy=relevance&sortOrder=descending"
341
+ )
342
+ response = requests.get(url, timeout=20)
343
+ response.raise_for_status()
344
+ root = ET.fromstring(response.text)
345
+ ns = {"atom": "http://www.w3.org/2005/Atom"}
346
+ papers = []
347
+ for entry in root.findall("atom:entry", ns):
348
+ title = " ".join((entry.findtext("atom:title", default="", namespaces=ns) or "").split())
349
+ summary = " ".join((entry.findtext("atom:summary", default="", namespaces=ns) or "").split())
350
+ published = entry.findtext("atom:published", default="", namespaces=ns)
351
+ paper_id = entry.findtext("atom:id", default="", namespaces=ns)
352
+ authors = [a.findtext("atom:name", default="", namespaces=ns) for a in entry.findall("atom:author", ns)]
353
+ pdf_url = ""
354
+ for link in entry.findall("atom:link", ns):
355
+ if link.attrib.get("title") == "pdf":
356
+ pdf_url = link.attrib.get("href", "")
357
+ break
358
+ papers.append(
359
+ {
360
+ "id": paper_id or title,
361
+ "title": title,
362
+ "summary": summary,
363
+ "published": published[:10],
364
+ "authors": ", ".join([a for a in authors[:4] if a]),
365
+ "url": paper_id,
366
+ "pdf": pdf_url,
367
+ }
368
+ )
369
+ return papers
370
+
371
+
372
+ def format_papers_html(papers):
373
+ if not papers:
374
+ return '<div class="panel papers-panel"><p>No papers found yet.</p></div>'
375
+
376
+ items = []
377
+ for paper in papers:
378
+ summary = html.escape((paper.get("summary") or "")[:280])
379
+ items.append(
380
+ f"""
381
+ <article class="paper-card">
382
+ <div class="paper-topline">
383
+ <span class="paper-badge">{html.escape(paper.get('published', '')) or 'Paper'}</span>
384
+ <span class="paper-badge alt">{html.escape(paper.get('authors', 'Unknown authors'))}</span>
385
+ </div>
386
+ <h4>{html.escape(paper.get('title', 'Untitled'))}</h4>
387
+ <p>{summary}</p>
388
+ <div class="paper-links">
389
+ <a href="{html.escape(paper.get('url', '#'))}" target="_blank" rel="noopener noreferrer">Abstract</a>
390
+ <a href="{html.escape(paper.get('pdf', '#'))}" target="_blank" rel="noopener noreferrer">PDF</a>
391
+ </div>
392
+ </article>
393
+ """
394
+ )
395
+ return '<div class="papers-grid">' + ''.join(items) + '</div>'
396
+
397
+
398
+ def uploaded_pdf_summary(file_obj):
399
+ if not file_obj:
400
+ return "No PDF uploaded yet."
401
+ path = getattr(file_obj, "name", None) or str(file_obj)
402
+ p = Path(path)
403
+ return f"Uploaded PDF ready for ingestion: {p.name}. You can parse this next with a paper reader, chunker, or citation extractor."
404
+
405
+
406
+ def build_learning_graph_state(query, papers, uploaded_name=None):
407
+ nodes = [{"id": "query", "label": query or "Research topic", "kind": "query"}]
408
+ edges = []
409
+ for i, paper in enumerate(papers[:5], start=1):
410
+ pid = f"paper_{i}"
411
+ nodes.append({"id": pid, "label": paper["title"], "kind": "paper"})
412
+ edges.append(("query", pid))
413
+ if uploaded_name:
414
+ nodes.append({"id": "upload", "label": uploaded_name, "kind": "upload"})
415
+ edges.append(("query", "upload"))
416
+ if len(nodes) > 2:
417
+ edges.append(("upload", "paper_1"))
418
+ return nodes, edges
419
+
420
+
421
+ def run_paper_search(query, pdf_file):
422
+ if not query.strip() and not pdf_file:
423
+ empty_graph = build_learning_graph_html([], [], "Self-Learning Knowledge Graph")
424
+ return (
425
+ empty_graph,
426
+ '<div class="panel papers-panel"><p>Enter a topic or upload a PDF to start learning.</p></div>',
427
+ build_journal_html("biomaterials cardiac repair"),
428
+ "No PDF uploaded yet.",
429
+ )
430
+
431
+ papers = []
432
+ if query.strip():
433
+ try:
434
+ papers = search_arxiv(query.strip(), max_results=5)
435
+ except Exception as e:
436
+ papers = []
437
+ error_html = f'<div class="panel papers-panel"><p>Paper search failed: {html.escape(str(e))}</p></div>'
438
+ graph_nodes, graph_edges = build_learning_graph_state(query.strip(), [], Path(getattr(pdf_file, "name", "uploaded.pdf")).name if pdf_file else None)
439
+ return (
440
+ build_learning_graph_html(graph_nodes, graph_edges),
441
+ error_html,
442
+ build_journal_html(query.strip()),
443
+ uploaded_pdf_summary(pdf_file),
444
+ )
445
+
446
+ uploaded_name = None
447
+ if pdf_file:
448
+ uploaded_name = Path(getattr(pdf_file, "name", str(pdf_file))).name
449
+
450
+ graph_nodes, graph_edges = build_learning_graph_state(query.strip(), papers, uploaded_name)
451
+ graph_html = build_learning_graph_html(graph_nodes, graph_edges)
452
+ papers_html = format_papers_html(papers)
453
+ journals_html = build_journal_html(query.strip())
454
+ pdf_summary = uploaded_pdf_summary(pdf_file)
455
+ return graph_html, papers_html, journals_html, pdf_summary
456
+
457
+
458
+ def run_discovery(query, model_name):
459
+ random.seed(len(query) + len(model_name))
460
+
461
+ if "curie" in query.lower() or "einstein" in query.lower():
462
+ primary = "Map the anomaly first, then force a distant analogy before composing the experimental programme."
463
+ path = ["seed", "bio", "card", "immune", "trial"]
464
+ else:
465
+ primary = "Use a self-assembling conductive scaffold that transforms mechanical strain into local regenerative signalling."
466
+ path = DEFAULT_PATH
467
+
468
+ summaries = [
469
+ "Normalises the user prompt into a graph-searchable seed and isolates the tension inside the question.",
470
+ "Finds remote conceptual bridges instead of staying near the starting domain cluster.",
471
+ "Pulls evidence packets and conflict signals required for grounded hypothesis formation.",
472
+ "Generates cross-domain analogies with a bias toward mechanism transfer rather than keyword similarity.",
473
+ "Composes the lead hypothesis and two structurally different variants.",
474
+ "Attacks weak assumptions, hidden confounders, and feasibility gaps.",
475
+ "Produces a staged validation plan with measurable falsification criteria."
476
+ ]
477
+
478
+ reasoning = [
479
+ {
480
+ "step": i + 1,
481
+ "agent": AGENTS[i],
482
+ "tag": ["input", "graph", "evidence", "analogy", "compose", "critique", "experiment"][i],
483
+ "summary": summaries[i],
484
+ }
485
+ for i in range(7)
486
+ ]
487
+
488
+ result = {
489
+ "summary": "A deeper route was chosen through the connectome, with live alternatives preserved as swappable cards so the reasoning path can be inspected rather than hidden.",
490
+ "primary_hypothesis": primary,
491
+ "reasoning": reasoning,
492
+ "cards": CANDIDATES,
493
+ "path": path,
494
+ "metrics": {
495
+ "Novelty": 93,
496
+ "Mechanistic clarity": 89,
497
+ "Experimental tractability": 82,
498
+ "Cross-domain distance": 91,
499
+ },
500
+ }
501
+
502
+ chat_html = build_chat_html(query, result)
503
+ connectome_html = build_connectome_html(path)
504
+ cards_html = build_cards_html(CANDIDATES)
505
+ timeline_html = build_agent_timeline(reasoning)
506
+
507
+ metrics = "\n".join([f"- {k}: {v}/100" for k, v in result["metrics"].items()])
508
+ hypothesis = (
509
+ "# Discovery Output\n\n"
510
+ f"**Model:** {model_name}\n\n"
511
+ f"**Primary hypothesis:** {result['primary_hypothesis']}\n\n"
512
+ "## Scoring\n"
513
+ f"{metrics}\n\n"
514
+ "## Experimental outline\n"
515
+ "1. Construct the candidate material or protocol.\n"
516
+ "2. Test mechanistic signal expression under controlled conditions.\n"
517
+ "3. Compare against baseline and nearest-neighbour alternatives.\n"
518
+ "4. Falsify using the adversarial risk criteria surfaced in the reasoning path.\n"
519
+ )
520
+
521
+ return chat_html, connectome_html, timeline_html, cards_html, hypothesis, build_models_html(model_name)
522
+
523
+
524
+ CSS = r"""
525
+ :root {
526
+ --bg: #ffffff;
527
+ --panel: #ffffff;
528
+ --line: rgba(0, 0, 0, 0.12);
529
+ --text: #111111;
530
+ --muted: #5b5b5b;
531
+ --soft: rgba(0, 0, 0, 0.62);
532
+ --gold: #ff6600;
533
+ --teal: #17b8a6;
534
+ --blue: #628dff;
535
+ --chosen: #ff7a1a;
536
+ --idle: #b8d8ff;
537
+ --idle-stroke: #5e8fe6;
538
+ --query-node: #ffd8b3;
539
+ --paper-node: #d7f6f2;
540
+ --upload-node: #e7defe;
541
+ --shadow: 0 16px 40px rgba(0, 0, 0, 0.12);
542
+ }
543
+ html, body, .gradio-container {
544
+ background: #ffffff !important;
545
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
546
+ }
547
+ .gradio-container {
548
+ max-width: 1640px !important;
549
+ padding: 20px !important;
550
+ }
551
+ #dvnc-shell {
552
+ border: 1px solid var(--line);
553
+ border-radius: 28px;
554
+ overflow: hidden;
555
+ background: #ffffff;
556
+ box-shadow: var(--shadow);
557
+ padding: 20px 22px 22px;
558
+ }
559
+ .hero-bar {
560
+ display: flex;
561
+ justify-content: space-between;
562
+ align-items: center;
563
+ gap: 16px;
564
+ padding-bottom: 12px;
565
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
566
+ margin-bottom: 16px;
567
+ }
568
+ .brand { display: flex; align-items: center; gap: 14px; }
569
+ .logo {
570
+ width: 42px; height: 42px; border-radius: 14px; display: grid; place-items: center; color: var(--gold);
571
+ background: linear-gradient(135deg, rgba(255, 122, 26, 0.12), rgba(23, 184, 166, 0.10));
572
+ border: 1px solid rgba(0, 0, 0, 0.08);
573
+ }
574
+ .logo svg { width: 24px; height: 24px; }
575
+ .brand h1 { font-size: 1.05rem; margin: 0; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
576
+ .brand p { margin: 3px 0 0; color: var(--muted); font-size: .84rem; }
577
+ .status { display: flex; gap: 10px; align-items: center; color: var(--soft); font-size: .85rem; }
578
+ .status-dot {
579
+ width: 10px; height: 10px; border-radius: 50%; background: var(--teal);
580
+ box-shadow: 0 0 0 6px rgba(23, 184, 166, 0.10), 0 0 14px rgba(23, 184, 166, 0.25);
581
+ }
582
+ .panel {
583
+ background: #ffffff; border: 1px solid var(--line); border-radius: 22px; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
584
+ }
585
+ .querybox textarea, .querybox input { background: transparent !important; color: var(--text) !important; }
586
+ .querybox, .querybox > div { background: #ffffff !important; border-radius: 18px !important; border-color: var(--line) !important; }
587
+ .chat-panel { padding: 18px; min-height: 280px; }
588
+ .chat-thread { display: flex; flex-direction: column; gap: 14px; }
589
+ .bubble { max-width: 88%; padding: 16px 18px; border-radius: 22px; border: 1px solid var(--line); }
590
+ .bubble p { margin: 8px 0 0; line-height: 1.6; font-size: .96rem; color: var(--text); }
591
+ .bubble .role { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
592
+ .bubble-user { align-self: flex-end; background: linear-gradient(135deg, rgba(98, 141, 255, 0.16), rgba(98, 141, 255, 0.08)); }
593
+ .bubble-ai { align-self: flex-start; background: #ffffff; }
594
+ .bubble-system { align-self: flex-start; background: linear-gradient(135deg, rgba(255, 122, 26, 0.10), rgba(255, 122, 26, 0.04)); }
595
+ .model-switcher { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
596
+ .model-pill {
597
+ padding: 14px; border: 1px solid var(--line); border-radius: 18px; display: flex; flex-direction: column; gap: 4px; min-height: 98px; background: #ffffff;
598
+ }
599
+ .model-pill.active {
600
+ border-color: rgba(255, 122, 26, 0.40);
601
+ background: linear-gradient(135deg, rgba(255, 122, 26, 0.10), rgba(255, 255, 255, 0.96));
602
+ }
603
+ .model-name { font-weight: 650; color: var(--text); }
604
+ .model-tag { font-size: .76rem; text-transform: uppercase; letter-spacing: .12em; color: var(--gold); }
605
+ .model-pill small { color: var(--muted); line-height: 1.45; }
606
+ .brain-shell { padding: 18px; }
607
+ .brain-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; margin-bottom: 10px; }
608
+ .eyebrow { font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; color: var(--gold); margin: 0 0 4px; }
609
+ .brain-header h3 { margin: 0; font-size: 1.12rem; color: var(--text); }
610
+ .brain-legend { display: flex; gap: 14px; color: var(--muted); font-size: .8rem; flex-wrap: wrap; }
611
+ .dot { width: 10px; height: 10px; display: inline-block; border-radius: 50%; margin-right: 6px; }
612
+ .dot-live { background: var(--chosen); box-shadow: 0 0 10px rgba(255, 122, 26, 0.35); }
613
+ .dot-chosen { background: var(--chosen); }
614
+ .dot-idle { background: var(--idle); border: 1px solid var(--idle-stroke); }
615
+ .dot-query { background: var(--query-node); border: 1px solid #de9e58; }
616
+ .dot-paper { background: var(--paper-node); border: 1px solid #4fb3a5; }
617
+ .dot-upload { background: var(--upload-node); border: 1px solid #8f73d9; }
618
+ .brain-stage {
619
+ position: relative; min-height: 420px; overflow: hidden; background: linear-gradient(180deg, rgba(250, 250, 250, 1), rgba(255, 255, 255, 1));
620
+ border: 1px solid rgba(0, 0, 0, 0.05); border-radius: 20px;
621
+ }
622
+ .brain-svg { width: 100%; height: 520px; display: block; }
623
+ .edge { stroke: rgba(0, 0, 0, 0.12); stroke-width: 2.4; }
624
+ .edge.active {
625
+ stroke: var(--chosen); stroke-width: 4.2; stroke-linecap: round; filter: drop-shadow(0 0 6px rgba(255, 122, 26, 0.45)); stroke-dasharray: 8 12; animation: pulseEdge 1.5s linear infinite;
626
+ }
627
+ .node { stroke-width: 2.2; transition: all .25s ease; }
628
+ .node.idle { fill: var(--idle); stroke: var(--idle-stroke); }
629
+ .node.chosen { fill: var(--chosen); stroke: #ffb16d; }
630
+ .halo { fill: none; }
631
+ .halo.active { stroke: rgba(255, 122, 26, 0.18); stroke-width: 12; }
632
+ .label { fill: #2c2c2c; font-size: 13px; font-weight: 500; letter-spacing: .01em; }
633
+ .label.active { fill: #111111; font-weight: 700; }
634
+ .learn-edge { stroke: rgba(0, 0, 0, 0.18); stroke-width: 2.2; stroke-linecap: round; }
635
+ .learn-node { stroke-width: 2.2; }
636
+ .learn-node.query { fill: var(--query-node); stroke: #de9e58; }
637
+ .learn-node.paper { fill: var(--paper-node); stroke: #36a091; }
638
+ .learn-node.upload { fill: var(--upload-node); stroke: #7e63cb; }
639
+ .learn-label { fill: #1e1e1e; font-size: 12px; font-weight: 600; }
640
+ .learning-empty { display: grid; place-items: center; }
641
+ .empty-graph-copy { text-align: center; max-width: 440px; padding: 40px 20px; }
642
+ .empty-graph-copy h4 { margin: 0 0 10px; font-size: 1.05rem; }
643
+ .empty-graph-copy p { margin: 0; color: var(--muted); line-height: 1.6; }
644
+ .timeline { display: flex; flex-direction: column; gap: 10px; }
645
+ .agent-step { border: 1px solid var(--line); border-radius: 18px; background: #ffffff; overflow: hidden; }
646
+ .agent-summary { list-style: none; display: grid; grid-template-columns: 42px 1fr; gap: 12px; align-items: center; padding: 12px; cursor: pointer; }
647
+ .agent-summary::-webkit-details-marker { display: none; }
648
+ .agent-index {
649
+ width: 42px; height: 42px; border-radius: 14px; display: grid; place-items: center; font-weight: 700; color: var(--gold);
650
+ background: rgba(255, 122, 26, 0.08); border: 1px solid rgba(255, 122, 26, 0.18);
651
+ }
652
+ .agent-head { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
653
+ .agent-head h4 { margin: 0; font-size: .98rem; color: var(--text); }
654
+ .agent-head span { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
655
+ .agent-copy { padding: 0 14px 16px 66px; }
656
+ .agent-copy p { margin: 0; color: #2d2d2d; font-size: .93rem; line-height: 1.6; }
657
+ .candidate-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
658
+ .candidate-card { background: none; perspective: 1400px; min-height: 330px; }
659
+ .candidate-card-inner { position: relative; width: 100%; min-height: 330px; transition: transform .8s cubic-bezier(.2,.7,.1,1); transform-style: preserve-3d; }
660
+ .candidate-card:hover .candidate-card-inner, .candidate-card:focus .candidate-card-inner, .candidate-card:focus-within .candidate-card-inner { transform: rotateY(180deg); }
661
+ .candidate-face {
662
+ position: absolute; inset: 0; padding: 20px; border-radius: 22px; border: 1px solid var(--line); background: #ffffff; color: var(--text);
663
+ backface-visibility: hidden; box-shadow: 0 12px 24px rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; gap: 14px;
664
+ }
665
+ .candidate-back { transform: rotateY(180deg); background: #ffffff; }
666
+ .candidate-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
667
+ .chip {
668
+ font-size: .72rem; text-transform: uppercase; letter-spacing: .12em; color: #0b6f66; padding: 7px 10px; border-radius: 999px;
669
+ background: rgba(23, 184, 166, 0.08); border: 1px solid rgba(23, 184, 166, 0.18);
670
+ }
671
+ .chip.alt { color: var(--gold); background: rgba(255, 122, 26, 0.08); border-color: rgba(255, 122, 26, 0.18); }
672
+ .score { font-weight: 700; color: var(--gold); }
673
+ .candidate-face h4 { margin: 0; font-size: 1.08rem; line-height: 1.35; color: var(--text); }
674
+ .candidate-face p { margin: 0; color: #1e1e1e; line-height: 1.65; font-size: .96rem; overflow-wrap: anywhere; }
675
+ .meta-row { margin-top: auto; display: flex; justify-content: space-between; color: var(--muted); font-size: .88rem; gap: 14px; }
676
+ .mini { margin-top: 8px; align-self: flex-start; color: var(--text); padding: 10px 12px; border-radius: 14px; border: 1px solid var(--line); background: #ffffff; }
677
+ .papers-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
678
+ .paper-card { border: 1px solid var(--line); border-radius: 18px; padding: 16px; background: #ffffff; }
679
+ .paper-topline { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
680
+ .paper-badge { font-size: .72rem; padding: 6px 10px; border-radius: 999px; background: rgba(98, 141, 255, 0.08); color: #3456b5; border: 1px solid rgba(98, 141, 255, 0.18); }
681
+ .paper-badge.alt { background: rgba(0, 0, 0, 0.04); color: #444; border-color: rgba(0, 0, 0, 0.08); }
682
+ .paper-card h4 { margin: 0 0 10px; line-height: 1.35; font-size: 1rem; }
683
+ .paper-card p { margin: 0 0 12px; line-height: 1.6; color: #222; }
684
+ .paper-links { display: flex; gap: 12px; flex-wrap: wrap; }
685
+ .paper-links a, .journal-card, .upload-note a { color: #0b63ce; text-decoration: none; }
686
+ .journal-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
687
+ .journal-card {
688
+ border: 1px solid var(--line); border-radius: 18px; padding: 16px; display: flex; justify-content: space-between; gap: 14px; align-items: center; background: #ffffff;
689
+ }
690
+ .journal-card h4 { margin: 0 0 6px; color: var(--text); }
691
+ .journal-card p { margin: 0; color: var(--muted); line-height: 1.5; }
692
+ .upload-note {
693
+ border: 1px dashed rgba(0,0,0,.16); border-radius: 18px; padding: 16px; background: rgba(0,0,0,.015); color: #1f1f1f; line-height: 1.6;
694
+ }
695
+ .prosebox { padding: 18px; white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; line-height: 1.55; color: #1b1b1b; }
696
+ .gr-button-primary { background: linear-gradient(135deg, rgba(255, 122, 26, 0.92), rgba(240, 108, 22, 0.92)) !important; color: #ffffff !important; border: none !important; }
697
+ .gr-button-secondary { background: #ffffff !important; color: var(--text) !important; border: 1px solid var(--line) !important; }
698
+ footer { display: none !important; }
699
+ @keyframes pulseEdge { to { stroke-dashoffset: -40; } }
700
+ @media (max-width: 1180px) {
701
+ .model-switcher, .candidate-grid, .papers-grid, .journal-grid { grid-template-columns: 1fr; }
702
+ .brain-svg { height: 460px; }
703
+ }
704
+ """
705
+
706
+
707
+ HEAD = """
708
+ <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">
709
+ <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>
710
+ <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap\" rel=\"stylesheet\">
711
+ """
712
+
713
+
714
+ with gr.Blocks(css=CSS, head=HEAD, theme=gr.themes.Base(), fill_height=True) as demo:
715
+ gr.HTML(
716
+ """
717
+ <div id=\"dvnc-shell\">
718
+ <div class=\"hero-bar\">
719
+ <div class=\"brand\">
720
+ <div class=\"logo\" aria-hidden=\"true\">
721
+ <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\">
722
+ <path d=\"M5 17L12 4l7 13\"/>
723
+ <path d=\"M8.5 12.5h7\"/>
724
+ <circle cx=\"12\" cy=\"12\" r=\"1.8\" fill=\"currentColor\" stroke=\"none\"/>
725
+ </svg>
726
+ </div>
727
+ <div>
728
+ <h1>DVNC.AI</h1>
729
+ <p>Sovereign discovery instrument · connectome-native reasoning</p>
730
+ </div>
731
+ </div>
732
+ <div class=\"status\"><span class=\"status-dot\"></span><span>Live orchestration</span></div>
733
+ </div>
734
+ </div>
735
+ """
736
+ )
737
+
738
+ with gr.Tabs():
739
+ with gr.Tab("Discovery Engine"):
740
+ model_html = gr.HTML(build_models_html("DVNC Sovereign"))
741
+ with gr.Row():
742
+ with gr.Column(scale=2):
743
+ model = gr.Dropdown(
744
+ choices=[m["name"] for m in MODELS],
745
+ value="DVNC Sovereign",
746
+ label="Model tier",
747
+ )
748
+ query = gr.Textbox(
749
+ label="Discovery query",
750
+ elem_classes=["querybox"],
751
+ placeholder="Enter a scientific question, anomaly, or breakthrough direction…",
752
+ lines=4,
753
+ )
754
+ with gr.Row():
755
+ run_btn = gr.Button("Run discovery", variant="primary")
756
+ example_btn = gr.Button("Load example", variant="secondary")
757
+ chat = gr.HTML(
758
+ """
759
+ <div class=\"panel chat-panel\">
760
+ <div class=\"chat-thread\">
761
+ <div class=\"bubble bubble-ai\">
762
+ <span class=\"role\">DVNC</span>
763
+ <p>Enter a query to activate the 7-agent discovery stack and illuminate the chosen path through the 3D connectome.</p>
764
+ </div>
765
+ </div>
766
+ </div>
767
+ """
768
+ )
769
+ timeline = gr.HTML('<div class="panel" style="padding:18px"><div class="timeline"></div></div>')
770
+
771
+ with gr.Column(scale=3):
772
+ connectome = gr.HTML(build_connectome_html(DEFAULT_PATH))
773
+ cards = gr.HTML('<div class="panel" style="padding:20px"><div class="candidate-grid"></div></div>')
774
+
775
+ output = gr.Markdown("# Discovery Output\n\nAwaiting query.")
776
+
777
+ with gr.Tab("Self-Learning Graph"):
778
+ with gr.Row():
779
+ with gr.Column(scale=2):
780
+ paper_query = gr.Textbox(
781
+ label="Research topic",
782
+ elem_classes=["querybox"],
783
+ placeholder="e.g. self-assembling conductive biomaterials for cardiac repair",
784
+ lines=3,
785
+ )
786
+ pdf_upload = gr.File(label="Upload PDF papers", file_types=[".pdf"], file_count="single")
787
+ with gr.Row():
788
+ learn_btn = gr.Button("Grow knowledge graph", variant="primary")
789
+ load_topic_btn = gr.Button("Load example topic", variant="secondary")
790
+ upload_status = gr.Markdown("No PDF uploaded yet.")
791
+ journal_panel = gr.HTML(build_journal_html("biomaterials cardiac repair"))
792
+ with gr.Column(scale=3):
793
+ learning_graph = gr.HTML(build_learning_graph_html([], []))
794
+ papers_panel = gr.HTML('<div class="panel papers-panel" style="padding:18px"><p>Search arXiv, pull linked PDFs, and grow the graph from retrieved papers.</p></div>')
795
+
796
+ def load_example():
797
+ return "How could a self-assembling conductive biomaterial improve cardiac tissue regeneration by converting mechanical strain into repair signalling?"
798
+
799
+ def load_paper_topic():
800
+ return "self-assembling conductive biomaterials for cardiac repair"
801
+
802
+ example_btn.click(fn=load_example, outputs=query)
803
+ load_topic_btn.click(fn=load_paper_topic, outputs=paper_query)
804
+ run_btn.click(
805
+ fn=run_discovery,
806
+ inputs=[query, model],
807
+ outputs=[chat, connectome, timeline, cards, output, model_html],
808
+ )
809
+ learn_btn.click(
810
+ fn=run_paper_search,
811
+ inputs=[paper_query, pdf_upload],
812
+ outputs=[learning_graph, papers_panel, journal_panel, upload_status],
813
+ )
814
+
815
+
816
+ if __name__ == "__main__":
817
+ demo.launch()