Spaces:
Running on Zero
Running on Zero
update app
Browse files
app.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
import os
|
| 3 |
import io
|
| 4 |
import json
|
|
@@ -468,30 +467,30 @@ async def homepage(request: Request):
|
|
| 468 |
/* ββ Top Bar ββ */
|
| 469 |
.top-bar {
|
| 470 |
position: sticky; top: 0; left: 0; right: 0;
|
| 471 |
-
height:
|
| 472 |
-
background: rgba(13,13,15,0.
|
| 473 |
border-bottom: 1px solid var(--node-border);
|
| 474 |
-
display: flex; align-items: center; padding: 0
|
| 475 |
-
gap:
|
| 476 |
backdrop-filter: blur(12px);
|
| 477 |
}
|
| 478 |
-
.top-bar .logo
|
| 479 |
-
.top-bar .sep
|
| 480 |
-
.top-bar .sub
|
| 481 |
.top-bar .badge {
|
| 482 |
margin-left: auto;
|
| 483 |
-
background: rgba(124,106,247,0.
|
| 484 |
-
border: 1px solid rgba(124,106,247,0.
|
| 485 |
-
padding:
|
| 486 |
-
font-size:
|
| 487 |
}
|
| 488 |
|
| 489 |
/* ββ Canvas ββ */
|
| 490 |
#canvas {
|
| 491 |
position: relative;
|
| 492 |
-
width:
|
| 493 |
-
min-height: calc(100vh -
|
| 494 |
-
height:
|
| 495 |
margin: 0 auto;
|
| 496 |
}
|
| 497 |
|
|
@@ -502,101 +501,100 @@ async def homepage(request: Request):
|
|
| 502 |
overflow: visible;
|
| 503 |
}
|
| 504 |
path.wire {
|
| 505 |
-
fill: none; stroke: var(--wire); stroke-width: 2;
|
| 506 |
stroke-linecap: round;
|
| 507 |
}
|
| 508 |
path.wire.active {
|
| 509 |
-
stroke: var(--wire-active); stroke-width:
|
| 510 |
-
stroke-dasharray:
|
| 511 |
-
animation: flow 0.
|
| 512 |
}
|
| 513 |
-
@keyframes flow { to { stroke-dashoffset: -
|
| 514 |
|
| 515 |
/* ββ Nodes ββ */
|
| 516 |
.node {
|
| 517 |
position: absolute;
|
| 518 |
-
width:
|
| 519 |
background: var(--node-bg);
|
| 520 |
border: 1px solid var(--node-border);
|
| 521 |
-
border-radius:
|
| 522 |
-
box-shadow: 0
|
| 523 |
z-index: 10;
|
| 524 |
display: flex; flex-direction: column;
|
| 525 |
transition: box-shadow 0.2s;
|
| 526 |
}
|
| 527 |
.node:hover {
|
| 528 |
-
box-shadow: 0
|
| 529 |
-
0 0 0 1px rgba(124,106,247,0.
|
| 530 |
}
|
| 531 |
-
.node.fixed-height { height:
|
| 532 |
|
| 533 |
.node-header {
|
| 534 |
background: var(--node-header);
|
| 535 |
-
padding:
|
| 536 |
border-bottom: 1px solid var(--node-border);
|
| 537 |
-
border-radius:
|
| 538 |
-
font-size:
|
| 539 |
cursor: grab;
|
| 540 |
display: flex; justify-content: space-between; align-items: center;
|
| 541 |
flex-shrink: 0;
|
| 542 |
user-select: none;
|
| 543 |
-
letter-spacing: 0.03em;
|
| 544 |
}
|
| 545 |
.node-header:active { cursor: grabbing; }
|
| 546 |
.node-header .id {
|
| 547 |
-
font-size:
|
| 548 |
background: rgba(255,255,255,0.04);
|
| 549 |
-
padding:
|
| 550 |
}
|
| 551 |
|
| 552 |
.node-body {
|
| 553 |
-
padding:
|
| 554 |
-
display: flex; flex-direction: column; gap:
|
| 555 |
flex: 1; overflow: hidden;
|
| 556 |
}
|
| 557 |
|
| 558 |
/* ββ Ports ββ */
|
| 559 |
.port {
|
| 560 |
position: absolute;
|
| 561 |
-
width:
|
| 562 |
background: var(--node-bg);
|
| 563 |
border: 2px solid var(--port);
|
| 564 |
border-radius: 50%; z-index: 30;
|
| 565 |
}
|
| 566 |
-
.port.out { right: -
|
| 567 |
-
.port.in { left: -
|
| 568 |
|
| 569 |
/* ββ Labels ββ */
|
| 570 |
label {
|
| 571 |
-
font-size:
|
| 572 |
-
font-weight: 600; display: block; margin-bottom:
|
| 573 |
-
letter-spacing: 0.
|
| 574 |
}
|
| 575 |
|
| 576 |
input[type="file"] { display: none; }
|
| 577 |
|
| 578 |
/* ββ Upload Zone ββ */
|
| 579 |
.file-upload {
|
| 580 |
-
border:
|
| 581 |
-
border-radius:
|
| 582 |
text-align: center; cursor: pointer;
|
| 583 |
-
font-size:
|
| 584 |
transition: border-color 0.2s, background 0.2s;
|
| 585 |
background: rgba(255,255,255,0.01);
|
| 586 |
-
display: flex; flex-direction: column; align-items: center; gap:
|
| 587 |
}
|
| 588 |
.file-upload:hover {
|
| 589 |
border-color: var(--accent);
|
| 590 |
background: rgba(124,106,247,0.04);
|
| 591 |
}
|
| 592 |
-
.file-upload svg { opacity: 0.
|
| 593 |
-
.file-upload:hover svg { opacity: 0.
|
| 594 |
|
| 595 |
/* ββ Preview wrapper ββ */
|
| 596 |
.preview-wrap {
|
| 597 |
display: none;
|
| 598 |
position: relative;
|
| 599 |
-
border-radius:
|
| 600 |
overflow: hidden;
|
| 601 |
border: 1px solid var(--node-border);
|
| 602 |
background: #000;
|
|
@@ -605,7 +603,7 @@ async def homepage(request: Request):
|
|
| 605 |
|
| 606 |
.img-preview {
|
| 607 |
width: 100%;
|
| 608 |
-
height:
|
| 609 |
object-fit: contain;
|
| 610 |
display: block;
|
| 611 |
}
|
|
@@ -613,34 +611,35 @@ async def homepage(request: Request):
|
|
| 613 |
/* ββ Clear button ββ */
|
| 614 |
.clear-btn {
|
| 615 |
position: absolute;
|
| 616 |
-
top:
|
| 617 |
-
width:
|
| 618 |
border-radius: 50%;
|
| 619 |
-
background: rgba(13,13,15,0.
|
| 620 |
-
border: 1px solid
|
| 621 |
color: var(--accent3);
|
| 622 |
cursor: pointer;
|
| 623 |
display: flex; align-items: center; justify-content: center;
|
| 624 |
-
transition: background 0.
|
| 625 |
z-index: 20;
|
| 626 |
-
backdrop-filter: blur(
|
| 627 |
}
|
| 628 |
.clear-btn:hover {
|
| 629 |
-
background: rgba(255,107,107,0.
|
| 630 |
border-color: var(--accent3);
|
| 631 |
-
transform: scale(1.
|
| 632 |
}
|
| 633 |
-
.clear-btn:active { transform: scale(0.
|
| 634 |
.clear-btn svg { pointer-events: none; }
|
| 635 |
|
| 636 |
/* ββ Filename chip ββ */
|
| 637 |
.img-chip {
|
| 638 |
display: none;
|
| 639 |
-
align-items: center; gap:
|
| 640 |
-
background: rgba(124,106,247,0.
|
| 641 |
-
border: 1px solid rgba(124,106,247,0.
|
| 642 |
-
border-radius:
|
| 643 |
-
padding:
|
|
|
|
| 644 |
overflow: hidden;
|
| 645 |
}
|
| 646 |
.img-chip.visible { display: flex; }
|
|
@@ -654,48 +653,46 @@ async def homepage(request: Request):
|
|
| 654 |
white-space: nowrap; flex: 1;
|
| 655 |
color: var(--text); font-size: 9px;
|
| 656 |
}
|
| 657 |
-
.img-chip .chip-size {
|
|
|
|
|
|
|
| 658 |
|
| 659 |
-
/* ββ Inputs ββ */
|
| 660 |
select, textarea {
|
| 661 |
width: 100%;
|
| 662 |
-
background: rgba(0,0,0,0.
|
| 663 |
border: 1px solid var(--node-border);
|
| 664 |
-
color: var(--text); padding:
|
| 665 |
border-radius: 5px; outline: none;
|
| 666 |
-
font-size:
|
| 667 |
resize: none; transition: border-color 0.2s;
|
| 668 |
-
line-height: 1.4;
|
| 669 |
}
|
| 670 |
select:focus, textarea:focus { border-color: var(--accent); }
|
| 671 |
select option { background: #1c1c26; }
|
| 672 |
|
| 673 |
-
/* ββ Run Button ββ */
|
| 674 |
button.run-btn {
|
| 675 |
background: linear-gradient(135deg, var(--accent), #9b59b6);
|
| 676 |
color: #fff; border: none;
|
| 677 |
-
padding:
|
| 678 |
-
font-weight: 700; font-size:
|
| 679 |
font-family: 'JetBrains Mono', monospace;
|
| 680 |
cursor: pointer;
|
| 681 |
transition: opacity 0.2s, transform 0.1s;
|
| 682 |
-
display: flex; justify-content: center; align-items: center; gap:
|
| 683 |
-
letter-spacing: 0.
|
| 684 |
}
|
| 685 |
-
button.run-btn:hover
|
| 686 |
-
button.run-btn:active
|
| 687 |
button.run-btn:disabled {
|
| 688 |
background: var(--node-border); cursor: not-allowed; color: #555;
|
| 689 |
}
|
| 690 |
|
| 691 |
-
/* ββ Output ββ */
|
| 692 |
.output-box {
|
| 693 |
-
background: rgba(0,0,0,0.
|
| 694 |
border: 1px solid var(--node-border);
|
| 695 |
-
border-radius: 5px; padding:
|
| 696 |
flex: 1; overflow-y: auto;
|
| 697 |
-
font-size:
|
| 698 |
-
color: #
|
| 699 |
user-select: text;
|
| 700 |
font-family: 'JetBrains Mono', monospace;
|
| 701 |
}
|
|
@@ -714,53 +711,48 @@ async def homepage(request: Request):
|
|
| 714 |
.ground-placeholder {
|
| 715 |
position: absolute; inset: 0;
|
| 716 |
display: flex; align-items: center; justify-content: center;
|
| 717 |
-
font-size:
|
| 718 |
-
text-align: center; padding: 8px; line-height: 1.5;
|
| 719 |
}
|
| 720 |
|
| 721 |
-
/* ββ Model info box ββ */
|
| 722 |
-
.model-info-box {
|
| 723 |
-
border-radius: 5px; padding: 7px 8px;
|
| 724 |
-
font-size: 9px; color: var(--muted); line-height: 1.5;
|
| 725 |
-
flex-shrink: 0;
|
| 726 |
-
}
|
| 727 |
-
|
| 728 |
-
/* ββ Model badges ββ */
|
| 729 |
-
.model-badge {
|
| 730 |
-
display: inline-block; padding: 1px 6px;
|
| 731 |
-
border-radius: 3px; font-size: 8px; font-weight: 700;
|
| 732 |
-
letter-spacing: 0.06em; text-transform: uppercase;
|
| 733 |
-
}
|
| 734 |
-
.model-badge.q4b { background: rgba(255,200,80,0.14); color: #ffc850; border: 1px solid rgba(255,200,80,0.32); }
|
| 735 |
-
.model-badge.q2b { background: rgba(124,106,247,0.18); color: var(--accent); border: 1px solid rgba(124,106,247,0.28); }
|
| 736 |
-
.model-badge.qvl { background: rgba(255,150,50,0.14); color: #ff9632; border: 1px solid rgba(255,150,50,0.32); }
|
| 737 |
-
.model-badge.lfm450 { background: rgba(78,205,196,0.14); color: var(--accent2); border: 1px solid rgba(78,205,196,0.28); }
|
| 738 |
-
.model-badge.lfm16 { background: rgba(107,203,119,0.14); color: #6bcb77; border: 1px solid rgba(107,203,119,0.32); }
|
| 739 |
-
.model-badge.qunred { background: rgba(255,80,160,0.14); color: #ff50a0; border: 1px solid rgba(255,80,160,0.32); }
|
| 740 |
-
.model-badge.q25vl3b { background: rgba(80,180,255,0.14); color: #50b4ff; border: 1px solid rgba(80,180,255,0.32); }
|
| 741 |
-
|
| 742 |
-
/* ββ Loader ββ */
|
| 743 |
.loader {
|
| 744 |
-
width:
|
| 745 |
-
border: 2px solid rgba(255,255,255,0.
|
| 746 |
border-top-color: #fff; border-radius: 50%;
|
| 747 |
-
animation: spin 0.
|
| 748 |
display: none;
|
| 749 |
}
|
| 750 |
@keyframes spin { to { transform: rotate(360deg); } }
|
| 751 |
|
| 752 |
-
/* ββ Status dot ββ */
|
| 753 |
.status-dot {
|
| 754 |
-
width:
|
| 755 |
-
background: var(--muted); display: inline-block; margin-right:
|
| 756 |
-
flex-shrink: 0;
|
| 757 |
}
|
| 758 |
.status-dot.active {
|
| 759 |
background: var(--accent2);
|
| 760 |
box-shadow: 0 0 5px var(--accent2);
|
| 761 |
}
|
| 762 |
|
| 763 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 764 |
</style>
|
| 765 |
</head>
|
| 766 |
<body>
|
|
@@ -769,7 +761,7 @@ async def homepage(request: Request):
|
|
| 769 |
<span class="logo">MULTIMODAL EDGE</span>
|
| 770 |
<span class="sep">|</span>
|
| 771 |
<span class="sub">Node-Based Inference Canvas</span>
|
| 772 |
-
<span class="badge">v2.5
|
| 773 |
</div>
|
| 774 |
|
| 775 |
<div id="canvas">
|
|
@@ -781,37 +773,43 @@ async def homepage(request: Request):
|
|
| 781 |
</svg>
|
| 782 |
|
| 783 |
<!-- βββ ID 01 : Image Input βββ -->
|
| 784 |
-
<div class="node fixed-height" id="node-img" style="left:
|
| 785 |
<div class="node-header">
|
| 786 |
<span><span class="status-dot" id="dot-img"></span>Input Image</span>
|
| 787 |
-
<span class="id">ID
|
| 788 |
</div>
|
| 789 |
<div class="node-body">
|
| 790 |
<div>
|
| 791 |
<label>Upload Image</label>
|
|
|
|
|
|
|
| 792 |
<div class="file-upload" id="dropZone">
|
| 793 |
-
<svg width="
|
| 794 |
stroke="#7c6af7" stroke-width="1.5"
|
| 795 |
stroke-linecap="round" stroke-linejoin="round">
|
| 796 |
-
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
| 797 |
<circle cx="8.5" cy="8.5" r="1.5"/>
|
| 798 |
<polyline points="21 15 16 10 5 21"/>
|
| 799 |
</svg>
|
| 800 |
-
<span>Click or drop image</span>
|
| 801 |
<input type="file" id="fileInput" accept="image/*">
|
| 802 |
</div>
|
|
|
|
|
|
|
| 803 |
<div class="preview-wrap" id="previewWrap">
|
| 804 |
<img id="imgPreview" class="img-preview" />
|
| 805 |
-
<button class="clear-btn" id="clearBtn" title="
|
| 806 |
-
<svg width="
|
| 807 |
-
stroke="currentColor" stroke-width="2.
|
| 808 |
stroke-linecap="round" stroke-linejoin="round">
|
| 809 |
<line x1="18" y1="6" x2="6" y2="18"/>
|
| 810 |
<line x1="6" y1="6" x2="18" y2="18"/>
|
| 811 |
</svg>
|
| 812 |
</button>
|
| 813 |
</div>
|
| 814 |
-
|
|
|
|
|
|
|
| 815 |
<span class="chip-dot"></span>
|
| 816 |
<span class="chip-name" id="chipName">β</span>
|
| 817 |
<span class="chip-size" id="chipSize"></span>
|
|
@@ -822,10 +820,10 @@ async def homepage(request: Request):
|
|
| 822 |
</div>
|
| 823 |
|
| 824 |
<!-- βββ ID 02 : Model Selector βββ -->
|
| 825 |
-
<div class="node fixed-height" id="node-model" style="left:
|
| 826 |
<div class="node-header">
|
| 827 |
<span><span class="status-dot" id="dot-model"></span>Model Selector</span>
|
| 828 |
-
<span class="id">ID
|
| 829 |
</div>
|
| 830 |
<div class="node-body">
|
| 831 |
<div>
|
|
@@ -841,9 +839,10 @@ async def homepage(request: Request):
|
|
| 841 |
</select>
|
| 842 |
</div>
|
| 843 |
<div id="modelInfoBox" class="model-info-box"
|
| 844 |
-
style="background:rgba(255,200,80,0.
|
| 845 |
<span class="model-badge q4b">QWEN 3.5 Β· 4B</span><br><br>
|
| 846 |
-
Qwen3.5
|
|
|
|
| 847 |
</div>
|
| 848 |
<div style="flex:1;"></div>
|
| 849 |
</div>
|
|
@@ -851,11 +850,11 @@ async def homepage(request: Request):
|
|
| 851 |
</div>
|
| 852 |
|
| 853 |
<!-- βββ ID 03 : Task Config βββ -->
|
| 854 |
-
<div class="node fixed-height" id="node-task" style="left:
|
| 855 |
<div class="port in" id="port-task-in" style="top:50%;transform:translateY(-50%);"></div>
|
| 856 |
<div class="node-header">
|
| 857 |
<span><span class="status-dot" id="dot-task"></span>Task Config</span>
|
| 858 |
-
<span class="id">ID
|
| 859 |
</div>
|
| 860 |
<div class="node-body">
|
| 861 |
<div>
|
|
@@ -869,11 +868,11 @@ async def homepage(request: Request):
|
|
| 869 |
</div>
|
| 870 |
<div>
|
| 871 |
<label>Prompt Directive</label>
|
| 872 |
-
<textarea id="promptInput" rows="
|
| 873 |
-
placeholder="e.g.,
|
| 874 |
</div>
|
| 875 |
<button class="run-btn" id="runBtn">
|
| 876 |
-
<span>
|
| 877 |
<span class="loader" id="btnLoader"></span>
|
| 878 |
</button>
|
| 879 |
</div>
|
|
@@ -881,11 +880,11 @@ async def homepage(request: Request):
|
|
| 881 |
</div>
|
| 882 |
|
| 883 |
<!-- βββ ID 04 : Output Stream βββ -->
|
| 884 |
-
<div class="node fixed-height" id="node-out" style="left:
|
| 885 |
<div class="port in" id="port-out-in" style="top:50%;transform:translateY(-50%);"></div>
|
| 886 |
<div class="node-header">
|
| 887 |
<span><span class="status-dot" id="dot-out"></span>Output Stream</span>
|
| 888 |
-
<span class="id">ID
|
| 889 |
</div>
|
| 890 |
<div class="node-body">
|
| 891 |
<label>Streamed Result</label>
|
|
@@ -894,11 +893,11 @@ async def homepage(request: Request):
|
|
| 894 |
</div>
|
| 895 |
|
| 896 |
<!-- βββ ID 05 : Grounding Visualiser βββ -->
|
| 897 |
-
<div class="node fixed-height" id="node-gnd" style="left:
|
| 898 |
<div class="port in" id="port-gnd-in" style="top:50%;transform:translateY(-50%);"></div>
|
| 899 |
<div class="node-header">
|
| 900 |
<span><span class="status-dot" id="dot-gnd"></span>View Grounding</span>
|
| 901 |
-
<span class="id">ID
|
| 902 |
</div>
|
| 903 |
<div class="node-body">
|
| 904 |
<label>Point / Detect Overlay</label>
|
|
@@ -981,6 +980,7 @@ requestAnimationFrame(updateWires);
|
|
| 981 |
// FILE UPLOAD + CLEAR
|
| 982 |
// ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 983 |
let currentFile = null;
|
|
|
|
| 984 |
const dropZone = document.getElementById('dropZone');
|
| 985 |
const fileInput = document.getElementById('fileInput');
|
| 986 |
const previewWrap = document.getElementById('previewWrap');
|
|
@@ -991,10 +991,10 @@ const chipName = document.getElementById('chipName');
|
|
| 991 |
const chipSize = document.getElementById('chipSize');
|
| 992 |
const dotImg = document.getElementById('dot-img');
|
| 993 |
|
| 994 |
-
function formatBytes(
|
| 995 |
-
if (
|
| 996 |
-
if (
|
| 997 |
-
return (
|
| 998 |
}
|
| 999 |
|
| 1000 |
function handleFile(file) {
|
|
@@ -1043,32 +1043,53 @@ dotModel.classList.add('active');
|
|
| 1043 |
|
| 1044 |
const MODEL_INFO = {
|
| 1045 |
qwen_4b: {
|
| 1046 |
-
html: `<span class="model-badge q4b">QWEN 3.5 Β· 4B</span><br><br>
|
| 1047 |
-
|
|
|
|
|
|
|
|
|
|
| 1048 |
},
|
| 1049 |
qwen_2b: {
|
| 1050 |
-
html: `<span class="model-badge q2b">QWEN 3.5 Β· 2B</span><br><br>
|
| 1051 |
-
|
|
|
|
|
|
|
|
|
|
| 1052 |
},
|
| 1053 |
qwen_vl: {
|
| 1054 |
-
html: `<span class="model-badge qvl">QWEN3-VL Β· 2B</span><br><br>
|
| 1055 |
-
|
|
|
|
|
|
|
|
|
|
| 1056 |
},
|
| 1057 |
lfm_450: {
|
| 1058 |
-
html: `<span class="model-badge lfm450">LFM Β· 450M</span><br><br>
|
| 1059 |
-
|
|
|
|
|
|
|
|
|
|
| 1060 |
},
|
| 1061 |
lfm_16: {
|
| 1062 |
-
html: `<span class="model-badge lfm16">LFM Β· 1.6B</span><br><br>
|
| 1063 |
-
|
|
|
|
|
|
|
|
|
|
| 1064 |
},
|
| 1065 |
qwen_unredacted: {
|
| 1066 |
-
html: `<span class="model-badge qunred">QWEN 3.5 Β· 2B UNREDACTED</span><br><br>
|
| 1067 |
-
|
|
|
|
|
|
|
|
|
|
| 1068 |
},
|
| 1069 |
qwen25_vl_3b: {
|
| 1070 |
-
html: `<span class="model-badge q25vl3b">QWEN 2.5-VL Β· 3B</span><br><br>
|
| 1071 |
-
|
|
|
|
|
|
|
|
|
|
| 1072 |
},
|
| 1073 |
};
|
| 1074 |
|
|
@@ -1086,7 +1107,7 @@ modelSelect.onchange = () => {
|
|
| 1086 |
const categorySelect = document.getElementById('categorySelect');
|
| 1087 |
const promptInput = document.getElementById('promptInput');
|
| 1088 |
const PLACEHOLDERS = {
|
| 1089 |
-
Query: 'e.g., Count the boats and describe the
|
| 1090 |
Caption: 'e.g., short | normal | detailed',
|
| 1091 |
Point: 'e.g., The gun held by the person.',
|
| 1092 |
Detect: 'e.g., The headlight of the car.',
|
|
@@ -1104,10 +1125,10 @@ function safeParseJSON(text) {
|
|
| 1104 |
.replace(/\\s*```$/, '')
|
| 1105 |
.trim();
|
| 1106 |
try { return JSON.parse(text); } catch(_) {}
|
| 1107 |
-
const
|
| 1108 |
-
if (
|
| 1109 |
-
const
|
| 1110 |
-
if (
|
| 1111 |
return null;
|
| 1112 |
}
|
| 1113 |
|
|
@@ -1143,6 +1164,7 @@ function roundRect(ctx, x, y, w, h, r) {
|
|
| 1143 |
function drawGrounding(imgSrc, jsonText) {
|
| 1144 |
const parsed = safeParseJSON(jsonText);
|
| 1145 |
if (!parsed) { console.warn('Grounding: could not parse JSON:', jsonText); return; }
|
|
|
|
| 1146 |
const img = new Image();
|
| 1147 |
img.onload = () => {
|
| 1148 |
const W = img.naturalWidth, H = img.naturalHeight;
|
|
@@ -1157,9 +1179,11 @@ function drawGrounding(imgSrc, jsonText) {
|
|
| 1157 |
gCtx.font = `bold ${fs}px JetBrains Mono, monospace`;
|
| 1158 |
|
| 1159 |
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
|
|
| 1160 |
items.forEach((item, i) => {
|
| 1161 |
const col = PALETTE[i % PALETTE.length];
|
| 1162 |
|
|
|
|
| 1163 |
let bbox = null;
|
| 1164 |
if (item?.bbox_2d?.length === 4) bbox = item.bbox_2d;
|
| 1165 |
else if (item?.bbox?.length === 4) bbox = item.bbox;
|
|
@@ -1168,13 +1192,17 @@ function drawGrounding(imgSrc, jsonText) {
|
|
| 1168 |
|
| 1169 |
if (bbox) {
|
| 1170 |
let [x1,y1,x2,y2] = bbox;
|
| 1171 |
-
if (x1<=1&&y1<=1&&x2<=1&&y2<=1) {
|
| 1172 |
-
|
|
|
|
|
|
|
| 1173 |
const lbl = item?.label || `${i+1}`;
|
| 1174 |
-
|
| 1175 |
-
gCtx.
|
|
|
|
| 1176 |
gCtx.strokeStyle = col;
|
| 1177 |
-
gCtx.strokeRect(x1,y1,bw,bh);
|
|
|
|
| 1178 |
const tw = gCtx.measureText(lbl).width;
|
| 1179 |
const ph = fs*1.4, pw = tw+10;
|
| 1180 |
const lx = x1, ly = Math.max(0, y1-ph);
|
|
@@ -1185,6 +1213,7 @@ function drawGrounding(imgSrc, jsonText) {
|
|
| 1185 |
return;
|
| 1186 |
}
|
| 1187 |
|
|
|
|
| 1188 |
let pt = null;
|
| 1189 |
if (item?.point_2d?.length === 2) pt = item.point_2d;
|
| 1190 |
else if (item?.point?.length === 2) pt = item.point;
|
|
@@ -1193,16 +1222,19 @@ function drawGrounding(imgSrc, jsonText) {
|
|
| 1193 |
|
| 1194 |
if (pt) {
|
| 1195 |
let [x,y] = pt;
|
| 1196 |
-
if (x<=1&&y<=1) { x*=W; y*=H; }
|
| 1197 |
const r = Math.max(8, W/60);
|
| 1198 |
const lbl = item?.label || `${i+1}`;
|
|
|
|
| 1199 |
gCtx.beginPath();
|
| 1200 |
gCtx.arc(x, y, r*1.6, 0, Math.PI*2);
|
| 1201 |
-
gCtx.fillStyle = hexToRgba(col,0.15); gCtx.fill();
|
|
|
|
| 1202 |
gCtx.beginPath();
|
| 1203 |
gCtx.arc(x, y, r, 0, Math.PI*2);
|
| 1204 |
gCtx.fillStyle = col; gCtx.fill();
|
| 1205 |
gCtx.strokeStyle = '#fff'; gCtx.stroke();
|
|
|
|
| 1206 |
gCtx.fillStyle = '#fff';
|
| 1207 |
gCtx.fillText(lbl, x+r+4, y+fs*0.4);
|
| 1208 |
}
|
|
@@ -1301,4 +1333,4 @@ runBtn.onclick = async () => {
|
|
| 1301 |
"""
|
| 1302 |
|
| 1303 |
if __name__ == "__main__":
|
| 1304 |
-
app.launch(show_error=True)
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import io
|
| 3 |
import json
|
|
|
|
| 467 |
/* ββ Top Bar ββ */
|
| 468 |
.top-bar {
|
| 469 |
position: sticky; top: 0; left: 0; right: 0;
|
| 470 |
+
height: 42px;
|
| 471 |
+
background: rgba(13,13,15,0.95);
|
| 472 |
border-bottom: 1px solid var(--node-border);
|
| 473 |
+
display: flex; align-items: center; padding: 0 20px;
|
| 474 |
+
gap: 12px; z-index: 1000;
|
| 475 |
backdrop-filter: blur(12px);
|
| 476 |
}
|
| 477 |
+
.top-bar .logo { font-size: 13px; font-weight: 700; color: var(--accent); letter-spacing: 0.05em; }
|
| 478 |
+
.top-bar .sep { color: var(--node-border); }
|
| 479 |
+
.top-bar .sub { font-size: 11px; color: var(--muted); }
|
| 480 |
.top-bar .badge {
|
| 481 |
margin-left: auto;
|
| 482 |
+
background: rgba(124,106,247,0.15);
|
| 483 |
+
border: 1px solid rgba(124,106,247,0.3);
|
| 484 |
+
padding: 3px 10px; border-radius: 20px;
|
| 485 |
+
font-size: 10px; color: var(--accent);
|
| 486 |
}
|
| 487 |
|
| 488 |
/* ββ Canvas ββ */
|
| 489 |
#canvas {
|
| 490 |
position: relative;
|
| 491 |
+
width: 1360px;
|
| 492 |
+
min-height: calc(100vh - 42px);
|
| 493 |
+
height: 900px;
|
| 494 |
margin: 0 auto;
|
| 495 |
}
|
| 496 |
|
|
|
|
| 501 |
overflow: visible;
|
| 502 |
}
|
| 503 |
path.wire {
|
| 504 |
+
fill: none; stroke: var(--wire); stroke-width: 2.5;
|
| 505 |
stroke-linecap: round;
|
| 506 |
}
|
| 507 |
path.wire.active {
|
| 508 |
+
stroke: var(--wire-active); stroke-width: 3;
|
| 509 |
+
stroke-dasharray: 8 4;
|
| 510 |
+
animation: flow 0.6s linear infinite;
|
| 511 |
}
|
| 512 |
+
@keyframes flow { to { stroke-dashoffset: -24; } }
|
| 513 |
|
| 514 |
/* ββ Nodes ββ */
|
| 515 |
.node {
|
| 516 |
position: absolute;
|
| 517 |
+
width: 295px;
|
| 518 |
background: var(--node-bg);
|
| 519 |
border: 1px solid var(--node-border);
|
| 520 |
+
border-radius: 9px;
|
| 521 |
+
box-shadow: 0 8px 28px rgba(0,0,0,0.5);
|
| 522 |
z-index: 10;
|
| 523 |
display: flex; flex-direction: column;
|
| 524 |
transition: box-shadow 0.2s;
|
| 525 |
}
|
| 526 |
.node:hover {
|
| 527 |
+
box-shadow: 0 8px 28px rgba(0,0,0,0.5),
|
| 528 |
+
0 0 0 1px rgba(124,106,247,0.3);
|
| 529 |
}
|
| 530 |
+
.node.fixed-height { height: 340px; }
|
| 531 |
|
| 532 |
.node-header {
|
| 533 |
background: var(--node-header);
|
| 534 |
+
padding: 7px 12px;
|
| 535 |
border-bottom: 1px solid var(--node-border);
|
| 536 |
+
border-radius: 9px 9px 0 0;
|
| 537 |
+
font-size: 11px; font-weight: 700;
|
| 538 |
cursor: grab;
|
| 539 |
display: flex; justify-content: space-between; align-items: center;
|
| 540 |
flex-shrink: 0;
|
| 541 |
user-select: none;
|
|
|
|
| 542 |
}
|
| 543 |
.node-header:active { cursor: grabbing; }
|
| 544 |
.node-header .id {
|
| 545 |
+
font-size: 10px; color: var(--muted);
|
| 546 |
background: rgba(255,255,255,0.04);
|
| 547 |
+
padding: 2px 7px; border-radius: 4px;
|
| 548 |
}
|
| 549 |
|
| 550 |
.node-body {
|
| 551 |
+
padding: 10px;
|
| 552 |
+
display: flex; flex-direction: column; gap: 8px;
|
| 553 |
flex: 1; overflow: hidden;
|
| 554 |
}
|
| 555 |
|
| 556 |
/* ββ Ports ββ */
|
| 557 |
.port {
|
| 558 |
position: absolute;
|
| 559 |
+
width: 11px; height: 11px;
|
| 560 |
background: var(--node-bg);
|
| 561 |
border: 2px solid var(--port);
|
| 562 |
border-radius: 50%; z-index: 30;
|
| 563 |
}
|
| 564 |
+
.port.out { right: -6px; }
|
| 565 |
+
.port.in { left: -6px; }
|
| 566 |
|
| 567 |
/* ββ Labels ββ */
|
| 568 |
label {
|
| 569 |
+
font-size: 10px; color: var(--muted);
|
| 570 |
+
font-weight: 600; display: block; margin-bottom: 3px;
|
| 571 |
+
letter-spacing: 0.07em; text-transform: uppercase;
|
| 572 |
}
|
| 573 |
|
| 574 |
input[type="file"] { display: none; }
|
| 575 |
|
| 576 |
/* ββ Upload Zone ββ */
|
| 577 |
.file-upload {
|
| 578 |
+
border: 1.5px dashed var(--node-border);
|
| 579 |
+
border-radius: 7px; padding: 12px 10px;
|
| 580 |
text-align: center; cursor: pointer;
|
| 581 |
+
font-size: 11px; color: var(--muted);
|
| 582 |
transition: border-color 0.2s, background 0.2s;
|
| 583 |
background: rgba(255,255,255,0.01);
|
| 584 |
+
display: flex; flex-direction: column; align-items: center; gap: 5px;
|
| 585 |
}
|
| 586 |
.file-upload:hover {
|
| 587 |
border-color: var(--accent);
|
| 588 |
background: rgba(124,106,247,0.04);
|
| 589 |
}
|
| 590 |
+
.file-upload svg { opacity: 0.5; transition: opacity 0.2s; }
|
| 591 |
+
.file-upload:hover svg { opacity: 0.9; }
|
| 592 |
|
| 593 |
/* ββ Preview wrapper ββ */
|
| 594 |
.preview-wrap {
|
| 595 |
display: none;
|
| 596 |
position: relative;
|
| 597 |
+
border-radius: 7px;
|
| 598 |
overflow: hidden;
|
| 599 |
border: 1px solid var(--node-border);
|
| 600 |
background: #000;
|
|
|
|
| 603 |
|
| 604 |
.img-preview {
|
| 605 |
width: 100%;
|
| 606 |
+
height: 170px;
|
| 607 |
object-fit: contain;
|
| 608 |
display: block;
|
| 609 |
}
|
|
|
|
| 611 |
/* ββ Clear button ββ */
|
| 612 |
.clear-btn {
|
| 613 |
position: absolute;
|
| 614 |
+
top: 6px; right: 6px;
|
| 615 |
+
width: 24px; height: 24px;
|
| 616 |
border-radius: 50%;
|
| 617 |
+
background: rgba(13,13,15,0.80);
|
| 618 |
+
border: 1px solid var(--node-border);
|
| 619 |
color: var(--accent3);
|
| 620 |
cursor: pointer;
|
| 621 |
display: flex; align-items: center; justify-content: center;
|
| 622 |
+
transition: background 0.18s, border-color 0.18s, transform 0.12s;
|
| 623 |
z-index: 20;
|
| 624 |
+
backdrop-filter: blur(6px);
|
| 625 |
}
|
| 626 |
.clear-btn:hover {
|
| 627 |
+
background: rgba(255,107,107,0.18);
|
| 628 |
border-color: var(--accent3);
|
| 629 |
+
transform: scale(1.08);
|
| 630 |
}
|
| 631 |
+
.clear-btn:active { transform: scale(0.95); }
|
| 632 |
.clear-btn svg { pointer-events: none; }
|
| 633 |
|
| 634 |
/* ββ Filename chip ββ */
|
| 635 |
.img-chip {
|
| 636 |
display: none;
|
| 637 |
+
align-items: center; gap: 6px;
|
| 638 |
+
background: rgba(124,106,247,0.08);
|
| 639 |
+
border: 1px solid rgba(124,106,247,0.22);
|
| 640 |
+
border-radius: 5px;
|
| 641 |
+
padding: 4px 8px;
|
| 642 |
+
font-size: 9px; color: var(--muted);
|
| 643 |
overflow: hidden;
|
| 644 |
}
|
| 645 |
.img-chip.visible { display: flex; }
|
|
|
|
| 653 |
white-space: nowrap; flex: 1;
|
| 654 |
color: var(--text); font-size: 9px;
|
| 655 |
}
|
| 656 |
+
.img-chip .chip-size {
|
| 657 |
+
color: var(--muted); flex-shrink: 0; font-size: 9px;
|
| 658 |
+
}
|
| 659 |
|
|
|
|
| 660 |
select, textarea {
|
| 661 |
width: 100%;
|
| 662 |
+
background: rgba(0,0,0,0.3);
|
| 663 |
border: 1px solid var(--node-border);
|
| 664 |
+
color: var(--text); padding: 7px 9px;
|
| 665 |
border-radius: 5px; outline: none;
|
| 666 |
+
font-size: 11px; font-family: 'JetBrains Mono', monospace;
|
| 667 |
resize: none; transition: border-color 0.2s;
|
|
|
|
| 668 |
}
|
| 669 |
select:focus, textarea:focus { border-color: var(--accent); }
|
| 670 |
select option { background: #1c1c26; }
|
| 671 |
|
|
|
|
| 672 |
button.run-btn {
|
| 673 |
background: linear-gradient(135deg, var(--accent), #9b59b6);
|
| 674 |
color: #fff; border: none;
|
| 675 |
+
padding: 8px; border-radius: 6px;
|
| 676 |
+
font-weight: 700; font-size: 11px;
|
| 677 |
font-family: 'JetBrains Mono', monospace;
|
| 678 |
cursor: pointer;
|
| 679 |
transition: opacity 0.2s, transform 0.1s;
|
| 680 |
+
display: flex; justify-content: center; align-items: center; gap: 8px;
|
| 681 |
+
letter-spacing: 0.04em; flex-shrink: 0;
|
| 682 |
}
|
| 683 |
+
button.run-btn:hover { opacity: 0.9; }
|
| 684 |
+
button.run-btn:active { transform: scale(0.98); }
|
| 685 |
button.run-btn:disabled {
|
| 686 |
background: var(--node-border); cursor: not-allowed; color: #555;
|
| 687 |
}
|
| 688 |
|
|
|
|
| 689 |
.output-box {
|
| 690 |
+
background: rgba(0,0,0,0.4);
|
| 691 |
border: 1px solid var(--node-border);
|
| 692 |
+
border-radius: 5px; padding: 10px;
|
| 693 |
flex: 1; overflow-y: auto;
|
| 694 |
+
font-size: 11px; line-height: 1.6;
|
| 695 |
+
color: #c8c8e0; white-space: pre-wrap;
|
| 696 |
user-select: text;
|
| 697 |
font-family: 'JetBrains Mono', monospace;
|
| 698 |
}
|
|
|
|
| 711 |
.ground-placeholder {
|
| 712 |
position: absolute; inset: 0;
|
| 713 |
display: flex; align-items: center; justify-content: center;
|
| 714 |
+
font-size: 11px; color: var(--muted); text-align: center; padding: 10px;
|
|
|
|
| 715 |
}
|
| 716 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 717 |
.loader {
|
| 718 |
+
width: 11px; height: 11px;
|
| 719 |
+
border: 2px solid rgba(255,255,255,0.3);
|
| 720 |
border-top-color: #fff; border-radius: 50%;
|
| 721 |
+
animation: spin 0.7s linear infinite;
|
| 722 |
display: none;
|
| 723 |
}
|
| 724 |
@keyframes spin { to { transform: rotate(360deg); } }
|
| 725 |
|
|
|
|
| 726 |
.status-dot {
|
| 727 |
+
width: 6px; height: 6px; border-radius: 50%;
|
| 728 |
+
background: var(--muted); display: inline-block; margin-right: 6px;
|
|
|
|
| 729 |
}
|
| 730 |
.status-dot.active {
|
| 731 |
background: var(--accent2);
|
| 732 |
box-shadow: 0 0 5px var(--accent2);
|
| 733 |
}
|
| 734 |
|
| 735 |
+
/* ββ Model badges ββ */
|
| 736 |
+
.model-badge {
|
| 737 |
+
display: inline-block; padding: 2px 7px;
|
| 738 |
+
border-radius: 4px; font-size: 9px; font-weight: 700;
|
| 739 |
+
letter-spacing: 0.06em; text-transform: uppercase;
|
| 740 |
+
}
|
| 741 |
+
.model-badge.q4b { background: rgba(255,200,80,0.15); color: #ffc850; border: 1px solid rgba(255,200,80,0.35); }
|
| 742 |
+
.model-badge.q2b { background: rgba(124,106,247,0.2); color: var(--accent); border: 1px solid rgba(124,106,247,0.3); }
|
| 743 |
+
.model-badge.qvl { background: rgba(255,150,50,0.15); color: #ff9632; border: 1px solid rgba(255,150,50,0.35); }
|
| 744 |
+
.model-badge.lfm450 { background: rgba(78,205,196,0.15); color: var(--accent2); border: 1px solid rgba(78,205,196,0.3); }
|
| 745 |
+
.model-badge.lfm16 { background: rgba(107,203,119,0.15); color: #6bcb77; border: 1px solid rgba(107,203,119,0.35); }
|
| 746 |
+
.model-badge.qunred { background: rgba(255,80,160,0.15); color: #ff50a0; border: 1px solid rgba(255,80,160,0.35); }
|
| 747 |
+
.model-badge.q25vl3b { background: rgba(80,180,255,0.15); color: #50b4ff; border: 1px solid rgba(80,180,255,0.35); }
|
| 748 |
+
|
| 749 |
+
.model-info-box {
|
| 750 |
+
border-radius: 6px; padding: 9px;
|
| 751 |
+
font-size: 10px; color: var(--muted); line-height: 1.55;
|
| 752 |
+
flex-shrink: 0;
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
.canvas-footer { height: 36px; }
|
| 756 |
</style>
|
| 757 |
</head>
|
| 758 |
<body>
|
|
|
|
| 761 |
<span class="logo">MULTIMODAL EDGE</span>
|
| 762 |
<span class="sep">|</span>
|
| 763 |
<span class="sub">Node-Based Inference Canvas</span>
|
| 764 |
+
<span class="badge">v2.5 β HEPTA MODEL</span>
|
| 765 |
</div>
|
| 766 |
|
| 767 |
<div id="canvas">
|
|
|
|
| 773 |
</svg>
|
| 774 |
|
| 775 |
<!-- βββ ID 01 : Image Input βββ -->
|
| 776 |
+
<div class="node fixed-height" id="node-img" style="left:40px; top:52px;">
|
| 777 |
<div class="node-header">
|
| 778 |
<span><span class="status-dot" id="dot-img"></span>Input Image</span>
|
| 779 |
+
<span class="id">ID: 01</span>
|
| 780 |
</div>
|
| 781 |
<div class="node-body">
|
| 782 |
<div>
|
| 783 |
<label>Upload Image</label>
|
| 784 |
+
|
| 785 |
+
<!-- Drop zone -->
|
| 786 |
<div class="file-upload" id="dropZone">
|
| 787 |
+
<svg width="30" height="30" viewBox="0 0 24 24" fill="none"
|
| 788 |
stroke="#7c6af7" stroke-width="1.5"
|
| 789 |
stroke-linecap="round" stroke-linejoin="round">
|
| 790 |
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
| 791 |
<circle cx="8.5" cy="8.5" r="1.5"/>
|
| 792 |
<polyline points="21 15 16 10 5 21"/>
|
| 793 |
</svg>
|
| 794 |
+
<span>Click or drop image here</span>
|
| 795 |
<input type="file" id="fileInput" accept="image/*">
|
| 796 |
</div>
|
| 797 |
+
|
| 798 |
+
<!-- Preview -->
|
| 799 |
<div class="preview-wrap" id="previewWrap">
|
| 800 |
<img id="imgPreview" class="img-preview" />
|
| 801 |
+
<button class="clear-btn" id="clearBtn" title="Remove image">
|
| 802 |
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
| 803 |
+
stroke="currentColor" stroke-width="2.5"
|
| 804 |
stroke-linecap="round" stroke-linejoin="round">
|
| 805 |
<line x1="18" y1="6" x2="6" y2="18"/>
|
| 806 |
<line x1="6" y1="6" x2="18" y2="18"/>
|
| 807 |
</svg>
|
| 808 |
</button>
|
| 809 |
</div>
|
| 810 |
+
|
| 811 |
+
<!-- Filename chip -->
|
| 812 |
+
<div class="img-chip" id="imgChip" style="margin-top:6px;">
|
| 813 |
<span class="chip-dot"></span>
|
| 814 |
<span class="chip-name" id="chipName">β</span>
|
| 815 |
<span class="chip-size" id="chipSize"></span>
|
|
|
|
| 820 |
</div>
|
| 821 |
|
| 822 |
<!-- βββ ID 02 : Model Selector βββ -->
|
| 823 |
+
<div class="node fixed-height" id="node-model" style="left:40px; top:412px;">
|
| 824 |
<div class="node-header">
|
| 825 |
<span><span class="status-dot" id="dot-model"></span>Model Selector</span>
|
| 826 |
+
<span class="id">ID: 02</span>
|
| 827 |
</div>
|
| 828 |
<div class="node-body">
|
| 829 |
<div>
|
|
|
|
| 839 |
</select>
|
| 840 |
</div>
|
| 841 |
<div id="modelInfoBox" class="model-info-box"
|
| 842 |
+
style="background:rgba(255,200,80,0.07);border:1px solid rgba(255,200,80,0.3);">
|
| 843 |
<span class="model-badge q4b">QWEN 3.5 Β· 4B</span><br><br>
|
| 844 |
+
Qwen3.5 4B multimodal model by Alibaba Cloud.
|
| 845 |
+
Enhanced capacity over 2B β richer reasoning, better instruction following.
|
| 846 |
</div>
|
| 847 |
<div style="flex:1;"></div>
|
| 848 |
</div>
|
|
|
|
| 850 |
</div>
|
| 851 |
|
| 852 |
<!-- βββ ID 03 : Task Config βββ -->
|
| 853 |
+
<div class="node fixed-height" id="node-task" style="left:425px; top:52px;">
|
| 854 |
<div class="port in" id="port-task-in" style="top:50%;transform:translateY(-50%);"></div>
|
| 855 |
<div class="node-header">
|
| 856 |
<span><span class="status-dot" id="dot-task"></span>Task Config</span>
|
| 857 |
+
<span class="id">ID: 03</span>
|
| 858 |
</div>
|
| 859 |
<div class="node-body">
|
| 860 |
<div>
|
|
|
|
| 868 |
</div>
|
| 869 |
<div>
|
| 870 |
<label>Prompt Directive</label>
|
| 871 |
+
<textarea id="promptInput" rows="4"
|
| 872 |
+
placeholder="e.g., Count the total number of boats and describe the environment."></textarea>
|
| 873 |
</div>
|
| 874 |
<button class="run-btn" id="runBtn">
|
| 875 |
+
<span>Execute</span>
|
| 876 |
<span class="loader" id="btnLoader"></span>
|
| 877 |
</button>
|
| 878 |
</div>
|
|
|
|
| 880 |
</div>
|
| 881 |
|
| 882 |
<!-- βββ ID 04 : Output Stream βββ -->
|
| 883 |
+
<div class="node fixed-height" id="node-out" style="left:810px; top:52px;">
|
| 884 |
<div class="port in" id="port-out-in" style="top:50%;transform:translateY(-50%);"></div>
|
| 885 |
<div class="node-header">
|
| 886 |
<span><span class="status-dot" id="dot-out"></span>Output Stream</span>
|
| 887 |
+
<span class="id">ID: 04</span>
|
| 888 |
</div>
|
| 889 |
<div class="node-body">
|
| 890 |
<label>Streamed Result</label>
|
|
|
|
| 893 |
</div>
|
| 894 |
|
| 895 |
<!-- βββ ID 05 : Grounding Visualiser βββ -->
|
| 896 |
+
<div class="node fixed-height" id="node-gnd" style="left:810px; top:412px;">
|
| 897 |
<div class="port in" id="port-gnd-in" style="top:50%;transform:translateY(-50%);"></div>
|
| 898 |
<div class="node-header">
|
| 899 |
<span><span class="status-dot" id="dot-gnd"></span>View Grounding</span>
|
| 900 |
+
<span class="id">ID: 05</span>
|
| 901 |
</div>
|
| 902 |
<div class="node-body">
|
| 903 |
<label>Point / Detect Overlay</label>
|
|
|
|
| 980 |
// FILE UPLOAD + CLEAR
|
| 981 |
// ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 982 |
let currentFile = null;
|
| 983 |
+
|
| 984 |
const dropZone = document.getElementById('dropZone');
|
| 985 |
const fileInput = document.getElementById('fileInput');
|
| 986 |
const previewWrap = document.getElementById('previewWrap');
|
|
|
|
| 991 |
const chipSize = document.getElementById('chipSize');
|
| 992 |
const dotImg = document.getElementById('dot-img');
|
| 993 |
|
| 994 |
+
function formatBytes(bytes) {
|
| 995 |
+
if (bytes < 1024) return bytes + ' B';
|
| 996 |
+
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
| 997 |
+
return (bytes / 1048576).toFixed(1) + ' MB';
|
| 998 |
}
|
| 999 |
|
| 1000 |
function handleFile(file) {
|
|
|
|
| 1043 |
|
| 1044 |
const MODEL_INFO = {
|
| 1045 |
qwen_4b: {
|
| 1046 |
+
html: `<span class="model-badge q4b">QWEN 3.5 Β· 4B</span><br><br>
|
| 1047 |
+
Qwen3.5 4B multimodal model by Alibaba Cloud.
|
| 1048 |
+
Enhanced capacity over 2B β richer reasoning & better instruction following.`,
|
| 1049 |
+
bg: 'rgba(255,200,80,0.07)',
|
| 1050 |
+
border: 'rgba(255,200,80,0.30)',
|
| 1051 |
},
|
| 1052 |
qwen_2b: {
|
| 1053 |
+
html: `<span class="model-badge q2b">QWEN 3.5 Β· 2B</span><br><br>
|
| 1054 |
+
Qwen3.5 2B multimodal model by Alibaba Cloud.
|
| 1055 |
+
Lightweight & fast β ideal for quick Query, Caption, Point & Detect tasks.`,
|
| 1056 |
+
bg: 'rgba(124,106,247,0.07)',
|
| 1057 |
+
border: 'rgba(124,106,247,0.25)',
|
| 1058 |
},
|
| 1059 |
qwen_vl: {
|
| 1060 |
+
html: `<span class="model-badge qvl">QWEN3-VL Β· 2B</span><br><br>
|
| 1061 |
+
Qwen3-VL-2B-Instruct β dedicated vision-language model by Alibaba Cloud.
|
| 1062 |
+
Strong spatial grounding, OCR & instruction-following.`,
|
| 1063 |
+
bg: 'rgba(255,150,50,0.07)',
|
| 1064 |
+
border: 'rgba(255,150,50,0.25)',
|
| 1065 |
},
|
| 1066 |
lfm_450: {
|
| 1067 |
+
html: `<span class="model-badge lfm450">LFM Β· 450M</span><br><br>
|
| 1068 |
+
LFM2.5-VL 450M by LiquidAI. Ultra-lightweight edge model
|
| 1069 |
+
with solid grounding capabilities.`,
|
| 1070 |
+
bg: 'rgba(78,205,196,0.07)',
|
| 1071 |
+
border: 'rgba(78,205,196,0.25)',
|
| 1072 |
},
|
| 1073 |
lfm_16: {
|
| 1074 |
+
html: `<span class="model-badge lfm16">LFM Β· 1.6B</span><br><br>
|
| 1075 |
+
LFM2.5-VL 1.6B by LiquidAI. Larger liquid-state model offering
|
| 1076 |
+
enhanced reasoning & richer visual understanding.`,
|
| 1077 |
+
bg: 'rgba(107,203,119,0.07)',
|
| 1078 |
+
border: 'rgba(107,203,119,0.25)',
|
| 1079 |
},
|
| 1080 |
qwen_unredacted: {
|
| 1081 |
+
html: `<span class="model-badge qunred">QWEN 3.5 Β· 2B UNREDACTED MAX</span><br><br>
|
| 1082 |
+
Qwen3.5-2B-Unredacted-MAX by prithivMLmods. Fine-tuned variant of Qwen3.5-2B
|
| 1083 |
+
with uncensored & extended instruction-following capabilities.`,
|
| 1084 |
+
bg: 'rgba(255,80,160,0.07)',
|
| 1085 |
+
border: 'rgba(255,80,160,0.25)',
|
| 1086 |
},
|
| 1087 |
qwen25_vl_3b: {
|
| 1088 |
+
html: `<span class="model-badge q25vl3b">QWEN 2.5-VL Β· 3B</span><br><br>
|
| 1089 |
+
Qwen2.5-VL-3B-Instruct by Alibaba Cloud. Powerful 3B vision-language model
|
| 1090 |
+
with strong grounding, OCR & multi-task visual reasoning.`,
|
| 1091 |
+
bg: 'rgba(80,180,255,0.07)',
|
| 1092 |
+
border: 'rgba(80,180,255,0.25)',
|
| 1093 |
},
|
| 1094 |
};
|
| 1095 |
|
|
|
|
| 1107 |
const categorySelect = document.getElementById('categorySelect');
|
| 1108 |
const promptInput = document.getElementById('promptInput');
|
| 1109 |
const PLACEHOLDERS = {
|
| 1110 |
+
Query: 'e.g., Count the total number of boats and describe the environment.',
|
| 1111 |
Caption: 'e.g., short | normal | detailed',
|
| 1112 |
Point: 'e.g., The gun held by the person.',
|
| 1113 |
Detect: 'e.g., The headlight of the car.',
|
|
|
|
| 1125 |
.replace(/\\s*```$/, '')
|
| 1126 |
.trim();
|
| 1127 |
try { return JSON.parse(text); } catch(_) {}
|
| 1128 |
+
const arrMatch = text.match(/\\[[\\s\\S]*?\\]/);
|
| 1129 |
+
if (arrMatch) { try { return JSON.parse(arrMatch[0]); } catch(_) {} }
|
| 1130 |
+
const objMatch = text.match(/\\{[\\s\\S]*?\\}/);
|
| 1131 |
+
if (objMatch) { try { return JSON.parse(objMatch[0]); } catch(_) {} }
|
| 1132 |
return null;
|
| 1133 |
}
|
| 1134 |
|
|
|
|
| 1164 |
function drawGrounding(imgSrc, jsonText) {
|
| 1165 |
const parsed = safeParseJSON(jsonText);
|
| 1166 |
if (!parsed) { console.warn('Grounding: could not parse JSON:', jsonText); return; }
|
| 1167 |
+
|
| 1168 |
const img = new Image();
|
| 1169 |
img.onload = () => {
|
| 1170 |
const W = img.naturalWidth, H = img.naturalHeight;
|
|
|
|
| 1179 |
gCtx.font = `bold ${fs}px JetBrains Mono, monospace`;
|
| 1180 |
|
| 1181 |
const items = Array.isArray(parsed) ? parsed : [parsed];
|
| 1182 |
+
|
| 1183 |
items.forEach((item, i) => {
|
| 1184 |
const col = PALETTE[i % PALETTE.length];
|
| 1185 |
|
| 1186 |
+
// ββ Bounding box ββ
|
| 1187 |
let bbox = null;
|
| 1188 |
if (item?.bbox_2d?.length === 4) bbox = item.bbox_2d;
|
| 1189 |
else if (item?.bbox?.length === 4) bbox = item.bbox;
|
|
|
|
| 1192 |
|
| 1193 |
if (bbox) {
|
| 1194 |
let [x1,y1,x2,y2] = bbox;
|
| 1195 |
+
if (x1 <= 1 && y1 <= 1 && x2 <= 1 && y2 <= 1) {
|
| 1196 |
+
x1*=W; y1*=H; x2*=W; y2*=H;
|
| 1197 |
+
}
|
| 1198 |
+
const bw = x2-x1, bh = y2-y1;
|
| 1199 |
const lbl = item?.label || `${i+1}`;
|
| 1200 |
+
|
| 1201 |
+
gCtx.fillStyle = hexToRgba(col, 0.18);
|
| 1202 |
+
gCtx.fillRect(x1, y1, bw, bh);
|
| 1203 |
gCtx.strokeStyle = col;
|
| 1204 |
+
gCtx.strokeRect(x1, y1, bw, bh);
|
| 1205 |
+
|
| 1206 |
const tw = gCtx.measureText(lbl).width;
|
| 1207 |
const ph = fs*1.4, pw = tw+10;
|
| 1208 |
const lx = x1, ly = Math.max(0, y1-ph);
|
|
|
|
| 1213 |
return;
|
| 1214 |
}
|
| 1215 |
|
| 1216 |
+
// ββ Point ββ
|
| 1217 |
let pt = null;
|
| 1218 |
if (item?.point_2d?.length === 2) pt = item.point_2d;
|
| 1219 |
else if (item?.point?.length === 2) pt = item.point;
|
|
|
|
| 1222 |
|
| 1223 |
if (pt) {
|
| 1224 |
let [x,y] = pt;
|
| 1225 |
+
if (x <= 1 && y <= 1) { x*=W; y*=H; }
|
| 1226 |
const r = Math.max(8, W/60);
|
| 1227 |
const lbl = item?.label || `${i+1}`;
|
| 1228 |
+
|
| 1229 |
gCtx.beginPath();
|
| 1230 |
gCtx.arc(x, y, r*1.6, 0, Math.PI*2);
|
| 1231 |
+
gCtx.fillStyle = hexToRgba(col, 0.15); gCtx.fill();
|
| 1232 |
+
|
| 1233 |
gCtx.beginPath();
|
| 1234 |
gCtx.arc(x, y, r, 0, Math.PI*2);
|
| 1235 |
gCtx.fillStyle = col; gCtx.fill();
|
| 1236 |
gCtx.strokeStyle = '#fff'; gCtx.stroke();
|
| 1237 |
+
|
| 1238 |
gCtx.fillStyle = '#fff';
|
| 1239 |
gCtx.fillText(lbl, x+r+4, y+fs*0.4);
|
| 1240 |
}
|
|
|
|
| 1333 |
"""
|
| 1334 |
|
| 1335 |
if __name__ == "__main__":
|
| 1336 |
+
app.launch(show_error=True)
|