Files changed (1) hide show
  1. app.py +198 -21
app.py CHANGED
@@ -614,6 +614,97 @@ footer{display:none!important}
614
  color:#FF69B4;
615
  }
616
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
  /* ── JSON Panel ── */
618
  .json-panel{
619
  background:#18181b;
@@ -723,7 +814,7 @@ footer{display:none!important}
723
  80%{transform:translateX(3px)}
724
  }
725
 
726
- /* ── Primary Button — Force white text & SVG in ALL themes ── */
727
  .btn-run{
728
  display:flex;align-items:center;justify-content:center;gap:8px;
729
  width:100%;
@@ -822,7 +913,7 @@ footer{display:none!important}
822
  fill:#ffffff!important;
823
  }
824
 
825
- /* ── Comprehensive white-force for run button across every theme ── */
826
  #custom-run-btn,
827
  #custom-run-btn *,
828
  #custom-run-btn span,
@@ -836,7 +927,7 @@ footer{display:none!important}
836
  fill:#ffffff!important;
837
  }
838
 
839
- /* ── Mode button active state — white in ALL themes ── */
840
  .mode-btn.active,
841
  .mode-btn.active *,
842
  .mode-btn.active span,
@@ -1134,9 +1225,9 @@ footer{display:none!important}
1134
  ::-webkit-scrollbar-thumb{background:#27272a;border-radius:4px}
1135
  ::-webkit-scrollbar-thumb:hover{background:#3f3f46}
1136
 
1137
- /* ══════════════════════════════════════════════════════════════
1138
- FORCE WHITE on Run Button + Mode Buttons in EVERY theme
1139
- ══════════════════════════════════════════════════════════════ */
1140
 
1141
  body:not(.dark) .btn-run,
1142
  body:not(.dark) .btn-run *,
@@ -1235,6 +1326,9 @@ body:not(.dark) #mode-mover.active *{
1235
  .app-main-right{width:100%}
1236
  .app-main-left{border-right:none;border-bottom:1px solid #27272a}
1237
  .mode-switcher{flex-wrap:wrap}
 
 
 
1238
  }
1239
  """
1240
 
@@ -1645,6 +1739,23 @@ function initCanvasBbox() {
1645
  }
1646
  function hideStatus() { status.style.display = 'none'; }
1647
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1648
  function resetCanvas() {
1649
  baseImg = null;
1650
  boxes.length = 0;
@@ -1800,20 +1911,7 @@ function initCanvasBbox() {
1800
  if (!file || !file.type.startsWith('image/')) return;
1801
  const reader = new FileReader();
1802
  reader.onload = (event) => {
1803
- const dataUrl = event.target.result;
1804
- const img = new window.Image();
1805
- img.crossOrigin = 'anonymous';
1806
- img.onload = () => {
1807
- baseImg = img;
1808
- boxes.length = 0;
1809
- window.__bboxBoxes = boxes;
1810
- selectedIdx = -1;
1811
- fitSize(img.naturalWidth, img.naturalHeight);
1812
- syncToGradio(); redraw(); hideStatus();
1813
- uploadPrompt.style.display = 'none';
1814
- syncImageToGradio(dataUrl);
1815
- };
1816
- img.src = dataUrl;
1817
  };
1818
  reader.readAsDataURL(file);
1819
  }
@@ -2021,6 +2119,54 @@ function initCanvasBbox() {
2021
  });
2022
  }
2023
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2024
  new ResizeObserver(() => {
2025
  if (baseImg) { fitSize(baseImg.naturalWidth, baseImg.naturalHeight); redraw(); }
2026
  }).observe(wrap);
@@ -2300,6 +2446,34 @@ with gr.Blocks() as demo:
2300
  <kbd>Reset</kbd> removes image
2301
  </div>
2302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2303
  <div class="json-panel">
2304
  <div class="json-panel-title">Bounding Boxes</div>
2305
  <div class="json-panel-content" id="bbox-json-content">[
@@ -2435,5 +2609,8 @@ with gr.Blocks() as demo:
2435
  if __name__ == "__main__":
2436
  demo.launch(
2437
  css=css,
2438
- mcp_server=True, ssr_mode=False, show_error=True
 
 
 
2439
  )
 
614
  color:#FF69B4;
615
  }
616
 
617
+ /* ── Examples Section ── */
618
+ .examples-section{
619
+ background:#141416;
620
+ border-top:1px solid #27272a;
621
+ padding:12px 16px 14px;
622
+ }
623
+ .examples-title{
624
+ font-size:12px;
625
+ font-weight:600;
626
+ color:#71717a;
627
+ text-transform:uppercase;
628
+ letter-spacing:.8px;
629
+ margin-bottom:10px;
630
+ display:flex;
631
+ align-items:center;
632
+ gap:8px;
633
+ }
634
+ .examples-title::before{
635
+ content:'\u25c6';
636
+ color:#FF1493;
637
+ font-size:10px;
638
+ }
639
+ .examples-grid{
640
+ display:flex;
641
+ gap:10px;
642
+ overflow-x:auto;
643
+ padding-bottom:4px;
644
+ }
645
+ .examples-grid::-webkit-scrollbar{height:4px}
646
+ .examples-grid::-webkit-scrollbar-track{background:transparent}
647
+ .examples-grid::-webkit-scrollbar-thumb{background:#27272a;border-radius:2px}
648
+ .example-card{
649
+ flex:0 0 auto;
650
+ width:120px;
651
+ cursor:pointer;
652
+ border-radius:10px;
653
+ border:2px solid #27272a;
654
+ overflow:hidden;
655
+ background:#09090b;
656
+ transition:all .2s ease;
657
+ display:flex;
658
+ flex-direction:column;
659
+ user-select:none;
660
+ }
661
+ .example-card:hover{
662
+ border-color:#FF1493;
663
+ transform:translateY(-2px);
664
+ box-shadow:0 6px 20px rgba(255,20,147,.25);
665
+ }
666
+ .example-card:active{
667
+ transform:translateY(0);
668
+ border-color:#FF69B4;
669
+ }
670
+ .example-card.loading{
671
+ opacity:0.5;
672
+ pointer-events:none;
673
+ }
674
+ .example-card.loading .example-label{
675
+ color:#FF69B4;
676
+ }
677
+ .example-card .example-thumb{
678
+ width:100%;
679
+ height:80px;
680
+ object-fit:cover;
681
+ display:block;
682
+ background:#09090b;
683
+ }
684
+ .example-card .example-thumb-placeholder{
685
+ width:100%;
686
+ height:80px;
687
+ display:flex;
688
+ align-items:center;
689
+ justify-content:center;
690
+ background:linear-gradient(135deg,#1a1a20 0%,#0f0f13 100%);
691
+ color:#3f3f46;
692
+ font-size:24px;
693
+ }
694
+ .example-card .example-label{
695
+ display:block;
696
+ padding:6px 8px;
697
+ font-size:11px;
698
+ font-weight:600;
699
+ color:#a1a1aa;
700
+ text-align:center;
701
+ white-space:nowrap;
702
+ overflow:hidden;
703
+ text-overflow:ellipsis;
704
+ border-top:1px solid #27272a;
705
+ background:#0d0d10;
706
+ }
707
+
708
  /* ── JSON Panel ── */
709
  .json-panel{
710
  background:#18181b;
 
814
  80%{transform:translateX(3px)}
815
  }
816
 
817
+ /* ── Primary Button ── */
818
  .btn-run{
819
  display:flex;align-items:center;justify-content:center;gap:8px;
820
  width:100%;
 
913
  fill:#ffffff!important;
914
  }
915
 
916
+ /* ── Comprehensive white-force for run button ── */
917
  #custom-run-btn,
918
  #custom-run-btn *,
919
  #custom-run-btn span,
 
927
  fill:#ffffff!important;
928
  }
929
 
930
+ /* ── Mode button active state ── */
931
  .mode-btn.active,
932
  .mode-btn.active *,
933
  .mode-btn.active span,
 
1225
  ::-webkit-scrollbar-thumb{background:#27272a;border-radius:4px}
1226
  ::-webkit-scrollbar-thumb:hover{background:#3f3f46}
1227
 
1228
+ /* ══════════════════════════════════════════════
1229
+ FORCE WHITE on Run Button + Mode Buttons
1230
+ ══════════════════════════════════════════════ */
1231
 
1232
  body:not(.dark) .btn-run,
1233
  body:not(.dark) .btn-run *,
 
1326
  .app-main-right{width:100%}
1327
  .app-main-left{border-right:none;border-bottom:1px solid #27272a}
1328
  .mode-switcher{flex-wrap:wrap}
1329
+ .examples-grid{gap:8px}
1330
+ .example-card{width:100px}
1331
+ .example-card .example-thumb{height:64px}
1332
  }
1333
  """
1334
 
 
1739
  }
1740
  function hideStatus() { status.style.display = 'none'; }
1741
 
1742
+ /* ── Shared image loader (used by file input, drag-drop, examples) ── */
1743
+ function loadImageFromDataUrl(dataUrl) {
1744
+ const img = new window.Image();
1745
+ img.crossOrigin = 'anonymous';
1746
+ img.onload = () => {
1747
+ baseImg = img;
1748
+ boxes.length = 0;
1749
+ window.__bboxBoxes = boxes;
1750
+ selectedIdx = -1;
1751
+ fitSize(img.naturalWidth, img.naturalHeight);
1752
+ syncToGradio(); redraw(); hideStatus();
1753
+ uploadPrompt.style.display = 'none';
1754
+ syncImageToGradio(dataUrl);
1755
+ };
1756
+ img.src = dataUrl;
1757
+ }
1758
+
1759
  function resetCanvas() {
1760
  baseImg = null;
1761
  boxes.length = 0;
 
1911
  if (!file || !file.type.startsWith('image/')) return;
1912
  const reader = new FileReader();
1913
  reader.onload = (event) => {
1914
+ loadImageFromDataUrl(event.target.result);
 
 
 
 
 
 
 
 
 
 
 
 
 
1915
  };
1916
  reader.readAsDataURL(file);
1917
  }
 
2119
  });
