TheHickman commited on
Commit
dd9f125
·
verified ·
1 Parent(s): aea4cf1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -184
app.py CHANGED
@@ -122,214 +122,79 @@ def switch_content(choice):
122
  # ---- All the magic: floating sticky button + popup tooltip ----
123
  HEAD_HTML = """
124
  <style>
125
- /* ── Floating toolbar ─────────────────────────────────────────── */
126
- #explain-fab {
127
- position: fixed;
128
- bottom: 36px;
129
- right: 36px;
130
- z-index: 99999;
131
- display: flex;
132
- align-items: center;
133
- gap: 10px;
134
- background: #1e1b4b;
135
- color: #fff;
136
- border-radius: 999px;
137
- padding: 12px 22px;
138
- box-shadow: 0 6px 28px rgba(0,0,0,0.4);
139
- cursor: pointer;
140
- border: none;
141
- font-size: 15px;
142
- font-weight: 600;
143
- letter-spacing: 0.02em;
144
- opacity: 0;
145
- transform: translateY(12px);
146
- pointer-events: none;
147
- transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s;
148
- white-space: nowrap;
149
- }
150
- #explain-fab.visible {
151
- opacity: 1;
152
- transform: translateY(0);
153
- pointer-events: all;
154
- }
155
- #explain-fab:hover:not(:disabled) { background: #3730a3; }
156
- #explain-fab:disabled { background: #4b5563; cursor: not-allowed; }
157
- #explain-fab-preview {
158
- max-width: 180px;
159
- overflow: hidden;
160
- text-overflow: ellipsis;
161
- white-space: nowrap;
162
- font-size: 12px;
163
- font-weight: 400;
164
- opacity: 0.7;
165
- border-left: 1px solid rgba(255,255,255,0.3);
166
- padding-left: 10px;
167
- }
168
  </style>
169
  <script>
170
- (function () {
171
- 'use strict';
172
- let savedRange = null;
173
- let selectedText = '';
174
- let isExplaining = false;
175
- let lastSeenOutput = '';
176
- let popup = null;
177
 
178
  function buildFAB() {
179
- if (document.getElementById('explain-fab')) return;
180
- const btn = document.createElement('button');
181
- btn.id = 'explain-fab';
182
- btn.innerHTML = '<span id="explain-fab-label">Explain 🧠</span><span id="explain-fab-preview"></span>';
183
  document.body.appendChild(btn);
184
- btn.addEventListener('click', onExplainClick);
185
  }
186
 
187
  function showFAB(text) {
188
- const fab = document.getElementById('explain-fab');
189
- const label = document.getElementById('explain-fab-label');
190
- const preview = document.getElementById('explain-fab-preview');
191
- if (!fab) return;
192
- label.textContent = 'Explain 🧠';
193
- preview.textContent = text.length > 30 ? text.slice(0, 30) + '…' : text;
194
- fab.disabled = false;
195
- fab.classList.add('visible');
196
  }
197
 
198
- function hideFAB() {
199
- const fab = document.getElementById('explain-fab');
200
- if (fab) fab.classList.remove('visible');
201
- }
202
 
203
- function setFABLoading() {
204
- const fab = document.getElementById('explain-fab');
205
- const label = document.getElementById('explain-fab-label');
206
- const preview = document.getElementById('explain-fab-preview');
207
- if (!fab) return;
208
- label.textContent = 'Thinking…';
209
- preview.textContent = '';
210
- fab.disabled = true;
211
- }
212
-
213
- document.addEventListener('mouseup', function (e) {
214
- if (e.target.closest('#explain-fab') || isExplaining) return;
215
- setTimeout(function () {
216
- const sel = window.getSelection();
217
- if (!sel || sel.rangeCount === 0) return;
218
- const text = sel.toString().trim();
219
- if (text.length < 3) { hideFAB(); return; }
220
- savedRange = sel.getRangeAt(0).cloneRange();
221
- selectedText = text;
222
- showFAB(text);
223
- pushToGradioInput(text);
224
- }, 50);
225
  });
226
 
227
- function onExplainClick() {
228
- if (!selectedText || !savedRange || isExplaining) return;
229
- isExplaining = true;
230
- setFABLoading();
231
- const hiddenBtn = document.querySelector('#hidden-explain-trigger button');
232
- if (hiddenBtn) hiddenBtn.click();
233
- }
234
-
235
- function watchOutput() {
236
- const target = document.querySelector('#hidden-output');
237
- if (!target) {
238
- setTimeout(watchOutput, 500);
239
- return;
240
- }
241
-
242
- const observer = new MutationObserver(() => {
243
- const ta = document.querySelector('#hidden-output textarea');
244
- if (!ta) return;
245
-
246
- const val = ta.value.trim();
247
- if (isExplaining && val && val !== lastSeenOutput) {
248
- lastSeenOutput = val;
249
- showPopup(val);
250
- }
251
- });
252
-
253
- observer.observe(target, { subtree: true, childList: true });
254
- }
255
-
256
- function showPopup(text) {
257
- if (popup) popup.remove();
258
- popup = document.createElement('div');
259
- popup.style.position = 'absolute';
260
- popup.style.background = '#fef3c7';
261
- popup.style.border = '1px solid #f59e0b';
262
- popup.style.borderRadius = '6px';
263
- popup.style.padding = '10px';
264
- popup.style.maxWidth = '300px';
265
- popup.style.zIndex = 99999;
266
- popup.style.boxShadow = '0 4px 20px rgba(0,0,0,0.3)';
267
- popup.style.fontSize = '14px';
268
- popup.style.lineHeight = '1.5';
269
- popup.style.color = '#1c1917';
270
- popup.textContent = text;
271
-
272
- document.body.appendChild(popup);
273
-
274
- // Position near selection
275
- const rect = savedRange.getBoundingClientRect();
276
- popup.style.top = `${window.scrollY + rect.bottom + 8}px`;
277
- popup.style.left = `${window.scrollX + rect.left}px`;
278
-
279
- // Remove after 20s
280
- setTimeout(() => { if(popup) popup.remove(); popup = null; }, 20000);
281
-
282
- isExplaining = false;
283
- selectedText = '';
284
- savedRange = null;
285
  hideFAB();
286
  }
287
 
288
- function pushToGradioInput(value) {
289
- const ta = document.querySelector('#hidden-input textarea');
290
- if (!ta) return;
291
- const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
292
- setter.call(ta, value);
293
- ta.dispatchEvent(new Event('input', { bubbles: true }));
294
- ta.dispatchEvent(new Event('change', { bubbles: true }));
295
- }
296
-
297
- function boot() {
298
- buildFAB();
299
- watchOutput();
 
 
 
 
 
 
300
  }
301
 
302
- if (document.readyState === 'loading') {
303
- document.addEventListener('DOMContentLoaded', boot);
304
- } else {
305
- setTimeout(boot, 800);
306
- }
307
  })();
308
  </script>
309
  """
