TheHickman commited on
Commit
aea4cf1
Β·
verified Β·
1 Parent(s): 27cfa2a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -82
app.py CHANGED
@@ -119,7 +119,7 @@ def switch_content(choice):
119
  return YOUR_WORK_HTML if choice == "My Work" else HF_REFERENCE_HTML
120
 
121
 
122
- # ---- All the magic: floating sticky button + inline DOM replacement ----
123
  HEAD_HTML = """
124
  <style>
125
  /* ── Floating toolbar ─────────────────────────────────────────── */
@@ -154,7 +154,6 @@ HEAD_HTML = """
154
  }
155
  #explain-fab:hover:not(:disabled) { background: #3730a3; }
156
  #explain-fab:disabled { background: #4b5563; cursor: not-allowed; }
157
-
158
  #explain-fab-preview {
159
  max-width: 180px;
160
  overflow: hidden;
@@ -166,44 +165,16 @@ HEAD_HTML = """
166
  border-left: 1px solid rgba(255,255,255,0.3);
167
  padding-left: 10px;
168
  }
169
-
170
- /* ── Inline replacement styles ───────────────────────────────── */
171
- .ai-inline {
172
- background: linear-gradient(135deg, #fef3c7, #fde68a);
173
- border-left: 3px solid #f59e0b;
174
- border-radius: 4px;
175
- padding: 1px 6px;
176
- font-style: italic;
177
- color: #1c1917;
178
- cursor: help;
179
- transition: background 0.3s;
180
- }
181
- .ai-inline:hover {
182
- background: linear-gradient(135deg, #fde68a, #fbbf24);
183
- }
184
- .ai-inline-loading {
185
- background: #e5e7eb !important;
186
- border-left-color: #9ca3af !important;
187
- color: #6b7280 !important;
188
- animation: ai-pulse 1.2s ease-in-out infinite;
189
- }
190
- @keyframes ai-pulse {
191
- 0%, 100% { opacity: 0.5; }
192
- 50% { opacity: 1; }
193
- }
194
  </style>
195
-
196
  <script>
197
  (function () {
198
  'use strict';
199
-
200
  let savedRange = null;
201
  let selectedText = '';
202
- let placeholder = null;
203
  let isExplaining = false;
204
  let lastSeenOutput = '';
 
205
 
206
- /* ── Build the floating button ──────────────────────────────────── */
207
  function buildFAB() {
208
  if (document.getElementById('explain-fab')) return;
209
  const btn = document.createElement('button');
@@ -239,7 +210,6 @@ HEAD_HTML = """
239
  fab.disabled = true;
240
  }
241
 
242
- /* ── Selection tracking ─────────────────────────────────────────── */
243
  document.addEventListener('mouseup', function (e) {
244
  if (e.target.closest('#explain-fab') || isExplaining) return;
245
  setTimeout(function () {
@@ -254,70 +224,67 @@ HEAD_HTML = """
254
  }, 50);
255
  });
256
 
257
- /* ── Explain click ──────────────────────────────────────────────── */
258
  function onExplainClick() {
259
  if (!selectedText || !savedRange || isExplaining) return;
260
  isExplaining = true;
261
  setFABLoading();
262
-
263
- /* Delete the highlighted text and drop in a loading span */
264
- try {
265
- savedRange.deleteContents();
266
- placeholder = document.createElement('span');
267
- placeholder.className = 'ai-inline ai-inline-loading';
268
- placeholder.textContent = '⏳ explaining…';
269
- savedRange.insertNode(placeholder);
270
- window.getSelection().removeAllRanges();
271
- } catch (err) {
272
- console.warn('[explain] range insert failed', err);
273
- }
274
-
275
- /* Trigger the hidden Gradio button β†’ calls Python backend */
276
  const hiddenBtn = document.querySelector('#hidden-explain-trigger button');
277
  if (hiddenBtn) hiddenBtn.click();
278
  }
279
 
280
- /* ── Poll the hidden output textarea for results ────────────────── */
281
- function watchOutput() {
282
- const target = document.querySelector('#hidden-output');
283
- if (!target) {
284
- setTimeout(watchOutput, 500);
285
- return;
286
- }
287
-
288
- const observer = new MutationObserver(() => {
289
- const ta = document.querySelector('#hidden-output textarea');
290
- if (!ta) return;
291
-
292
- const val = ta.value.trim();
293
- if (isExplaining && val && val !== lastSeenOutput) {
294
- lastSeenOutput = val;
295
- applyExplanation(val);
296
- }
297
- });
298
-
299
- observer.observe(target, { subtree: true, childList: true });
300
  }
301
 
302
- function applyExplanation(text) {
303
- if (placeholder) {
304
- if (!text || text.startsWith('__NO_SELECTION__') || text.startsWith('__ERROR__')) {
305
- /* Restore original text on failure */
306
- placeholder.replaceWith(document.createTextNode(selectedText));
307
- } else {
308
- placeholder.classList.remove('ai-inline-loading');
309
- placeholder.textContent = text;
310
- placeholder.title = 'AI explanation β€” original: "' + selectedText + '"';
311
  }
312
- placeholder = null;
313
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  isExplaining = false;
315
- savedRange = null;
316
  selectedText = '';
 
317
  hideFAB();
318
  }
319
 
320
- /* ── Push selected text into the hidden Gradio textbox ─────────── */
321
  function pushToGradioInput(value) {
322
  const ta = document.querySelector('#hidden-input textarea');
323
  if (!ta) return;
@@ -327,7 +294,6 @@ HEAD_HTML = """
327
  ta.dispatchEvent(new Event('change', { bubbles: true }));
328
  }
329
 
330
- /* ── Boot ───────────────────────────────────────────────────────── */
331
  function boot() {
332
  buildFAB();
333
  watchOutput();
@@ -346,7 +312,7 @@ HEAD_HTML = """
346
  with gr.Blocks(head=HEAD_HTML) as demo:
347
  gr.Markdown(
348
  "### πŸ“˜ Highlight any text β€” a floating **Explain 🧠** button will appear. "
349
- "Click it to replace the selection with an inline AI explanation."
350
  )
351
 
352
  view_toggle = gr.Radio(
 
119
  return YOUR_WORK_HTML if choice == "My Work" else HF_REFERENCE_HTML
120
 
121
 
122
+ # ---- All the magic: floating sticky button + popup tooltip ----
123
  HEAD_HTML = """
124
  <style>
125
  /* ── Floating toolbar ─────────────────────────────────────────── */
 
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;
 
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');
 
210
  fab.disabled = true;
211
  }
212
 
 
213
  document.addEventListener('mouseup', function (e) {
214
  if (e.target.closest('#explain-fab') || isExplaining) return;
215
  setTimeout(function () {
 
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;
 
294
  ta.dispatchEvent(new Event('change', { bubbles: true }));
295
  }
296
 
 
297
  function boot() {
298
  buildFAB();
299
  watchOutput();
 
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(