Spaces:
Sleeping
Sleeping
Commit ·
bc5a8d0
1
Parent(s): a1da080
new UI
Browse files- app/index.html +11 -10
- app/static/script.js +92 -68
- app/static/styles.css +75 -0
app/index.html
CHANGED
|
@@ -684,7 +684,7 @@
|
|
| 684 |
</div>
|
| 685 |
|
| 686 |
<h3 class="text-xl font-bold text-white mb-2 text-center tracking-wide uppercase">
|
| 687 |
-
|
| 688 |
</h3>
|
| 689 |
<p
|
| 690 |
id="loading-text"
|
|
@@ -706,15 +706,16 @@
|
|
| 706 |
</div>
|
| 707 |
</div>
|
| 708 |
|
| 709 |
-
<div
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
|
|
|
| 718 |
</div>
|
| 719 |
|
| 720 |
<div
|
|
|
|
| 684 |
</div>
|
| 685 |
|
| 686 |
<h3 class="text-xl font-bold text-white mb-2 text-center tracking-wide uppercase">
|
| 687 |
+
Processing
|
| 688 |
</h3>
|
| 689 |
<p
|
| 690 |
id="loading-text"
|
|
|
|
| 706 |
</div>
|
| 707 |
</div>
|
| 708 |
|
| 709 |
+
<div id="image-modal" class="hidden fixed inset-0 bg-black/95 z-[10000] flex items-center justify-center p-8 backdrop-blur-sm group" onclick="this.classList.add('hidden')">
|
| 710 |
+
<div class="absolute inset-0 pointer-events-none z-10 bg-scanline opacity-20"></div>
|
| 711 |
+
<div class="absolute inset-0 pointer-events-none z-20 border-[1px] border-white/10 m-10 rounded-lg">
|
| 712 |
+
<div class="absolute top-0 left-0 w-4 h-4 border-t-2 border-l-2 border-violet-500"></div>
|
| 713 |
+
<div class="absolute top-0 right-0 w-4 h-4 border-t-2 border-r-2 border-violet-500"></div>
|
| 714 |
+
<div class="absolute bottom-0 left-0 w-4 h-4 border-b-2 border-l-2 border-violet-500"></div>
|
| 715 |
+
<div class="absolute bottom-0 right-0 w-4 h-4 border-b-2 border-r-2 border-violet-500"></div>
|
| 716 |
+
</div>
|
| 717 |
+
|
| 718 |
+
<img id="modal-image" class="max-w-full max-h-full object-contain rounded shadow-2xl border border-white/10 transition-transform duration-500 scale-95 opacity-0 modal-img-anim" />
|
| 719 |
</div>
|
| 720 |
|
| 721 |
<div
|
app/static/script.js
CHANGED
|
@@ -69,19 +69,19 @@ document
|
|
| 69 |
document
|
| 70 |
.getElementById("start-clustering-btn")
|
| 71 |
.addEventListener("click", async () => {
|
| 72 |
-
|
| 73 |
-
|
| 74 |
if (isConfigOpen) toggleConfigBtn.click();
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
| 76 |
loadingOverlay.classList.remove("hidden");
|
|
|
|
|
|
|
| 77 |
const hero = document.getElementById("hero-landing");
|
| 78 |
-
if (hero)
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
setTimeout(() => {
|
| 82 |
-
hero.classList.add("hidden");
|
| 83 |
-
}, 800);
|
| 84 |
-
}
|
| 85 |
loadingText.textContent = "Uploading images...";
|
| 86 |
loadingBar.style.width = "10%";
|
| 87 |
|
|
@@ -98,7 +98,7 @@ document
|
|
| 98 |
}
|
| 99 |
}
|
| 100 |
|
| 101 |
-
loadingText.textContent = "
|
| 102 |
loadingBar.style.width = "60%";
|
| 103 |
|
| 104 |
try {
|
|
@@ -744,41 +744,42 @@ document.getElementById("toggle-lines").onchange = () => {
|
|
| 744 |
|
| 745 |
// Improved tab switching with proper cleanup
|
| 746 |
document.querySelectorAll(".tab-button").forEach((btn) => {
|
| 747 |
-
|
| 748 |
-
const
|
| 749 |
-
const
|
| 750 |
-
|
| 751 |
-
// Update tab UI
|
| 752 |
-
document
|
| 753 |
-
.querySelectorAll(".tab-button")
|
| 754 |
-
.forEach((b) => b.classList.remove("active"));
|
| 755 |
-
document
|
| 756 |
-
.querySelectorAll(".tab-content")
|
| 757 |
-
.forEach((c) => c.classList.add("hidden"));
|
| 758 |
-
btn.classList.add("active");
|
| 759 |
-
document.getElementById(`tab-${newTab}`).classList.remove("hidden");
|
| 760 |
-
|
| 761 |
-
// Handle universe tab specifically
|
| 762 |
-
if (oldTab === "universe" && newTab !== "universe") {
|
| 763 |
-
// Leaving universe tab - stop rotation
|
| 764 |
-
stopRotation();
|
| 765 |
-
}
|
| 766 |
|
| 767 |
-
|
|
|
|
| 768 |
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
Plotly.Plots.resize("plotly-div");
|
| 774 |
-
if (document.getElementById("toggle-rotate").checked)
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 782 |
}
|
| 783 |
});
|
| 784 |
});
|
|
@@ -801,6 +802,7 @@ function renderClusterList() {
|
|
| 801 |
|
| 802 |
function loadCluster(name) {
|
| 803 |
currentClusterName = name;
|
|
|
|
| 804 |
document
|
| 805 |
.querySelectorAll(".cluster-button")
|
| 806 |
.forEach((b) => b.classList.remove("active"));
|
|
@@ -813,49 +815,73 @@ function loadCluster(name) {
|
|
| 813 |
document.getElementById(
|
| 814 |
"thumbnail-header"
|
| 815 |
).textContent = `Cluster Content: ${name}`;
|
|
|
|
| 816 |
const q = qualityScores[name]?.images || [];
|
| 817 |
|
| 818 |
document.getElementById("delete-btn").disabled = false;
|
| 819 |
document.getElementById("move-btn").disabled = false;
|
| 820 |
document.getElementById("smart-cleanup-btn").disabled = false;
|
| 821 |
|
| 822 |
-
currentGroups[name].forEach((path) => {
|
| 823 |
const url = `${API_URL}/results/${currentSessionId}/clusters/${path}`;
|
| 824 |
const info = q.find((i) => i.path === path);
|
| 825 |
const isBest = info?.is_best;
|
| 826 |
|
| 827 |
const div = document.createElement("div");
|
|
|
|
|
|
|
|
|
|
|
|
|
| 828 |
div.className = `thumbnail-card rounded p-2 flex flex-col relative group ${
|
| 829 |
isBest ? "best-quality" : ""
|
| 830 |
}`;
|
| 831 |
div.dataset.path = path;
|
|
|
|
| 832 |
div.innerHTML = `
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 855 |
div.querySelector("input").onclick = (e) => {
|
| 856 |
e.stopPropagation();
|
| 857 |
div.classList.toggle("selected", e.target.checked);
|
| 858 |
};
|
|
|
|
| 859 |
gallery.appendChild(div);
|
| 860 |
});
|
| 861 |
}
|
|
@@ -1088,14 +1114,12 @@ document.getElementById("keep-best-btn").onclick = () => {
|
|
| 1088 |
}
|
| 1089 |
};
|
| 1090 |
|
| 1091 |
-
// Modal click outside properly
|
| 1092 |
document.getElementById("image-modal").onclick = (e) => {
|
| 1093 |
if (e.target.id === "image-modal") {
|
| 1094 |
e.target.classList.add("hidden");
|
| 1095 |
}
|
| 1096 |
};
|
| 1097 |
|
| 1098 |
-
// Cleanup on page unload
|
| 1099 |
window.addEventListener("beforeunload", () => {
|
| 1100 |
stopRotation();
|
| 1101 |
Object.values(chartInstances).forEach((chart) => {
|
|
|
|
| 69 |
document
|
| 70 |
.getElementById("start-clustering-btn")
|
| 71 |
.addEventListener("click", async () => {
|
| 72 |
+
if (!uploadedFiles || uploadedFiles.length === 0) return alert("Please select a folder first.");
|
| 73 |
+
|
| 74 |
if (isConfigOpen) toggleConfigBtn.click();
|
| 75 |
+
document.body.classList.add("warp-active");
|
| 76 |
+
|
| 77 |
+
await new Promise(r => setTimeout(r, 800));
|
| 78 |
+
|
| 79 |
loadingOverlay.classList.remove("hidden");
|
| 80 |
+
document.body.classList.remove("warp-active");
|
| 81 |
+
|
| 82 |
const hero = document.getElementById("hero-landing");
|
| 83 |
+
if (hero) hero.classList.add("hidden");
|
| 84 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
loadingText.textContent = "Uploading images...";
|
| 86 |
loadingBar.style.width = "10%";
|
| 87 |
|
|
|
|
| 98 |
}
|
| 99 |
}
|
| 100 |
|
| 101 |
+
loadingText.textContent = "This may takes a few minutes...";
|
| 102 |
loadingBar.style.width = "60%";
|
| 103 |
|
| 104 |
try {
|
|
|
|
| 744 |
|
| 745 |
// Improved tab switching with proper cleanup
|
| 746 |
document.querySelectorAll(".tab-button").forEach((btn) => {
|
| 747 |
+
btn.addEventListener("click", () => {
|
| 748 |
+
const newTabId = btn.dataset.tab;
|
| 749 |
+
const currentActiveContent = document.querySelector(".tab-content.fade-in");
|
| 750 |
+
const newContent = document.getElementById(`tab-${newTabId}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 751 |
|
| 752 |
+
document.querySelectorAll(".tab-button").forEach((b) => b.classList.remove("active"));
|
| 753 |
+
btn.classList.add("active");
|
| 754 |
|
| 755 |
+
const showNewTab = () => {
|
| 756 |
+
document.querySelectorAll(".tab-content").forEach(c => {
|
| 757 |
+
c.classList.remove("active-tab", "fade-in");
|
| 758 |
+
c.style.display = "none";
|
| 759 |
+
});
|
| 760 |
+
|
| 761 |
+
newContent.style.display = "block";
|
| 762 |
+
requestAnimationFrame(() => {
|
| 763 |
+
newContent.classList.add("active-tab", "fade-in");
|
| 764 |
+
});
|
| 765 |
+
|
| 766 |
+
if (newTabId === "universe") {
|
| 767 |
+
universeState.currentTab = "universe";
|
| 768 |
+
setTimeout(() => {
|
| 769 |
Plotly.Plots.resize("plotly-div");
|
| 770 |
+
if (document.getElementById("toggle-rotate").checked) startRotation();
|
| 771 |
+
}, 350);
|
| 772 |
+
} else {
|
| 773 |
+
stopRotation();
|
| 774 |
+
universeState.currentTab = newTabId;
|
| 775 |
+
}
|
| 776 |
+
};
|
| 777 |
+
|
| 778 |
+
if (currentActiveContent && currentActiveContent !== newContent) {
|
| 779 |
+
currentActiveContent.classList.remove("fade-in");
|
| 780 |
+
setTimeout(showNewTab, 300);
|
| 781 |
+
} else {
|
| 782 |
+
showNewTab();
|
| 783 |
}
|
| 784 |
});
|
| 785 |
});
|
|
|
|
| 802 |
|
| 803 |
function loadCluster(name) {
|
| 804 |
currentClusterName = name;
|
| 805 |
+
|
| 806 |
document
|
| 807 |
.querySelectorAll(".cluster-button")
|
| 808 |
.forEach((b) => b.classList.remove("active"));
|
|
|
|
| 815 |
document.getElementById(
|
| 816 |
"thumbnail-header"
|
| 817 |
).textContent = `Cluster Content: ${name}`;
|
| 818 |
+
|
| 819 |
const q = qualityScores[name]?.images || [];
|
| 820 |
|
| 821 |
document.getElementById("delete-btn").disabled = false;
|
| 822 |
document.getElementById("move-btn").disabled = false;
|
| 823 |
document.getElementById("smart-cleanup-btn").disabled = false;
|
| 824 |
|
| 825 |
+
currentGroups[name].forEach((path, index) => {
|
| 826 |
const url = `${API_URL}/results/${currentSessionId}/clusters/${path}`;
|
| 827 |
const info = q.find((i) => i.path === path);
|
| 828 |
const isBest = info?.is_best;
|
| 829 |
|
| 830 |
const div = document.createElement("div");
|
| 831 |
+
|
| 832 |
+
const delay = Math.min(index * 30, 1000);
|
| 833 |
+
div.style.animationDelay = `${delay}ms`;
|
| 834 |
+
|
| 835 |
div.className = `thumbnail-card rounded p-2 flex flex-col relative group ${
|
| 836 |
isBest ? "best-quality" : ""
|
| 837 |
}`;
|
| 838 |
div.dataset.path = path;
|
| 839 |
+
|
| 840 |
div.innerHTML = `
|
| 841 |
+
<input type="checkbox" class="mb-1 z-20 accent-violet-500 cursor-pointer">
|
| 842 |
+
${isBest ? '<div class="best-badge">BEST</div>' : ""}
|
| 843 |
+
${
|
| 844 |
+
info
|
| 845 |
+
? `<div class="quality-badge" style="background:${
|
| 846 |
+
info.quality_color
|
| 847 |
+
}">${info.scores.total.toFixed(0)}
|
| 848 |
+
<div class="quality-details"><div class="quality-metric"><span class="quality-metric-label">Res</span><span class="quality-metric-value">${
|
| 849 |
+
info.scores.resolution
|
| 850 |
+
}</span></div><div class="quality-metric"><span class="quality-metric-label">Sharp</span><span class="quality-metric-value">${
|
| 851 |
+
info.scores.sharpness
|
| 852 |
+
}</span></div></div>
|
| 853 |
+
</div>`
|
| 854 |
+
: ""
|
| 855 |
+
}
|
| 856 |
+
<div class="relative overflow-hidden rounded aspect-square bg-black">
|
| 857 |
+
<img src="${url}" loading="lazy" class="w-full h-full object-contain transition-transform duration-300 group-hover:scale-110 cursor-zoom-in">
|
| 858 |
+
</div>
|
| 859 |
+
<div class="text-[10px] text-gray-400 truncate mt-2 font-mono text-center">${path
|
| 860 |
+
.split("/")
|
| 861 |
+
.pop()}</div>
|
| 862 |
+
`;
|
| 863 |
+
|
| 864 |
+
const img = div.querySelector('img');
|
| 865 |
+
img.onclick = (e) => {
|
| 866 |
+
e.stopPropagation();
|
| 867 |
+
const modal = document.getElementById('image-modal');
|
| 868 |
+
const modalImg = document.getElementById('modal-image');
|
| 869 |
+
|
| 870 |
+
modalImg.src = url;
|
| 871 |
+
|
| 872 |
+
modalImg.classList.remove('show');
|
| 873 |
+
modal.classList.remove('hidden');
|
| 874 |
+
|
| 875 |
+
setTimeout(() => {
|
| 876 |
+
modalImg.classList.add('show');
|
| 877 |
+
}, 50);
|
| 878 |
+
};
|
| 879 |
+
|
| 880 |
div.querySelector("input").onclick = (e) => {
|
| 881 |
e.stopPropagation();
|
| 882 |
div.classList.toggle("selected", e.target.checked);
|
| 883 |
};
|
| 884 |
+
|
| 885 |
gallery.appendChild(div);
|
| 886 |
});
|
| 887 |
}
|
|
|
|
| 1114 |
}
|
| 1115 |
};
|
| 1116 |
|
|
|
|
| 1117 |
document.getElementById("image-modal").onclick = (e) => {
|
| 1118 |
if (e.target.id === "image-modal") {
|
| 1119 |
e.target.classList.add("hidden");
|
| 1120 |
}
|
| 1121 |
};
|
| 1122 |
|
|
|
|
| 1123 |
window.addEventListener("beforeunload", () => {
|
| 1124 |
stopRotation();
|
| 1125 |
Object.values(chartInstances).forEach((chart) => {
|
app/static/styles.css
CHANGED
|
@@ -659,4 +659,79 @@ body {
|
|
| 659 |
|
| 660 |
.text-glow-anim {
|
| 661 |
animation: text-shimmer 4s infinite alternate;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
|
|
|
| 659 |
|
| 660 |
.text-glow-anim {
|
| 661 |
animation: text-shimmer 4s infinite alternate;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
.warp-overlay {
|
| 665 |
+
position: fixed;
|
| 666 |
+
inset: 0;
|
| 667 |
+
background: black;
|
| 668 |
+
z-index: 9000;
|
| 669 |
+
opacity: 0;
|
| 670 |
+
pointer-events: none;
|
| 671 |
+
transition: opacity 0.5s;
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
.warp-active .star-layer {
|
| 675 |
+
/* Kéo dài các ngôi sao thành vạch sáng */
|
| 676 |
+
transform-origin: center center;
|
| 677 |
+
transition: transform 0.8s cubic-bezier(0.7, 0, 0.3, 1), opacity 0.5s;
|
| 678 |
+
transform: scale(5) rotate(10deg);
|
| 679 |
+
opacity: 0;
|
| 680 |
+
filter: blur(2px);
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
.warp-active .hero-content-wrapper {
|
| 684 |
+
/* Nội dung chính phóng to và biến mất */
|
| 685 |
+
animation: hyperspace-jump 0.8s cubic-bezier(0.7, 0, 0.3, 1) forwards;
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
@keyframes hyperspace-jump {
|
| 689 |
+
0% { transform: scale(1); opacity: 1; filter: blur(0); }
|
| 690 |
+
40% { opacity: 0.8; filter: blur(2px); }
|
| 691 |
+
100% { transform: scale(4); opacity: 0; filter: blur(20px); }
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
@keyframes sci-fi-reveal {
|
| 695 |
+
0% {
|
| 696 |
+
opacity: 0;
|
| 697 |
+
transform: translateY(20px) scale(0.9);
|
| 698 |
+
filter: brightness(2) blur(4px);
|
| 699 |
+
}
|
| 700 |
+
100% {
|
| 701 |
+
opacity: 1;
|
| 702 |
+
transform: translateY(0) scale(1);
|
| 703 |
+
filter: brightness(1) blur(0);
|
| 704 |
+
}
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
+
.thumbnail-card {
|
| 708 |
+
opacity: 0;
|
| 709 |
+
animation: sci-fi-reveal 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
.tab-content {
|
| 713 |
+
transition: opacity 0.3s ease-in-out, transform 0.3s ease-out;
|
| 714 |
+
opacity: 0;
|
| 715 |
+
transform: scale(0.98);
|
| 716 |
+
display: none;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
.tab-content.active-tab {
|
| 720 |
+
display: block;
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
.tab-content.fade-in {
|
| 724 |
+
opacity: 1;
|
| 725 |
+
transform: scale(1);
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
.bg-scanline {
|
| 729 |
+
background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.3) 51%);
|
| 730 |
+
background-size: 100% 4px;
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
.modal-img-anim.show {
|
| 734 |
+
transform: scale(1);
|
| 735 |
+
opacity: 1;
|
| 736 |
+
box-shadow: 0 0 50px rgba(139, 92, 246, 0.2);
|
| 737 |
}
|