4
File size: 19,478 Bytes
9b96c37
 
1361cf1
 
9b96c37
 
1361cf1
9b96c37
 
 
ff07764
 
9b96c37
 
1361cf1
ff07764
 
 
 
1361cf1
9b96c37
 
1361cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b96c37
1361cf1
9b96c37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1361cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
"""
DVNC.AI β€” root app.py
Fixed: all internal module imports now use correct underscore names
matching the actual function/constant names in dvnc_ai_v2_hf/.
"""

# ── Standard library ──────────────────────────────────────────────────────
import html
import random
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional

# Ensure the repository root is on sys.path so the package is importable
ROOT = Path(__file__).resolve().parent
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

# ── Third-party ───────────────────────────────────────────────────────────
import gradio as gr

# ── Internal modules ──────────────────────────────────────────────────────
from dvnc_ai_v2_hf.agent_route_cards import build_agent_route_cards_html
from dvnc_ai_v2_hf.discovery_app_bridge import (
    get_default_route_state,
    get_discovery_css,
    get_initial_discovery_timeline_html,
)
from dvnc_ai_v2_hf.dvnc_ui_layout import get_dvnc_layout_css
from dvnc_ai_v2_hf.graph_canvas_patch import render_graph_canvas_html
from dvnc_ai_v2_hf.self_learning_graph import (
    DEFAULT_SOURCES,
    SEARCH_MODES,
    SOURCE_OPTIONS,
    build_journal_html,
    ingest_selected_papers,
    parse_uploaded_pdf,
    render_parse_result,
    run_paper_discovery,
    safe_text,
)

# ── Constants ─────────────────────────────────────────────────────────────
MODELS = [
    {
        "name": "DVNC Sovereign",
        "tag": "flagship",
        "desc": "Maximum depth orchestration for frontier discovery",
    },
    {
        "name": "DVNC Atlas",
        "tag": "research",
        "desc": "Balanced reasoning, graph traversal, and synthesis",
    },
    {
        "name": "DVNC Curie",
        "tag": "lab",
        "desc": "Experimental hypothesis generation for anomalous signals",
    },
]

AGENTS = [
    "Query Interpreter",
    "Graph Divergence Mapper",
    "Evidence Harvester",
    "Analogy Engine",
    "Hypothesis Composer",
    "Adversarial Critic",
    "Experimental Program Designer",
]

NODES = [
    {"id": "seed", "label": "Seed Query", "group": "core", "x": 10, "y": 0, "z": 0},
    {"id": "bio", "label": "Biomaterials", "group": "domain", "x": 24, "y": 12, "z": -8},
    {"id": "card", "label": "Cardiac Repair", "group": "domain", "x": 38, "y": 3, "z": 14},
    {"id": "nano", "label": "Nanostructure", "group": "bridge", "x": 24, "y": -18, "z": 16},
    {"id": "selfasm", "label": "Self-Assembly", "group": "bridge", "x": 40, "y": -16, "z": -16},
    {"id": "electro", "label": "Electro-signalling", "group": "mechanism", "x": 58, "y": 10, "z": -10},
    {"id": "immune", "label": "Immune Modulation", "group": "mechanism", "x": 64, "y": -8, "z": 10},
    {"id": "trial", "label": "Validation Path", "group": "outcome", "x": 80, "y": 0, "z": 0},
    {"id": "alt1", "label": "Piezoelectric Scaffold", "group": "candidate", "x": 56, "y": 26, "z": 14},
    {"id": "alt2", "label": "Peptide Mesh", "group": "candidate", "x": 54, "y": -27, "z": -14},
]

EDGES = [
    ("seed", "bio"), ("seed", "nano"), ("bio", "card"), ("nano", "selfasm"),
    ("selfasm", "electro"), ("card", "immune"), ("electro", "trial"),
    ("immune", "trial"), ("card", "alt1"), ("selfasm", "alt2"),
    ("alt1", "trial"), ("alt2", "trial"),
]

