Merge static frontend and a template/generator with basic options
This commit is contained in:
57
frontend/templates/base.html
Normal file
57
frontend/templates/base.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Image Generator{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{% if use_cdn %}https://cdn.simplecss.org/simple.min.css{% else %}simple.min.css{% endif %}">
|
||||
<style>
|
||||
#result {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
#loading {
|
||||
display: none;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.image-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.loading-overlay {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid #f3f3f3;
|
||||
border-top: 5px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: auto;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
.generating img {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
</style>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
137
frontend/templates/index.html
Normal file
137
frontend/templates/index.html
Normal file
@@ -0,0 +1,137 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<div id="error" class="error" style="display: none;"></div>
|
||||
|
||||
<form id="generateForm" onsubmit="generateImage(event)">
|
||||
<div class="form-group" style="text-align: center;">
|
||||
{% if not fixed_workflow %}
|
||||
<select name="workflow" required>
|
||||
<option value="">Select a workflow...</option>
|
||||
</select>
|
||||
{% endif %}
|
||||
|
||||
{% if not hide_seed %}
|
||||
<input type="number" name="seed" min="0" placeholder="Leave empty for random seed">
|
||||
{% endif %}
|
||||
|
||||
<button type="submit">Generate Image</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="loading" style="display: none;"></div>
|
||||
<div id="result">
|
||||
<div class="image-container">
|
||||
<img id="generatedImage" style="max-width: 512px; max-height: 512px; display: none; cursor: pointer;">
|
||||
<div class="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function submitGenerate() {
|
||||
const form = document.getElementById('generateForm');
|
||||
const event = new Event('submit', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
form.dispatchEvent(event);
|
||||
}
|
||||
const API_URL = '{{ api_url }}';
|
||||
{% if fixed_workflow %}const FIXED_WORKFLOW = '{{ fixed_workflow }}';{% endif %}
|
||||
|
||||
function generateImage(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const form = event.target;
|
||||
const loading = document.getElementById('loading');
|
||||
const image = document.getElementById('generatedImage');
|
||||
const workflowId = {% if fixed_workflow %}FIXED_WORKFLOW{% else %}form.workflow.value{% endif %};
|
||||
const seedInput = form.querySelector('input[name="seed"]');
|
||||
const seed = seedInput ? seedInput.value : null;
|
||||
|
||||
loading.style.display = 'block';
|
||||
const imageContainer = document.querySelector('.image-container');
|
||||
const loadingOverlay = document.querySelector('.loading-overlay');
|
||||
if (imageContainer) {
|
||||
imageContainer.classList.add('generating');
|
||||
}
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.display = 'flex';
|
||||
}
|
||||
|
||||
// Construct the appropriate URL based on whether we have a seed
|
||||
// Ensure we're using the full URL
|
||||
const baseUrl = API_URL.replace(/\/$/, ''); // Remove trailing slash if present
|
||||
const url = seed
|
||||
? `${baseUrl}/workflows/${workflowId}/image/${seed}`
|
||||
: `${baseUrl}/workflows/${workflowId}/image`;
|
||||
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Generation failed');
|
||||
return response.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
image.src = url;
|
||||
image.style.display = 'block';
|
||||
image.onclick = submitGenerate;
|
||||
const imageContainer = document.querySelector('.image-container');
|
||||
const loadingOverlay = document.querySelector('.loading-overlay');
|
||||
if (imageContainer) {
|
||||
imageContainer.classList.remove('generating');
|
||||
}
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.display = 'none';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error: ' + error.message);
|
||||
const imageContainer = document.querySelector('.image-container');
|
||||
const loadingOverlay = document.querySelector('.loading-overlay');
|
||||
if (imageContainer) {
|
||||
imageContainer.classList.remove('generating');
|
||||
}
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
// Load workflows when page loads
|
||||
window.addEventListener('load', async () => {
|
||||
{% if fixed_workflow %}
|
||||
// Skip workflow loading if we're using a fixed workflow
|
||||
return;
|
||||
{% else %}
|
||||
try {
|
||||
const baseUrl = API_URL.replace(/\/$/, ''); // Remove trailing slash if present
|
||||
const response = await fetch(`${baseUrl}/workflows`);
|
||||
if (!response.ok) throw new Error('Failed to fetch workflows');
|
||||
|
||||
const workflows = await response.json();
|
||||
const select = document.querySelector('select[name="workflow"]');
|
||||
|
||||
workflows.forEach(workflow => {
|
||||
const option = document.createElement('option');
|
||||
option.value = workflow.id;
|
||||
option.textContent = `${workflow.id} - ${workflow.handle}`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
// If there's only one workflow, select it automatically
|
||||
if (workflows.length === 1) {
|
||||
select.value = workflows[0].id;
|
||||
}
|
||||
} catch (error) {
|
||||
const errorDiv = document.getElementById('error');
|
||||
errorDiv.textContent = `Error: ${error.message}`;
|
||||
errorDiv.style.display = 'block';
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user