prithivMLmods commited on
Commit
dc24e06
·
verified ·
1 Parent(s): f00582f

update [input panel]

Browse files
Files changed (1) hide show
  1. index.html +227 -134
index.html CHANGED
@@ -3,15 +3,15 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Qwen3.8 Studio · 27B Object Detection</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
9
  *{box-sizing:border-box;margin:0;padding:0}
10
  :root{
11
  --bg:#0a0a0c;--panel:#131316;--panel2:#0e0e11;--canvas:#08080a;
12
- --border:#2a1f1f;--border2:#3f2f2f;
13
  --text:#e4e4e7;--muted:#a1a1aa;--dim:#6b6b75;--faint:#4a4a52;
14
- --accent:#FF4500;--accent2:#FF6B33;--accent3:#FF9966;
15
  --menubar-h:48px;--statusbar-h:30px;--rail-w:52px;--inspector-w:360px;--filmstrip-h:132px;
16
  }
17
  html,body{height:100%}
@@ -38,7 +38,7 @@ body{
38
  padding:0 16px;z-index:50;
39
  }
40
  .menubar-logo{
41
- width:28px;height:28px;background:linear-gradient(135deg,#FF4500,#FF6B33,#FF9966);
42
  border-radius:8px;display:flex;align-items:center;justify-content:center;
43
  box-shadow:0 2px 8px rgba(255,69,0,.35);flex-shrink:0;
44
  }
@@ -115,6 +115,7 @@ body{
115
  font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--faint);
116
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;flex:1;
117
  }
 
118
 
