Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 +
|
| 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 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 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 |
-
|
| 303 |
-
|
| 304 |
-
if (!
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
placeholder.title = 'AI explanation β original: "' + selectedText + '"';
|
| 311 |
}
|
| 312 |
-
|
| 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
|
| 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(
|