2120
  }
2121
 
2122
+ /* ── Example Cards Click Handler ── */
2123
+ function loadExampleFromUrl(url, cardEl) {
2124
+ if (cardEl) {
2125
+ cardEl.classList.add('loading');
2126
+ const lbl = cardEl.querySelector('.example-label');
2127
+ if (lbl) lbl.textContent = 'Loading\u2026';
2128
+ }
2129
+ showStatus('Loading example\u2026');
2130
+
2131
+ fetch(url)
2132
+ .then(r => {
2133
+ if (!r.ok) throw new Error('HTTP ' + r.status);
2134
+ return r.blob();
2135
+ })
2136
+ .then(blob => {
2137
+ const reader = new FileReader();
2138
+ reader.onload = (event) => {
2139
+ loadImageFromDataUrl(event.target.result);
2140
+ if (cardEl) {
2141
+ cardEl.classList.remove('loading');
2142
+ const lbl = cardEl.querySelector('.example-label');
2143
+ if (lbl) lbl.textContent = cardEl.getAttribute('data-label') || 'Example';
2144
+ }
2145
+ showStatus('Example loaded');
2146
+ setTimeout(hideStatus, 1500);
2147
+ window.showToast('Example image loaded. Draw bounding boxes and click Run.', 'success', 'Image Ready');
2148
+ };
2149
+ reader.readAsDataURL(blob);
2150
+ })
2151
+ .catch(err => {
2152
+ console.error('Failed to load example:', err);
2153
+ if (cardEl) {
2154
+ cardEl.classList.remove('loading');
2155
+ const lbl = cardEl.querySelector('.example-label');
2156
+ if (lbl) lbl.textContent = cardEl.getAttribute('data-label') || 'Example';
2157
+ }
2158
+ hideStatus();
2159
+ window.showToast('Could not load example image. Make sure example files exist in the <b>examples/</b> folder.', 'error', 'Load Failed');
2160
+ });
2161
+ }
2162
+
2163
+ document.querySelectorAll('.example-card').forEach(card => {
2164
+ card.addEventListener('click', () => {
2165
+ const src = card.getAttribute('data-src');
2166
+ if (src) loadExampleFromUrl(src, card);
2167
+ });
2168
+ });
2169
+
2170
  new ResizeObserver(() => {
2171
  if (baseImg) { fitSize(baseImg.naturalWidth, baseImg.naturalHeight); redraw(); }
2172
  }).observe(wrap);
 