DEFAULT_PATH = ["seed", "nano", "selfasm", "electro", "trial"]

CANDIDATES = [
    {
        "title": "Piezoelectric Scaffold Cascade",
        "front": "Use mechano-electric scaffolds to convert cardiac strain into micro-current signalling.",
        "back": "Discovery path: anomalous healing signal -> piezoelectric analog -> ion-channel entrainment -> tissue regeneration. Risk: power density and fibrosis coupling.",
        "score": 92,
        "novelty": "High",
        "agent": "Hypothesis Composer",
    },
    {
        "title": "Peptide Self-Assembly Mesh",
        "front": "Deploy dynamic peptide meshes that self-assemble around damaged myocardium and guide repair.",
        "back": "Discovery path: self-assembly -> local immune choreography -> regenerative substrate formation. Risk: degradation timing and targeting specificity.",
        "score": 88,
        "novelty": "High",
        "agent": "Analogy Engine",
    },
    {
        "title": "Immune-Tuned Conductive Hydrogel",
        "front": "Blend conductivity with macrophage-state modulation to reduce scarring and restore conduction.",
        "back": "Discovery path: inflammation mismatch -> conductive medium -> macrophage polarization -> synchronized healing. Risk: persistence and biocompatibility.",
        "score": 85,
        "novelty": "Medium-High",
        "agent": "Adversarial Critic",
    },
]

ACADEMIC_INSIGHTS = [
    {
        "hypothesis": "Implementation of mechano-electric scaffolds to transduce cardiac strain into localized micro-current signalling for myocardial regeneration.",
        "metrics": {
            "Novelty": 92,
            "Mechanistic clarity": 85,
            "Experimental tractability": 78,
            "Cross-domain distance": 94,
        },
        "outline": (
            "1. Synthesize candidate piezoelectric biomaterial scaffolds with tunable strain-electric coupling.\n"
            "2. Evaluate in vitro electromechanical transduction and subsequent ion-channel entrainment.\n"
            "3. Conduct in vivo comparative models to assess regenerative efficacy against gold-standard substrates.\n"
            "4. Rigorously validate to exclude pathological fibrosis and power-density toxicity."
        ),
        "path": ["seed", "bio", "card", "alt1", "trial"],
    },
    {
        "hypothesis": "Deployment of dynamic peptide networks that self-assemble post-infarction to orchestrate local immunological responses and guide substrate regeneration.",
        "metrics": {
            "Novelty": 88,
            "Mechanistic clarity": 82,
            "Experimental tractability": 86,
            "Cross-domain distance": 85,
        },
        "outline": (
            "1. Formulate peptide sequences programmed for triggered in situ self-assembly within the myocardial infarct zone.\n"
            "2. Quantify macrophage polarization and local immune choreography post-deployment.\n"
            "3. Map the temporospatial degradation profile against de novo tissue formation.\n"
            "4. Falsify against off-target aggregation and delayed clearance risks."
        ),
        "path": ["seed", "nano", "selfasm", "alt2", "trial"],
    },
    {
        "hypothesis": "Integration of conductive hydrogels with immunomodulatory properties to simultaneously bridge electrical uncoupling and mitigate adverse fibrotic scarring.",
        "metrics": {
            "Novelty": 85,
            "Mechanistic clarity": 90,
            "Experimental tractability": 88,
            "Cross-domain distance": 79,
        },
        "outline": (
            "1. Fabricate biocompatible hydrogels featuring precisely tuned electrical conductivity and immunomodulatory motifs.\n"
            "2. Monitor electrophysiological synchronization across the scaffold-tissue interface.\n"
            "3. Assess macrophage state transitions and suppression of adverse fibrotic remodelling.\n"
            "4. Validate long-term persistence, hemocompatibility, and mechanical integration."
        ),
        "path": ["seed", "bio", "card", "immune", "trial"],
    },
]

