prithivMLmods commited on
Commit
ee59a4d
Β·
verified Β·
1 Parent(s): dd6fcaa

update app

Browse files
Files changed (1) hide show
  1. app.py +156 -29
app.py CHANGED
@@ -165,7 +165,6 @@ def generate_inference_stream(
165
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3.5-4B model not loaded.'})}\n\n"
166
  yield "data: [DONE]\n\n"
167
  return
168
-
169
  messages = [{"role": "user", "content": [
170
  {"type": "image", "image": image},
171
  {"type": "text", "text": full_prompt},
@@ -197,7 +196,6 @@ def generate_inference_stream(
197
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3.5-2B model not loaded.'})}\n\n"
198
  yield "data: [DONE]\n\n"
199
  return
200
-
201
  messages = [{"role": "user", "content": [
202
  {"type": "image", "image": image},
203
  {"type": "text", "text": full_prompt},
@@ -229,7 +227,6 @@ def generate_inference_stream(
229
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3-VL model not loaded.'})}\n\n"
230
  yield "data: [DONE]\n\n"
231
  return
232
-
233
  messages = [{"role": "user", "content": [
234
  {"type": "image", "image": image},
235
  {"type": "text", "text": full_prompt},
@@ -261,7 +258,6 @@ def generate_inference_stream(
261
  yield f"data: {json.dumps({'chunk': '[Error] LFM-450M model not loaded.'})}\n\n"
262
  yield "data: [DONE]\n\n"
263
  return
264
-
265
  conversation = [{"role": "user", "content": [
266
  {"type": "image", "image": image},
267
  {"type": "text", "text": full_prompt},
@@ -290,7 +286,6 @@ def generate_inference_stream(
290
  yield f"data: {json.dumps({'chunk': '[Error] LFM-1.6B model not loaded.'})}\n\n"
291
  yield "data: [DONE]\n\n"
292
  return
293
-
294
  conversation = [{"role": "user", "content": [
295
  {"type": "image", "image": image},
296
  {"type": "text", "text": full_prompt},
@@ -319,7 +314,6 @@ def generate_inference_stream(
319
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3.5-2B-Unredacted-MAX model not loaded.'})}\n\n"
320
  yield "data: [DONE]\n\n"
321
  return
322
-
323
  messages = [{"role": "user", "content": [
324
  {"type": "image", "image": image},
325
  {"type": "text", "text": full_prompt},
@@ -442,7 +436,7 @@ async def homepage(request: Request):
442
  position: relative;
443
  width: 1560px;
444
  min-height: calc(100vh - 50px);
445
- height: 1060px;
446
  margin: 0 auto;
447
  }
448
 
@@ -478,7 +472,7 @@ async def homepage(request: Request):
478
  .node:hover {
479
  box-shadow: 0 10px 34px rgba(0,0,0,0.5), 0 0 0 1px rgba(124,106,247,0.3);
480
  }
481
- .node.fixed-height { height: 390px; }
482
 
483
  .node-header {
484
  background: var(--node-header);
@@ -524,25 +518,92 @@ async def homepage(request: Request):
524
 
525
  input[type="file"] { display: none; }
526
 
 
 
 
 
 
 
527
  .file-upload {
528
  border: 1.5px dashed var(--node-border);
529
- border-radius: 8px; padding: 14px 12px;
530
  text-align: center; cursor: pointer;
531
  font-size: 12px; color: var(--muted);
532
  transition: border-color 0.2s, background 0.2s;
533
  background: rgba(255,255,255,0.01);
534
  display: flex; flex-direction: column; align-items: center; gap: 6px;
535
  }
536
- .file-upload:hover { border-color: var(--accent); background: rgba(124,106,247,0.04); }
 
 
 
537
  .file-upload svg { opacity: 0.5; transition: opacity 0.2s; }
538
  .file-upload:hover svg { opacity: 0.9; }
539
 
 
 
 
 
 
 
 
 
 
 
 
540
  .img-preview {
541
- width: 100%; height: 200px;
542
- object-fit: contain; border-radius: 6px;
543
- display: none; background: #000;
 
 
 
 
 
 
 
 
 
 
544
  border: 1px solid var(--node-border);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  }
 
546
 
547
  select, textarea {
548
  width: 100%;
@@ -569,7 +630,9 @@ async def homepage(request: Request):
569
  }
570
  button.run-btn:hover { opacity: 0.9; }
571
  button.run-btn:active { transform: scale(0.98); }
572
- button.run-btn:disabled { background: var(--node-border); cursor: not-allowed; color: #555; }
 
 
573
 
574
  .output-box {
575
  background: rgba(0,0,0,0.4);
@@ -582,7 +645,7 @@ async def homepage(request: Request):
582
  font-family: 'JetBrains Mono', monospace;
583
  }
584
 
585
- /* Grounding */
586
  .ground-canvas-wrap {
587
  position: relative; flex: 1;
588
  border: 1px solid var(--node-border);
@@ -665,6 +728,8 @@ async def homepage(request: Request):
665
  <div class="node-body">
666
  <div>
667
  <label>Upload Image</label>
 
 
668
  <div class="file-upload" id="dropZone">
669
  <svg width="34" height="34" viewBox="0 0 24 24" fill="none"
670
  stroke="#7c6af7" stroke-width="1.5"
@@ -676,14 +741,34 @@ async def homepage(request: Request):
676
  <span>Click or drop image here</span>
677
  <input type="file" id="fileInput" accept="image/*">
678
  </div>
679
- <img id="imgPreview" class="img-preview" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  </div>
681
  </div>
682
  <div class="port out" id="port-img-out" style="top:50%;transform:translateY(-50%);"></div>
683
  </div>
684
 
685
  <!-- ─── ID 02 : Model Selector ─── -->
686
- <div class="node fixed-height" id="node-model" style="left:48px; top:472px;">
687
  <div class="node-header">
688
  <span><span class="status-dot" id="dot-model"></span>Model Selector</span>
689
  <span class="id">ID: 02</span>
@@ -755,7 +840,7 @@ async def homepage(request: Request):
755
  </div>
756
 
757
  <!-- ─── ID 05 : Grounding Visualiser ─── -->
758
- <div class="node fixed-height" id="node-gnd" style="left:932px; top:472px;">
759
  <div class="port in" id="port-gnd-in" style="top:50%;transform:translateY(-50%);"></div>
760
  <div class="node-header">
761
  <span><span class="status-dot" id="dot-gnd"></span>View Grounding</span>
@@ -799,10 +884,10 @@ function bezier(p1, p2) {
799
 
800
  function updateWires() {
801
  const wires = [
802
- ['wire-img-task', 'port-img-out', 'port-task-in'],
803
- ['wire-model-task', 'port-model-out', 'port-task-in'],
804
- ['wire-task-out', 'port-task-out', 'port-out-in'],
805
- ['wire-task-gnd', 'port-task-out', 'port-gnd-in'],
806
  ];
807
  for (const [id, from, to] of wires) {
808
  const el = document.getElementById(id);
@@ -839,26 +924,68 @@ document.addEventListener('scroll', updateWires, true);
839
  requestAnimationFrame(updateWires);
840
 
841
  // ══════════════════════════════════════════════
842
- // FILE UPLOAD
843
  // ══════════════════════════════════════════════
844
  let currentFile = null;
 
845
  const dropZone = document.getElementById('dropZone');
846
  const fileInput = document.getElementById('fileInput');
 
847
  const imgPreview = document.getElementById('imgPreview');
 
 
 
 
848
  const dotImg = document.getElementById('dot-img');
849
 
 
 
 
 
 
 
850
  function handleFile(file) {
851
  if (!file || !file.type.startsWith('image/')) return;
852
  currentFile = file;
 
 
853
  imgPreview.src = URL.createObjectURL(file);
854
- imgPreview.style.display = 'block';
855
- dropZone.style.display = 'none';
 
 
 
 
 
 
856
  dotImg.classList.add('active');
857
  requestAnimationFrame(updateWires);
858
  }
859
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  dropZone.onclick = () => fileInput.click();
861
  fileInput.onchange = e => handleFile(e.target.files[0]);
 
 
862
  dropZone.ondragover = e => { e.preventDefault(); dropZone.style.borderColor = 'var(--accent)'; };
863
  dropZone.ondragleave = () => { dropZone.style.borderColor = ''; };
864
  dropZone.ondrop = e => {
@@ -922,9 +1049,9 @@ const MODEL_INFO = {
922
  modelSelect.onchange = () => {
923
  const info = MODEL_INFO[modelSelect.value];
924
  if (!info) return;
925
- modelInfoBox.innerHTML = info.html;
926
- modelInfoBox.style.background = info.bg;
927
- modelInfoBox.style.border = `1px solid ${info.border}`;
928
  };
929
 
930
  // ══════════════════════════════════════════════
@@ -1083,7 +1210,7 @@ const dotGnd = document.getElementById('dot-gnd');
1083
  runBtn.onclick = async () => {
1084
  if (!currentFile) { alert('Please upload an image into the Input Node.'); return; }
1085
  const promptStr = promptInput.value.trim();
1086
- if (!promptStr) { alert('Please enter a prompt directive.'); return; }
1087
 
1088
  runBtn.disabled = true;
1089
  btnLoader.style.display = 'inline-block';
 
165
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3.5-4B model not loaded.'})}\n\n"
166
  yield "data: [DONE]\n\n"
167
  return
 
168
  messages = [{"role": "user", "content": [
169
  {"type": "image", "image": image},
170
  {"type": "text", "text": full_prompt},
 
196
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3.5-2B model not loaded.'})}\n\n"
197
  yield "data: [DONE]\n\n"
198
  return
 
199
  messages = [{"role": "user", "content": [
200
  {"type": "image", "image": image},
201
  {"type": "text", "text": full_prompt},
 
227
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3-VL model not loaded.'})}\n\n"
228
  yield "data: [DONE]\n\n"
229
  return
 
230
  messages = [{"role": "user", "content": [
231
  {"type": "image", "image": image},
232
  {"type": "text", "text": full_prompt},
 
258
  yield f"data: {json.dumps({'chunk': '[Error] LFM-450M model not loaded.'})}\n\n"
259
  yield "data: [DONE]\n\n"
260
  return
 
261
  conversation = [{"role": "user", "content": [
262
  {"type": "image", "image": image},
263
  {"type": "text", "text": full_prompt},
 
286
  yield f"data: {json.dumps({'chunk': '[Error] LFM-1.6B model not loaded.'})}\n\n"
287
  yield "data: [DONE]\n\n"
288
  return
 
289
  conversation = [{"role": "user", "content": [
290
  {"type": "image", "image": image},
291
  {"type": "text", "text": full_prompt},
 
314
  yield f"data: {json.dumps({'chunk': '[Error] Qwen3.5-2B-Unredacted-MAX model not loaded.'})}\n\n"
315
  yield "data: [DONE]\n\n"
316
  return
 
317
  messages = [{"role": "user", "content": [
318
  {"type": "image", "image": image},
319
  {"type": "text", "text": full_prompt},
 
436
  position: relative;
437
  width: 1560px;
438
  min-height: calc(100vh - 50px);
439
+ height: 1080px;
440
  margin: 0 auto;
441
  }
442
 
 
472
  .node:hover {
473
  box-shadow: 0 10px 34px rgba(0,0,0,0.5), 0 0 0 1px rgba(124,106,247,0.3);
474
  }
475
+ .node.fixed-height { height: 420px; }
476
 
477
  .node-header {
478
  background: var(--node-header);
 
518
 
519
  input[type="file"] { display: none; }
520
 
521
+ /* ── Image Upload Zone ── */
522
+ .upload-area {
523
+ position: relative;
524
+ display: flex; flex-direction: column; gap: 0;
525
+ }
526
+
527
  .file-upload {
528
  border: 1.5px dashed var(--node-border);
529
+ border-radius: 8px; padding: 18px 12px;
530
  text-align: center; cursor: pointer;
531
  font-size: 12px; color: var(--muted);
532
  transition: border-color 0.2s, background 0.2s;
533
  background: rgba(255,255,255,0.01);
534
  display: flex; flex-direction: column; align-items: center; gap: 6px;
535
  }
536
+ .file-upload:hover {
537
+ border-color: var(--accent);
538
+ background: rgba(124,106,247,0.04);
539
+ }
540
  .file-upload svg { opacity: 0.5; transition: opacity 0.2s; }
541
  .file-upload:hover svg { opacity: 0.9; }
542
 
543
+ /* ── Preview wrapper ── */
544
+ .preview-wrap {
545
+ display: none;
546
+ position: relative;
547
+ border-radius: 8px;
548
+ overflow: hidden;
549
+ border: 1px solid var(--node-border);
550
+ background: #000;
551
+ }
552
+ .preview-wrap.visible { display: block; }
553
+
554
  .img-preview {
555
+ width: 100%;
556
+ height: 210px;
557
+ object-fit: contain;
558
+ display: block;
559
+ }
560
+
561
+ /* ── Clear button overlaid on preview ── */
562
+ .clear-btn {
563
+ position: absolute;
564
+ top: 7px; right: 7px;
565
+ width: 28px; height: 28px;
566
+ border-radius: 50%;
567
+ background: rgba(13,13,15,0.82);
568
  border: 1px solid var(--node-border);
569
+ color: var(--accent3);
570
+ cursor: pointer;
571
+ display: flex; align-items: center; justify-content: center;
572
+ transition: background 0.18s, border-color 0.18s, transform 0.12s;
573
+ z-index: 20;
574
+ backdrop-filter: blur(6px);
575
+ }
576
+ .clear-btn:hover {
577
+ background: rgba(255,107,107,0.18);
578
+ border-color: var(--accent3);
579
+ transform: scale(1.08);
580
+ }
581
+ .clear-btn:active { transform: scale(0.96); }
582
+ .clear-btn svg { pointer-events: none; }
583
+
584
+ /* ── Image filename chip ── */
585
+ .img-chip {
586
+ display: none;
587
+ align-items: center; gap: 7px;
588
+ background: rgba(124,106,247,0.08);
589
+ border: 1px solid rgba(124,106,247,0.22);
590
+ border-radius: 6px;
591
+ padding: 5px 10px;
592
+ font-size: 10px; color: var(--muted);
593
+ overflow: hidden;
594
+ }
595
+ .img-chip.visible { display: flex; }
596
+ .img-chip .chip-dot {
597
+ width: 6px; height: 6px; border-radius: 50%;
598
+ background: var(--accent2); flex-shrink: 0;
599
+ box-shadow: 0 0 5px var(--accent2);
600
+ }
601
+ .img-chip .chip-name {
602
+ overflow: hidden; text-overflow: ellipsis;
603
+ white-space: nowrap; flex: 1;
604
+ color: var(--text);
605
  }
606
+ .img-chip .chip-size { color: var(--muted); flex-shrink: 0; }
607
 
608
  select, textarea {
609
  width: 100%;
 
630
  }
631
  button.run-btn:hover { opacity: 0.9; }
632
  button.run-btn:active { transform: scale(0.98); }
633
+ button.run-btn:disabled {
634
+ background: var(--node-border); cursor: not-allowed; color: #555;
635
+ }
636
 
637
  .output-box {
638
  background: rgba(0,0,0,0.4);
 
645
  font-family: 'JetBrains Mono', monospace;
646
  }
647
 
648
+ /* ── Grounding ── */
649
  .ground-canvas-wrap {
650
  position: relative; flex: 1;
651
  border: 1px solid var(--node-border);
 
728
  <div class="node-body">
729
  <div>
730
  <label>Upload Image</label>
731
+
732
+ <!-- Drop zone (shown when no image) -->
733
  <div class="file-upload" id="dropZone">
734
  <svg width="34" height="34" viewBox="0 0 24 24" fill="none"
735
  stroke="#7c6af7" stroke-width="1.5"
 
741
  <span>Click or drop image here</span>
742
  <input type="file" id="fileInput" accept="image/*">
743
  </div>
744
+
745
+ <!-- Preview (shown when image loaded) -->
746
+ <div class="preview-wrap" id="previewWrap">
747
+ <img id="imgPreview" class="img-preview" />
748
+ <!-- Clear button -->
749
+ <button class="clear-btn" id="clearBtn" title="Remove image">
750
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
751
+ stroke="currentColor" stroke-width="2.5"
752
+ stroke-linecap="round" stroke-linejoin="round">
753
+ <line x1="18" y1="6" x2="6" y2="18"/>
754
+ <line x1="6" y1="6" x2="18" y2="18"/>
755
+ </svg>
756
+ </button>
757
+ </div>
758
+
759
+ <!-- Filename chip -->
760
+ <div class="img-chip" id="imgChip" style="margin-top:7px;">
761
+ <span class="chip-dot"></span>
762
+ <span class="chip-name" id="chipName">β€”</span>
763
+ <span class="chip-size" id="chipSize"></span>
764
+ </div>
765
  </div>
766
  </div>
767
  <div class="port out" id="port-img-out" style="top:50%;transform:translateY(-50%);"></div>
768
  </div>
769
 
770
  <!-- ─── ID 02 : Model Selector ─── -->
771
+ <div class="node fixed-height" id="node-model" style="left:48px; top:502px;">
772
  <div class="node-header">
773
  <span><span class="status-dot" id="dot-model"></span>Model Selector</span>
774
  <span class="id">ID: 02</span>
 
840
  </div>
841
 
842
  <!-- ─── ID 05 : Grounding Visualiser ─── -->
843
+ <div class="node fixed-height" id="node-gnd" style="left:932px; top:502px;">
844
  <div class="port in" id="port-gnd-in" style="top:50%;transform:translateY(-50%);"></div>
845
  <div class="node-header">
846
  <span><span class="status-dot" id="dot-gnd"></span>View Grounding</span>
 
884
 
885
  function updateWires() {
886
  const wires = [
887
+ ['wire-img-task', 'port-img-out', 'port-task-in'],
888
+ ['wire-model-task', 'port-model-out', 'port-task-in'],
889
+ ['wire-task-out', 'port-task-out', 'port-out-in'],
890
+ ['wire-task-gnd', 'port-task-out', 'port-gnd-in'],
891
  ];
892
  for (const [id, from, to] of wires) {
893
  const el = document.getElementById(id);
 
924
  requestAnimationFrame(updateWires);
925
 
926
  // ══════════════════════════════════════════════
927
+ // FILE UPLOAD + CLEAR
928
  // ══════════════════════════════════════════════
929
  let currentFile = null;
930
+
931
  const dropZone = document.getElementById('dropZone');
932
  const fileInput = document.getElementById('fileInput');
933
+ const previewWrap= document.getElementById('previewWrap');
934
  const imgPreview = document.getElementById('imgPreview');
935
+ const clearBtn = document.getElementById('clearBtn');
936
+ const imgChip = document.getElementById('imgChip');
937
+ const chipName = document.getElementById('chipName');
938
+ const chipSize = document.getElementById('chipSize');
939
  const dotImg = document.getElementById('dot-img');
940
 
941
+ function formatBytes(bytes) {
942
+ if (bytes < 1024) return bytes + ' B';
943
+ if (bytes < 1024*1024) return (bytes/1024).toFixed(1) + ' KB';
944
+ return (bytes/(1024*1024)).toFixed(1) + ' MB';
945
+ }
946
+
947
  function handleFile(file) {
948
  if (!file || !file.type.startsWith('image/')) return;
949
  currentFile = file;
950
+
951
+ // preview
952
  imgPreview.src = URL.createObjectURL(file);
953
+ previewWrap.classList.add('visible');
954
+ dropZone.style.display = 'none';
955
+
956
+ // chip
957
+ chipName.textContent = file.name;
958
+ chipSize.textContent = formatBytes(file.size);
959
+ imgChip.classList.add('visible');
960
+
961
  dotImg.classList.add('active');
962
  requestAnimationFrame(updateWires);
963
  }
964
 
965
+ function clearImage() {
966
+ currentFile = null;
967
+
968
+ // reset preview
969
+ imgPreview.src = '';
970
+ previewWrap.classList.remove('visible');
971
+ dropZone.style.display = '';
972
+
973
+ // reset chip
974
+ imgChip.classList.remove('visible');
975
+ chipName.textContent = 'β€”';
976
+ chipSize.textContent = '';
977
+
978
+ // reset file input so same file can be re-selected
979
+ fileInput.value = '';
980
+
981
+ dotImg.classList.remove('active');
982
+ requestAnimationFrame(updateWires);
983
+ }
984
+
985
  dropZone.onclick = () => fileInput.click();
986
  fileInput.onchange = e => handleFile(e.target.files[0]);
987
+ clearBtn.onclick = (e) => { e.stopPropagation(); clearImage(); };
988
+
989
  dropZone.ondragover = e => { e.preventDefault(); dropZone.style.borderColor = 'var(--accent)'; };
990
  dropZone.ondragleave = () => { dropZone.style.borderColor = ''; };
991
  dropZone.ondrop = e => {
 
1049
  modelSelect.onchange = () => {
1050
  const info = MODEL_INFO[modelSelect.value];
1051
  if (!info) return;
1052
+ modelInfoBox.innerHTML = info.html;
1053
+ modelInfoBox.style.background = info.bg;
1054
+ modelInfoBox.style.border = `1px solid ${info.border}`;
1055
  };
1056
 
1057
  // ══════════════════════════════════════════════
 
1210
  runBtn.onclick = async () => {
1211
  if (!currentFile) { alert('Please upload an image into the Input Node.'); return; }
1212
  const promptStr = promptInput.value.trim();
1213
+ if (!promptStr) { alert('Please enter a prompt directive.'); return; }
1214
 
1215
  runBtn.disabled = true;
1216
  btnLoader.style.display = 'inline-block';