310
 
311
-
312
  with gr.Blocks(head=HEAD_HTML) as demo:
313
- gr.Markdown(
314
- "### 📘 Highlight any text — a floating **Explain 🧠** button will appear. "
315
- "Click it to show an AI explanation in a popup near your selection."
316
- )
317
-
318
- view_toggle = gr.Radio(
319
- choices=["My Work", "HF Reference"],
320
- value="My Work",
321
- label="View",
322
- )
323
  content_display = gr.HTML(YOUR_WORK_HTML)
324
- view_toggle.change(fn=switch_content, inputs=view_toggle, outputs=content_display)
325
-
326
- # Hidden plumbing — not shown to the user
327
- with gr.Row(visible=False):
328
- hidden_input = gr.Textbox(elem_id="hidden-input", label="hidden-input")
329
- hidden_output = gr.Textbox(elem_id="hidden-output", label="hidden-output")
330
- with gr.Column(elem_id="hidden-explain-trigger"):
331
- hidden_btn = gr.Button("hidden")
332
 
333
- hidden_btn.click(fn=explain_text, inputs=hidden_input, outputs=hidden_output)
 
 
334
 
335
  demo.launch()
 
122
  # ---- All the magic: floating sticky button + popup tooltip ----
123
  HEAD_HTML = """
124
  <style>
125
+ /* Floating FAB style … same as before … */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  </style>
127
  <script>
128
+ (function(){
129
+ let selectedText="", savedRange=null, popup=null;
 
 
 
 
 
130
 
131
  function buildFAB() {
132
+ if(document.getElementById("explain-fab")) return;
133
+ const btn=document.createElement("button");
134
+ btn.id="explain-fab"; btn.innerHTML="Explain 🧠";
 
135
  document.body.appendChild(btn);
136
+ btn.addEventListener("click", onExplainClick);
137
  }
138
 
139
  function showFAB(text) {
140
+ const fab=document.getElementById("explain-fab");
141
+ if(!fab) return;
142
+ fab.style.opacity="1"; fab.disabled=false;
143
+ fab.dataset.text=text;
 
 
 
 
144
  }
145
 
146
+ function hideFAB() { const fab=document.getElementById("explain-fab"); if(fab) fab.style.opacity="0"; }
 
 
 
147
 
148
+ document.addEventListener("mouseup", e=>{
149
+ const sel=window.getSelection();
150
+ if(!sel || sel.rangeCount===0) return hideFAB();
151
+ const text=sel.toString().trim();
152
+ if(text.length<3) return hideFAB();
153
+ selectedText=text;
154
+ savedRange=sel.getRangeAt(0).cloneRange();
155
+ showFAB(text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  });
157
 
158
+ async function onExplainClick() {
159
+ if(!selectedText) return;
160
+ const response=await gradioApp().getElement("hidden-btn").clickAsync(selectedText);
161
+ showPopup(response);
162
+ selectedText=""; savedRange=null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  hideFAB();
164
  }
165
 
166
+ function showPopup(text){
167
+ if(popup) popup.remove();
168
+ popup=document.createElement("div");
169
+ popup.style.position="absolute";
170
+ popup.style.background="#fef3c7";
171
+ popup.style.border="1px solid #f59e0b";
172
+ popup.style.padding="10px";
173
+ popup.style.borderRadius="6px";
174
+ popup.style.maxWidth="300px";
175
+ popup.style.zIndex=99999;
176
+ popup.textContent=text;
177
+ document.body.appendChild(popup);
178
+ if(savedRange){
179
+ const rect=savedRange.getBoundingClientRect();
180
+ popup.style.top=`${window.scrollY+rect.bottom+8}px`;
181
+ popup.style.left=`${window.scrollX+rect.left}px`;
182
+ }
183
+ setTimeout(()=>{ if(popup) popup.remove(); popup=null; },20000);
184
  }
185
 
186
+ document.addEventListener("DOMContentLoaded", buildFAB);
 
 
 
 
187
  })();
188
  </script>
189
  """
190
 
 
191
  with gr.Blocks(head=HEAD_HTML) as demo:
192
+ view_toggle = gr.Radio(choices=["My Work","HF Reference"], value="My Work", label="View")
 
 
 
 
 
 
 
 
 
193
  content_display = gr.HTML(YOUR_WORK_HTML)
194
+ view_toggle.change(switch_content, view_toggle, content_display)
 
 
 
 
 
 
 
195
 
196
+ # Hidden plumbing
197
+ hidden_btn = gr.Button("hidden-btn", visible=False)
198
+ hidden_btn.click(explain_text, inputs=gr.Textbox(visible=False), outputs=gr.Textbox(visible=False))
199
 
200
  demo.launch()