Files
ai-media-hub/frontend/app.js
AI Assistant d030e737cb
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
Initial commit for AI Media Hub
2026-03-12 14:13:05 +09:00

199 lines
7.5 KiB
JavaScript

const statusText = document.getElementById('status-text');
const wsIndicator = document.getElementById('ws-indicator');
const searchBtn = document.getElementById('search-btn');
const searchInput = document.getElementById('search-input');
const resultsContainer = document.getElementById('discovery-results');
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
const dlBtn = document.getElementById('dl-btn');
const dlUrl = document.getElementById('dl-url');
const dlStart = document.getElementById('dl-start');
const dlEnd = document.getElementById('dl-end');
const confirmModal = document.getElementById('confirm-modal');
const dlConfirmBtn = document.getElementById('dl-confirm-btn');
const dlCancelBtn = document.getElementById('dl-cancel-btn');
// --- WebSocket ---
let ws;
function connectWS() {
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = `${proto}://${window.location.host}/ws`;
// For local dev without docker, port might be 3000
const finalWsUrl = window.location.port === '' ? `${proto}://${window.location.hostname}:3000/ws` : wsUrl;
ws = new WebSocket(finalWsUrl);
ws.onopen = () => {
wsIndicator.className = 'h-2 w-2 rounded-full bg-green-500';
};
ws.onmessage = (event) => {
statusText.textContent = event.data;
// flash text
statusText.classList.add('text-blue-400');
setTimeout(() => statusText.classList.remove('text-blue-400'), 500);
};
ws.onclose = () => {
wsIndicator.className = 'h-2 w-2 rounded-full bg-red-500';
setTimeout(connectWS, 2000); // Reconnect
};
}
connectWS();
// --- Zone A: Search ---
searchBtn.addEventListener('click', async () => {
const query = searchInput.value.trim();
if (!query) return;
searchBtn.disabled = true;
searchBtn.textContent = '검색 중...';
resultsContainer.innerHTML = '<div class="col-span-full h-full flex items-center justify-center text-gray-500 animate-pulse">인공지능이 이미지를 탐색하고 선별 중입니다...</div>';
try {
const port = window.location.port ? `:${window.location.port}` : ':3000';
const res = await fetch(`http://${window.location.hostname}${port}/api/search?q=${encodeURIComponent(query)}`);
const data = await res.json();
if (data.error) {
resultsContainer.innerHTML = `<div class="col-span-full text-red-500 p-4">Error: ${data.error}</div>`;
} else if (data.recommended && data.recommended.length > 0) {
resultsContainer.innerHTML = '';
data.recommended.forEach((item, index) => {
const delay = index * 100;
resultsContainer.innerHTML += `
<div class="recommend-card bg-[#252525] rounded-lg overflow-hidden border border-purple-500/30 hover:border-purple-500 transition-colors cursor-pointer group relative" style="animation-delay: ${delay}ms; opacity:0;">
<span class="absolute top-2 left-2 bg-purple-600/90 text-xs px-2 py-1 rounded text-white font-medium backdrop-blur-sm z-10 shadow-lg border border-purple-400/50">✨ AI Recommended</span>
<div class="h-32 w-full bg-black overflow-hidden relative">
<img src="${item.url}" class="w-full h-full object-cover opacity-80 group-hover:opacity-100 group-hover:scale-105 transition-all duration-300" onerror="this.src='https://via.placeholder.com/300x200?text=Image+Load+Error'" />
</div>
<div class="p-3">
<p class="text-xs text-gray-300 line-clamp-3 leading-relaxed">${item.reason}</p>
</div>
<a href="${item.url}" target="_blank" class="absolute inset-0"></a>
</div>
`;
});
} else {
resultsContainer.innerHTML = `<div class="col-span-full text-gray-500 p-4">결과를 찾을 수 없습니다.</div>`;
}
} catch (err) {
resultsContainer.innerHTML = `<div class="col-span-full text-red-500 p-4">Exception: ${err.message}</div>`;
} finally {
searchBtn.disabled = false;
searchBtn.textContent = '검색';
}
});
// --- Zone B: Upload ---
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('border-purple-500', 'bg-[#303030]');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('border-purple-500', 'bg-[#303030]');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('border-purple-500', 'bg-[#303030]');
if (e.dataTransfer.files.length > 0) {
uploadFile(e.dataTransfer.files[0]);
}
});
dropzone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
uploadFile(e.target.files[0]);
}
});
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
statusText.textContent = `Uploading ${file.name}...`;
try {
const port = window.location.port ? `:${window.location.port}` : ':3000';
const res = await fetch(`http://${window.location.hostname}${port}/api/upload`, {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.error) {
statusText.textContent = `Upload Error: ${data.error}`;
} else {
statusText.textContent = `Upload Success: ${data.filename}`;
}
} catch (err) {
statusText.textContent = `Upload Failed: ${err.message}`;
}
}
// --- Zone C: Download ---
async function requestDownload(confirm = false) {
const url = dlUrl.value.trim();
if (!url) return;
dlBtn.disabled = true;
dlBtn.textContent = '요청 중...';
const payload = {
url: url,
start: dlStart.value.trim(),
end: dlEnd.value.trim()
};
try {
const port = window.location.port ? `:${window.location.port}` : ':3000';
const res = await fetch(`http://${window.location.hostname}${port}/api/download${confirm ? '?confirm=true' : ''}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.status === 409) {
// Duplicate!
confirmModal.classList.remove('hidden');
confirmModal.classList.add('flex');
dlBtn.disabled = false;
dlBtn.textContent = '다운로드 시작';
return;
}
const data = await res.json();
if (data.error) {
statusText.textContent = `Download Error: ${data.error}`;
} else {
// The WS will handle the progress
dlUrl.value = '';
dlStart.value = '';
dlEnd.value = '';
confirmModal.classList.add('hidden');
confirmModal.classList.remove('flex');
}
} catch (err) {
statusText.textContent = `Download Request Failed: ${err.message}`;
} finally {
dlBtn.disabled = false;
dlBtn.textContent = '다운로드 시작';
}
}
dlBtn.addEventListener('click', () => requestDownload(false));
dlConfirmBtn.addEventListener('click', () => requestDownload(true));
dlCancelBtn.addEventListener('click', () => {
confirmModal.classList.add('hidden');
confirmModal.classList.remove('flex');
});