Mccscs2's picture
This app is not generating an image when I hit the Generate Image button
f8c34a7 verified
// State Management
const AppState = {
currentImage: null,
currentVideo: null,
isGenerating: false,
history: JSON.parse(localStorage.getItem('generationHistory') || '[]'),
settings: {
width: 1024,
height: 1024,
steps: 30,
cfgScale: 7.5,
seed: -1,
motionStrength: 5,
videoDuration: 4
}
};
// Utility Functions
const Utils = {
generateId: () => Math.random().toString(36).substr(2, 9),
async hashString(str) {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
},
getRandomSeed() {
return Math.floor(Math.random() * 2147483647);
},
formatDate(date) {
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date);
},
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
};
// API Simulation (Mock)
const APIService = {
async generateImage(prompt, negativePrompt, settings) {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 2000));
// Generate deterministic seed from prompt for consistent demo results
const seed = settings.seed > 0 ? settings.seed : Math.abs(Utils.hashString(prompt).split('').reduce((a,b)=>a+b.charCodeAt(0),0));
// Use static.photos with random category based on prompt content
const categories = ['technology', 'abstract', 'nature', 'people', 'cityscape', 'minimal'];
const category = categories[seed % categories.length];
const dimensions = settings.width > settings.height ? '1200x630' : settings.width < settings.height ? '640x360' : '1024x576';
return {
id: Utils.generateId(),
url: `http://static.photos/${category}/${dimensions}/${seed % 999}`,
prompt: prompt,
negativePrompt: negativePrompt,
settings: {...settings},
timestamp: new Date(),
seed: seed,
type: 'image'
};
},
async generateVideo(imageUrl, prompt, settings) {
await new Promise(resolve => setTimeout(resolve, 3000 + Math.random() * 3000));
const seed = Utils.getRandomSeed();
return {
id: Utils.generateId(),
imageUrl: imageUrl,
videoUrl: `http://static.photos/technology/640x360/${seed % 999}`, // Simulated video thumbnail
prompt: prompt,
settings: {...settings},
timestamp: new Date(),
duration: settings.videoDuration || 4,
type: 'video'
};
}
};
// UI Components Logic
const UI = {
showLoading(element, text = 'Generating...') {
element.innerHTML = `
<div class="flex flex-col items-center justify-center space-y-4 p-8">
<div class="relative w-16 h-16">
<div class="absolute inset-0 border-4 border-primary-500/30 rounded-full"></div>
<div class="absolute inset-0 border-4 border-t-primary-500 border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
</div>
<p class="text-primary-400 animate-pulse font-mono text-sm">${text}</p>
<div class="w-48 h-1 bg-slate-800 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-primary-500 to-secondary-500 animate-[shimmer_2s_infinite]" style="width: 0%; animation: loadingProgress 3s ease-out forwards;"></div>
</div>
</div>
<style>
@keyframes loadingProgress {
0% { width: 0%; }
100% { width: 100%; }
}
</style>
`;
},
showError(element, message) {
element.innerHTML = `
<div class="flex flex-col items-center justify-center space-y-3 p-8 text-red-400">
<i data-feather="alert-circle" class="w-12 h-12"></i>
<p class="text-center">${message}</p>
<button onclick="this.closest('.error-container').remove()" class="px-4 py-2 bg-red-500/20 hover:bg-red-500/30 rounded-lg text-sm transition-colors">
Dismiss
</button>
</div>
`;
// Fade in effect for image
const img = container.querySelector('img');
if (img) {
img.onload = () => {
img.style.opacity = '1';
};
// Force load if cached
if (img.complete) {
img.style.opacity = '1';
}
}
if (typeof feather !== 'undefined') {
feather.replace();
}
},
createImageCard(item) {
return `
<div class="group relative bg-slate-900/50 rounded-xl overflow-hidden border border-slate-700/50 hover:border-primary-500/50 transition-all duration-300 hover:glow-primary" data-id="${item.id}">
<div class="aspect-video relative overflow-hidden bg-slate-800">
<img src="${item.url}" alt="${item.prompt}" class="w-full h-full object-cover img-zoom" loading="lazy">
<div class="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent opacity-60"></div>
<!-- Hover Actions -->
<div class="absolute inset-0 bg-slate-950/80 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center gap-3">
<button onclick="app.animateImage('${item.id}')" class="p-3 bg-primary-600 hover:bg-primary-500 rounded-full text-white transition-all hover:scale-110" title="Generate Video">
<i data-feather="film" class="w-5 h-5"></i>
</button>
<button onclick="app.downloadImage('${item.url}')" class="p-3 bg-slate-700 hover:bg-slate-600 rounded-full text-white transition-all hover:scale-110" title="Download">
<i data-feather="download" class="w-5 h-5"></i>
</button>
<button onclick="app.deleteItem('${item.id}')" class="p-3 bg-red-500/20 hover:bg-red-500/40 rounded-full text-red-400 transition-all hover:scale-110" title="Delete">
<i data-feather="trash-2" class="w-5 h-5"></i>
</button>
</div>
${item.type === 'video' ? `
<div class="absolute top-3 right-3 bg-secondary-500/90 text-white px-2 py-1 rounded text-xs font-bold flex items-center gap-1">
<i data-feather="video" class="w-3 h-3"></i> VIDEO
</div>
` : ''}
</div>
<div class="p-4 space-y-2">
<p class="text-sm text-slate-300 line-clamp-2 font-medium">${item.prompt}</p>
<div class="flex items-center justify-between text-xs text-slate-500">
<span>${Utils.formatDate(new Date(item.timestamp))}</span>
<span class="font-mono">Seed: ${item.seed || 'N/A'}</span>
</div>
</div>
</div>
`;
}
};
// Main Application Logic
class DreamMachineApp {
constructor() {
this.init();
}
init() {
this.bindEvents();
this.loadHistory();
this.setupRealtimeValidation();
}
bindEvents() {
// Generate Image Button - handle both shadow DOM and regular DOM
document.addEventListener('click', (e) => {
const generateBtn = e.target.closest('#generate-btn');
if (generateBtn) {
e.preventDefault();
e.stopPropagation();
this.generateImage();
}
if (e.target.closest('#generate-video-btn')) {
this.generateVideoFromCurrent();
}
});
// Also bind to shadow DOM buttons directly
this.bindShadowDomevents();
}
bindShadowDomevents() {
// Wait for components to be defined
const controls = document.querySelector('generator-controls');
if (controls && controls.shadowRoot) {
const btn = controls.shadowRoot.getElementById('generate-btn');
if (btn) {
btn.addEventListener('click', (e) => {
e.stopPropagation();
this.generateImage();
});
}
}
}
// Settings changes
document.addEventListener('change', (e) => {
if (e.target.hasAttribute('data-setting')) {
const key = e.target.getAttribute('data-setting');
AppState.settings[key] = parseFloat(e.target.value) || e.target.value;
}
});
// Aspect ratio presets
document.addEventListener('click', (e) => {
if (e.target.closest('[data-ratio]')) {
const ratio = e.target.closest('[data-ratio]').getAttribute('data-ratio');
this.setAspectRatio(ratio);
}
});
// Prompt enhancement
document.addEventListener('click', (e) => {
if (e.target.closest('.enhance-btn')) {
this.enhancePrompt();
}
});
}
setupRealtimeValidation() {
const promptInput = document.getElementById('prompt-input');
if (promptInput) {
promptInput.addEventListener('input', Utils.debounce(() => {
const length = promptInput.value.length;
const indicator = document.getElementById('prompt-length');
if (indicator) {
indicator.textContent = `${length} chars`;
indicator.className = `text-xs ${length > 200 ? 'text-yellow-400' : 'text-slate-500'}`;
}
}, 300));
}
}
setAspectRatio(ratio) {
const ratios = {
'1:1': [1024, 1024],
'16:9': [1024, 576],
'9:16': [576, 1024],
'4:3': [1024, 768],
'3:4': [768, 1024]
};
const [w, h] = ratios[ratio] || ratios['1:1'];
AppState.settings.width = w;
AppState.settings.height = h;
// Update UI indicators
document.querySelectorAll('[data-ratio]').forEach(el => {
el.classList.remove('bg-primary-600', 'text-white');
el.classList.add('bg-slate-800', 'text-slate-400');
});
const active = document.querySelector(`[data-ratio="${ratio}"]`);
if (active) {
active.classList.remove('bg-slate-800', 'text-slate-400');
active.classList.add('bg-primary-600', 'text-white');
}
}
async generateImage() {
// Get elements from shadow DOM if needed
const controls = document.querySelector('generator-controls');
let promptInput = document.getElementById('prompt-input');
let negativeInput = document.getElementById('negative-prompt');
// Use shadow DOM if regular DOM not found
const shadowRoot = controls?.shadowRoot;
if (shadowRoot) {
if (!promptInput) promptInput = shadowRoot.getElementById('prompt-input');
if (!negativeInput) negativeInput = shadowRoot.getElementById('negative-prompt');
}
const preview = document.querySelector('generator-preview');
const previewContainer = preview?.shadowRoot?.getElementById('preview-container') || document.getElementById('preview-container');
const promptValue = promptInput?.value?.trim();
if (!promptValue) {
if (promptInput) {
promptInput.style.borderColor = '#ef4444';
setTimeout(() => {
promptInput.style.borderColor = '';
}, 2000);
}
return;
}
const prompt = promptValue;
const negativePrompt = negativeInput ? negativeInput.value.trim() : '';
AppState.isGenerating = true;
UI.showLoading(previewContainer, 'Generating Image...');
try {
const result = await APIService.generateImage(prompt, negativePrompt, AppState.settings);
AppState.currentImage = result;
// Save to history
AppState.history.unshift(result);
localStorage.setItem('generationHistory', JSON.stringify(AppState.history));
this.renderPreview(result);
this.updateGallery();
} catch (error) {
console.error('Generation error:', error);
this.showErrorInPreview(preview || previewContainer, 'Generation failed. Please try again.');
} finally {
AppState.isGenerating = false;
}
}
showErrorInPreview(previewElement, message) {
const container = previewElement?.shadowRoot?.getElementById('preview-container') || previewElement;
if (!container) return;
const errorDiv = document.createElement('div');
errorDiv.className = 'error-container flex flex-col items-center justify-center space-y-3 p-8 text-red-400';
errorDiv.innerHTML = `
<i data-feather="alert-circle" class="w-12 h-12"></i>
<p class="text-center">${message}</p>
<button class="px-4 py-2 bg-red-500/20 hover:bg-red-500/30 rounded-lg text-sm transition-colors dismiss-error">
Dismiss
</button>
`;
container.innerHTML = '';
container.appendChild(errorDiv);
// Bind dismiss button
errorDiv.querySelector('.dismiss-error')?.addEventListener('click', () => {
errorDiv.remove();
});
if (typeof feather !== 'undefined') {
feather.replace();
}
}
renderPreview(item) {
const preview = document.querySelector('generator-preview');
const container = preview?.shadowRoot?.getElementById('preview-container') || document.getElementById('preview-container');
if (!container) return;
container.innerHTML = `
<div class="relative w-full h-full min-h-[400px] bg-slate-900 rounded-2xl overflow-hidden border border-slate-700">
<img src="${item.url}" class="w-full h-full object-contain max-h-[500px]" alt="Generated" onload="this.classList.add('loaded')" style="opacity: 0; transition: opacity 0.5s;" onload="this.style.opacity=1">
<!-- Action Bar -->
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-slate-950 to-transparent">
<div class="flex items-center justify-between">
<div class="flex gap-2">
<button onclick="app.generateVideoFromCurrent()" class="px-4 py-2 bg-secondary-600 hover:bg-secondary-500 text-white rounded-lg text-sm font-medium transition-all flex items-center gap-2 btn-glow">
<i data-feather="film" class="w-4 h-4"></i>
Animate to Video
</button>
<button onclick="app.downloadImage('${item.url}')" class="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded-lg text-sm font-medium transition-all">
<i data-feather="download" class="w-4 h-4"></i>
</button>
</div>
<div class="text-xs text-slate-400 font-mono">
${item.width || 1024}x${item.height || 1024}
</div>
</div>
</div>
<!-- Info Overlay -->
<div class="absolute top-4 left-4 max-w-md">
<div class="glass-panel px-3 py-2 rounded-lg text-xs text-slate-300">
<span class="text-slate-500">Prompt:</span> ${item.prompt.substring(0, 100)}${item.prompt.length > 100 ? '...' : ''}
</div>
</div>
</div>
`;
if (typeof feather !== 'undefined') {
feather.replace();
}
}
async generateVideoFromCurrent() {
if (!AppState.currentImage) return;
const preview = document.querySelector('generator-preview');
const container = preview?.shadowRoot?.getElementById('preview-container') || document.getElementById('preview-container');
this.showLoadingInContainer(container, 'Generating Video... This may take a minute');
try {
const result = await APIService.generateVideo(
AppState.currentImage.url,
AppState.currentImage.prompt + ', cinematic motion, smooth animation',
AppState.settings
);
AppState.currentVideo = result;
// Update history
AppState.history.unshift(result);
localStorage.setItem('generationHistory', JSON.stringify(AppState.history));
this.renderVideoPreview(result);
this.updateGallery();
} catch (error) {
console.error('Video generation error:', error);
this.showErrorInPreview(preview, 'Video generation failed.');
}
}
showLoadingInContainer(container, text = 'Generating...') {
if (!container) return;
container.innerHTML = `
<div class="flex flex-col items-center justify-center space-y-4 p-8">
<div class="relative w-16 h-16">
<div class="absolute inset-0 border-4 border-purple-500/30 rounded-full"></div>
<div class="absolute inset-0 border-4 border-t-purple-500 border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
</div>
<p class="text-purple-400 animate-pulse font-mono text-sm">${text}</p>
<div class="w-48 h-1 bg-slate-800 rounded-full overflow-hidden">
<div class="loading-bar h-full bg-gradient-to-r from-purple-500 to-cyan-500" style="width: 0%;"></div>
</div>
</div>
<style>
@keyframes loadingProgress {
0% { width: 0%; }
100% { width: 100%; }
}
.loading-bar {
animation: loadingProgress 3s ease-out forwards;
}
</style>
`;
}
renderVideoPreview(item) {
const preview = document.querySelector('generator-preview');
const container = preview?.shadowRoot?.getElementById('preview-container') || document.getElementById('preview-container');
if (!container) return;
container.innerHTML = `
<div class="relative w-full h-full min-h-[400px] bg-black rounded-2xl overflow-hidden border border-secondary-500/30">
<div class="video-container w-full h-full flex items-center justify-center">
<img src="${item.videoUrl}" class="w-full h-full object-contain" alt="Video thumbnail">
<!-- Play Button Overlay -->
<div class="absolute inset-0 flex items-center justify-center bg-black/40">
<button class="w-20 h-20 bg-secondary-500/90 hover:bg-secondary-400 rounded-full flex items-center justify-center text-white transition-all hover:scale-110 group">
<i data-feather="play" class="w-8 h-8 fill-current group-hover:translate-x-1 transition-transform"></i>
</button>
</div>
</div>
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black to-transparent">
<div class="flex items-center justify-between text-white">
<div class="flex items-center gap-3">
<span class="px-2 py-1 bg-secondary-500/20 rounded text-xs font-mono text-secondary-400">MP4</span>
<span class="text-sm">${item.duration}s @ 24fps</span>
</div>
<button onclick="app.downloadImage('${item.videoUrl}')" class="p-2 hover:bg-white/10 rounded-lg transition-colors">
<i data-feather="download" class="w-5 h-5"></i>
</button>
</div>
</div>
</div>
`;
feather.replace();
}
animateImage(id) {
const item = AppState.history.find(h => h.id === id);
if (item && item.type === 'image') {
AppState.currentImage = item;
this.generateVideoFromCurrent();
}
}
downloadImage(url) {
const a = document.createElement('a');
a.href = url;
a.download = `dream-machine-${Date.now()}.png`;
a.target = '_blank';
a.click();
}
deleteItem(id) {
AppState.history = AppState.history.filter(h => h.id !== id);
localStorage.setItem('generationHistory', JSON.stringify(AppState.history));
this.updateGallery();
}
updateGallery() {
const gallery = document.querySelector('gallery-grid');
const grid = gallery?.shadowRoot?.getElementById('gallery-grid') || document.getElementById('gallery-grid');
const countIndicator = gallery?.shadowRoot?.getElementById('gallery-count');
if (countIndicator) {
countIndicator.textContent = `${AppState.history.length} items`;
}
if (!grid) return;
if (AppState.history.length === 0) {
grid.innerHTML = `
<div class="empty-state">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" style="margin: 0 auto 1rem; opacity: 0.3;"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
<p>No generations yet</p>
<p style="font-size: 0.875rem; margin-top: 0.5rem; opacity: 0.7;">Your creations will be saved here automatically</p>
</div>
`;
} else {
grid.innerHTML = AppState.history.map(item => this.createImageCard(item)).join('');
}
if (typeof feather !== 'undefined') {
feather.replace();
}
}
createImageCard(item) {
const isVideo = item.type === 'video';
return `
<div class="group relative bg-slate-900/50 rounded-xl overflow-hidden border border-slate-700/50 hover:border-purple-500/50 transition-all duration-300" style="box-shadow: 0 0 20px rgba(139, 92, 246, 0.1);" data-id="${item.id}">
<div class="aspect-video relative overflow-hidden bg-slate-800">
<img src="${item.url || item.videoUrl}" alt="${item.prompt}" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" loading="lazy">
<div class="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent opacity-60"></div>
<div class="absolute inset-0 bg-slate-950/80 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center gap-3">
${!isVideo ? `<button class="animate-image-btn p-3 bg-purple-600 hover:bg-purple-500 rounded-full text-white transition-all hover:scale-110" data-id="${item.id}" title="Generate Video">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/><line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/><line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/><line x1="17" y1="7" x2="22" y2="7"/></svg>
</button>` : ''}
<button class="download-btn p-3 bg-slate-700 hover:bg-slate-600 rounded-full text-white transition-all hover:scale-110" data-url="${item.url || item.videoUrl}" title="Download">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</button>
<button class="delete-btn p-3 bg-red-500/20 hover:bg-red-500/40 rounded-full text-red-400 transition-all hover:scale-110" data-id="${item.id}" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
</button>
</div>
${isVideo ? `
<div class="absolute top-3 right-3 bg-cyan-500/90 text-white px-2 py-1 rounded text-xs font-bold flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
VIDEO
</div>
` : ''}
</div>
<div class="p-4 space-y-2">
<p class="text-sm text-slate-300 line-clamp-2 font-medium">${item.prompt}</p>
<div class="flex items-center justify-between text-xs text-slate-500">
<span>${Utils.formatDate(new Date(item.timestamp))}</span>
<span class="font-mono">Seed: ${item.seed || 'N/A'}</span>
</div>
</div>
</div>
`;
}
loadHistory() {
this.updateGallery();
}
enhancePrompt() {
const controls = document.querySelector('generator-controls');
const input = document.getElementById('prompt-input') || controls?.shadowRoot?.getElementById('prompt-input');
if (!input) return;
const current = input.value;
const enhancers = [
'8k resolution, photorealistic, highly detailed, professional photography',
'cinematic lighting, unreal engine 5, octane render',
'masterpiece, best quality, intricate details',
'trending on artstation, sharp focus, vivid colors'
];
const randomEnhancer = enhancers[Math.floor(Math.random() * enhancers.length)];
input.value = current ? `${current}, ${randomEnhancer}` : randomEnhancer;
// Visual feedback
input.classList.add('ring-2', 'ring-secondary-500');
setTimeout(() => input.classList.remove('ring-2', 'ring-secondary-500'), 500);
}
}
// Gallery button event delegation
document.addEventListener('click', (e) => {
const animateBtn = e.target.closest('.animate-image-btn');
const downloadBtn = e.target.closest('.download-btn');
const deleteBtn = e.target.closest('.delete-btn');
if (animateBtn) {
e.stopPropagation();
const id = animateBtn.getAttribute('data-id');
app.animateImage(id);
}
if (downloadBtn) {
e.stopPropagation();
const url = downloadBtn.getAttribute('data-url');
app.downloadImage(url);
}
if (deleteBtn) {
e.stopPropagation();
const id = deleteBtn.getAttribute('data-id');
app.deleteItem(id);
}
});
// Initialize
const app = new DreamMachineApp();
// Expose to window for onclick handlers
window.app = app;