119
  .canvas-stage{
120
  flex:1;position:relative;overflow:hidden;min-height:0;
@@ -155,7 +156,7 @@ body{
155
  .loader-text{font-size:13px;color:var(--muted);font-weight:500;text-align:center;padding:0 16px}
156
  .loader-bar-track{width:220px;height:4px;background:var(--border);border-radius:2px;overflow:hidden}
157
  .loader-bar-fill{
158
- height:100%;width:100%;background:linear-gradient(90deg,#FF4500,#FF6B33,#FF4500);
159
  background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:2px;
160
  transition:width .3s ease;
161
  }
@@ -169,8 +170,9 @@ body{
169
  }
170
  .filmstrip-section{
171
  display:flex;flex-direction:column;min-width:0;padding:8px 0 8px 14px;
172
- flex:1;
173
  }
 
 
174
  .filmstrip-label{
175
  font-size:9px;font-weight:700;color:var(--faint);letter-spacing:1.2px;
176
  text-transform:uppercase;margin-bottom:6px;display:flex;align-items:center;gap:8px;
@@ -287,7 +289,7 @@ body{
287
  box-shadow:0 4px 16px rgba(255,69,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
288
  }
289
  .btn-run:hover:not(:disabled){
290
- background:linear-gradient(135deg,#FF6B33,#FF4500);transform:translateY(-1px);
291
  box-shadow:0 6px 24px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
292
  }
293
  .btn-run:active:not(:disabled){transform:translateY(0)}
@@ -325,33 +327,6 @@ body{
325
  font-weight:500;padding:2px 7px;background:var(--panel2);border:1px solid var(--border);
326
  border-radius:5px;color:var(--muted);flex-shrink:0;
327
  }
328
- .dice-btn{
329
- display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;
330
- background:var(--panel2);border:1px solid var(--border);border-radius:5px;cursor:pointer;
331
- color:var(--dim);transition:all .15s;flex-shrink:0;padding:0;
332
- }
333
- .dice-btn:hover{border-color:var(--accent);color:var(--accent3)}
334
- .dice-btn svg{width:13px;height:13px;fill:currentColor}
335
- .checkbox-row{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--muted);margin-bottom:10px}
336
- .checkbox-row input[type="checkbox"]{accent-color:var(--accent);width:15px;height:15px;cursor:pointer}
337
- .checkbox-row label{color:var(--muted);font-size:12px;cursor:pointer}
338
-
339
- /* Text Output Area */
340
- .text-output-area {
341
- width: 100%;
342
- height: 140px;
343
- background: var(--panel2);
344
- border: 1px solid var(--border);
345
- border-radius: 8px;
346
- padding: 10px 13px;
347
- font-family: 'JetBrains Mono', monospace;
348
- font-size: 12px;
349
- color: var(--accent3);
350
- resize: vertical;
351
- outline: none;
352
- line-height: 1.5;
353
- margin-top: 10px;
354
- }
355
 
356
  /* Examples page */
357
  .examples-list{padding:12px 14px;display:flex;flex-direction:column;gap:10px}
@@ -479,6 +454,7 @@ body{
479
  .canvas-stage img.modern-out-img{max-width:calc(100% - 20px);max-height:52vh}
480
  .canvas-placeholder .cp-sub{font-size:11px;max-width:280px}
481
  .filmstrip{flex-direction:column;height:auto;max-height:none}
 
482
  .filmstrip-section{padding:8px 0 8px 12px}
483
  .fs-thumb{width:68px;height:68px}
484
  .fs-add{width:68px;height:68px}
@@ -514,9 +490,9 @@ body{
514
  <div class="menubar-logo">
515
  <svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12 23c-3.6 0-8-2.69-8-7.5 0-3.5 3-6.5 4.5-8 .27-.27.75-.08.75.28v2.44c0 .42.5.63.72.28C12.28 7.5 13 3 13 1c0-.42.48-.64.8-.35C18 4.5 20 9 20 12c0 5.5-3.5 11-8 11z"/></svg>
516
  </div>
517
- <div class="menubar-title">Qwen3.8 Studio <span>/ 27B Object Detection</span></div>
518
  <span class="menubar-badge">27B</span>
519
- <span class="menubar-badge fast">VISION MODEL</span>
520
  <div class="menubar-spacer"></div>
521
  <div class="menubar-status"><span class="dot" id="conn-dot"></span><span id="conn-text">connecting…</span></div>
522
  <a href="https://github.com/PRITHIVSAKTHIUR/Qwen3.8-27B-Object-Detection"
@@ -540,10 +516,7 @@ body{
540
  </button>
541
  <div class="rail-sep"></div>
542
  <div class="rail-label">Result</div>
543
- <button class="rail-btn" id="use-as-input-btn" title="Use result as input" disabled>
544
- <svg viewBox="0 0 24 24"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 014-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 01-4 4H3"/></svg>
545
- </button>
546
- <button class="rail-btn" id="dl-btn-output" title="Download result image" disabled>
547
  <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
548
  </button>
549
  <button class="rail-btn" id="zoom-btn" title="Zoom result" disabled>
@@ -559,12 +532,13 @@ body{
559
  <button class="seg-btn" id="seg-input">Input</button>
560
  </div>
561
  <div class="canvas-meta" id="canvas-meta">No image loaded</div>
 
562
  </div>
563
 
564
  <div class="canvas-stage" id="output-image-container">
565
  <div class="modern-loader" id="output-loader">
566
  <div class="loader-spinner"></div>
567
- <div class="loader-text" id="loader-text">Running inference&hellip;</div>
568
  <div class="loader-bar-track"><div class="loader-bar-fill" id="loader-bar-fill"></div></div>
569
  </div>
570
 
@@ -581,6 +555,18 @@ body{
581
  </div>
582
  </div>
583
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
584
  </div>
585
 
586
  <!-- ══ Inspector ══ -->
@@ -595,7 +581,7 @@ body{
595
  <div class="insp-section">
596
  <div class="insp-section-title"><span>Instruction</span><span class="char-count" id="char-count">0</span></div>
597
  <textarea id="custom-prompt-input" class="modern-textarea" rows="3"
598
- placeholder="e.g., Detect all cars / Point to the red sign / Map a path from door to lamp"></textarea>
599
  </div>
600
 
601
  <div class="insp-section">
@@ -606,9 +592,9 @@ body{
606
  <div class="insp-section">
607
  <div class="insp-section-title"><span>Task Type</span></div>
608
  <select id="custom-task-select" class="lora-native-select">
609
- <option value="Detect">Object Detection (Bounding Boxes)</option>
610
- <option value="Point">Point Localization (Keypoints)</option>
611
- <option value="Spatial">Spatial Guidance (Paths/Routes)</option>
612
  </select>
613
  </div>
614
 
@@ -621,33 +607,32 @@ body{
621
  </div>
622
 
623
  <div class="insp-section">
624
- <div class="insp-section-title"><span>Advanced Settings</span></div>
625
-
 
 
 
 
626
  <div class="slider-row">
627
- <label>GPU Dur.</label>
628
- <input type="range" id="custom-gpu-dur" min="60" max="300" step="30" value="90">
629
- <span class="slider-val" id="custom-gpu-dur-val">90s</span>
630
  </div>
631
-
632
  <div class="slider-row">
633
- <label>Radius</label>
634
- <input type="range" id="custom-radius" min="2" max="20" step="1" value="6">
635
- <span class="slider-val" id="custom-radius-val">6px</span>
636
  </div>
637
-
638
  <div class="slider-row">
639
- <label>Thick</label>
640
- <input type="range" id="custom-thick" min="1" max="8" step="1" value="2">
641
- <span class="slider-val" id="custom-thick-val">2px</span>
642
  </div>
643
-
644
  <div class="slider-row">
645
- <label>Text</label>
646
- <input type="range" id="custom-text" min="0.2" max="1.5" step="0.05" value="0.5">
647
- <span class="slider-val" id="custom-text-val">0.5</span>
648
  </div>
649
-
650
- <textarea id="text-output-area" class="text-output-area" placeholder="JSON / Text output will appear here..." readonly></textarea>
651
  </div>
652
  </div>
653
 
@@ -688,9 +673,12 @@ const btnClear = document.getElementById('tb-clear');
688
  const promptInput = document.getElementById('custom-prompt-input');
689
  const charCount = document.getElementById('char-count');
690
  const taskSelect = document.getElementById('custom-task-select');
 
691
  const runBtn = document.getElementById('custom-run-btn');
692
  const runBtnLabel = document.getElementById('run-btn-label');
693
  const cancelBtn = document.getElementById('custom-cancel-btn');
 
 
694
  const sbStatus = document.getElementById('sb-status');
695
  const loader = document.getElementById('output-loader');
696
  const loaderText = document.getElementById('loader-text');
@@ -698,9 +686,17 @@ const loaderFill = document.getElementById('loader-bar-fill');
698
  const outBody = document.getElementById('output-image-container');
699
  const outPh = document.getElementById('output-placeholder');
700
  const dlBtn = document.getElementById('dl-btn-output');
701
- const useAsInputBtn= document.getElementById('use-as-input-btn');
702
  const zoomBtn = document.getElementById('zoom-btn');
703
- const textOutput = document.getElementById('text-output-area');
 
 
 
 
 
 
 
 
 
704
  const lightbox = document.getElementById('lightbox');
705
  const lightboxImg = document.getElementById('lightbox-img');
706
  const dropOverlay = document.getElementById('drop-overlay');
@@ -709,27 +705,24 @@ const segResult = document.getElementById('seg-result');
709
  const segInput = document.getElementById('seg-input');
710
  const connDot = document.getElementById('conn-dot');
711
  const connText = document.getElementById('conn-text');
712
- const gpuDurSlider = document.getElementById('custom-gpu-dur');
713
- const gpuDurVal = document.getElementById('custom-gpu-dur-val');
714
- const radiusSlider = document.getElementById('custom-radius');
715
- const radiusVal = document.getElementById('custom-radius-val');
716
- const thickSlider = document.getElementById('custom-thick');
717
- const thickVal = document.getElementById('custom-thick-val');
718
- const textSlider = document.getElementById('custom-text');
719
- const textVal = document.getElementById('custom-text-val');
720
 
721
  /* ── State ── */
722
- let currentImage = null;
723
  let toastTimer = null;
724
  let running = false;
725
  let currentJob = null;
726
- let currentResult = null;
727
- let viewMode = 'result';
 
 
 
 
728
 
729
  const SUGGESTIONS = [
730
  ['Detect Cars', 'Detect all the cars.'],
731
- ['Point to Red', 'Point to the red object.'],
732
- ['Map Path', 'Map a path from the door to the window.'],
 
733
  ];
734
 
735
  /* ── Toast ── */
@@ -774,30 +767,37 @@ function setViewMode(mode) {
774
  segResult.addEventListener('click', () => setViewMode('result'));
775
  segInput.addEventListener('click', () => setViewMode('input'));
776
 
 
 
777
  function renderCanvas() {
778
- outBody.querySelectorAll('img.modern-out-img').forEach(e => e.remove());
779
  const showResult = viewMode === 'result' && currentResult;
780
  const showInput = viewMode === 'input' && currentImage;
781
 
782
  if (showResult || showInput) {
783
  outPh.style.display = 'none';
784
- const img = document.createElement('img');
785
- img.className = 'modern-out-img';
786
- img.addEventListener('click', () => {
787
- if (img.src) { lightboxImg.src = img.src; lightbox.classList.add('visible'); }
788
- });
789
- outBody.appendChild(img);
790
-
 
 
791
  if (showResult) {
792
- img.src = currentResult.image;
793
- canvasMeta.textContent = 'result · ' + (currentResult.task || 'inference');
794
  } else {
795
  img.src = currentImage.b64;
796
  canvasMeta.textContent = 'input · ' + (currentImage.name || 'image');
797
  }
798
  } else {
 
799
  outPh.style.display = '';
800
- canvasMeta.textContent = currentImage ? 'input ready — run an inference' : 'No image loaded';
 
 
801
  }
802
  updateRailState();
803
  }
@@ -806,38 +806,74 @@ function updateRailState() {
806
  const hasResult = !!currentResult;
807
  dlBtn.disabled = !hasResult;
808
  zoomBtn.disabled = !hasResult && !currentImage;
809
- useAsInputBtn.disabled = !hasResult;
810
  btnRemove.disabled = !currentImage;
811
  btnClear.disabled = !currentImage;
812
  }
813
 
814
  /* ── Inputs filmstrip ── */
 
 
 
 
 
 
815
  function setImage(b64, name) {
816
  currentImage = { b64, name: name || 'image' };
817
- renderCanvas();
818
- updateRailState();
 
 
819
  }
820
 
821
  function removeImage() {
822
  currentImage = null;
 
 
823
  renderCanvas();
824
  }
825
 
826
  function clearAll() {
827
  currentImage = null;
828
- currentResult = null;
829
- textOutput.value = '';
830
  renderCanvas();
831
  }
832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  function processFiles(files) {
834
  const file = files && files[0];
835
  if (!file || !file.type.startsWith('image/')) return 0;
836
  const reader = new FileReader();
837
- reader.onload = (e) => {
838
- setImage(e.target.result, file.name);
839
- setViewMode('input');
840
- };
841
  reader.readAsDataURL(file);
842
  return 1;
843
  }
@@ -932,14 +968,17 @@ function bindSlider(id, suffix='') {
932
  const valSpan = document.getElementById(id + '-val');
933
  slider.addEventListener('input', () => { valSpan.textContent = slider.value + suffix; });
934
  }
935
- bindSlider('custom-gpu-dur', 's');
936
- bindSlider('custom-radius', 'px');
937
- bindSlider('custom-thick', 'px');
938
- bindSlider('custom-text', '');
 
 
 
939
 
940
  /* ── Loader ── */
941
  function showLoader(text) {
942
- loaderText.textContent = text || 'Running inference…';
943
  loaderFill.classList.remove('determinate');
944
  loaderFill.style.width = '100%';
945
  loader.classList.add('active');
@@ -957,30 +996,62 @@ function hideLoader(statusText, cls) {
957
  setStatus(statusText || 'Done', cls || 'done');
958
  }
959
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960
  /* ── Lightbox ── */
961
  lightbox.addEventListener('click', () => lightbox.classList.remove('visible'));
962
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') lightbox.classList.remove('visible'); });
963
  zoomBtn.addEventListener('click', () => {
964
- const src = (viewMode === 'result' && currentResult) ? currentResult.image
 
965
  : (currentImage ? currentImage.b64 : null);
966
  if (src) { lightboxImg.src = src; lightbox.classList.add('visible'); }
967
  });
968
 
969
- /* ── Download / chain ── */
970
  dlBtn.addEventListener('click', () => {
971
- if (currentResult && currentResult.image) {
972
  const a = document.createElement('a');
973
- a.href = currentResult.image;
974
- a.download = 'qwen_inference_output.png';
975
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
976
  }
977
  });
978
- useAsInputBtn.addEventListener('click', () => {
979
- if (!currentResult) return;
980
- setImage(currentResult.image, 'inference_result.png');
981
- setViewMode('input');
982
- showToast('Result set as input image', 'info');
983
- });
984
 
985
  /* ── Backend connection ── */
986
  let client = null;
@@ -1011,13 +1082,19 @@ function renderConfig(cfg) {
1011
  card.innerHTML =
1012
  '<div class="example-thumb-wrap">' + thumbHtml + '</div>'
1013
  + '<div class="example-body">'
1014
- + '<div class="example-meta"><span class="example-lora-badge">' + ex.task + '</span></div>'
1015
  + '<div class="example-prompt-text"></div>'
1016
  + '</div>';
 
1017
  card.querySelector('.example-prompt-text').textContent = promptShort;
1018
  card.addEventListener('click', () => loadExample(card, ex.idx));
1019
  list.appendChild(card);
1020
  });
 
 
 
 
 
1021
  }
1022
 
1023
  async function loadExample(card, idx) {
@@ -1030,9 +1107,10 @@ async function loadExample(card, idx) {
1030
  const data = result.data[0];
1031
  if (data && data.status === 'ok' && data.image) {
1032
  if (data.prompt) { promptInput.value = data.prompt; updateCharCount(); }
1033
- if (data.task) taskSelect.value = data.task;
 
 
1034
  setImage(data.image, data.name || 'example.jpg');
1035
- setViewMode('input');
1036
  tabEdit.click();
1037
  showToast('Example loaded', 'info');
1038
  } else {
@@ -1059,7 +1137,7 @@ function validateBeforeRun() {
1059
  const hasImage = !!currentImage;
1060
  if (!hasImage && !promptVal) { showToast('Please upload an image and enter a prompt', 'error'); flashPromptError(); return false; }
1061
  if (!hasImage) { showToast('Please upload an image', 'error'); return false; }
1062
- if (!promptVal) { showToast('Please enter a prompt', 'warning'); flashPromptError(); return false; }
1063
  return true;
1064
  }
1065
 
@@ -1079,14 +1157,17 @@ async function runEdit() {
1079
  setViewMode('result');
1080
  showLoader('Submitting to queue…');
1081
 
 
 
 
1082
  const payload = {
1083
  image_b64: currentImage ? currentImage.b64 : '',
1084
- prompt: promptInput.value,
1085
- task: taskSelect.value,
1086
- point_radius: parseInt(radiusSlider.value) || 6,
1087
- box_thickness: parseInt(thickSlider.value) || 2,
1088
- text_scale: parseFloat(textSlider.value) || 0.5,
1089
- gpu_duration_seconds: parseInt(gpuDurSlider.value) || 90,
1090
  };
1091
 
1092
  try {
@@ -1101,27 +1182,36 @@ async function runEdit() {
1101
  setStatus('Queued');
1102
  } else if (msg.status === 'generating') {
1103
  setStatus('Processing…');
1104
- setLoaderProgress('Running Qwen3.8 vision model…', null);
 
 
 
 
 
 
 
 
 
 
 
1105
  } else if (msg.status === 'error') {
1106
  throw new Error(msg.message || 'Generation failed');
1107
  }
1108
  } else if (msg.type === 'data') {
1109
  const out = msg.data && msg.data[0];
1110
  if (out && out.image) {
1111
- currentResult = out;
1112
- if (out.text) {
1113
- textOutput.value = out.text;
1114
- }
1115
  hideLoader('Done', 'done');
1116
- showToast('Inference completed successfully', 'info');
1117
- renderCanvas();
1118
  } else {
1119
  hideLoader('Done', 'done');
1120
  }
1121
  }
1122
  }
1123
  } catch (e) {
1124
- console.error('Inference error:', e);
1125
  hideLoader('Error', 'error');
1126
  const msg = (e && e.message) ? e.message : 'Generation failed';
1127
  showToast(msg.length > 120 ? msg.slice(0, 120) + '…' : msg, 'error');
@@ -1140,7 +1230,7 @@ cancelBtn.addEventListener('click', async () => {
1140
  }
1141
  setRunningUI(false);
1142
  hideLoader('Cancelled');
1143
- showToast('Inference cancelled', 'warning');
1144
  });
1145
 
1146
  document.addEventListener('keydown', (e) => {
@@ -1150,6 +1240,9 @@ document.addEventListener('keydown', (e) => {
1150
  }
1151
  });
1152
 
 
 
 
1153
  renderCanvas();
1154
  </script>
1155
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Qwen3.8 Studio · Object Detection</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
9
  *{box-sizing:border-box;margin:0;padding:0}
10
  :root{
11
  --bg:#0a0a0c;--panel:#131316;--panel2:#0e0e11;--canvas:#08080a;
12
+ --border:#2a2323;--border2:#3f2f2f;
13
  --text:#e4e4e7;--muted:#a1a1aa;--dim:#6b6b75;--faint:#4a4a52;
14
+ --accent:#FF4500;--accent2:#FF6347;--accent3:#FFA07A;
15
  --menubar-h:48px;--statusbar-h:30px;--rail-w:52px;--inspector-w:360px;--filmstrip-h:132px;
16
  }
17
  html,body{height:100%}
 
38
  padding:0 16px;z-index:50;
39
  }
40
  .menubar-logo{
41
+ width:28px;height:28px;background:linear-gradient(135deg,#FF4500,#FF6347,#FFA07A);
42
  border-radius:8px;display:flex;align-items:center;justify-content:center;
43
  box-shadow:0 2px 8px rgba(255,69,0,.35);flex-shrink:0;
44
  }
 
115
  font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--faint);
116
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;flex:1;
117
  }
118
+ .canvas-actions{display:flex;align-items:center;gap:6px}
119
 
120
  .canvas-stage{
121
  flex:1;position:relative;overflow:hidden;min-height:0;
 
156
  .loader-text{font-size:13px;color:var(--muted);font-weight:500;text-align:center;padding:0 16px}
157
  .loader-bar-track{width:220px;height:4px;background:var(--border);border-radius:2px;overflow:hidden}
158
  .loader-bar-fill{
159
+ height:100%;width:100%;background:linear-gradient(90deg,#FF4500,#FF6347,#FF4500);
160
  background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:2px;
161
  transition:width .3s ease;
162
  }
 
170
  }
171
  .filmstrip-section{
172
  display:flex;flex-direction:column;min-width:0;padding:8px 0 8px 14px;
 
173
  }
174
+ .filmstrip-section.inputs{flex:1.2;border-right:1px solid var(--border)}
175
+ .filmstrip-section.history{flex:1}
176
  .filmstrip-label{
177
  font-size:9px;font-weight:700;color:var(--faint);letter-spacing:1.2px;
178
  text-transform:uppercase;margin-bottom:6px;display:flex;align-items:center;gap:8px;
 
289
  box-shadow:0 4px 16px rgba(255,69,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
290
  }
291
  .btn-run:hover:not(:disabled){
292
+ background:linear-gradient(135deg,#FF6347,#FF4500);transform:translateY(-1px);
293
  box-shadow:0 6px 24px rgba(255,69,0,.45),inset 0 1px 0 rgba(255,255,255,.15);
294
  }
295
  .btn-run:active:not(:disabled){transform:translateY(0)}
 
327
  font-weight:500;padding:2px 7px;background:var(--panel2);border:1px solid var(--border);
328
  border-radius:5px;color:var(--muted);flex-shrink:0;
329
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
331
  /* Examples page */
332
  .examples-list{padding:12px 14px;display:flex;flex-direction:column;gap:10px}
 
454
  .canvas-stage img.modern-out-img{max-width:calc(100% - 20px);max-height:52vh}
455
  .canvas-placeholder .cp-sub{font-size:11px;max-width:280px}
456
  .filmstrip{flex-direction:column;height:auto;max-height:none}
457
+ .filmstrip-section.inputs{border-right:none;border-bottom:1px solid var(--border)}
458
  .filmstrip-section{padding:8px 0 8px 12px}
459
  .fs-thumb{width:68px;height:68px}
460
  .fs-add{width:68px;height:68px}
 
490
  <div class="menubar-logo">
491
  <svg viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12 23c-3.6 0-8-2.69-8-7.5 0-3.5 3-6.5 4.5-8 .27-.27.75-.08.75.28v2.44c0 .42.5.63.72.28C12.28 7.5 13 3 13 1c0-.42.48-.64.8-.35C18 4.5 20 9 20 12c0 5.5-3.5 11-8 11z"/></svg>
492
  </div>
493
+ <div class="menubar-title">Qwen3.8 Studio <span>/ Object Detection</span></div>
494
  <span class="menubar-badge">27B</span>
495
+ <span class="menubar-badge fast">ZERO GPU</span>
496
  <div class="menubar-spacer"></div>
497
  <div class="menubar-status"><span class="dot" id="conn-dot"></span><span id="conn-text">connecting…</span></div>
498
  <a href="https://github.com/PRITHIVSAKTHIUR/Qwen3.8-27B-Object-Detection"
 
516
  </button>
517
  <div class="rail-sep"></div>
518
  <div class="rail-label">Result</div>
519
+ <button class="rail-btn" id="dl-btn-output" title="Download result" disabled>
 
 
 
520
  <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
521
  </button>
522
  <button class="rail-btn" id="zoom-btn" title="Zoom result" disabled>
 
532
  <button class="seg-btn" id="seg-input">Input</button>
533
  </div>
534
  <div class="canvas-meta" id="canvas-meta">No image loaded</div>
535
+ <div class="canvas-actions"></div>
536
  </div>
537
 
538
  <div class="canvas-stage" id="output-image-container">
539
  <div class="modern-loader" id="output-loader">
540
  <div class="loader-spinner"></div>
541
+ <div class="loader-text" id="loader-text">Processing image&hellip;</div>
542
  <div class="loader-bar-track"><div class="loader-bar-fill" id="loader-bar-fill"></div></div>
543
  </div>
544
 
 
555
  </div>
556
  </div>
557
  </div>
558
+
559
+ <!-- Filmstrip -->
560
+ <div class="filmstrip">
561
+ <div class="filmstrip-section inputs">
562
+ <div class="filmstrip-label">Input <span class="count" id="tb-image-count">0</span></div>
563
+ <div class="filmstrip-row" id="image-gallery-grid"></div>
564
+ </div>
565
+ <div class="filmstrip-section history">
566
+ <div class="filmstrip-label">History <span class="count" id="history-count">0</span></div>
567
+ <div class="filmstrip-row" id="history-strip"></div>
568
+ </div>
569
+ </div>
570
  </div>
571
 
572
  <!-- ══ Inspector ══ -->
 
581
  <div class="insp-section">
582
  <div class="insp-section-title"><span>Instruction</span><span class="char-count" id="char-count">0</span></div>
583
  <textarea id="custom-prompt-input" class="modern-textarea" rows="3"
584
+ placeholder="e.g., Detect all cars, Point to the red sign, Map a path from door to window…"></textarea>
585
  </div>
586
 
587
  <div class="insp-section">
 
592
  <div class="insp-section">
593
  <div class="insp-section-title"><span>Task Type</span></div>
594
  <select id="custom-task-select" class="lora-native-select">
595
+ <option value="Detect">Object Detection (Bounding Boxes)</option>
596
+ <option value="Point">Point Localization (Keypoints)</option>
597
+ <option value="Spatial">Spatial Guidance (Path Mapping)</option>
598
  </select>
599
  </div>
600
 
 
607
  </div>
608
 
609
  <div class="insp-section">
610
+ <div class="insp-section-title"><span>Output JSON</span></div>
611
+ <textarea id="custom-output-json" class="modern-textarea" rows="6" readonly placeholder="Result JSON will appear here..."></textarea>
612
+ </div>
613
+
614
+ <div class="insp-section">
615
+ <div class="insp-section-title"><span>Advanced</span></div>
616
  <div class="slider-row">
617
+ <label>Point R</label>
618
+ <input type="range" id="custom-point-r" min="2" max="20" step="1" value="4">
619
+ <span class="slider-val" id="custom-point-r-val">4</span>
620
  </div>
 
621
  <div class="slider-row">
622
+ <label>Box W</label>
623
+ <input type="range" id="custom-box-w" min="1" max="8" step="1" value="1">
624
+ <span class="slider-val" id="custom-box-w-val">1</span>
625
  </div>
 
626
  <div class="slider-row">
627
+ <label>Txt Scale</label>
628
+ <input type="range" id="custom-txt-s" min="0.2" max="1.5" step="0.05" value="0.4">
629
+ <span class="slider-val" id="custom-txt-s-val">0.4</span>
630
  </div>
 
631
  <div class="slider-row">
632
+ <label>GPU Dur</label>
633
+ <input type="range" id="custom-gpu-dur" min="0" max="6" step="1" value="1">
634
+ <span class="slider-val" id="custom-gpu-dur-val">90s</span>
635
  </div>
 
 
636
  </div>
637
  </div>
638
 
 
673
  const promptInput = document.getElementById('custom-prompt-input');
674
  const charCount = document.getElementById('char-count');
675
  const taskSelect = document.getElementById('custom-task-select');
676
+ const outputJson = document.getElementById('custom-output-json');
677
  const runBtn = document.getElementById('custom-run-btn');
678
  const runBtnLabel = document.getElementById('run-btn-label');
679
  const cancelBtn = document.getElementById('custom-cancel-btn');
680
+ const imgCountTb = document.getElementById('tb-image-count');
681
+ const imgCountSb = document.getElementById('sb-image-count');
682
  const sbStatus = document.getElementById('sb-status');
683
  const loader = document.getElementById('output-loader');
684
  const loaderText = document.getElementById('loader-text');
 
686
  const outBody = document.getElementById('output-image-container');
687
  const outPh = document.getElementById('output-placeholder');
688
  const dlBtn = document.getElementById('dl-btn-output');
 
689
  const zoomBtn = document.getElementById('zoom-btn');
690
+ const historyStrip = document.getElementById('history-strip');
691
+ const historyCount = document.getElementById('history-count');
692
+ const pointRSlider = document.getElementById('custom-point-r');
693
+ const pointRVal = document.getElementById('custom-point-r-val');
694
+ const boxWSlider = document.getElementById('custom-box-w');
695
+ const boxWVal = document.getElementById('custom-box-w-val');
696
+ const txtSSlider = document.getElementById('custom-txt-s');
697
+ const txtSVal = document.getElementById('custom-txt-s-val');
698
+ const gpuDurSlider = document.getElementById('custom-gpu-dur');
699
+ const gpuDurVal = document.getElementById('custom-gpu-dur-val');
700
  const lightbox = document.getElementById('lightbox');
701
  const lightboxImg = document.getElementById('lightbox-img');
702
  const dropOverlay = document.getElementById('drop-overlay');
 
705
  const segInput = document.getElementById('seg-input');
706
  const connDot = document.getElementById('conn-dot');
707
  const connText = document.getElementById('conn-text');
 
 
 
 
 
 
 
 
708
 
709
  /* ── State ── */
710
+ let currentImage = null; // {b64, name}
711
  let toastTimer = null;
712
  let running = false;
713
  let currentJob = null;
714
+ let currentResult = null; // {b64, text}
715
+ let history = [];
716
+ let historyIdx = -1;
717
+ let viewMode = 'result'; // 'result' | 'input'
718
+
719
+ const GPU_DURATIONS = [60, 90, 120, 150, 180, 250, 300];
720
 
721
  const SUGGESTIONS = [
722
  ['Detect Cars', 'Detect all the cars.'],
723
+ ['Detect People', 'Detect all the people in the scene.'],
724
+ ['Point to Sign', 'Point to the red stop sign.'],
725
+ ['Spatial Path', 'Map a path from the door to the lamp.'],
726
  ];
727
 
728
  /* ── Toast ── */
 
767
  segResult.addEventListener('click', () => setViewMode('result'));
768
  segInput.addEventListener('click', () => setViewMode('input'));
769
 
770
+ function getCanvasImg() { return outBody.querySelector('img.modern-out-img'); }
771
+
772
  function renderCanvas() {
773
+ let img = getCanvasImg();
774
  const showResult = viewMode === 'result' && currentResult;
775
  const showInput = viewMode === 'input' && currentImage;
776
 
777
  if (showResult || showInput) {
778
  outPh.style.display = 'none';
779
+ if (!img) {
780
+ img = document.createElement('img');
781
+ img.className = 'modern-out-img';
782
+ img.addEventListener('click', () => {
783
+ if (img.src) { lightboxImg.src = img.src; lightbox.classList.add('visible'); }
784
+ });
785
+ outBody.appendChild(img);
786
+ }
787
+ img.style.display = '';
788
  if (showResult) {
789
+ img.src = currentResult.b64;
790
+ canvasMeta.textContent = 'result';
791
  } else {
792
  img.src = currentImage.b64;
793
  canvasMeta.textContent = 'input · ' + (currentImage.name || 'image');
794
  }
795
  } else {
796
+ if (img) img.style.display = 'none';
797
  outPh.style.display = '';
798
+ canvasMeta.textContent = currentImage
799
+ ? 'input ready — run an edit'
800
+ : 'No image loaded';
801
  }
802
  updateRailState();
803
  }
 
806
  const hasResult = !!currentResult;
807
  dlBtn.disabled = !hasResult;
808
  zoomBtn.disabled = !hasResult && !currentImage;
 
809
  btnRemove.disabled = !currentImage;
810
  btnClear.disabled = !currentImage;
811
  }
812
 
813
  /* ── Inputs filmstrip ── */
814
+ function updateCounts() {
815
+ const n = currentImage ? 1 : 0;
816
+ imgCountTb.textContent = n;
817
+ imgCountSb.textContent = n > 0 ? '1 input · ' + history.length + ' result' + (history.length === 1 ? '' : 's') : 'No input';
818
+ }
819
+
820
  function setImage(b64, name) {
821
  currentImage = { b64, name: name || 'image' };
822
+ renderFilmstrip();
823
+ updateCounts();
824
+ if (!currentResult || viewMode === 'input') renderCanvas();
825
+ else setViewMode('input');
826
  }
827
 
828
  function removeImage() {
829
  currentImage = null;
830
+ renderFilmstrip();
831
+ updateCounts();
832
  renderCanvas();
833
  }
834
 
835
  function clearAll() {
836
  currentImage = null;
837
+ renderFilmstrip();
838
+ updateCounts();
839
  renderCanvas();
840
  }
841
 
842
+ function renderFilmstrip() {
843
+ galleryGrid.innerHTML = '';
844
+ if (!currentImage) {
845
+ const empty = document.createElement('div');
846
+ empty.className = 'fs-empty';
847
+ empty.textContent = 'Drop or paste an image…';
848
+ galleryGrid.appendChild(empty);
849
+ } else {
850
+ const t = document.createElement('div');
851
+ t.className = 'fs-thumb selected';
852
+ t.innerHTML = '<img src="' + currentImage.b64 + '" alt="">' +
853
+ '<span class="fs-badge">input</span>' +
854
+ '<button class="fs-remove">✕</button>';
855
+ t.addEventListener('click', (e) => {
856
+ if (e.target.closest('.fs-remove')) return;
857
+ setViewMode('input');
858
+ });
859
+ t.querySelector('.fs-remove').addEventListener('click', (e) => {
860
+ e.stopPropagation(); removeImage();
861
+ });
862
+ galleryGrid.appendChild(t);
863
+ }
864
+ const add = document.createElement('div');
865
+ add.className = 'fs-add';
866
+ add.innerHTML = '<span class="add-icon">+</span><span class="add-text">' + (currentImage ? 'Replace' : 'Add') + '</span>';
867
+ add.addEventListener('click', () => fileInput.click());
868
+ galleryGrid.appendChild(add);
869
+ updateRailState();
870
+ }
871
+
872
  function processFiles(files) {
873
  const file = files && files[0];
874
  if (!file || !file.type.startsWith('image/')) return 0;
875
  const reader = new FileReader();
876
+ reader.onload = (e) => setImage(e.target.result, file.name);
 
 
 
877
  reader.readAsDataURL(file);
878
  return 1;
879
  }
 
968
  const valSpan = document.getElementById(id + '-val');
969
  slider.addEventListener('input', () => { valSpan.textContent = slider.value + suffix; });
970
  }
971
+ bindSlider('custom-point-r');
972
+ bindSlider('custom-box-w');
973
+ bindSlider('custom-txt-s');
974
+
975
+ gpuDurSlider.addEventListener('input', () => {
976
+ gpuDurVal.textContent = GPU_DURATIONS[gpuDurSlider.value] + 's';
977
+ });
978
 
979
  /* ── Loader ── */
980
  function showLoader(text) {
981
+ loaderText.textContent = text || 'Processing image…';
982
  loaderFill.classList.remove('determinate');
983
  loaderFill.style.width = '100%';
984
  loader.classList.add('active');
 
996
  setStatus(statusText || 'Done', cls || 'done');
997
  }
998
 
999
+ /* ── Result / history ── */
1000
+ function showResult(entry) {
1001
+ currentResult = entry;
1002
+ setViewMode('result');
1003
+ outputJson.value = entry.text || '';
1004
+ dlBtn.disabled = false;
1005
+ zoomBtn.disabled = false;
1006
+ }
1007
+
1008
+ function renderHistory() {
1009
+ historyStrip.innerHTML = '';
1010
+ historyCount.textContent = history.length;
1011
+ if (history.length === 0) {
1012
+ const empty = document.createElement('div');
1013
+ empty.className = 'fs-empty';
1014
+ empty.textContent = 'No results yet';
1015
+ historyStrip.appendChild(empty);
1016
+ return;
1017
+ }
1018
+ history.forEach((entry, i) => {
1019
+ const t = document.createElement('div');
1020
+ t.className = 'fs-thumb' + (i === historyIdx ? ' selected' : '');
1021
+ const im = document.createElement('img');
1022
+ im.src = entry.b64;
1023
+ t.appendChild(im);
1024
+ t.addEventListener('click', () => { historyIdx = i; showResult(history[i]); renderHistory(); });
1025
+ historyStrip.appendChild(t);
1026
+ });
1027
+ }
1028
+
1029
+ function pushHistory(entry) {
1030
+ history.push(entry);
1031
+ historyIdx = history.length - 1;
1032
+ renderHistory();
1033
+ updateCounts();
1034
+ }
1035
+
1036
  /* ── Lightbox ── */
1037
  lightbox.addEventListener('click', () => lightbox.classList.remove('visible'));
1038
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') lightbox.classList.remove('visible'); });
1039
  zoomBtn.addEventListener('click', () => {
1040
+ const img = getCanvasImg();
1041
+ const src = (viewMode === 'result' && currentResult) ? currentResult.b64
1042
  : (currentImage ? currentImage.b64 : null);
1043
  if (src) { lightboxImg.src = src; lightbox.classList.add('visible'); }
1044
  });
1045
 
1046
+ /* ── Download ── */
1047
  dlBtn.addEventListener('click', () => {
1048
+ if (currentResult && currentResult.b64) {
1049
  const a = document.createElement('a');
1050
+ a.href = currentResult.b64;
1051
+ a.download = 'qwen_object_detection.png';
1052
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
1053
  }
1054
  });
 
 
 
 
 
 
1055
 
1056
  /* ── Backend connection ── */
1057
  let client = null;
 
1082
  card.innerHTML =
1083
  '<div class="example-thumb-wrap">' + thumbHtml + '</div>'
1084
  + '<div class="example-body">'
1085
+ + '<div class="example-meta"><span class="example-lora-badge"></span></div>'
1086
  + '<div class="example-prompt-text"></div>'
1087
  + '</div>';
1088
+ card.querySelector('.example-lora-badge').textContent = ex.task;
1089
  card.querySelector('.example-prompt-text').textContent = promptShort;
1090
  card.addEventListener('click', () => loadExample(card, ex.idx));
1091
  list.appendChild(card);
1092
  });
1093
+
1094
+ if (cfg.default_gpu_idx !== undefined) {
1095
+ gpuDurSlider.value = cfg.default_gpu_idx;
1096
+ gpuDurVal.textContent = GPU_DURATIONS[cfg.default_gpu_idx] + 's';
1097
+ }
1098
  }
1099
 
1100
  async function loadExample(card, idx) {
 
1107
  const data = result.data[0];
1108
  if (data && data.status === 'ok' && data.image) {
1109
  if (data.prompt) { promptInput.value = data.prompt; updateCharCount(); }
1110
+ if (data.task) {
1111
+ taskSelect.value = data.task;
1112
+ }
1113
  setImage(data.image, data.name || 'example.jpg');
 
1114
  tabEdit.click();
1115
  showToast('Example loaded', 'info');
1116
  } else {
 
1137
  const hasImage = !!currentImage;
1138
  if (!hasImage && !promptVal) { showToast('Please upload an image and enter a prompt', 'error'); flashPromptError(); return false; }
1139
  if (!hasImage) { showToast('Please upload an image', 'error'); return false; }
1140
+ if (!promptVal) { showToast('Please enter an edit prompt', 'warning'); flashPromptError(); return false; }
1141
  return true;
1142
  }
1143
 
 
1157
  setViewMode('result');
1158
  showLoader('Submitting to queue…');
1159
 
1160
+ const usedPrompt = promptInput.value;
1161
+ const usedTask = taskSelect.value;
1162
+
1163
  const payload = {
1164
  image_b64: currentImage ? currentImage.b64 : '',
1165
+ prompt: usedPrompt,
1166
+ task_type: usedTask,
1167
+ point_radius: parseInt(pointRSlider.value) || 4,
1168
+ box_thickness: parseInt(boxWSlider.value) || 1,
1169
+ text_scale: parseFloat(txtSSlider.value) || 0.4,
1170
+ gpu_duration_seconds: GPU_DURATIONS[gpuDurSlider.value],
1171
  };
1172
 
1173
  try {
 
1182
  setStatus('Queued');
1183
  } else if (msg.status === 'generating') {
1184
  setStatus('Processing…');
1185
+ const pd = msg.progress_data;
1186
+ if (pd && pd.length > 0) {
1187
+ const p = pd[pd.length - 1];
1188
+ if (p.index != null && p.length) {
1189
+ const pct = Math.round((p.index / p.length) * 100);
1190
+ setLoaderProgress('Generating… ' + p.index + '/' + p.length + ' (' + pct + '%)', p.index / p.length);
1191
+ } else {
1192
+ setLoaderProgress('Generating…', null);
1193
+ }
1194
+ } else {
1195
+ setLoaderProgress('Processing image…', null);
1196
+ }
1197
  } else if (msg.status === 'error') {
1198
  throw new Error(msg.message || 'Generation failed');
1199
  }
1200
  } else if (msg.type === 'data') {
1201
  const out = msg.data && msg.data[0];
1202
  if (out && out.image) {
1203
+ const entry = { b64: out.image, text: out.text };
1204
+ showResult(entry);
1205
+ pushHistory(entry);
 
1206
  hideLoader('Done', 'done');
1207
+ showToast('Inference successful', 'info');
 
1208
  } else {
1209
  hideLoader('Done', 'done');
1210
  }
1211
  }
1212
  }
1213
  } catch (e) {
1214
+ console.error('Edit error:', e);
1215
  hideLoader('Error', 'error');
1216
  const msg = (e && e.message) ? e.message : 'Generation failed';
1217
  showToast(msg.length > 120 ? msg.slice(0, 120) + '…' : msg, 'error');
 
1230
  }
1231
  setRunningUI(false);
1232
  hideLoader('Cancelled');
1233
+ showToast('Generation cancelled', 'warning');
1234
  });
1235
 
1236
  document.addEventListener('keydown', (e) => {
 
1240
  }
1241
  });
1242
 
1243
+ renderFilmstrip();
1244
+ renderHistory();
1245
+ updateCounts();
1246
  renderCanvas();
1247
  </script>
1248
  </body>