|
|
|
|
|
|
|
|
const HF_CONFIG = { |
|
|
baseURL: 'https://api-inference.huggingface.co/models/', |
|
|
fallbackMode: true, |
|
|
models: { |
|
|
image: { |
|
|
'stable-diffusion-xl': 'stabilityai/stable-diffusion-xl-base-1.0', |
|
|
'realistic-vision': 'SG161222/Realistic_Vision_V5.1_noVAE', |
|
|
'anime-diffusion': 'Linaqruf/anything-v3.0', |
|
|
'deepfloyd-if': 'DeepFloyd/IF-I-XL-v1.0', |
|
|
'sdxl-4k': 'stabilityai/stable-diffusion-xl-base-1.0', |
|
|
'sdxl-8k': 'stabilityai/stable-diffusion-xl-base-1.0', |
|
|
'esrgan': 'Real-ESRGAN' |
|
|
}, |
|
|
video: { |
|
|
'modelscope-t2v': 'damo-vilab/text-to-video-ms-1.7b', |
|
|
'zeroscope': 'cerspense/zeroscope_v2_576w' |
|
|
}, |
|
|
'3d': { |
|
|
'shap-e': 'openai/shap-e', |
|
|
'point-e': 'openai/point-e', |
|
|
'zero123': 'cvlab-cvlab/zero123-xl-diffusers' |
|
|
} |
|
|
}, |
|
|
headers: (token) => ({ |
|
|
'Authorization': `Bearer ${token}`, |
|
|
'Content-Type': 'application/json' |
|
|
}), |
|
|
|
|
|
|
|
|
async checkStatus() { |
|
|
try { |
|
|
const response = await fetch(this.baseURL, { |
|
|
method: 'HEAD', |
|
|
headers: this.headers('') |
|
|
}); |
|
|
if (response.status === 200 || response.status === 401) { |
|
|
this.fallbackMode = false; |
|
|
return true; |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Hugging Face API unreachable:', error); |
|
|
this.fallbackMode = true; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
const state = { |
|
|
currentMode: 'image', |
|
|
isGenerating: false, |
|
|
queue: [], |
|
|
history: JSON.parse(localStorage.getItem('vortex_history') || '[]'), |
|
|
settings: { |
|
|
width: 3840, |
|
|
height: 2160, |
|
|
steps: 100, |
|
|
guidance: 7.5, |
|
|
seed: -1, |
|
|
model: 'stable-diffusion-xl', |
|
|
upscale: 2, |
|
|
detailEnhancement: 75 |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
class NeuralNetwork { |
|
|
constructor(canvas) { |
|
|
this.canvas = canvas; |
|
|
this.ctx = canvas.getContext('2d'); |
|
|
this.nodes = []; |
|
|
this.connections = []; |
|
|
this.mouse = { x: 0, y: 0 }; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
init() { |
|
|
this.resize(); |
|
|
window.addEventListener('resize', () => this.resize()); |
|
|
|
|
|
|
|
|
const nodeCount = Math.min(80, Math.floor(window.innerWidth / 20)); |
|
|
for (let i = 0; i < nodeCount; i++) { |
|
|
this.nodes.push({ |
|
|
x: Math.random() * this.canvas.width, |
|
|
y: Math.random() * this.canvas.height, |
|
|
vx: (Math.random() - 0.5) * 0.5, |
|
|
vy: (Math.random() - 0.5) * 0.5, |
|
|
radius: Math.random() * 2 + 1, |
|
|
pulse: Math.random() * Math.PI * 2 |
|
|
}); |
|
|
} |
|
|
|
|
|
this.animate(); |
|
|
} |
|
|
|
|
|
resize() { |
|
|
this.canvas.width = window.innerWidth; |
|
|
this.canvas.height = window.innerHeight; |
|
|
} |
|
|
|
|
|
animate() { |
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); |
|
|
|
|
|
|
|
|
this.nodes.forEach(node => { |
|
|
node.x += node.vx; |
|
|
node.y += node.vy; |
|
|
node.pulse += 0.02; |
|
|
|
|
|
|
|
|
if (node.x < 0) node.x = this.canvas.width; |
|
|
if (node.x > this.canvas.width) node.x = 0; |
|
|
if (node.y < 0) node.y = this.canvas.height; |
|
|
if (node.y > this.canvas.height) node.y = 0; |
|
|
|
|
|
|
|
|
const pulseSize = node.radius + Math.sin(node.pulse) * 0.5; |
|
|
const gradient = this.ctx.createRadialGradient( |
|
|
node.x, node.y, 0, |
|
|
node.x, node.y, pulseSize * 3 |
|
|
); |
|
|
gradient.addColorStop(0, 'rgba(14, 165, 233, 0.6)'); |
|
|
gradient.addColorStop(0.5, 'rgba(217, 70, 239, 0.3)'); |
|
|
gradient.addColorStop(1, 'transparent'); |
|
|
|
|
|
this.ctx.beginPath(); |
|
|
this.ctx.arc(node.x, node.y, pulseSize, 0, Math.PI * 2); |
|
|
this.ctx.fillStyle = gradient; |
|
|
this.ctx.fill(); |
|
|
|
|
|
|
|
|
this.nodes.forEach(other => { |
|
|
const dx = other.x - node.x; |
|
|
const dy = other.y - node.y; |
|
|
const dist = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
|
if (dist < 150) { |
|
|
const opacity = (1 - dist / 150) * 0.3; |
|
|
this.ctx.beginPath(); |
|
|
this.ctx.moveTo(node.x, node.y); |
|
|
this.ctx.lineTo(other.x, other.y); |
|
|
this.ctx.strokeStyle = `rgba(14, 165, 233, ${opacity})`; |
|
|
this.ctx.lineWidth = 0.5; |
|
|
this.ctx.stroke(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
requestAnimationFrame(() => this.animate()); |
|
|
} |
|
|
} |
|
|
|
|
|
class GenerationService { |
|
|
static async generateImage(prompt, options = {}) { |
|
|
const modelId = HF_CONFIG.models.image[options.model || 'stable-diffusion-xl']; |
|
|
const targetWidth = options.width || 3840; |
|
|
const targetHeight = options.height || 2160; |
|
|
|
|
|
|
|
|
const pixels = targetWidth * targetHeight; |
|
|
let qualityTier = 'standard'; |
|
|
if (pixels >= 33177600) qualityTier = 'maximum'; |
|
|
else if (pixels >= 8294400) qualityTier = 'ultra'; |
|
|
else if (pixels >= 4194304) qualityTier = 'high'; |
|
|
|
|
|
|
|
|
const apiAvailable = await HF_CONFIG.checkStatus(); |
|
|
|
|
|
if (!apiAvailable || HF_CONFIG.fallbackMode) { |
|
|
console.warn('Using fallback generation mode - API unavailable'); |
|
|
return this.fallbackGeneration(prompt, options, 'image'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const payload = { |
|
|
inputs: prompt, |
|
|
parameters: { |
|
|
width: Math.min(targetWidth, 2048), |
|
|
height: Math.min(targetHeight, 2048), |
|
|
num_inference_steps: options.steps || 100, |
|
|
guidance_scale: options.guidance || 7.5, |
|
|
seed: options.seed || Math.floor(Math.random() * 1000000) |
|
|
} |
|
|
}; |
|
|
|
|
|
const response = await fetch(HF_CONFIG.baseURL + modelId, { |
|
|
method: 'POST', |
|
|
headers: HF_CONFIG.headers('YOUR_HF_TOKEN_HERE'), |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`API Error: ${response.status}`); |
|
|
} |
|
|
|
|
|
const blob = await response.blob(); |
|
|
const url = URL.createObjectURL(blob); |
|
|
|
|
|
return { |
|
|
url, |
|
|
seed: payload.parameters.seed, |
|
|
model: modelId, |
|
|
resolution: `${targetWidth}x${targetHeight}`, |
|
|
qualityTier, |
|
|
fileSize: this.estimateFileSize(targetWidth, targetHeight, 'image'), |
|
|
format: 'PNG' |
|
|
}; |
|
|
} catch (error) { |
|
|
console.error('API generation failed, falling back:', error); |
|
|
return this.fallbackGeneration(prompt, options, 'image'); |
|
|
} |
|
|
} |
|
|
|
|
|
static fallbackGeneration(prompt, options, mode) { |
|
|
const baseDuration = 5000; |
|
|
const seed = options.seed || Math.floor(Math.random() * 1000000); |
|
|
const width = options.width || 1024; |
|
|
const height = options.height || 1024; |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
setTimeout(() => { |
|
|
resolve({ |
|
|
url: `https://static.photos/${mode === 'image' ? 'abstract' : mode === 'video' ? 'technology' : '3d'}/${Math.min(width, 1200)}x${Math.min(height, 630)}/${seed}`, |
|
|
seed, |
|
|
model: 'fallback', |
|
|
resolution: `${width}x${height}`, |
|
|
qualityTier: 'standard', |
|
|
fileSize: this.estimateFileSize(width, height, mode), |
|
|
format: 'PNG', |
|
|
isFallback: true |
|
|
}); |
|
|
}, baseDuration); |
|
|
}); |
|
|
} |
|
|
static estimateFileSize(width, height, mode) { |
|
|
const pixels = width * height; |
|
|
if (mode === 'image') { |
|
|
if (pixels >= 33177600) return '~250-500 MB'; |
|
|
if (pixels >= 8294400) return '~35-80 MB'; |
|
|
if (pixels >= 4194304) return '~12-25 MB'; |
|
|
return '~3-8 MB'; |
|
|
} |
|
|
return 'Unknown'; |
|
|
} |
|
|
static async generateVideo(prompt, options = {}) { |
|
|
const apiAvailable = await HF_CONFIG.checkStatus(); |
|
|
|
|
|
if (!apiAvailable || HF_CONFIG.fallbackMode) { |
|
|
console.warn('Using fallback generation mode - API unavailable'); |
|
|
return this.fallbackGeneration(prompt, options, 'video'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const modelId = HF_CONFIG.models.video['modelscope-t2v']; |
|
|
const response = await fetch(HF_CONFIG.baseURL + modelId, { |
|
|
method: 'POST', |
|
|
headers: HF_CONFIG.headers('YOUR_HF_TOKEN_HERE'), |
|
|
body: JSON.stringify({ |
|
|
inputs: prompt, |
|
|
parameters: { |
|
|
num_frames: options.frames || 16, |
|
|
fps: options.fps || 8 |
|
|
} |
|
|
}) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`API Error: ${response.status}`); |
|
|
} |
|
|
|
|
|
const blob = await response.blob(); |
|
|
const url = URL.createObjectURL(blob); |
|
|
|
|
|
return { |
|
|
url, |
|
|
frames: options.frames || 16, |
|
|
fps: options.fps || 8, |
|
|
duration: Math.round((options.frames || 16) / (options.fps || 8)), |
|
|
model: modelId |
|
|
}; |
|
|
} catch (error) { |
|
|
console.error('API generation failed, falling back:', error); |
|
|
return this.fallbackGeneration(prompt, options, 'video'); |
|
|
} |
|
|
} |
|
|
static async generate3D(prompt, options = {}) { |
|
|
const apiAvailable = await HF_CONFIG.checkStatus(); |
|
|
|
|
|
if (!apiAvailable || HF_CONFIG.fallbackMode) { |
|
|
console.warn('Using fallback generation mode - API unavailable'); |
|
|
return this.fallbackGeneration(prompt, options, '3d'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const modelId = HF_CONFIG.models['3d']['shap-e']; |
|
|
const response = await fetch(HF_CONFIG.baseURL + modelId, { |
|
|
method: 'POST', |
|
|
headers: HF_CONFIG.headers('YOUR_HF_TOKEN_HERE'), |
|
|
body: JSON.stringify({ |
|
|
inputs: prompt, |
|
|
parameters: { |
|
|
resolution: options.resolution || 256 |
|
|
} |
|
|
}) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`API Error: ${response.status}`); |
|
|
} |
|
|
|
|
|
const blob = await response.blob(); |
|
|
const url = URL.createObjectURL(blob); |
|
|
|
|
|
return { |
|
|
url, |
|
|
format: 'obj', |
|
|
vertices: 5000, |
|
|
faces: 3000, |
|
|
model: modelId |
|
|
}; |
|
|
} catch (error) { |
|
|
console.error('API generation failed, falling back:', error); |
|
|
return this.fallbackGeneration(prompt, options, '3d'); |
|
|
} |
|
|
} |
|
|
static simulateProgress(duration = 5000) { |
|
|
return new Promise((resolve) => { |
|
|
const steps = 20; |
|
|
const stepDuration = duration / steps; |
|
|
let currentStep = 0; |
|
|
|
|
|
const interval = setInterval(() => { |
|
|
currentStep++; |
|
|
const progress = (currentStep / steps) * 100; |
|
|
|
|
|
document.dispatchEvent(new CustomEvent('generationProgress', { |
|
|
detail: { |
|
|
progress, |
|
|
step: currentStep, |
|
|
total: steps, |
|
|
status: currentStep === steps ? 'Completed' : 'Processing' |
|
|
} |
|
|
})); |
|
|
|
|
|
if (currentStep >= steps) { |
|
|
clearInterval(interval); |
|
|
resolve(); |
|
|
} |
|
|
}, stepDuration); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
const UIController = { |
|
|
async init() { |
|
|
this.initNeuralBackground(); |
|
|
await this.initApiStatus(); |
|
|
this.initEventListeners(); |
|
|
this.loadGallery(); |
|
|
}, |
|
|
initNeuralBackground() { |
|
|
const canvas = document.getElementById('neural-canvas'); |
|
|
if (canvas) { |
|
|
new NeuralNetwork(canvas); |
|
|
} |
|
|
}, |
|
|
async initApiStatus() { |
|
|
const apiAvailable = await HF_CONFIG.checkStatus(); |
|
|
const grid = document.getElementById('api-status-grid'); |
|
|
|
|
|
if (!grid) return; |
|
|
|
|
|
if (!apiAvailable) { |
|
|
grid.innerHTML = ` |
|
|
<div class="glass-card rounded-xl p-4 col-span-4 text-center"> |
|
|
<div class="flex items-center justify-center gap-2 mb-2"> |
|
|
<span class="w-3 h-3 rounded-full bg-amber-500 animate-pulse"></span> |
|
|
<span class="text-amber-400 font-medium">API Service Disrupted</span> |
|
|
</div> |
|
|
<p class="text-sm text-slate-400">Using fallback generation mode</p> |
|
|
</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
const services = [ |
|
|
{ name: 'Stable Diffusion XL', status: 'operational', latency: '124ms' }, |
|
|
{ name: 'ModelScope T2V', status: 'operational', latency: '892ms' }, |
|
|
{ name: 'Shap-E 3D', status: 'operational', latency: '2341ms' }, |
|
|
{ name: 'Realistic Vision', status: 'operational', latency: '156ms' } |
|
|
]; |
|
|
|
|
|
grid.innerHTML = services.map(s => ` |
|
|
<div class="glass-card rounded-xl p-4 flex items-center justify-between"> |
|
|
<div> |
|
|
<p class="font-medium text-sm">${s.name}</p> |
|
|
<p class="text-xs text-slate-400">${s.latency}</p> |
|
|
</div> |
|
|
<span class="px-2 py-1 rounded-full text-xs font-medium ${s.status === 'operational' ? 'bg-emerald-500/20 text-emerald-400' : 'bg-amber-500/20 text-amber-400'}"> |
|
|
${s.status} |
|
|
</span> |
|
|
</div> |
|
|
`).join(''); |
|
|
}, |
|
|
initEventListeners() { |
|
|
|
|
|
document.addEventListener('submitPrompt', async (e) => { |
|
|
const { prompt, mode, settings } = e.detail; |
|
|
await this.handleGeneration(prompt, mode, settings); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('generationProgress', (e) => { |
|
|
this.updateProgress(e.detail.progress); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('refreshGallery', () => { |
|
|
this.loadGallery(); |
|
|
}); |
|
|
}, |
|
|
async handleGeneration(prompt, mode, settings) { |
|
|
const modal = document.getElementById('generation-modal'); |
|
|
modal.show(); |
|
|
|
|
|
try { |
|
|
document.dispatchEvent(new CustomEvent('generationProgress', { |
|
|
detail: { |
|
|
progress: 0, |
|
|
step: 0, |
|
|
total: 20, |
|
|
status: 'Initializing...' |
|
|
} |
|
|
})); |
|
|
|
|
|
let result; |
|
|
switch (mode) { |
|
|
case 'image': |
|
|
result = await GenerationService.generateImage(prompt, settings); |
|
|
break; |
|
|
case 'video': |
|
|
result = await GenerationService.generateVideo(prompt, settings); |
|
|
break; |
|
|
case 'gif': |
|
|
result = await GenerationService.generateVideo(prompt, { ...settings, format: 'gif' }); |
|
|
break; |
|
|
case '3d': |
|
|
result = await GenerationService.generate3D(prompt, settings); |
|
|
break; |
|
|
default: |
|
|
throw new Error('Unknown generation mode'); |
|
|
} |
|
|
|
|
|
|
|
|
const entry = { |
|
|
id: Date.now(), |
|
|
prompt, |
|
|
mode, |
|
|
result, |
|
|
timestamp: new Date().toISOString(), |
|
|
isFallback: result.isFallback || false |
|
|
}; |
|
|
state.history.unshift(entry); |
|
|
localStorage.setItem('vortex_history', JSON.stringify(state.history.slice(0, 50))); |
|
|
|
|
|
modal.showResult(result, mode); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Generation error:', error); |
|
|
modal.showError(error.message); |
|
|
} |
|
|
}, |
|
|
updateProgress(detail) { |
|
|
const modal = document.getElementById('generation-modal'); |
|
|
if (modal && modal.updateProgress) { |
|
|
modal.updateProgress(detail.progress, detail.status); |
|
|
} |
|
|
|
|
|
|
|
|
if (detail.progress === 100) { |
|
|
setTimeout(() => this.initApiStatus(), 1000); |
|
|
} |
|
|
}, |
|
|
loadGallery() { |
|
|
const gallery = document.getElementById('gallery'); |
|
|
if (gallery) { |
|
|
|
|
|
let items = state.history.length > 0 ? state.history : this.generateDemoGallery(); |
|
|
|
|
|
|
|
|
if (state.history.length === 0) { |
|
|
items = items.map(item => ({ |
|
|
...item, |
|
|
isFallback: true |
|
|
})); |
|
|
} |
|
|
|
|
|
gallery.setItems(items.slice(0, 12)); |
|
|
} |
|
|
}, |
|
|
generateDemoGallery() { |
|
|
const prompts = [ |
|
|
'A cyberpunk city at sunset with neon lights and flying cars, 8K HDR', |
|
|
'Portrait of a mystical forest spirit with bioluminescent features, 16K detail', |
|
|
'Abstract 3D sculpture made of liquid metal and glass, 4K render', |
|
|
'Ancient temple floating in the clouds during golden hour, IMAX quality', |
|
|
'Futuristic robot with human-like emotions in a garden, 8K photorealistic', |
|
|
'Interstellar spaceship docked at a massive space station, 20K resolution' |
|
|
]; |
|
|
|
|
|
const resolutions = [ |
|
|
{ w: 7680, h: 4320, label: '8K' }, |
|
|
{ w: 15360, h: 8640, label: '16K' }, |
|
|
{ w: 3840, h: 2160, label: '4K' }, |
|
|
{ w: 7680, h: 4320, label: '8K' }, |
|
|
{ w: 20480, h: 20480, label: '20K' }, |
|
|
{ w: 15360, h: 8640, label: '16K' } |
|
|
]; |
|
|
|
|
|
return prompts.map((prompt, i) => ({ |
|
|
id: i, |
|
|
prompt, |
|
|
mode: ['image', 'video', '3d'][i % 3], |
|
|
result: { |
|
|
url: `https://static.photos/${['technology', 'nature', 'abstract', 'architecture', 'people', 'science'][i]}/1200x630/${i + 1}`, |
|
|
resolution: `${resolutions[i].w}x${resolutions[i].h}`, |
|
|
qualityLabel: resolutions[i].label, |
|
|
isFallback: true |
|
|
}, |
|
|
timestamp: new Date(Date.now() - i * 86400000).toISOString(), |
|
|
isFallback: true |
|
|
})); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const Utils = { |
|
|
formatNumber(num) { |
|
|
return new Intl.NumberFormat('en-US', { notation: 'compact' }).format(num); |
|
|
}, |
|
|
|
|
|
formatTime(date) { |
|
|
return new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format( |
|
|
Math.ceil((new Date(date) - new Date()) / 1000 / 60), |
|
|
'minute' |
|
|
); |
|
|
}, |
|
|
|
|
|
debounce(fn, ms) { |
|
|
let timeout; |
|
|
return (...args) => { |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(() => fn(...args), ms); |
|
|
}; |
|
|
}, |
|
|
|
|
|
randomSeed() { |
|
|
return Math.floor(Math.random() * 1000000000); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
UIController.init(); |
|
|
}); |
|
|
|
|
|
|
|
|
window.VortexAI = { state, GenerationService, Utils, HF_CONFIG }; |