# ── Utility helpers ───────────────────────────────────────────────────────

def norm_text(x: Optional[str]) -> str:
    return re.sub(r"\s+", " ", (x or "")).strip()


def build_learning_graph_html(nodes, edges, title="Self-Learning Knowledge Graph"):
    return render_graph_canvas_html(
        {
            "status": "ok" if (nodes or edges) else "empty",
            "nodes": nodes or [],
            "edges": edges or [],
        },
        title=title,
        height=780,
    )

# ── HTML builders ─────────────────────────────────────────────────────────

def build_connectome_html(path_ids: List[str]) -> str:
    active = set(path_ids)
    node_map = {n["id"]: n for n in NODES}
    path_pairs = {
        pair
        for i in range(len(path_ids) - 1)
        for pair in [(path_ids[i], path_ids[i + 1]), (path_ids[i + 1], path_ids[i])]
    }
    baselines, activelines, circles, labels = [], [], [], []
    for a, b in EDGES:
        na, nb = node_map[a], node_map[b]
        x1, y1 = na["x"] * 8 + 80, na["y"] * 6 + 280
        x2, y2 = nb["x"] * 8 + 80, nb["y"] * 6 + 280
        baselines.append(f'e class="edge" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}"/>')
        if (a, b) in path_pairs:
            activelines.append(f'e class="edge active" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}"/>')
    for n in NODES:
        cx, cy = n["x"] * 8 + 80, n["y"] * 6 + 280
        is_active = n["id"] in active
        state = "chosen" if is_active else "idle"
        halo_cls = "halo active" if is_active else "halo"
        lbl_cls = "label active" if is_active else "label"
        radius = 18 if is_active else 13
        halo_r = 30 if is_active else 0
        circles.append(
            f'ircle class="{halo_cls}" cx="{cx}" cy="{cy}" r="{halo_r}"/>'
            f'ircle class="node {state}" cx="{cx}" cy="{cy}" r="{radius}"/>'
            f'<title>{safe_text(n["label"])}</title>'
            f'<text class="{lbl_cls}" x="{cx}" y="{cy + radius + 14}" text-anchor="middle">{safe_text(n["label"][:18])}</text>'
        )
    return f"""<div class="brain-shell panel">
<div class="brain-header">
<div><p class="eyebrow">Connectome</p><h3>3D Connectome</h3></div>
<div class="brain-legend">
<span><span class="dot dot-live"></span>lit path</span>
<span><span class="dot dot-chosen"></span>chosen node</span>
<span><span class="dot dot-idle"></span>available node</span>
</div></div>
<div class="brain-stage">
<svg class="brain-svg" viewBox="0 0 880 560">
{''.join(baselines)} {''.join(activelines)} {''.join(circles)}
</svg></div></div>"""


def build_cards_html(cards: List[Dict]) -> str:
    items = []
    for i, c in enumerate(cards):
        items.append(
            f"""<div class="candidate-card" tabindex="0">
<div class="candidate-card-inner">
<div class="candidate-face">
<div class="candidate-top"><span class="chip">{safe_text(c["agent"])}</span><span class="score">{safe_text(c["score"])}</span></div>
<h4>{safe_text(c["title"])}</h4>
<p>{safe_text(c["front"])}</p>
<div class="meta-row"><span>Novelty <strong>{safe_text(c["novelty"])}</strong></span></div>
<button class="mini" onclick="triggerRouteSwap({i})">Use as main insight</button>
</div>
<div class="candidate-face candidate-back">
<div class="candidate-top"><span class="chip alt">Alternative path</span><span class="score">{safe_text(c["score"])}</span></div>
<h4>{safe_text(c["title"])}</h4>
<p>{safe_text(c["back"])}</p>
<div class="meta-row"><span>Swap into route <strong>Enabled</strong></span></div>
<button class="mini" onclick="triggerRouteSwap({i})">Use as main insight</button>
</div>
</div></div>"""
        )
    return '<div class="candidate-grid">' + "".join(items) + "</div>"


