Spaces:
Running
Running
add error message banner
Browse files
app.py
CHANGED
|
@@ -389,7 +389,124 @@ with gr.Blocks(title="🧳 Torn Foreign Stocks") as iface:
|
|
| 389 |
["tear gas in argentina", "smoke grenade in south africa", "flash grenade in switzerland"], c),
|
| 390 |
inputs=[capacity_slider], outputs=[result_df, meta_box])
|
| 391 |
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
gr.HTML("""
|
| 394 |
<script>
|
| 395 |
(function () {
|
|
|
|
| 389 |
["tear gas in argentina", "smoke grenade in south africa", "flash grenade in switzerland"], c),
|
| 390 |
inputs=[capacity_slider], outputs=[result_df, meta_box])
|
| 391 |
|
| 392 |
+
|
| 393 |
+
# --- JS: global error banner (captures JS errors & unhandled promise rejections) ---
|
| 394 |
+
gr.HTML("""
|
| 395 |
+
<script>
|
| 396 |
+
(function () {
|
| 397 |
+
const DEDUP_MS = 10000; // suppress duplicates for 10s
|
| 398 |
+
const errorTimestamps = new Map();
|
| 399 |
+
// Inject minimal styles
|
| 400 |
+
const css = `
|
| 401 |
+
.errban-wrap{position:fixed;inset:auto 0 0 0;top:0;z-index:2147483647;pointer-events:none}
|
| 402 |
+
.errban{pointer-events:auto;margin:8px;border-left:4px solid #ef4444;background:#fee2e2;color:#991b1b;
|
| 403 |
+
box-shadow:0 6px 16px rgba(0,0,0,.15);border-radius:8px;overflow:hidden;font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
|
| 404 |
+
.errban-hd{display:flex;gap:8px;align-items:center;padding:10px 12px}
|
| 405 |
+
.errban-title{font-weight:600;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 406 |
+
.errban-btn{appearance:none;border:0;background:transparent;color:#7f1d1d;cursor:pointer;padding:6px 8px;border-radius:6px}
|
| 407 |
+
.errban-btn:hover{background:rgba(153,27,27,.1)}
|
| 408 |
+
.errban-body{display:none;border-top:1px dashed rgba(153,27,27,.35);padding:10px 12px;background:#ffe4e6;max-height:40vh;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap}
|
| 409 |
+
@media (prefers-color-scheme: dark){
|
| 410 |
+
.errban{background:#2b0f12;color:#ffd7d7;border-left-color:#ff6b6b}
|
| 411 |
+
.errban-body{background:#361317}
|
| 412 |
+
.errban-btn{color:#ffd7d7}
|
| 413 |
+
.errban-btn:hover{background:rgba(255,215,215,.12)}
|
| 414 |
+
}`;
|
| 415 |
+
const style = document.createElement('style');
|
| 416 |
+
style.textContent = css;
|
| 417 |
+
document.head.appendChild(style);
|
| 418 |
+
// Root container
|
| 419 |
+
const root = document.createElement('div');
|
| 420 |
+
root.className = 'errban-wrap';
|
| 421 |
+
document.body.appendChild(root);
|
| 422 |
+
function now() { return Date.now(); }
|
| 423 |
+
function dedup(key) {
|
| 424 |
+
const t = errorTimestamps.get(key) || 0;
|
| 425 |
+
if (now() - t < DEDUP_MS) return true;
|
| 426 |
+
errorTimestamps.set(key, now());
|
| 427 |
+
return false;
|
| 428 |
+
}
|
| 429 |
+
function showBanner(title, details) {
|
| 430 |
+
const key = title + '|' + (details || '');
|
| 431 |
+
if (dedup(key)) return;
|
| 432 |
+
const el = document.createElement('div');
|
| 433 |
+
el.className = 'errban';
|
| 434 |
+
const hd = document.createElement('div');
|
| 435 |
+
hd.className = 'errban-hd';
|
| 436 |
+
const ttl = document.createElement('div');
|
| 437 |
+
ttl.className = 'errban-title';
|
| 438 |
+
ttl.textContent = title;
|
| 439 |
+
const copyBtn = document.createElement('button');
|
| 440 |
+
copyBtn.className = 'errban-btn';
|
| 441 |
+
copyBtn.textContent = 'Copy';
|
| 442 |
+
copyBtn.title = 'Copy error details';
|
| 443 |
+
copyBtn.onclick = async () => {
|
| 444 |
+
try {
|
| 445 |
+
await navigator.clipboard.writeText(details || title);
|
| 446 |
+
copyBtn.textContent = 'Copied!';
|
| 447 |
+
setTimeout(() => (copyBtn.textContent = 'Copy'), 1200);
|
| 448 |
+
} catch {}
|
| 449 |
+
};
|
| 450 |
+
const detBtn = document.createElement('button');
|
| 451 |
+
detBtn.className = 'errban-btn';
|
| 452 |
+
detBtn.textContent = 'Details';
|
| 453 |
+
const body = document.createElement('div');
|
| 454 |
+
body.className = 'errban-body';
|
| 455 |
+
body.textContent = (details || '').trim() || '(no additional details)';
|
| 456 |
+
detBtn.onclick = () => {
|
| 457 |
+
body.style.display = body.style.display === 'block' ? 'none' : 'block';
|
| 458 |
+
};
|
| 459 |
+
const closeBtn = document.createElement('button');
|
| 460 |
+
closeBtn.className = 'errban-btn';
|
| 461 |
+
closeBtn.textContent = 'Dismiss';
|
| 462 |
+
closeBtn.onclick = () => el.remove();
|
| 463 |
+
hd.append(ttl, copyBtn, detBtn, closeBtn);
|
| 464 |
+
el.appendChild(hd);
|
| 465 |
+
el.appendChild(body);
|
| 466 |
+
// Insert newest at top
|
| 467 |
+
root.prepend(el);
|
| 468 |
+
}
|
| 469 |
+
function formatErrorEvent(ev) {
|
| 470 |
+
const parts = [];
|
| 471 |
+
if (ev.message) parts.push(ev.message);
|
| 472 |
+
if (ev.filename) parts.push(`${ev.filename}${ev.lineno ? ':' + ev.lineno : ''}${ev.colno ? ':' + ev.colno : ''}`);
|
| 473 |
+
if (ev.error && ev.error.stack) parts.push(ev.error.stack);
|
| 474 |
+
return {
|
| 475 |
+
title: ev.message || 'Script error',
|
| 476 |
+
details: parts.join('\n')
|
| 477 |
+
};
|
| 478 |
+
}
|
| 479 |
+
function formatRejection(ev) {
|
| 480 |
+
const r = ev.reason;
|
| 481 |
+
let title = 'Unhandled promise rejection';
|
| 482 |
+
let details = '';
|
| 483 |
+
if (r && typeof r === 'object') {
|
| 484 |
+
title = r.message ? `${title}: ${r.message}` : title;
|
| 485 |
+
details = (r.stack || JSON.stringify(r, Object.getOwnPropertyNames(r))).toString();
|
| 486 |
+
} else {
|
| 487 |
+
details = String(r);
|
| 488 |
+
}
|
| 489 |
+
return { title, details };
|
| 490 |
+
}
|
| 491 |
+
// Global listeners
|
| 492 |
+
window.addEventListener('error', (ev) => {
|
| 493 |
+
const { title, details } = formatErrorEvent(ev);
|
| 494 |
+
showBanner(title, details);
|
| 495 |
+
});
|
| 496 |
+
window.addEventListener('unhandledrejection', (ev) => {
|
| 497 |
+
const { title, details } = formatRejection(ev);
|
| 498 |
+
showBanner(title, details);
|
| 499 |
+
});
|
| 500 |
+
// Manual reporting API
|
| 501 |
+
window.reportErrorBanner = function (err, context) {
|
| 502 |
+
const title = context ? `Error: ${context}` : (err && err.message) || 'Error';
|
| 503 |
+
const details = (err && err.stack) ? err.stack : String(err);
|
| 504 |
+
showBanner(title, details);
|
| 505 |
+
};
|
| 506 |
+
})();
|
| 507 |
+
</script>
|
| 508 |
+
""")
|
| 509 |
+
# --- JS: convert all UTC ISO timestamps to browser's local time ---
|
| 510 |
gr.HTML("""
|
| 511 |
<script>
|
| 512 |
(function () {
|