Spaces:
Sleeping
Sleeping
/* HTML generation */ | |
const TYPES = { | |
Grass: '🍃', | |
Fire: '🔥', | |
Water: '💧', | |
Lightning: '⚡', | |
Fighting: '✊', | |
Psychic: '👁️', | |
Colorless: '⭐', | |
Darkness: '🌑', | |
Metal: '⚙️', | |
Dragon: '🐲', | |
Fairy: '🧚', | |
}; | |
const energyHTML = (type, types = TYPES) => { | |
return `<span title="${type} energy" class="energy ${type.toLowerCase()}">${types[type]}</span>`; | |
}; | |
const attackDescriptionHTML = (text) => { | |
if (!text) { | |
return ''; | |
} | |
let fontSize; | |
if (text.length > 185) { | |
fontSize = 0.7; | |
} else if (text.length > 140) { | |
fontSize = 0.8; | |
} else if (text.length > 90) { | |
fontSize = 0.9; | |
} | |
return `<span class="attack-details"${fontSize ? ` style="font-size: ${fontSize.toString()}em"` : ''}>${text}</span>`; | |
}; | |
const attackRowsHTML = (attacks) => { | |
return attacks | |
.map((attack) => { | |
const { cost, damage, name, text } = attack; | |
return ` | |
<li class="attacks-row grid-three"> | |
<div class="attack-cost"> | |
${cost.map((energy) => energyHTML(energy)).join('')} | |
</div> | |
<span class="attack-text"> | |
<span class="attack-name">${name}</span> | |
${attackDescriptionHTML(text)} | |
</span> | |
<span class="attack-damage">${damage ? damage : ''}</span> | |
</li> | |
<hr role="presentation" />`; | |
}) | |
.join(''); | |
}; | |
const cardHTML = (details) => { | |
const { hp, energy_type, species, length, weight, attacks, weakness, resistance, retreat, description, rarity } = | |
details; | |
const poke_name = details.name; // `name` would be reserved JS word | |
return ` | |
<div class="pokecard ${energy_type.toLowerCase()}" data-displayed="true"> | |
<p class="evolves">Basic Pokémon</p> | |
<header> | |
<h1 class="name">${poke_name}</h1> | |
<div> | |
<span class="hp">${hp} HP</span> | |
${energyHTML(energy_type)} | |
</div> | |
</header> | |
<img class="picture frame" alt="AI generated Pokémon called ${poke_name}" /> | |
<div class="species frame"> | |
${species} Pokémon. Length: ${length.feet}'${length.inches}", Weight: ${weight} | |
</div> | |
<ul class="attacks"> | |
${attackRowsHTML(attacks)} | |
</ul> | |
<div class="multipliers"> | |
<div class="weakness"> | |
<span>weakness</span> | |
${weakness ? energyHTML(weakness) : ''} | |
</div> | |
<div class="resistance"> | |
<span>resistance</span> | |
${resistance ? energyHTML(resistance) : ''} | |
<span class="resistance-total" | |
>${resistance ? '-30' : ''}</span | |
> | |
</div> | |
<div class="retreat-cost"> | |
<span>retreat cost</span> | |
<div>${energyHTML('Colorless').repeat(retreat)}</div> | |
</div> | |
</div> | |
<p class="description frame">${description}</p> | |
<div class="footer grid-three"> | |
<span | |
><a | |
href="https://huggingface.co/minimaxir/ai-generated-pokemon-rudalle" | |
>Illus. Max Woolf</a | |
></span | |
> | |
<span><a href="">2022 Hugging Face</a></span> | |
<span>${rarity}</span> | |
</div> | |
</div>`; | |
}; | |
/* Utility */ | |
const getBasePath = () => { | |
return document.location.origin + document.location.pathname; | |
}; | |
const generateDetails = async () => { | |
const details = await fetch(`${getBasePath()}/details`); | |
return await details.json(); | |
}; | |
const createTask = async (prompt) => { | |
const taskResponse = await fetch(`${getBasePath()}task/create?prompt=${prompt}`); | |
const task = await taskResponse.json(); | |
return task; | |
}; | |
const queueTask = (task_id) => { | |
fetch(`${getBasePath()}task/queue?task_id=${task_id}`); | |
}; | |
const pollTask = async (task) => { | |
const taskResponse = await fetch(`${getBasePath()}task/poll?task_id=${task.task_id}`); | |
return await taskResponse.json(); | |
}; | |
const longPollTask = async (task, interval = 10_000, max) => { | |
if (task.status === 'complete' || (max && task.poll_count > max)) { | |
return task; | |
} | |
const taskResponse = await fetch(`${getBasePath()}task/poll?task_id=${task.task_id}`); | |
task = await taskResponse.json(); | |
if (task.status === 'complete' || task.poll_count > max) { | |
return task; | |
} | |
await new Promise((resolve) => setTimeout(resolve, interval)); | |
return await longPollTask(task, interval, max); | |
}; | |
/* DOM */ | |
const generateButton = document.querySelector('button.generate'); | |
const durationTimer = () => { | |
let duration = 0.0; | |
return (secondsElement) => { | |
const startTime = performance.now(); | |
const incrementSeconds = setInterval(() => { | |
duration += 0.1; | |
secondsElement.textContent = duration.toFixed(1); | |
}, 100); | |
const updateDuration = () => (duration = Number(((performance.now() - startTime) / 1_000).toFixed(1))); | |
window.addEventListener('focus', updateDuration); | |
return { | |
cleanup: () => { | |
updateDuration(); | |
clearInterval(incrementSeconds); | |
window.removeEventListener('focus', updateDuration); | |
secondsElement.textContent = duration.toFixed(1); | |
}, | |
}; | |
}; | |
}; | |
const rotateCard = () => { | |
const RANGE = 0.1; | |
const INTERVAL = 13; // ~75 per second | |
let previousTime = 0; | |
// Throttle closure | |
return (card, containerMouseEvent) => { | |
const currentTime = performance.now(); | |
if (currentTime - previousTime > INTERVAL) { | |
previousTime = currentTime; | |
const rect = card.getBoundingClientRect(); | |
const rotateX = (containerMouseEvent.clientY - rect.y - rect.height / 2) * RANGE; | |
const rotateY = -(containerMouseEvent.clientX - rect.x - rect.width / 2) * RANGE; | |
card.style.setProperty('--rotate-x', rotateX + 'deg'); | |
card.style.setProperty('--rotate-y', rotateY + 'deg'); | |
} | |
}; | |
}; | |
const cardRotationInitiator = (renderSection) => { | |
let currentCard; | |
return () => { | |
let handleMouseMove; | |
if (currentCard) { | |
handleMouseMove = rotateCard().bind(null, currentCard); | |
renderSection.removeEventListener('mousemove', handleMouseMove, true); | |
} | |
const newCard = document.querySelector('.pokecard'); | |
currentCard = newCard; | |
handleMouseMove = rotateCard().bind(null, newCard); | |
renderSection.addEventListener('mousemove', handleMouseMove, true); | |
}; | |
}; | |
let generating = false; | |
generateButton.addEventListener('click', async () => { | |
if (generating) { | |
return; | |
} | |
const renderSection = document.querySelector('section.render'); | |
const durationSeconds = document.querySelector('.duration > .seconds'); | |
const initialiseCardRotation = cardRotationInitiator(renderSection); | |
try { | |
generating = true; | |
const details = await generateDetails(); | |
const task = await createTask(details.energy_type); | |
queueTask(task.task_id); | |
const timer = durationTimer(); | |
const cleanupTimer = timer(durationSeconds).cleanup; | |
const completedTask = await longPollTask(task); | |
generating = false; | |
cleanupTimer(); | |
renderSection.innerHTML = cardHTML(details); | |
const picture = document.querySelector('img.picture'); | |
picture.src = completedTask.value; | |
initialiseCardRotation(); | |
} catch (err) { | |
generating = false; | |
console.error(err); | |
} | |
}); | |