2446
  <kbd>Reset</kbd> removes image
2447
  </div>
2448
 
2449
+ <!-- ── Quick Examples Section ── -->
2450
+ <div class="examples-section">
2451
+ <div class="examples-title">Quick Examples &mdash; Click to Load</div>
2452
+ <div class="examples-grid">
2453
+ <div class="example-card" data-src="/file=examples/1.jpg" data-label="Example 1">
2454
+ <img class="example-thumb" src="/file=examples/1.jpg" alt="Example 1" loading="lazy"
2455
+ onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"
2456
+ />
2457
+ <div class="example-thumb-placeholder" style="display:none">\U0001f5bc</div>
2458
+ <span class="example-label">Example 1</span>
2459
+ </div>
2460
+ <div class="example-card" data-src="/file=examples/2.jpg" data-label="Example 2">
2461
+ <img class="example-thumb" src="/file=examples/2.jpg" alt="Example 2" loading="lazy"
2462
+ onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"
2463
+ />
2464
+ <div class="example-thumb-placeholder" style="display:none">\U0001f5bc</div>
2465
+ <span class="example-label">Example 2</span>
2466
+ </div>
2467
+ <div class="example-card" data-src="/file=examples/3.jpg" data-label="Example 3">
2468
+ <img class="example-thumb" src="/file=examples/3.jpg" alt="Example 3" loading="lazy"
2469
+ onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"
2470
+ />
2471
+ <div class="example-thumb-placeholder" style="display:none">\U0001f5bc</div>
2472
+ <span class="example-label">Example 3</span>
2473
+ </div>
2474
+ </div>
2475
+ </div>
2476
+
2477
  <div class="json-panel">
2478
  <div class="json-panel-title">Bounding Boxes</div>
2479
  <div class="json-panel-content" id="bbox-json-content">[
 
2609
  if __name__ == "__main__":
2610
  demo.launch(
2611
  css=css,
2612
+ mcp_server=True,
2613
+ ssr_mode=False,
2614
+ show_error=True,
2615
+ allowed_paths=["examples"],
2616
  )