Adi12345 commited on
Commit
57025b0
·
verified ·
1 Parent(s): a248cb5

Create dvnc_flip_insight_patch.py

Browse files
Files changed (1) hide show
  1. dvnc_flip_insight_patch.py +227 -0
dvnc_flip_insight_patch.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Standalone non-destructive patch for DVNC.AI Hugging Face Space.
3
+
4
+ Purpose
5
+ - Fix Bug 1: hover/focus flip interaction makes the Flip Insight button unreliable.
6
+ - Fix Bug 2: brittle Gradio element ID lookup for route swap payload/apply controls.
7
+ - Fix Bug 3: route swap leaves timeline stale/blank by rebuilding/preserving the timeline output.
8
+
9
+ Design
10
+ - Leaves the existing codebase untouched.
11
+ - Intended to be imported and applied from a separate runner or notebook.
12
+ - Uses monkey-patching where possible.
13
+
14
+ Usage example
15
+ import app as dvnc_app
16
+ import dvnc_flip_insight_patch as patch
17
+ patch.apply_patch(dvnc_app)
18
+
19
+ If you want to keep the current app.py completely untouched, create a tiny launcher file
20
+ that imports the original app module and then calls apply_patch(dvnc_app).
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import re
26
+ from typing import Any, Callable
27
+
28
+
29
+ PATCH_SCRIPT = r"""
30
+ <script>
31
+ (function () {
32
+ if (window.__dvncFlipPatchLoaded) return;
33
+ window.__dvncFlipPatchLoaded = true;
34
+
35
+ function findByFragment(fragment) {
36
+ return document.getElementById(fragment) || document.querySelector('[id*="' + fragment + '"]');
37
+ }
38
+
39
+ function getInteractiveNode(root) {
40
+ if (!root) return null;
41
+ return root.matches?.('textarea,input,button') ? root : root.querySelector?.('textarea,input,button');
42
+ }
43
+
44
+ function triggerRouteSwapPatched(idx) {
45
+ try {
46
+ const payloadRoot = findByFragment('route_swap_payload');
47
+ const payload = getInteractiveNode(payloadRoot);
48
+ if (!payload) {
49
+ console.warn('[DVNC patch] route_swap_payload not found');
50
+ return;
51
+ }
52
+ payload.focus?.();
53
+ payload.value = String(idx);
54
+ ['input', 'change'].forEach(function (name) {
55
+ payload.dispatchEvent(new Event(name, { bubbles: true }));
56
+ });
57
+
58
+ window.setTimeout(function () {
59
+ const applyRoot = findByFragment('route_swap_apply');
60
+ const applyBtn = getInteractiveNode(applyRoot) || applyRoot;
61
+ if (!applyBtn) {
62
+ console.warn('[DVNC patch] route_swap_apply not found');
63
+ return;
64
+ }
65
+ applyBtn.click?.();
66
+ }, 180);
67
+ } catch (err) {
68
+ console.error('[DVNC patch] triggerRouteSwap failed', err);
69
+ }
70
+ }
71
+
72
+ window.triggerRouteSwap = triggerRouteSwapPatched;
73
+
74
+ document.addEventListener('click', function (e) {
75
+ const mini = e.target.closest('.candidate-back .mini');
76
+ if (mini) return;
77
+
78
+ const card = e.target.closest('.candidate-card');
79
+ if (!card) return;
80
+
81
+ card.classList.toggle('flipped');
82
+ }, true);
83
+
84
+ document.addEventListener('keydown', function (e) {
85
+ const card = e.target.closest && e.target.closest('.candidate-card');
86
+ if (!card) return;
87
+ if (e.key === 'Enter' || e.key === ' ') {
88
+ if (e.target.closest('.candidate-back .mini')) return;
89
+ e.preventDefault();
90
+ card.classList.toggle('flipped');
91
+ }
92
+ }, true);
93
+ })();
94
+ </script>
95
+ """
96
+
97
+ PATCH_STYLE = r"""
98
+ <style>
99
+ .candidate-card { cursor: pointer; }
100
+ .candidate-card.flipped .candidate-card-inner { transform: rotateY(180deg) !important; }
101
+ .candidate-card:hover .candidate-card-inner,
102
+ .candidate-card:focus .candidate-card-inner,
103
+ .candidate-card:focus-within .candidate-card-inner { transform: none !important; }
104
+ .candidate-back .mini { position: relative; z-index: 5; }
105
+ </style>
106
+ """
107
+
108
+
109
+ def _inject_assets(head: str) -> str:
110
+ if "__dvncFlipPatchLoaded" in head:
111
+ return head
112
+ return head + "\n" + PATCH_STYLE + "\n" + PATCH_SCRIPT
113
+
114
+
115
+ def _patch_head(module: Any) -> None:
116
+ for attr in ("HEAD", "head", "CUSTOM_HEAD"):
117
+ if hasattr(module, attr):
118
+ value = getattr(module, attr)
119
+ if isinstance(value, str):
120
+ setattr(module, attr, _inject_assets(value))
121
+ return
122
+
123
+
124
+ def _patch_cards_builder(module: Any) -> None:
125
+ if not hasattr(module, "build_cards_html"):
126
+ return
127
+ original = module.build_cards_html
128
+
129
+ def wrapped(*args, **kwargs):
130
+ out = original(*args, **kwargs)
131
+ if not isinstance(out, str):
132
+ return out
133
+ if "candidate-card" not in out:
134
+ return out
135
+
136
+ out = re.sub(
137
+ r'(<div\s+class="candidate-card"\b)',
138
+ r'\1 tabindex="0" role="button" aria-label="Flip insight card"',
139
+ out,
140
+ count=0,
141
+ )
142
+ return out
143
+
144
+ module.build_cards_html = wrapped
145
+
146
+
147
+ def _build_timeline_from_state(module: Any, route_state: Any) -> Any:
148
+ if route_state is None:
149
+ return None
150
+
151
+ builder = getattr(module, "build_agent_route_cards_html", None)
152
+ if not callable(builder):
153
+ return None
154
+
155
+ try:
156
+ variants = route_state.get("variants") if isinstance(route_state, dict) else None
157
+ active_idx = route_state.get("active_variant", 0) if isinstance(route_state, dict) else 0
158
+ if not variants or active_idx >= len(variants):
159
+ return None
160
+ variant = variants[active_idx] or {}
161
+ steps = variant.get("steps") or variant.get("route") or []
162
+ if not steps:
163
+ return None
164
+
165
+ normalized = []
166
+ for i, step in enumerate(steps):
167
+ if isinstance(step, dict):
168
+ normalized.append(
169
+ {
170
+ "step": step.get("step", i + 1),
171
+ "agent": step.get("agent", f"Step {i+1}"),
172
+ "tag": step.get("tag", step.get("mode", "")),
173
+ "summary": step.get("summary", step.get("description", "")),
174
+ }
175
+ )
176
+ else:
177
+ normalized.append(
178
+ {
179
+ "step": i + 1,
180
+ "agent": f"Step {i+1}",
181
+ "tag": "",
182
+ "summary": str(step),
183
+ }
184
+ )
185
+ return builder(normalized)
186
+ except Exception:
187
+ return None
188
+
189
+
190
+ def _patch_apply_route_swap(module: Any) -> None:
191
+ if not hasattr(module, "apply_route_swap"):
192
+ return
193
+
194
+ original = module.apply_route_swap
195
+
196
+ def wrapped(*args, **kwargs):
197
+ result = original(*args, **kwargs)
198
+ if not isinstance(result, tuple):
199
+ return result
200
+
201
+ if len(result) == 5:
202
+ chat_html, connectome_html, timeline_value, hypothesis_md, route_state = result
203
+ timeline_rebuilt = _build_timeline_from_state(module, route_state)
204
+ if timeline_rebuilt is not None:
205
+ gr = getattr(module, "gr", None)
206
+ if gr is not None and hasattr(gr, "update"):
207
+ timeline_value = gr.update(value=timeline_rebuilt)
208
+ else:
209
+ timeline_value = timeline_rebuilt
210
+ return chat_html, connectome_html, timeline_value, hypothesis_md, route_state
211
+ return result
212
+
213
+ module.apply_route_swap = wrapped
214
+
215
+
216
+ def _patch_rendered_html(module: Any) -> None:
217
+ for attr in ("DISCOVERY_CARD_CSS",):
218
+ if hasattr(module, attr) and isinstance(getattr(module, attr), str):
219
+ setattr(module, attr, getattr(module, attr) + "\n" + PATCH_STYLE)
220
+
221
+
222
+ def apply_patch(module: Any) -> Any:
223
+ _patch_head(module)
224
+ _patch_rendered_html(module)
225
+ _patch_cards_builder(module)
226
+ _patch_apply_route_swap(module)
227
+ return module