Spaces:
Running
Running
<html><head><base href="https://treegen.ai"><title>Promptimg - AI Image Prompt Generator</title> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
margin: 0; | |
padding: 0; | |
background-color: #0f1628; | |
color: #ffffff; | |
display: flex; | |
flex-direction: column; | |
min-height: 100vh; | |
} | |
.container { | |
max-width: 100%; | |
margin: 0 auto; | |
padding: 20px; | |
flex: 1; | |
} | |
h1 { | |
color: #ffffff; | |
text-align: center; | |
font-size: 2rem; | |
} | |
#config { | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: center; | |
align-items: center; | |
margin-bottom: 20px; | |
background-color: #1e293b; | |
padding: 15px; | |
border-radius: 8px; | |
} | |
#config label { | |
margin-right: 15px; | |
font-size: 14px; | |
margin-bottom: 5px; | |
} | |
#config input[type="number"] { | |
width: 60px; | |
padding: 5px; | |
background-color: #2d3748; | |
border: 1px solid #4a5568; | |
color: #ffffff; | |
border-radius: 4px; | |
margin-bottom: 5px; | |
} | |
#config input[type="range"] { | |
width: 150px; | |
margin: 0 10px 5px 10px; | |
} | |
#prompt-container { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
gap: 20px; | |
overflow-x: auto; | |
} | |
.prompt-layer { | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: center; | |
gap: 20px; | |
padding: 10px; | |
position: relative; | |
width: 100%; | |
} | |
.prompt-group { | |
background-color: rgba(59, 130, 246, 0.1); | |
border: 1px solid rgba(59, 130, 246, 0.3); | |
border-radius: 10px; | |
padding: 15px; | |
margin-top: 30px; | |
position: relative; | |
display: flex; | |
flex-direction: row; | |
align-items: center; | |
flex-wrap: wrap; | |
justify-content: center; | |
width: 100%; | |
max-width: 600px; | |
} | |
.prompt-group::before { | |
content: ''; | |
position: absolute; | |
top: -20px; | |
left: 50%; | |
width: 2px; | |
height: 20px; | |
background-color: rgba(59, 130, 246, 0.5); | |
} | |
.prompt-group-title { | |
position: absolute; | |
top: -25px; | |
left: 50%; | |
transform: translateX(-50%); | |
background-color: #0f1628; | |
padding: 0 10px; | |
font-size: 12px; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
max-width: 90%; | |
} | |
.prompt-group-title-hidden { | |
display: none; | |
} | |
.prompt-group-user-input { | |
position: absolute; | |
top: -25px; | |
left: 50%; | |
transform: translateX(-50%); | |
background-color: #0f1628; | |
padding: 0 10px; | |
font-size: 12px; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
max-width: 90%; | |
} | |
.prompt-card { | |
position: relative; | |
cursor: pointer; | |
flex-shrink: 0; | |
margin: 5px; | |
width: 100px; | |
height: 100px; | |
} | |
.prompt-card img { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
border-radius: 50%; | |
transition: all 0.3s ease; | |
} | |
.prompt-card:hover img { | |
filter: brightness(70%); | |
} | |
.prompt-card p { | |
display: none; | |
} | |
#prompt-input { | |
width: 100%; | |
padding: 10px; | |
margin-bottom: 20px; | |
border: 1px solid #3b4b66; | |
border-radius: 4px; | |
background-color: #1e293b; | |
color: #ffffff; | |
box-sizing: border-box; | |
} | |
#generate-btn { | |
display: block; | |
width: 100%; | |
max-width: 250px; | |
margin: 0 auto 20px; | |
padding: 10px; | |
background-color: #3b82f6; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
font-size: 16px; | |
} | |
#generate-btn:hover { | |
background-color: #2563eb; | |
} | |
.modal { | |
display: none; | |
position: fixed; | |
z-index: 1000; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
overflow: auto; | |
background-color: rgba(0,0,0,0.8); | |
} | |
.modal-content { | |
background-color: #1e293b; | |
margin: 5% auto; | |
padding: 20px; | |
border: 1px solid #3b4b66; | |
width: 90%; | |
max-width: 800px; | |
border-radius: 10px; | |
color: #ffffff; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
position: relative; | |
} | |
.close { | |
color: #aaa; | |
position: absolute; | |
top: 10px; | |
right: 20px; | |
font-size: 28px; | |
font-weight: bold; | |
cursor: pointer; | |
} | |
.modal-content img { | |
max-width: 100%; | |
max-height: 60vh; | |
object-fit: contain; | |
margin-bottom: 20px; | |
} | |
.action-buttons { | |
display: none; | |
position: absolute; | |
bottom: -30px; | |
left: 50%; | |
transform: translateX(-50%); | |
background-color: rgba(59, 130, 246, 0.8); | |
border-radius: 20px; | |
padding: 5px 10px; | |
z-index: 10; | |
} | |
.action-button { | |
background: none; | |
border: none; | |
color: white; | |
cursor: pointer; | |
font-size: 16px; | |
padding: 5px; | |
margin: 0 5px; | |
} | |
.action-button:hover { | |
color: #f0f0f0; | |
} | |
#download-btn { | |
position: absolute; | |
top: 10px; | |
right: 60px; | |
background-color: #3b82f6; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
padding: 5px 10px; | |
cursor: pointer; | |
font-size: 14px; | |
} | |
#download-btn:hover { | |
background-color: #2563eb; | |
} | |
footer { | |
background-color: #1e293b; | |
color: #ffffff; | |
text-align: center; | |
padding: 10px; | |
font-size: 14px; | |
} | |
.nav-arrow { | |
position: absolute; | |
top: 50%; | |
transform: translateY(-50%); | |
font-size: 2rem; | |
color: #ffffff; | |
background-color: rgba(59, 130, 246, 0.5); | |
border: none; | |
border-radius: 50%; | |
width: 50px; | |
height: 50px; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
cursor: pointer; | |
transition: background-color 0.3s ease; | |
} | |
.nav-arrow:hover { | |
background-color: rgba(59, 130, 246, 0.8); | |
} | |
#prev-arrow { | |
left: 20px; | |
} | |
#next-arrow { | |
right: 20px; | |
} | |
#thumbnails { | |
display: flex; | |
justify-content: center; | |
gap: 10px; | |
margin-top: 20px; | |
flex-wrap: wrap; | |
} | |
.thumbnail { | |
width: 60px; | |
height: 60px; | |
border-radius: 50%; | |
object-fit: cover; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
} | |
.thumbnail:hover, .thumbnail.active { | |
border: 2px solid #3b82f6; | |
transform: scale(1.1); | |
} | |
.group-action-buttons { | |
display: none; | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background-color: rgba(59, 130, 246, 0.8); | |
border-radius: 20px; | |
padding: 5px 10px; | |
z-index: 10; | |
} | |
.prompt-layer { | |
overflow: visible; | |
} | |
.prompt-group { | |
overflow: visible; | |
} | |
.add-node-btn { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
background-color: rgba(59, 130, 246, 0.2); | |
color: white; | |
border: 2px dashed rgba(59, 130, 246, 0.5); | |
border-radius: 50%; | |
width: 100px; | |
height: 100px; | |
font-size: 40px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
} | |
.add-node-btn:hover { | |
background-color: rgba(59, 130, 246, 0.4); | |
} | |
.parent-image { | |
position: absolute; | |
top: -15px; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 30px; | |
height: 30px; | |
border-radius: 50%; | |
overflow: hidden; | |
border: 2px solid #3b82f6; | |
} | |
.parent-image img { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.edit-prompt-modal { | |
display: none; | |
position: fixed; | |
z-index: 2000; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0,0,0,0.8); | |
} | |
.edit-prompt-content { | |
background-color: #1e293b; | |
margin: 10% auto; | |
padding: 20px; | |
border: 1px solid #3b4b66; | |
width: 90%; | |
max-width: 600px; | |
border-radius: 10px; | |
} | |
.edit-prompt-textarea { | |
width: 100%; | |
height: 100px; | |
margin-bottom: 10px; | |
padding: 10px; | |
background-color: #2d3748; | |
border: 1px solid #4a5568; | |
color: #ffffff; | |
border-radius: 4px; | |
resize: vertical; | |
box-sizing: border-box; | |
} | |
.edit-prompt-buttons { | |
display: flex; | |
justify-content: flex-end; | |
gap: 10px; | |
} | |
.edit-prompt-btn { | |
padding: 5px 10px; | |
background-color: #3b82f6; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
} | |
.edit-prompt-btn:hover { | |
background-color: #2563eb; | |
} | |
.highlighted-prompt { | |
margin-top: 10px; | |
font-size: 14px; | |
line-height: 1.4; | |
} | |
.highlighted-prompt span { | |
padding: 2px 4px; | |
border-radius: 3px; | |
} | |
.loading-message { | |
font-size: 18px; | |
color: #ffffff; | |
text-align: center; | |
margin: 20px 0; | |
} | |
@keyframes pulse { | |
0% { opacity: 0.6; } | |
50% { opacity: 1; } | |
100% { opacity: 0.6; } | |
} | |
.loading-message { | |
animation: pulse 1.5s infinite; | |
} | |
.image-details { | |
margin-top: 20px; | |
text-align: left; | |
width: 100%; | |
} | |
.image-details p { | |
margin: 5px 0; | |
} | |
.image-details .parent-prompt, | |
.image-details .user-input { | |
font-size: 0.9em; | |
color: #a0aec0; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.loading-icon { | |
animation: spin 1s linear infinite; | |
} | |
.group-placeholder { | |
background-color: rgba(59, 130, 246, 0.1); | |
border: 1px dashed rgba(59, 130, 246, 0.3); | |
border-radius: 10px; | |
padding: 15px; | |
margin-top: 30px; | |
text-align: center; | |
min-width: 200px; | |
min-height: 100px; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
} | |
.loading-overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.7); | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
border-radius: 50%; | |
z-index: 10; | |
} | |
.sidebar { | |
position: fixed; | |
top: 0; | |
left: -300px; | |
width: 100%; | |
max-width: 300px; | |
height: 100%; | |
background-color: #1e293b; | |
transition: left 0.3s ease-in-out; | |
z-index: 1000; | |
overflow-y: auto; | |
} | |
.sidebar.open { | |
left: 0; | |
} | |
.sidebar-content { | |
padding: 20px; | |
color: #ffffff; | |
} | |
.sidebar h2 { | |
margin-top: 0; | |
} | |
.sidebar ul { | |
padding-left: 20px; | |
} | |
.sidebar li { | |
margin-bottom: 10px; | |
} | |
.toggle-sidebar { | |
position: fixed; | |
top: 20px; | |
left: 20px; | |
z-index: 1001; | |
background-color: #3b82f6; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
padding: 10px; | |
cursor: pointer; | |
} | |
.modal-image-container { | |
position: relative; | |
max-width: 100%; | |
max-height: 60vh; | |
"" | |
margin-bottom: 20px; | |
} | |
.modal-image-container img { | |
max-width: 100%; | |
max-height: 60vh; | |
object-fit: contain; | |
width: auto; | |
} | |
.modal-prompt-overlay { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
width: 100%; | |
background-color: rgba(0, 0, 0, 0.7); | |
color: white; | |
padding: 10px; | |
box-sizing: border-box; | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
} | |
.modal-image-container:hover .modal-prompt-overlay { | |
opacity: 1; | |
} | |
.circular-button { | |
width: 40px; | |
height: 40px; | |
border-radius: 50%; | |
background-color: #3b82f6; | |
color: white; | |
border: none; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
cursor: pointer; | |
margin: 0 5px; | |
transition: background-color 0.3s ease; | |
} | |
.circular-button:hover { | |
background-color: #2563eb; | |
} | |
.modal-buttons { | |
display: flex; | |
justify-content: center; | |
margin-top: 10px; | |
} | |
.action-toggle { | |
display: none; | |
position: absolute; | |
bottom: -15px; | |
left: 50%; | |
transform: translateX(-50%); | |
background-color: rgba(59, 130, 246, 0.8); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 30px; | |
height: 30px; | |
font-size: 16px; | |
cursor: pointer; | |
z-index: 9; | |
} | |
.group-action-toggle { | |
display: none; | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background-color: rgba(59, 130, 246, 0.8); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 30px; | |
height: 30px; | |
font-size: 16px; | |
cursor: pointer; | |
z-index: 9; | |
} | |
@media (min-width: 769px) { | |
.prompt-card:hover .action-buttons { | |
display: flex; | |
} | |
.prompt-group:hover .group-action-buttons { | |
display: flex; | |
} | |
.action-toggle, .group-action-toggle { | |
display: none ; | |
} | |
} | |
@media (max-width: 768px) { | |
.container { | |
padding: 10px; | |
} | |
h1 { | |
font-size: 1.5rem; | |
} | |
#config { | |
flex-direction: column; | |
align-items: flex-start; | |
} | |
#config label, #config input { | |
margin-bottom: 10px; | |
} | |
.prompt-group { | |
padding: 10px; | |
} | |
.nav-arrow { | |
font-size: 1.5rem; | |
width: 40px; | |
height: 40px; | |
} | |
#prev-arrow { | |
left: 10px; | |
} | |
#next-arrow { | |
right: 10px; | |
} | |
.thumbnail { | |
width: 50px; | |
height: 50px; | |
} | |
.action-buttons, .group-action-buttons { | |
display: none; | |
} | |
.action-toggle, .group-action-toggle { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
} | |
</style> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
</head> | |
<body> | |
<button class="toggle-sidebar" id="toggleSidebar"> | |
<i class="fas fa-bars"></i> | |
</button> | |
<div class="sidebar" id="sidebar"> | |
<div class="sidebar-content"> | |
<h2><i class="fas fa-lightbulb"></i> How to Use Promptimg</h2> | |
<ol> | |
<li>Enter an initial concept in the input field.</li> | |
<li>Click "Generate image prompts" to create a group of prompts.</li> | |
<li>Explore the generated images by clicking on them.</li> | |
<li>Use the action buttons to: | |
<ul> | |
<li><i class="fas fa-redo"></i> Regenerate an image or entire group of prompts</li> | |
<li><i class="fas fa-edit"></i> Edit a prompt</li> | |
<li><i class="fas fa-trash"></i> Delete a prompt</li> | |
<li><i class="fas fa-sitemap"></i> Create child nodes (bifurcate)</li> | |
</ul> | |
</li> | |
<li>Adjust the number of prompts and node size using the controls at the top.</li> | |
<li>Create new groups by bifurcating or adding nodes to existing groups.</li> | |
</ol> | |
<div class="sidebar-footer"> | |
<p><i class="fas fa-code"></i> Promptimg v0.1.0-release</p> | |
<p><i class="fas fa-heart"></i> Made with love by @eechever</p> | |
</div> | |
</div> | |
</div> | |
<div class="container"> | |
<h1>Promptimg - Find your dream prompts & images</h1> | |
<div id="config"> | |
<label for="num-prompts">Number of prompts:</label> | |
<input type="number" id="num-prompts" min="1" max="10" value="3"> | |
<label for="node-size">Node size:</label> | |
<input type="range" id="node-size" min="20" max="200" value="100"> | |
<span id="node-size-value">100px</span> | |
</div> | |
<input type="text" id="prompt-input" placeholder="Enter your initial concept"> | |
<button id="generate-btn">Generate image prompts</button> | |
<div id="prompt-container"></div> | |
</div> | |
<div id="imageModal" class="modal"> | |
<div class="modal-content"> | |
<span class="close">×</span> | |
<button id="prev-arrow" class="nav-arrow"><i class="fas fa-chevron-left"></i></button> | |
<button id="next-arrow" class="nav-arrow"><i class="fas fa-chevron-right"></i></button> | |
<div class="modal-image-container"> | |
<img id="modalImage" src="" alt="Full size image"> | |
<div class="modal-prompt-overlay"> | |
<p id="modalDescription"></p> | |
</div> | |
</div> | |
<div class="modal-buttons"> | |
<button id="copyPromptBtn" class="circular-button" title="Copy Prompt"> | |
<i class="fas fa-copy"></i> | |
</button> | |
<button id="downloadImageBtn" class="circular-button" title="Download Image"> | |
<i class="fas fa-download"></i> | |
</button> | |
</div> | |
<div class="image-details"> | |
<p id="userInput" class="user-input"></p> | |
</div> | |
<div id="thumbnails"></div> | |
</div> | |
</div> | |
<div id="editPromptModal" class="edit-prompt-modal"> | |
<div class="edit-prompt-content"> | |
<textarea id="editPromptTextarea" class="edit-prompt-textarea"></textarea> | |
<div class="edit-prompt-buttons"> | |
<button id="cancelEditPrompt" class="edit-prompt-btn">Cancel</button> | |
<button id="saveEditPrompt" class="edit-prompt-btn">Save</button> | |
</div> | |
</div> | |
</div> | |
<footer> | |
<p>Made with ❤️ by <a href="https://www.linkedin.com/in/eechev/" target="_blank" style="color: white; text-decoration: none; transition: opacity 0.3s ease;" onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'">@eechever</a> and WebSim</p> | |
<a href="https://buymeacoffee.com/eechever" target="_blank" style="display: inline-block; background-color: #FFDD00; color: #000000; padding: 8px 16px; text-decoration: none; border-radius: 5px; font-weight: bold; margin-top: 10px;"> | |
<i class="fas fa-coffee" style="margin-right: 5px;"></i> Buy me a coffee | |
</a> | |
</footer> | |
<a id="hiddenDownloadLink" style="display: none;"></a> | |
<script> | |
const promptContainer = document.getElementById('prompt-container'); | |
const promptInput = document.getElementById('prompt-input'); | |
const generateBtn = document.getElementById('generate-btn'); | |
const numPromptsInput = document.getElementById('num-prompts'); | |
const nodeSizeInput = document.getElementById('node-size'); | |
const nodeSize = document.getElementById('node-size-value'); | |
const modal = document.getElementById('imageModal'); | |
const modalImage = document.getElementById('modalImage'); | |
const modalDescription = document.getElementById('modalDescription'); | |
const userInput = document.getElementById('userInput'); | |
const closeModal = document.getElementsByClassName('close')[0]; | |
const prevArrow = document.getElementById('prev-arrow'); | |
const nextArrow = document.getElementById('next-arrow'); | |
const thumbnailsContainer = document.getElementById('thumbnails'); | |
const editPromptModal = document.getElementById('editPromptModal'); | |
const editPromptTextarea = document.getElementById('editPromptTextarea'); | |
const cancelEditPrompt = document.getElementById('cancelEditPrompt'); | |
const saveEditPrompt = document.getElementById('saveEditPrompt'); | |
const sidebar = document.getElementById('sidebar'); | |
const toggleSidebar = document.getElementById('toggleSidebar'); | |
const copyPromptBtn = document.getElementById('copyPromptBtn'); | |
const downloadImageBtn = document.getElementById('downloadImageBtn'); | |
const hiddenDownloadLink = document.getElementById('hiddenDownloadLink'); | |
let currentGroupImages = []; | |
let currentImageIndex = 0; | |
let currentEditingCard = null; | |
let currentOpenActionButtons = null; | |
generateBtn.addEventListener('click', () => { | |
const initialConcept = promptInput.value; | |
if (initialConcept) { | |
setButtonLoading(generateBtn); | |
generatePrompts(initialConcept); | |
} | |
}); | |
nodeSizeInput.addEventListener('input', () => { | |
nodeSize.textContent = `${nodeSizeInput.value}px`; | |
updateNodeSize(); | |
}); | |
closeModal.onclick = function() { | |
modal.style.display = "none"; | |
} | |
window.onclick = function(event) { | |
if (event.target == modal) { | |
modal.style.display = "none"; | |
} | |
if (event.target == editPromptModal) { | |
editPromptModal.style.display = "none"; | |
} | |
if (window.innerWidth <= 768 && currentOpenActionButtons && !event.target.closest('.action-buttons') && !event.target.closest('.action-toggle') && !event.target.closest('.group-action-buttons') && !event.target.closest('.group-action-toggle')) { | |
currentOpenActionButtons.style.display = 'none'; | |
currentOpenActionButtons = null; | |
} | |
} | |
prevArrow.addEventListener('click', () => navigateImages(-1)); | |
nextArrow.addEventListener('click', () => navigateImages(1)); | |
document.addEventListener('keydown', (e) => { | |
if (modal.style.display === "block") { | |
if (e.key === "ArrowLeft") navigateImages(-1); | |
if (e.key === "ArrowRight") navigateImages(1); | |
} | |
}); | |
cancelEditPrompt.addEventListener('click', () => { | |
editPromptModal.style.display = "none"; | |
}); | |
saveEditPrompt.addEventListener('click', () => { | |
if (currentEditingCard) { | |
const newPrompt = editPromptTextarea.value; | |
const promptData = JSON.parse(currentEditingCard.querySelector('p').textContent); | |
promptData.text = newPrompt; | |
currentEditingCard.querySelector('p').textContent = JSON.stringify(promptData); | |
regenerateImage(currentEditingCard); | |
editPromptModal.style.display = "none"; | |
} | |
}); | |
toggleSidebar.addEventListener('click', () => { | |
sidebar.classList.toggle('open'); | |
}); | |
copyPromptBtn.addEventListener('click', () => { | |
const promptText = modalDescription.textContent; | |
navigator.clipboard.writeText(promptText).then(() => { | |
alert('Prompt copied to clipboard!'); | |
}).catch(err => { | |
console.error('Failed to copy text: ', err); | |
}); | |
}); | |
downloadImageBtn.addEventListener('click', function(e) { | |
e.preventDefault(); | |
hiddenDownloadLink.click(); | |
}); | |
function updateNodeSize() { | |
const size = nodeSizeInput.value; | |
const promptCards = document.querySelectorAll('.prompt-card, .add-node-btn'); | |
promptCards.forEach(card => { | |
card.style.width = `${size}px`; | |
card.style.height = `${size}px`; | |
}); | |
} | |
async function generatePrompts(initialConcept) { | |
const placeholder = createGroupPlaceholder("Generating prompts..."); | |
const layer = getOrCreateRootLayer(); | |
layer.appendChild(placeholder); | |
const numPrompts = parseInt(numPromptsInput.value); | |
const complexPrompts = await generateComplexPrompts(initialConcept, numPrompts); | |
placeholder.innerHTML = "Generating images..."; | |
const group = await createPromptGroup(complexPrompts, initialConcept, initialConcept); | |
layer.replaceChild(group, placeholder); | |
updateNodeSize(); | |
resetButtonLoading(generateBtn); | |
} | |
function getOrCreateRootLayer() { | |
let rootLayer = promptContainer.querySelector('.prompt-layer'); | |
if (!rootLayer) { | |
rootLayer = createPromptLayer(); | |
promptContainer.appendChild(rootLayer); | |
} | |
return rootLayer; | |
} | |
function createPromptLayer() { | |
const layer = document.createElement('div'); | |
layer.className = 'prompt-layer'; | |
return layer; | |
} | |
function createGroupPlaceholder(message) { | |
const placeholder = document.createElement('div'); | |
placeholder.className = 'group-placeholder'; | |
placeholder.innerHTML = ` | |
<i class="fas fa-spinner fa-spin fa-2x"></i> | |
<p>${message}</p> | |
`; | |
return placeholder; | |
} | |
async function createPromptGroup(prompts, parentPrompt, userInputText, parentImage = null) { | |
const group = document.createElement('div'); | |
group.className = 'prompt-group'; | |
const hiddenTitle = document.createElement('div'); | |
hiddenTitle.className = 'prompt-group-title prompt-group-title-hidden'; | |
hiddenTitle.textContent = parentPrompt; | |
group.appendChild(hiddenTitle); | |
const userInputTitle = document.createElement('div'); | |
userInputTitle.className = 'prompt-group-user-input'; | |
userInputTitle.textContent = userInputText; | |
group.appendChild(userInputTitle); | |
if (parentImage) { | |
const parentImageContainer = document.createElement('div'); | |
parentImageContainer.className = 'parent-image'; | |
const img = document.createElement('img'); | |
img.src = parentImage; | |
img.alt = 'Parent Image'; | |
parentImageContainer.appendChild(img); | |
group.appendChild(parentImageContainer); | |
} | |
const groupActionButtons = document.createElement('div'); | |
groupActionButtons.className = 'group-action-buttons'; | |
groupActionButtons.innerHTML = ` | |
<button class="action-button" title="Regenerate group"><i class="fas fa-redo"></i></button> | |
<button class="action-button" title="Edit group prompt"><i class="fas fa-edit"></i></button> | |
<button class="action-button" title="Delete group"><i class="fas fa-trash"></i></button> | |
`; | |
group.appendChild(groupActionButtons); | |
const groupActionToggle = document.createElement('button'); | |
groupActionToggle.className = 'group-action-toggle'; | |
groupActionToggle.innerHTML = '<i class="fas fa-ellipsis-v"></i>'; | |
group.appendChild(groupActionToggle); | |
const regenerateGroupBtn = groupActionButtons.querySelector('.action-button:nth-child(1)'); | |
const editGroupBtn = groupActionButtons.querySelector('.action-button:nth-child(2)'); | |
const deleteGroupBtn = groupActionButtons.querySelector('.action-button:nth-child(3)'); | |
regenerateGroupBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
regenerateGroup(group); | |
}); | |
editGroupBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
editGroupPrompt(group); | |
}); | |
deleteGroupBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
deleteGroup(group); | |
}); | |
groupActionToggle.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
toggleGroupActionButtons(group); | |
}); | |
for (const prompt of prompts) { | |
const imageUrl = await generateImage(prompt.text); | |
const promptCard = createPromptCard(prompt, imageUrl, parentPrompt, userInputText); | |
group.appendChild(promptCard); | |
} | |
const addNodeBtn = document.createElement('div'); | |
addNodeBtn.className = 'add-node-btn prompt-card'; | |
addNodeBtn.innerHTML = '<i class="fas fa-plus"></i>'; | |
addNodeBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
addNewNode(group); | |
}); | |
group.appendChild(addNodeBtn); | |
return group; | |
} | |
function createPromptCard(prompt, imageUrl, parentPrompt, userInputText) { | |
const card = document.createElement('div'); | |
card.className = 'prompt-card'; | |
card.innerHTML = ` | |
<img src="${imageUrl}" alt="Generated image for ${prompt.text}"> | |
<p>${JSON.stringify({...prompt, parentPrompt, userInputText})}</p> | |
<div class="action-buttons"> | |
<button class="action-button" title="Regenerate image"><i class="fas fa-redo"></i></button> | |
<button class="action-button" title="Edit prompt"><i class="fas fa-edit"></i></button> | |
<button class="action-button" title="Delete prompt"><i class="fas fa-trash"></i></button> | |
<button class="action-button" title="Bifurcate/Create nodes"><i class="fas fa-sitemap"></i></button> | |
</div> | |
<button class="action-toggle"><i class="fas fa-ellipsis-v"></i></button> | |
`; | |
const regenerateBtn = card.querySelector('.action-button:nth-child(1)'); | |
const editBtn = card.querySelector('.action-button:nth-child(2)'); | |
const deleteBtn = card.querySelector('.action-button:nth-child(3)'); | |
const bifurcateBtn = card.querySelector('.action-button:nth-child(4)'); | |
const actionToggle = card.querySelector('.action-toggle'); | |
const actionButtons = card.querySelector('.action-buttons'); | |
regenerateBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
regenerateImage(card); | |
}); | |
editBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
editPromptWithModal(card); | |
}); | |
deleteBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
deletePrompt(card); | |
}); | |
bifurcateBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
createChildNodesWithPrompt(card); | |
}); | |
actionToggle.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
toggleActionButtons(card); | |
}); | |
card.addEventListener('click', () => showFullImage(card)); | |
return card; | |
} | |
function toggleActionButtons(card) { | |
const actionButtons = card.querySelector('.action-buttons'); | |
if (window.innerWidth <= 768) { | |
if (currentOpenActionButtons && currentOpenActionButtons !== actionButtons) { | |
currentOpenActionButtons.style.display = 'none'; | |
} | |
actionButtons.style.display = actionButtons.style.display === 'flex' ? 'none' : 'flex'; | |
currentOpenActionButtons = actionButtons.style.display === 'flex' ? actionButtons : null; | |
} | |
} | |
function toggleGroupActionButtons(group) { | |
const groupActionButtons = group.querySelector('.group-action-buttons'); | |
if (window.innerWidth <= 768) { | |
if (currentOpenActionButtons && currentOpenActionButtons !== groupActionButtons) { | |
currentOpenActionButtons.style.display = 'none'; | |
} | |
groupActionButtons.style.display = groupActionButtons.style.display === 'flex' ? 'none' : 'flex'; | |
currentOpenActionButtons = groupActionButtons.style.display === 'flex' ? groupActionButtons : null; | |
} | |
} | |
function showFullImage(card) { | |
const group = card.closest('.prompt-group'); | |
currentGroupImages = Array.from(group.querySelectorAll('.prompt-card')).filter(card => !card.classList.contains('add-node-btn')); | |
currentImageIndex = currentGroupImages.indexOf(card); | |
updateModalImage(); | |
modal.style.display = "block"; | |
updateThumbnails(); | |
} | |
function updateModalImage() { | |
const currentCard = currentGroupImages[currentImageIndex]; | |
const imgSrc = currentCard.querySelector('img').src; | |
const promptData = JSON.parse(currentCard.querySelector('p').textContent); | |
modalImage.src = imgSrc; | |
modalDescription.textContent = promptData.text; | |
userInput.textContent = `User Input: ${promptData.userInputText}`; | |
// Update hidden download link | |
const fileName = `${promptData.text.slice(0, 30).replace(/[^a-z0-9]/gi, '_').toLowerCase()}.jpg`; | |
hiddenDownloadLink.href = imgSrc; | |
hiddenDownloadLink.download = fileName; | |
} | |
function updateThumbnails() { | |
thumbnailsContainer.innerHTML = ''; | |
currentGroupImages.forEach((card, index) => { | |
const thumbnail = document.createElement('img'); | |
thumbnail.src = card.querySelector('img').src; | |
thumbnail.alt = `Thumbnail ${index + 1}`; | |
thumbnail.classList.add('thumbnail'); | |
if (index === currentImageIndex) thumbnail.classList.add('active'); | |
thumbnail.addEventListener('click', () => { | |
currentImageIndex = index; | |
updateModalImage(); | |
updateThumbnails(); | |
}); | |
thumbnailsContainer.appendChild(thumbnail); | |
}); | |
} | |
function navigateImages(direction) { | |
currentImageIndex = (currentImageIndex + direction + currentGroupImages.length) % currentGroupImages.length; | |
updateModalImage(); | |
updateThumbnails(); | |
} | |
async function regenerateImage(card) { | |
const promptData = JSON.parse(card.querySelector('p').textContent); | |
setCardLoading(card); | |
const newImageUrl = await generateImage(promptData.text); | |
const oldImg = card.querySelector('img'); | |
const newImg = document.createElement('img'); | |
newImg.src = newImageUrl; | |
newImg.alt = `Generated image for ${promptData.text}`; | |
oldImg.parentNode.replaceChild(newImg, oldImg); | |
resetCardLoading(card); | |
} | |
function editPromptWithModal(card) { | |
currentEditingCard = card; | |
const currentPrompt = JSON.parse(card.querySelector('p').textContent); | |
editPromptTextarea.value = currentPrompt.text; | |
editPromptModal.style.display = "block"; | |
} | |
function deletePrompt(card) { | |
if (confirm("Are you sure you want to delete this prompt?")) { | |
const parentGroup = card.closest('.prompt-group'); | |
card.remove(); | |
if (parentGroup.querySelectorAll('.prompt-card').length === 1) { // Only the add-node-btn left | |
deleteGroup(parentGroup); | |
} | |
} | |
} | |
async function createChildNodesWithPrompt(card) { | |
const promptData = JSON.parse(card.querySelector('p').textContent); | |
const userPrompt = prompt("Enter an optional prompt to add to the request (or leave blank):"); | |
if (userPrompt === null) { | |
return; // User canceled the operation | |
} | |
setButtonLoading(card.querySelector('.action-button:nth-child(4)')); | |
const placeholder = createGroupPlaceholder("Generating prompts..."); | |
let nextLayer = card.closest('.prompt-layer').nextElementSibling; | |
if (!nextLayer || !nextLayer.classList.contains('prompt-layer')) { | |
nextLayer = createPromptLayer(); | |
promptContainer.insertBefore(nextLayer, card.closest('.prompt-layer').nextElementSibling); | |
} | |
nextLayer.appendChild(placeholder); | |
const childNodes = await generateComplexPrompts(promptData.text, 3, userPrompt); | |
placeholder.innerHTML = "Generating images..."; | |
const parentImage = card.querySelector('img').src; | |
const newGroup = await createPromptGroup(childNodes, promptData.text, userPrompt || promptData.text, parentImage); | |
nextLayer.replaceChild(newGroup, placeholder); | |
updateNodeSize(); | |
resetButtonLoading(card.querySelector('.action-button:nth-child(4)')); | |
} | |
async function regenerateGroup(group) { | |
const groupTitle = group.querySelector('.prompt-group-title'); | |
const basePrompt = groupTitle.textContent; | |
const numPrompts = group.querySelectorAll('.prompt-card:not(.add-node-btn)').length; | |
const userInputTitle = group.querySelector('.prompt-group-user-input'); | |
const userInputText = userInputTitle.textContent; | |
setButtonLoading(group.querySelector('.action-button:nth-child(1)')); | |
const placeholder = createGroupPlaceholder("Generating prompts..."); | |
group.innerHTML = ''; | |
group.appendChild(placeholder); | |
const newPrompts = await generateComplexPrompts(basePrompt, numPrompts, userInputText); | |
placeholder.innerHTML = "Generating images..."; | |
group.innerHTML = ''; | |
group.appendChild(groupTitle); | |
group.appendChild(userInputTitle); | |
for (const prompt of newPrompts) { | |
const newImageUrl = await generateImage(prompt.text); | |
const promptCard = createPromptCard(prompt, newImageUrl, basePrompt, userInputText); | |
group.appendChild(promptCard); | |
} | |
const addNodeBtn = document.createElement('div'); | |
addNodeBtn.className = 'add-node-btn prompt-card'; | |
addNodeBtn.innerHTML = '<i class="fas fa-plus"></i>'; | |
addNodeBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
addNewNode(group); | |
}); | |
group.appendChild(addNodeBtn); | |
updateNodeSize(); | |
resetButtonLoading(group.querySelector('.action-button:nth-child(1)')); | |
} | |
async function editGroupPrompt(group) { | |
const groupTitle = group.querySelector('.prompt-group-title'); | |
const currentPrompt = groupTitle.textContent; | |
const userInputTitle = group.querySelector('.prompt-group-user-input'); | |
const userIndication = prompt("Enter a new prompt for this group:", currentPrompt); | |
if (userIndication !== null && userIndication !== "") { | |
groupTitle.textContent = currentPrompt; // Keep the original parent prompt | |
userInputTitle.textContent = userIndication; // Update user input | |
await regenerateGroup(group); | |
} | |
} | |
function deleteGroup(group) { | |
if (confirm("Are you sure you want to delete this entire group?")) { | |
const parentLayer = group.closest('.prompt-layer'); | |
group.remove(); | |
if (parentLayer.querySelectorAll('.prompt-group').length === 0) { | |
parentLayer.remove(); | |
} | |
} | |
} | |
async function addNewNode(group) { | |
const groupTitle = group.querySelector('.prompt-group-title'); | |
const basePrompt = groupTitle.textContent; | |
const userInputTitle = group.querySelector('.prompt-group-user-input'); | |
const userInputText = userInputTitle.textContent; | |
setButtonLoading(group.querySelector('.add-node-btn')); | |
const newPrompt = await generateComplexPrompts(basePrompt, 1, userInputText); | |
const newImageUrl = await generateImage(newPrompt[0].text); | |
const promptCard = createPromptCard(newPrompt[0], newImageUrl, basePrompt, userInputText); | |
group.insertBefore(promptCard, group.querySelector('.add-node-btn')); | |
updateNodeSize(); | |
resetButtonLoading(group.querySelector('.add-node-btn')); | |
} | |
async function generateComplexPrompts(basePrompt, count, userPrompt = "") { | |
const response = await fetch('https://websim.ai/api/web', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
prompt: `Generate ${count} unique and creative image prompts based on this concept: "${basePrompt}". ${userPrompt ? `Include this additional context: "${userPrompt}".` : ''} Each prompt should be detailed and vivid, suitable for AI image generation. Respond in the following JSON format: | |
{ | |
"prompts": [ | |
{ | |
"text": "Full prompt text" | |
}, | |
... | |
] | |
}`, | |
}), | |
}); | |
const data = await response.json(); | |
return data.prompts; | |
} | |
async function generateImage(prompt) { | |
// Use a placeholder loading image | |
const placeholderUrl = 'https://via.placeholder.com/400x400.png?text=Loading...'; | |
// Simulate image generation delay | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
// For this example, we'll still use a placeholder image service | |
// In a real application, you would integrate with an actual AI image generation API | |
return `https://picsum.photos/400?random=${Math.random()}`; | |
} | |
function setButtonLoading(button) { | |
const originalContent = button.innerHTML; | |
button.disabled = true; | |
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; | |
button.dataset.originalContent = originalContent; | |
} | |
function resetButtonLoading(button) { | |
const originalContent = button.dataset.originalContent; | |
if (originalContent) { | |
button.innerHTML = originalContent; | |
button.disabled = false; | |
delete button.dataset.originalContent; | |
} | |
} | |
function setCardLoading(card) { | |
const loadingOverlay = document.createElement('div'); | |
loadingOverlay.className = 'loading-overlay'; | |
loadingOverlay.innerHTML = '<i class="fas fa-spinner fa-spin fa-2x"></i>'; | |
card.appendChild(loadingOverlay); | |
} | |
function resetCardLoading(card) { | |
const loadingOverlay = card.querySelector('.loading-overlay'); | |
if (loadingOverlay) { | |
"" | |
loadingOverlay.remove(); | |
} | |
} | |
// Add this code to restore default link behavior after the page is loaded | |
document.addEventListener('DOMContentLoaded', function() { | |
const restoreDefaultLinkBehavior = function(element) { | |
const links = element.getElementsByTagName('a'); | |
for (let i = 0; i < links.length; i++) { | |
links[i].addEventListener('click', function(event) { | |
event.stopPropagation(); | |
}); | |
} | |
}; | |
// Restore default behavior for existing links | |
restoreDefaultLinkBehavior(document); | |
// Use a MutationObserver to handle dynamically added content | |
const observer = new MutationObserver(function(mutations) { | |
mutations.forEach(function(mutation) { | |
if (mutation.type === 'childList') { | |
mutation.addedNodes.forEach(function(node) { | |
if (node.nodeType === 1) { // ELEMENT_NODE | |
restoreDefaultLinkBehavior(node); | |
} | |
}); | |
} | |
}); | |
}); | |
// Configure the observer to watch for changes in the entire document | |
const config = { childList: true, subtree: true }; | |
observer.observe(document.body, config); | |
}); | |
// Modify the download functionality | |
downloadImageBtn.addEventListener('click', function(e) { | |
e.preventDefault(); | |
const imgSrc = modalImage.src; | |
const promptData = JSON.parse(currentGroupImages[currentImageIndex].querySelector('p').textContent); | |
const fileName = `${promptData.text.slice(0, 30).replace(/[^a-z0-9]/gi, '_').toLowerCase()}.jpg`; | |
fetch(imgSrc) | |
.then(resp => resp.blob()) | |
.then(blob => { | |
const url = window.URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.style.display = 'none'; | |
a.href = url; | |
a.download = fileName; | |
document.body.appendChild(a); | |
a.click(); | |
window.URL.revokeObjectURL(url); | |
}) | |
.catch(() => alert('An error occurred while downloading the image.')); | |
}); | |
</script> | |
</body></html> |