def build_chat_html(query: str, result: Dict) -> str:
    return f"""<div class="chat-panel panel"><div class="chat-thread">
<div class="bubble bubble-user"><span class="role">You</span><p>{safe_text(query)}</p></div>
<div class="bubble bubble-ai"><span class="role">DVNC Sovereign</span><p>{safe_text(result["summary"])}</p></div>
<div class="bubble bubble-system"><span class="role">Discovery Signal</span>
<p><strong>Primary hypothesis:</strong> {safe_text(result["primary_hypothesis"])}</p>
</div></div></div>"""


def build_models_html(selected: str) -> str:
    items = []
    for m in MODELS:
        active = "active" if m["name"] == selected else ""
        items.append(
            f'<div class="model-pill {active}"><span class="model-name">{safe_text(m["name"])}</span>'
            f'<span class="model-tag">{safe_text(m["tag"])}</span>'
            f'<small>{safe_text(m["desc"])}</small></div>'
        )
    return '<div class="model-switcher">' + "".join(items) + "</div>"

# ── Discovery logic ───────────────────────────────────────────────────────

def run_discovery(query: str, model_name: str):
    random.seed(len(query or "") + len(model_name or ""))
    if "curie" in (query or "").lower() or "einstein" in (query or "").lower():
        primary = "Map the anomaly first, then force a distant analogy before composing the experimental programme."
        path = ["seed", "bio", "card", "immune", "trial"]
    else:
        primary = (
            "Utilization of a self-assembling conductive scaffold to transduce mechanical "
            "strain into localized regenerative signalling pathways."
        )
        path = DEFAULT_PATH

    summaries = [
        "Normalises the user prompt into a graph-searchable seed and isolates the tension inside the question.",
        "Finds remote conceptual bridges instead of staying near the starting domain cluster.",
        "Pulls evidence packets and conflict signals required for grounded hypothesis formation.",
        "Generates cross-domain analogies with a bias toward mechanism transfer rather than keyword similarity.",
        "Composes the lead hypothesis and two structurally different variants.",
        "Attacks weak assumptions, hidden confounders, and feasibility gaps.",
        "Produces a staged validation plan with measurable falsification criteria.",
    ]
    tags = ["input", "graph", "evidence", "analogy", "compose", "critique", "experiment"]
    reasoning = [
        {"step": i + 1, "agent": AGENTS[i], "tag": tags[i], "summary": summaries[i]}
        for i in range(7)
    ]
    result = {
        "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."
        ),
        "primary_hypothesis": primary,
        "reasoning": reasoning,
        "cards": CANDIDATES,
        "path": path,
        "metrics": {
            "Novelty": 93,
            "Mechanistic clarity": 89,
            "Experimental tractability": 82,
            "Cross-domain distance": 91,
        },
    }
    chat_html = build_chat_html(query, result)
    connectome_html = build_connectome_html(path)
    timeline_html = build_agent_route_cards_html(reasoning)
    metrics_md = "\n".join(f"- {k}: {v}/100" for k, v in result["metrics"].items())
    hypothesis_md = (
        "# Discovery Output\n\n"
        f"**Model:** {model_name}\n\n"
        f"**Primary hypothesis:** {result['primary_hypothesis']}\n\n"
        "## Scoring\n"
        f"{metrics_md}\n\n"
        "## Experimental outline\n"
        "1. Construct the candidate material or protocol.\n"
        "2. Test mechanistic signal expression under controlled conditions.\n"
        "3. Compare against baseline and nearest-neighbour alternatives.\n"
        "4. Falsify using the adversarial risk criteria surfaced in the reasoning path.\n"
    )
    cards_html = build_cards_html(CANDIDATES)
    route_state = get_default_route_state()
    return (
        chat_html,
        connectome_html,
        timeline_html,
        cards_html,
        hypothesis_md,
        build_models_html(model_name),
        route_state,
    )


