ai-frame-image-server/templates/create_image.html
Karl bb4adbff2c feat(openrouter): add support for listing and using free OpenRouter models
Add a new configuration option `list_all_free_models` to enable fetching and displaying free models from OpenRouter. Enhance model loading functions to include free models when enabled, and implement fallback logic in prompt generation to try alternative models if the primary one fails. Update the UI to display free models in a separate optgroup.
2025-10-30 17:15:59 +00:00

391 lines
11 KiB
HTML

{% extends "base.html" %}
{% block title %}Create An Image{% endblock %}
{% block head %}
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
padding: 20px;
}
textarea {
width: 80vw;
height: 200px;
border-radius: 10px;
padding: 15px;
font-size: 16px;
font-family: monospace;
resize: none;
margin-bottom: 20px;
background: #111;
color: #eee;
border: 1px solid #333;
}
.button-group {
display: flex;
gap: 20px;
align-items: center;
}
.model-selection {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
margin: 20px 0;
width: 100%;
max-width: 800px;
}
.model-group {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.model-group label {
font-weight: bold;
color: #ddd;
}
button,
select {
background: #333;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
min-width: 150px;
}
button:hover,
select:hover {
background: #555;
}
#spinner-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
visibility: hidden;
z-index: 1000;
}
.spinner {
width: 50px;
height: 50px;
border: 6px solid #555;
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (max-width: 600px) {
body {
min-height: 100dvh;
height: auto;
justify-content: flex-start;
padding-top: 40px;
}
.button-group {
flex-direction: column;
align-items: stretch;
width: 100%;
}
.model-selection {
flex-direction: column;
align-items: stretch;
}
.model-group {
align-items: stretch;
}
button,
select {
width: 100%;
}
textarea {
height: 150px;
}
}
.queue-dropdown {
position: absolute;
top: 100%;
right: 0;
background: #222;
border: 1px solid #444;
border-radius: 5px;
padding: 10px;
z-index: 1001;
display: none;
max-height: 300px;
overflow-y: auto;
width: 400px;
}
.queue-item {
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #333;
}
.queue-item:last-child {
border-bottom: none;
}
.queue-item .prompt {
font-size: 0.9em;
color: #aaa;
white-space: normal;
word-wrap: break-word;
position: relative;
cursor: pointer;
}
.queue-item .prompt:hover::after {
content: "Model: " attr(data-model);
position: absolute;
bottom: 100%;
left: 0;
background: #333;
color: #00aaff;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
white-space: nowrap;
z-index: 1002;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
</style>
{% endblock %}
{% block content %}
<div class="queue-container" style="position: fixed; top: 20px; right: 20px; z-index: 1000;">
<button id="queue-btn" style="background: #333; color: white; border: none; padding: 5px 10px; border-radius: 5px; cursor: pointer;">
Queue: <span id="queue-count">{{ queue_count | default(0) }}</span>
</button>
<div id="queue-dropdown" class="queue-dropdown">
<!-- Queue items will be populated here -->
</div>
</div>
<h1 style="margin-bottom: 20px;">Create An Image</h1>
<textarea id="prompt-box" placeholder="Enter your custom prompt here..."></textarea>
<div class="button-group">
<button onclick="showSpinner(); location.href='/'">Back</button>
<button onclick="sendPrompt()">Send Prompt</button>
<button onclick="randomPrompt()">Random Prompt</button>
</div>
<div class="model-selection">
<div class="model-group">
<label for="model-select">Image Model:</label>
<select id="model-select">
<option value="" selected>Random Image Model</option>
{% if flux_models %}
<optgroup label="FLUX">
{% for m in flux_models %}
<option value="{{ m }}">{{ m.rsplit('.', 1)[0] if '.' in m else m }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if qwen_models %}
<optgroup label="Qwen">
{% for m in qwen_models %}
<option value="{{ m }}">{{ m.rsplit('.', 1)[0] if '.' in m else m }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if sdxl_models %}
<optgroup label="SDXL">
{% for m in sdxl_models %}
<option value="{{ m }}">{{ m.rsplit('.', 1)[0] if '.' in m else m }}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
</div>
<div class="model-group">
<label for="prompt-model-select">Prompt Model:</label>
<select id="prompt-model-select">
<option value="" selected>Random Prompt Model</option>
{% if openwebui_models %}
<optgroup label="OpenWebUI">
{% for m in openwebui_models %}
<option value="openwebui:{{ m }}">{{ m }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if openrouter_models %}
<optgroup label="OpenRouter">
{% for m in openrouter_models %}
<option value="openrouter:{{ m }}">{{ m }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% if openrouter_free_models %}
<optgroup label="OpenRouter Free">
{% for m in openrouter_free_models %}
<option value="openrouter:{{ m }}">{{ m }}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
</div>
<div class="model-group">
<label for="topic-select">Topic:</label>
<select id="topic-select">
<option value="">No Topic</option>
<option value="random">Random</option>
<optgroup label="Topics">
{% for t in topics %}
<option value="{{ t }}">{{ t }}</option>
{% endfor %}
</optgroup>
</select>
</div>
</div>
<div id="spinner-overlay">
<div class="spinner"></div>
</div>
{% endblock %}
{% block scripts %}
<script>
const overlay = document.getElementById('spinner-overlay');
function showSpinner() { overlay.style.visibility = 'visible'; }
function sendPrompt() {
showSpinner();
const prompt = document.getElementById('prompt-box').value;
const model = document.getElementById('model-select').value;
const promptModel = document.getElementById('prompt-model-select').value;
const formData = new URLSearchParams();
formData.append('prompt', prompt);
formData.append('model', model);
formData.append('prompt_model', promptModel);
fetch('/create', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formData.toString()
})
.then(response => {
window.location.href = response.redirected ? response.url : '/create';
})
.catch(error => {
overlay.style.visibility = 'hidden';
alert("Error sending prompt: " + error);
});
}
function randomPrompt() {
showSpinner();
const model = document.getElementById('model-select').value;
const promptModel = document.getElementById('prompt-model-select').value;
const topic = document.getElementById('topic-select').value;
const formData = new URLSearchParams();
formData.append('model', model);
formData.append('prompt_model', promptModel);
formData.append('topic', topic);
fetch('/create', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formData.toString()
})
.then(response => {
window.location.href = response.redirected ? response.url : '/create';
})
.catch(error => {
overlay.style.visibility = 'hidden';
alert("Error requesting random prompt: " + error);
});
}
document.addEventListener('DOMContentLoaded', function() {
const queueBtn = document.getElementById('queue-btn');
const queueDropdown = document.getElementById('queue-dropdown');
const queueCountSpan = document.getElementById('queue-count');
// Toggle dropdown visibility
queueBtn.addEventListener('click', function(e) {
e.stopPropagation();
if (queueDropdown.style.display === 'block') {
queueDropdown.style.display = 'none';
} else {
fetchQueueDetails();
queueDropdown.style.display = 'block';
}
});
// Close dropdown when clicking outside
document.addEventListener('click', function() {
queueDropdown.style.display = 'none';
});
// Prevent dropdown from closing when clicking inside it
queueDropdown.addEventListener('click', function(e) {
e.stopPropagation();
});
function fetchQueueDetails() {
fetch('/api/queue')
.then(response => response.json())
.then(jobs => {
queueCountSpan.textContent = jobs.length;
const container = queueDropdown;
container.innerHTML = '';
if (jobs.length === 0) {
container.innerHTML = '<div class="queue-item">No jobs in queue</div>';
return;
}
jobs.forEach(job => {
const item = document.createElement('div');
item.className = 'queue-item';
item.innerHTML = `
<div class="prompt" data-model="${job.model}">${job.prompt}</div>
`;
container.appendChild(item);
});
})
.catch(error => {
console.error('Error fetching queue:', error);
queueDropdown.innerHTML = '<div class="queue-item">Error loading queue</div>';
});
}
});
</script>
{% endblock %}