Spaces:
Running
Running
Failed to generate 3D model
Browse files- index.html +2 -2
- script.js +110 -38
- style.css +13 -3
index.html
CHANGED
|
@@ -7,8 +7,7 @@
|
|
| 7 |
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎨</text></svg>">
|
| 8 |
<link rel="stylesheet" href="style.css">
|
| 9 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
-
|
| 11 |
-
<script>
|
| 12 |
tailwind.config = {
|
| 13 |
theme: {
|
| 14 |
extend: {
|
|
@@ -48,6 +47,7 @@
|
|
| 48 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
|
| 49 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
|
| 50 |
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1000.0.min.js"></script>
|
|
|
|
| 51 |
</head>
|
| 52 |
<body class="bg-gradient-to-br from-primary-500 via-secondary-500 to-primary-600 min-h-screen">
|
| 53 |
<custom-header></custom-header>
|
|
|
|
| 7 |
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎨</text></svg>">
|
| 8 |
<link rel="stylesheet" href="style.css">
|
| 9 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
+
<script>
|
|
|
|
| 11 |
tailwind.config = {
|
| 12 |
theme: {
|
| 13 |
extend: {
|
|
|
|
| 47 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
|
| 48 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
|
| 49 |
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1000.0.min.js"></script>
|
| 50 |
+
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1000.0.min.js"></script>
|
| 51 |
</head>
|
| 52 |
<body class="bg-gradient-to-br from-primary-500 via-secondary-500 to-primary-600 min-h-screen">
|
| 53 |
<custom-header></custom-header>
|
script.js
CHANGED
|
@@ -23,6 +23,22 @@ let scene, camera, renderer, controls, currentModel;
|
|
| 23 |
let currentTaskId = null;
|
| 24 |
let statusCheckInterval = null;
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
// Initialize AWS S3
|
| 27 |
AWS.config.update({
|
| 28 |
accessKeyId: API_CONFIG.S3_ACCESS,
|
|
@@ -162,53 +178,64 @@ const progressBar = document.getElementById('progressBar');
|
|
| 162 |
const progressFill = document.getElementById('progressFill');
|
| 163 |
|
| 164 |
fileInput.addEventListener('change', handleFileSelect);
|
| 165 |
-
generateBtn.addEventListener('
|
| 166 |
-
|
| 167 |
function handleFileSelect(event) {
|
| 168 |
const file = event.target.files[0];
|
| 169 |
if (file && file.type.startsWith('image/')) {
|
| 170 |
const reader = new FileReader();
|
| 171 |
reader.onload = function(e) {
|
| 172 |
-
previewImg
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
};
|
| 176 |
reader.readAsDataURL(file);
|
| 177 |
}
|
| 178 |
}
|
| 179 |
-
|
| 180 |
// Drag and drop
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
| 187 |
|
| 188 |
-
uploadArea.addEventListener('dragleave', () => {
|
| 189 |
-
|
| 190 |
-
});
|
| 191 |
|
| 192 |
-
uploadArea.addEventListener('drop', (e) => {
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
}
|
| 201 |
});
|
| 202 |
-
|
| 203 |
// 3D Model Generation
|
| 204 |
async function generate3DModel() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
const file = fileInput.files[0];
|
| 206 |
if (!file) {
|
| 207 |
showStatus('error', 'Please select an image first', 'alert-circle');
|
| 208 |
return;
|
| 209 |
}
|
| 210 |
-
|
| 211 |
-
showStatus('processing', 'Uploading image...', 'upload-cloud');
|
| 212 |
progressBar.classList.remove('hidden');
|
| 213 |
updateProgress(20);
|
| 214 |
|
|
@@ -234,8 +261,13 @@ async function generate3DModel() {
|
|
| 234 |
progressBar.classList.add('hidden');
|
| 235 |
}
|
| 236 |
}
|
| 237 |
-
|
| 238 |
async function uploadToS3(file) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
const fileName = `uploads/${Date.now()}_${file.name}`;
|
| 240 |
const params = {
|
| 241 |
Bucket: API_CONFIG.BUCKET_NAME,
|
|
@@ -314,7 +346,9 @@ function pollTaskStatus(taskId) {
|
|
| 314 |
showStatus('success', '3D model generated successfully!', 'check-circle');
|
| 315 |
|
| 316 |
// Create a demo 3D model
|
| 317 |
-
|
|
|
|
|
|
|
| 318 |
|
| 319 |
// Display mock model info
|
| 320 |
displayModelInfo({
|
|
@@ -332,8 +366,7 @@ function pollTaskStatus(taskId) {
|
|
| 332 |
|
| 333 |
return;
|
| 334 |
}
|
| 335 |
-
|
| 336 |
-
// Real API polling (uncomment when you have real API)
|
| 337 |
/*
|
| 338 |
statusCheckInterval = setInterval(async () => {
|
| 339 |
try {
|
|
@@ -410,6 +443,19 @@ function load3DModel(modelUrl) {
|
|
| 410 |
|
| 411 |
// Create a demo 3D model for demonstration purposes
|
| 412 |
function createDemoModel() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
// Remove existing model
|
| 414 |
if (currentModel) {
|
| 415 |
scene.remove(currentModel);
|
|
@@ -453,6 +499,11 @@ function displayModelInfo(data) {
|
|
| 453 |
const modelInfo = document.getElementById('modelInfo');
|
| 454 |
const modelDetails = document.getElementById('modelDetails');
|
| 455 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
modelDetails.innerHTML = `
|
| 457 |
<p><strong>Format:</strong> ${data.format || 'GLTF'}</p>
|
| 458 |
<p><strong>Quality:</strong> ${data.quality || 'High'}</p>
|
|
@@ -463,9 +514,13 @@ function displayModelInfo(data) {
|
|
| 463 |
|
| 464 |
modelInfo.classList.remove('hidden');
|
| 465 |
}
|
| 466 |
-
|
| 467 |
function addToRecentGallery(modelUrl) {
|
| 468 |
const recentModels = document.getElementById('recentModels');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
const card = document.createElement('div');
|
| 470 |
card.className = 'aspect-square bg-gray-100 rounded-lg overflow-hidden model-card hover-lift';
|
| 471 |
card.innerHTML = `
|
|
@@ -480,8 +535,12 @@ function addToRecentGallery(modelUrl) {
|
|
| 480 |
recentModels.removeChild(recentModels.lastChild);
|
| 481 |
}
|
| 482 |
}
|
| 483 |
-
|
| 484 |
function showStatus(type, message, icon) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
statusContainer.classList.remove('hidden');
|
| 486 |
statusBox.className = `rounded-xl p-4 transition-all duration-300 status-${type}`;
|
| 487 |
|
|
@@ -492,19 +551,32 @@ function showStatus(type, message, icon) {
|
|
| 492 |
statusIcon.innerHTML = iconHtml;
|
| 493 |
statusText.textContent = message;
|
| 494 |
|
| 495 |
-
|
|
|
|
|
|
|
| 496 |
}
|
| 497 |
-
|
| 498 |
function updateProgress(percent) {
|
| 499 |
-
progressFill
|
|
|
|
|
|
|
| 500 |
}
|
| 501 |
-
|
| 502 |
// Initialize viewer on load
|
| 503 |
window.addEventListener('load', () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
setTimeout(() => {
|
| 505 |
initViewer();
|
| 506 |
}, 100);
|
| 507 |
});
|
| 508 |
|
| 509 |
-
// Generate button click handler
|
| 510 |
-
document.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
let currentTaskId = null;
|
| 24 |
let statusCheckInterval = null;
|
| 25 |
|
| 26 |
+
// DOM element references
|
| 27 |
+
let fileInput, imagePreview, previewImg, generateBtn, statusContainer, statusBox, statusIcon, statusText, progressBar, progressFill;
|
| 28 |
+
|
| 29 |
+
// Initialize DOM elements
|
| 30 |
+
function initDOMElements() {
|
| 31 |
+
fileInput = document.getElementById('fileInput');
|
| 32 |
+
imagePreview = document.getElementById('imagePreview');
|
| 33 |
+
previewImg = document.getElementById('previewImg');
|
| 34 |
+
generateBtn = document.getElementById('generateBtn');
|
| 35 |
+
statusContainer = document.getElementById('statusContainer');
|
| 36 |
+
statusBox = document.getElementById('statusBox');
|
| 37 |
+
statusIcon = document.getElementById('statusIcon');
|
| 38 |
+
statusText = document.getElementById('statusText');
|
| 39 |
+
progressBar = document.getElementById('progressBar');
|
| 40 |
+
progressFill = document.getElementById('progressFill');
|
| 41 |
+
}
|
| 42 |
// Initialize AWS S3
|
| 43 |
AWS.config.update({
|
| 44 |
accessKeyId: API_CONFIG.S3_ACCESS,
|
|
|
|
| 178 |
const progressFill = document.getElementById('progressFill');
|
| 179 |
|
| 180 |
fileInput.addEventListener('change', handleFileSelect);
|
| 181 |
+
generateBtn.addEventListener('click', generate3DModel);
|
|
|
|
| 182 |
function handleFileSelect(event) {
|
| 183 |
const file = event.target.files[0];
|
| 184 |
if (file && file.type.startsWith('image/')) {
|
| 185 |
const reader = new FileReader();
|
| 186 |
reader.onload = function(e) {
|
| 187 |
+
if (previewImg) {
|
| 188 |
+
previewImg.src = e.target.result;
|
| 189 |
+
}
|
| 190 |
+
if (imagePreview) {
|
| 191 |
+
imagePreview.classList.remove('hidden');
|
| 192 |
+
imagePreview.classList.add('animate-float');
|
| 193 |
+
}
|
| 194 |
};
|
| 195 |
reader.readAsDataURL(file);
|
| 196 |
}
|
| 197 |
}
|
|
|
|
| 198 |
// Drag and drop
|
| 199 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 200 |
+
const uploadArea = document.querySelector('label[for="fileInput"] > div');
|
| 201 |
+
|
| 202 |
+
if (uploadArea) {
|
| 203 |
+
uploadArea.addEventListener('dragover', (e) => {
|
| 204 |
+
e.preventDefault();
|
| 205 |
+
uploadArea.classList.add('drag-active');
|
| 206 |
+
});
|
| 207 |
|
| 208 |
+
uploadArea.addEventListener('dragleave', () => {
|
| 209 |
+
uploadArea.classList.remove('drag-active');
|
| 210 |
+
});
|
| 211 |
|
| 212 |
+
uploadArea.addEventListener('drop', (e) => {
|
| 213 |
+
e.preventDefault();
|
| 214 |
+
uploadArea.classList.remove('drag-active');
|
| 215 |
+
|
| 216 |
+
const files = e.dataTransfer.files;
|
| 217 |
+
if (files.length > 0 && files[0].type.startsWith('image/')) {
|
| 218 |
+
if (fileInput) {
|
| 219 |
+
fileInput.files = files;
|
| 220 |
+
handleFileSelect({ target: { files } });
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
});
|
| 224 |
}
|
| 225 |
});
|
|
|
|
| 226 |
// 3D Model Generation
|
| 227 |
async function generate3DModel() {
|
| 228 |
+
if (!fileInput) {
|
| 229 |
+
console.error('File input not found');
|
| 230 |
+
return;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
const file = fileInput.files[0];
|
| 234 |
if (!file) {
|
| 235 |
showStatus('error', 'Please select an image first', 'alert-circle');
|
| 236 |
return;
|
| 237 |
}
|
| 238 |
+
showStatus('processing', 'Uploading image...', 'upload-cloud');
|
|
|
|
| 239 |
progressBar.classList.remove('hidden');
|
| 240 |
updateProgress(20);
|
| 241 |
|
|
|
|
| 261 |
progressBar.classList.add('hidden');
|
| 262 |
}
|
| 263 |
}
|
|
|
|
| 264 |
async function uploadToS3(file) {
|
| 265 |
+
// Demo mode - simulate upload
|
| 266 |
+
if (API_CONFIG.DEMO_MODE) {
|
| 267 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 268 |
+
return `https://demo-server.com/uploads/${Date.now()}_${file.name}`;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
const fileName = `uploads/${Date.now()}_${file.name}`;
|
| 272 |
const params = {
|
| 273 |
Bucket: API_CONFIG.BUCKET_NAME,
|
|
|
|
| 346 |
showStatus('success', '3D model generated successfully!', 'check-circle');
|
| 347 |
|
| 348 |
// Create a demo 3D model
|
| 349 |
+
setTimeout(() => {
|
| 350 |
+
createDemoModel();
|
| 351 |
+
}, 100);
|
| 352 |
|
| 353 |
// Display mock model info
|
| 354 |
displayModelInfo({
|
|
|
|
| 366 |
|
| 367 |
return;
|
| 368 |
}
|
| 369 |
+
// Real API polling (uncomment when you have real API)
|
|
|
|
| 370 |
/*
|
| 371 |
statusCheckInterval = setInterval(async () => {
|
| 372 |
try {
|
|
|
|
| 443 |
|
| 444 |
// Create a demo 3D model for demonstration purposes
|
| 445 |
function createDemoModel() {
|
| 446 |
+
// Check if Three.js is available
|
| 447 |
+
if (typeof THREE === 'undefined') {
|
| 448 |
+
console.error('Three.js not loaded');
|
| 449 |
+
showStatus('error', '3D engine not available', 'x-circle');
|
| 450 |
+
return;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
if (!scene) {
|
| 454 |
+
console.error('Scene not initialized');
|
| 455 |
+
showStatus('error', '3D viewer not ready', 'x-circle');
|
| 456 |
+
return;
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
// Remove existing model
|
| 460 |
if (currentModel) {
|
| 461 |
scene.remove(currentModel);
|
|
|
|
| 499 |
const modelInfo = document.getElementById('modelInfo');
|
| 500 |
const modelDetails = document.getElementById('modelDetails');
|
| 501 |
|
| 502 |
+
if (!modelInfo || !modelDetails) {
|
| 503 |
+
console.error('Model info elements not found');
|
| 504 |
+
return;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
modelDetails.innerHTML = `
|
| 508 |
<p><strong>Format:</strong> ${data.format || 'GLTF'}</p>
|
| 509 |
<p><strong>Quality:</strong> ${data.quality || 'High'}</p>
|
|
|
|
| 514 |
|
| 515 |
modelInfo.classList.remove('hidden');
|
| 516 |
}
|
|
|
|
| 517 |
function addToRecentGallery(modelUrl) {
|
| 518 |
const recentModels = document.getElementById('recentModels');
|
| 519 |
+
if (!recentModels) {
|
| 520 |
+
console.error('Recent models container not found');
|
| 521 |
+
return;
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
const card = document.createElement('div');
|
| 525 |
card.className = 'aspect-square bg-gray-100 rounded-lg overflow-hidden model-card hover-lift';
|
| 526 |
card.innerHTML = `
|
|
|
|
| 535 |
recentModels.removeChild(recentModels.lastChild);
|
| 536 |
}
|
| 537 |
}
|
|
|
|
| 538 |
function showStatus(type, message, icon) {
|
| 539 |
+
if (!statusContainer || !statusBox || !statusIcon || !statusText) {
|
| 540 |
+
console.error('Status elements not found');
|
| 541 |
+
return;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
statusContainer.classList.remove('hidden');
|
| 545 |
statusBox.className = `rounded-xl p-4 transition-all duration-300 status-${type}`;
|
| 546 |
|
|
|
|
| 551 |
statusIcon.innerHTML = iconHtml;
|
| 552 |
statusText.textContent = message;
|
| 553 |
|
| 554 |
+
if (typeof feather !== 'undefined') {
|
| 555 |
+
feather.replace();
|
| 556 |
+
}
|
| 557 |
}
|
|
|
|
| 558 |
function updateProgress(percent) {
|
| 559 |
+
if (progressFill) {
|
| 560 |
+
progressFill.style.width = `${percent}%`;
|
| 561 |
+
}
|
| 562 |
}
|
|
|
|
| 563 |
// Initialize viewer on load
|
| 564 |
window.addEventListener('load', () => {
|
| 565 |
+
// Initialize Feather icons first
|
| 566 |
+
if (typeof feather !== 'undefined') {
|
| 567 |
+
feather.replace();
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
// Initialize viewer after DOM is ready
|
| 571 |
setTimeout(() => {
|
| 572 |
initViewer();
|
| 573 |
}, 100);
|
| 574 |
});
|
| 575 |
|
| 576 |
+
// Generate button click handler - wrap in DOMContentLoaded to ensure elements exist
|
| 577 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 578 |
+
const generateBtnElement = document.getElementById('generateBtn');
|
| 579 |
+
if (generateBtnElement) {
|
| 580 |
+
generateBtnElement.addEventListener('click', generate3DModel);
|
| 581 |
+
}
|
| 582 |
+
});
|
style.css
CHANGED
|
@@ -47,14 +47,13 @@
|
|
| 47 |
::-webkit-scrollbar-thumb:hover {
|
| 48 |
background: linear-gradient(135deg, #5a67d8 0%, #d946ef 100%);
|
| 49 |
}
|
| 50 |
-
|
| 51 |
/* Loading states */
|
| 52 |
.loading-spinner {
|
| 53 |
border: 3px solid #f3f3f3;
|
| 54 |
border-top: 3px solid #667eea;
|
| 55 |
border-radius: 50%;
|
| 56 |
-
width:
|
| 57 |
-
height:
|
| 58 |
animation: spin 1s linear infinite;
|
| 59 |
}
|
| 60 |
|
|
@@ -63,6 +62,17 @@
|
|
| 63 |
100% { transform: rotate(360deg); }
|
| 64 |
}
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
/* Glassmorphism effect */
|
| 67 |
.glass {
|
| 68 |
background: rgba(255, 255, 255, 0.1);
|
|
|
|
| 47 |
::-webkit-scrollbar-thumb:hover {
|
| 48 |
background: linear-gradient(135deg, #5a67d8 0%, #d946ef 100%);
|
| 49 |
}
|
|
|
|
| 50 |
/* Loading states */
|
| 51 |
.loading-spinner {
|
| 52 |
border: 3px solid #f3f3f3;
|
| 53 |
border-top: 3px solid #667eea;
|
| 54 |
border-radius: 50%;
|
| 55 |
+
width: 20px;
|
| 56 |
+
height: 20px;
|
| 57 |
animation: spin 1s linear infinite;
|
| 58 |
}
|
| 59 |
|
|
|
|
| 62 |
100% { transform: rotate(360deg); }
|
| 63 |
}
|
| 64 |
|
| 65 |
+
/* Status container fixes */
|
| 66 |
+
#statusContainer {
|
| 67 |
+
transition: all 0.3s ease;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#statusIcon {
|
| 71 |
+
display: flex;
|
| 72 |
+
align-items: center;
|
| 73 |
+
justify-content: center;
|
| 74 |
+
min-width: 24px;
|
| 75 |
+
}
|
| 76 |
/* Glassmorphism effect */
|
| 77 |
.glass {
|
| 78 |
background: rgba(255, 255, 255, 0.1);
|