def apply_route_swap(query: str, model_name: str, route_swap_payload: str, route_state):
    try:
        idx = int(route_swap_payload)
    except Exception:
        idx = 0
    if not (0 <= idx < len(ACADEMIC_INSIGHTS)):
        idx = 0
    academic = ACADEMIC_INSIGHTS[idx]
    connectome_html = build_connectome_html(academic["path"])
    result = {
        "summary": (
            "Main insight formally adopted. The connectome pathway and validation protocol "
            "have been realigned to the selected candidate methodology."
        ),
        "primary_hypothesis": academic["hypothesis"],
    }
    chat_html = build_chat_html(query, result)
    metrics_md = "\n".join(f"- {k}: {v}/100" for k, v in academic["metrics"].items())
    hypothesis_md = (
        "# Discovery Output\n\n"
        f"**Model:** {model_name}\n\n"
        f"**Primary hypothesis:** {academic['hypothesis']}\n\n"
        "## Scoring\n"
        f"{metrics_md}\n\n"
        "## Experimental outline\n"
        f"{academic['outline']}\n"
    )
    return chat_html, connectome_html, gr.update(), hypothesis_md, route_state

# ── Example loaders ──────────────────────────────────────────────────────

def load_example() -> str:
    return "How could a self-assembling conductive biomaterial improve cardiac tissue regeneration by converting mechanical strain into repair signalling?"


def load_paper_topic() -> str:
    return "self-assembling conductive biomaterials for cardiac repair"

# ── CSS / HEAD ────────────────────────────────────────────────────────────

BASE_CSS = r"""
:root {
  --bg:#ffffff; --panel:#ffffff; --line:rgba(0,0,0,.12); --text:#111111;
  --muted:#5b5b5b; --soft:rgba(0,0,0,.62); --gold:#ff6600; --teal:#17b8a6;
  --blue:#628dff; --chosen:#ff7a1a; --idle:#b8d8ff; --idle-stroke:#5e8fe6;
  --query-node:#ffd8b3; --paper-node:#d7f6f2; --upload-node:#e7defe;
  --shadow:0 16px 40px rgba(0,0,0,.12);
}
html,body,.gradio-container{background:#ffffff !important;font-family:Inter,ui-sans-serif,system-ui,sans-serif;}
.gradio-container{max-width:1640px !important;padding:20px !important;}
#dvnc-shell{border:1px solid var(--line);border-radius:28px;overflow:hidden;background:#ffffff;box-shadow:var(--shadow);padding:20px 22px 22px;}
.hero-bar{display:flex;justify-content:space-between;align-items:center;gap:16px;padding-bottom:12px;border-bottom:1px solid rgba(0,0,0,.06);margin-bottom:16px;}
.brand{display:flex;align-items:center;gap:14px;}
.logo{width:42px;height:42px;border-radius:14px;display:grid;place-items:center;color:var(--gold);background:linear-gradient(135deg,rgba(255,122,26,.12),rgba(23,184,166,.10));border:1px solid rgba(0,0,0,.08);}
.brand h1{font-size:1.05rem;margin:0;font-weight:700;letter-spacing:.12em;text-transform:uppercase;}
.brand p{margin:3px 0 0;color:var(--muted);font-size:.84rem;}
.status{display:flex;gap:10px;align-items:center;color:var(--soft);font-size:.85rem;}
.status-dot{width:10px;height:10px;border-radius:50%;background:var(--teal);box-shadow:0 0 0 6px rgba(23,184,166,.10),0 0 14px rgba(23,184,166,.25);}
.panel{background:#ffffff;border:1px solid var(--line);border-radius:22px;box-shadow:inset 0 1px 0 rgba(255,255,255,.