amuse-model-alchemist / index.html
eric164's picture
“Gebruik dit aanvullende gedeelte als uitbreidingsspecificatie voor de functionaliteit.”
35ea023 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Amuse Model Alchemist</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.waves.min.js"></script>
<style>
.dropzone {
border: 2px dashed #4b5563;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #3b82f6;
background-color: rgba(59, 130, 246, 0.05);
}
.progress-bar {
transition: width 0.3s ease;
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<div id="vanta-bg" class="fixed inset-0 -z-10"></div>
<div class="container mx-auto px-4 py-12">
<!-- Header -->
<header class="text-center mb-12">
<div class="inline-block bg-gradient-to-r from-blue-500 to-purple-600 p-2 rounded-lg mb-4">
<i data-feather="cpu" class="w-12 h-12 text-white"></i>
</div>
<h1 class="text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-500">
Amuse Model Alchemist
</h1>
<p class="text-xl text-gray-300 max-w-2xl mx-auto">
Transform your Stable Diffusion models into ONNX/Olive artifacts with alchemical precision
</p>
</header>
<!-- Main Card -->
<div class="bg-gray-800/80 backdrop-blur-lg rounded-xl shadow-2xl overflow-hidden max-w-4xl mx-auto">
<!-- Tabs -->
<div class="flex border-b border-gray-700">
<button class="tab-btn active px-6 py-4 font-medium text-blue-400 border-b-2 border-blue-400">
<i data-feather="upload" class="mr-2"></i> Convert Model
</button>
<button class="tab-btn px-6 py-4 font-medium text-gray-400 hover:text-white">
<i data-feather="terminal" class="mr-2"></i> CLI Mode
</button>
<button class="tab-btn px-6 py-4 font-medium text-gray-400 hover:text-white" data-tab="settings">
<i data-feather="settings" class="mr-2"></i> Settings
</button>
</div>
<!-- Content -->
<div class="p-8" id="tabContent">
<!-- Settings Tab Content -->
<div id="settingsTab" class="hidden">
<div class="space-y-6">
<h2 class="text-2xl font-bold mb-6">Application Settings</h2>
<div class="bg-gray-700/50 rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4 flex items-center">
<i data-feather="save" class="mr-2"></i> Storage Settings
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Default Output Directory</label>
<div class="flex">
<input type="text" id="outputDir" class="flex-1 bg-gray-700 border border-gray-600 rounded-l-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Path to output directory">
<button id="browseDirBtn" class="px-4 py-2 bg-gray-600 hover:bg-gray-500 rounded-r-lg">
<i data-feather="folder" class="w-4 h-4"></i>
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="flex items-center">
<input type="checkbox" id="createSubdir" class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500 mr-2" checked>
<span>Create timestamped subdirectory for each conversion</span>
</label>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" id="overwriteFiles" class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500 mr-2">
<span>Overwrite existing files</span>
</label>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" id="saveSettings" class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500 mr-2" checked>
<span>Save settings on exit</span>
</label>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" id="autoClose" class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500 mr-2" checked>
<span>Auto-close after successful conversion</span>
</label>
</div>
</div>
</div>
</div>
<div class="flex justify-end">
<button id="saveSettingsBtn" class="px-6 py-2.5 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 rounded-lg font-medium flex items-center justify-center">
<i data-feather="save" class="mr-2"></i> Save Settings
</button>
</div>
<!-- Current Settings Summary -->
<div class="bg-gray-700/50 rounded-lg p-4">
<button id="settingsSummaryToggle" class="flex items-center text-blue-400 hover:text-blue-300 w-full justify-between">
<span class="font-medium">Current Settings Summary</span>
<i data-feather="chevron-down" class="w-4 h-4"></i>
</button>
<div id="settingsSummary" class="hidden mt-4 text-sm space-y-2">
<div><span class="text-gray-400">Output Directory:</span> <span id="currentOutputDir"></span></div>
<div><span class="text-gray-400">Create Subdirectories:</span> <span id="currentSubdir"></span></div>
<div><span class="text-gray-400">Overwrite Files:</span> <span id="currentOverwrite"></span></div>
<div><span class="text-gray-400">Save Settings:</span> <span id="currentSaveSettings"></span></div>
<div><span class="text-gray-400">Auto-Close:</span> <span id="currentAutoClose"></span></div>
</div>
</div>
</div>
</div>
<!-- Convert Model Tab Content -->
<div id="convertTab">
<!-- Drop Zone -->
<div id="dropzone" class="dropzone rounded-lg p-12 text-center mb-8 cursor-pointer">
<div class="flex flex-col items-center justify-center space-y-4">
<i data-feather="upload-cloud" class="w-12 h-12 text-gray-400"></i>
<h3 class="text-xl font-semibold">Drag & Drop Model Files Here</h3>
<p class="text-gray-400">or click to browse (.ckpt, .safetensors, or Diffusers folder)</p>
<input type="file" id="fileInput" class="hidden" webkitdirectory mozdirectory msdirectory odirectory directory multiple>
<button id="browseBtn" class="mt-4 px-6 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
Select Files
</button>
</div>
</div>
<!-- Selected Files -->
<div id="selectedFiles" class="hidden mb-8">
<h3 class="text-lg font-semibold mb-3 flex items-center">
<i data-feather="file" class="mr-2"></i> Selected Files
</h3>
<div class="bg-gray-700/50 rounded-lg p-4 max-h-48 overflow-y-auto">
<div id="fileList" class="space-y-2"></div>
</div>
</div>
<!-- Conversion Options -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div>
<label class="block text-sm font-medium mb-2">Target Hardware</label>
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="directml">DirectML (Windows Default)</option>
<option value="cuda">NVIDIA CUDA</option>
<option value="cpu">CPU (OpenVINO)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-2">Precision</label>
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="fp16">FP16 (Recommended)</option>
<option value="int8">INT8 (Quantized)</option>
<option value="fp32">FP32 (Full Precision)</option>
</select>
</div>
</div>
<!-- Advanced Options -->
<div class="mb-6">
<button id="advancedToggle" class="flex items-center text-blue-400 hover:text-blue-300">
<i data-feather="chevron-down" class="mr-2 w-4 h-4"></i> Advanced Options
</button>
<div id="advancedOptions" class="hidden mt-4 space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium mb-2">Model Type</label>
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="auto">Auto-detect</option>
<option value="sd1">SD v1.x</option>
<option value="sd2">SD v2.x</option>
<option value="sdxl">SDXL</option>
<option value="sd3">SD3</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-2">Output Format</label>
<select class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="olive">Olive Optimized</option>
<option value="onnx">Raw ONNX</option>
<option value="amuse">Amuse Ready</option>
</select>
</div>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500 mr-2">
<span>Run Smoke Test After Conversion</span>
</label>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500 mr-2">
<span>Generate Amuse Import Package</span>
</label>
</div>
</div>
</div>
<!-- Legal Warning -->
<div class="bg-yellow-900/30 border border-yellow-800 rounded-lg p-4 mb-8">
<div class="flex items-start">
<i data-feather="alert-triangle" class="text-yellow-400 mr-3 mt-1"></i>
<div>
<h4 class="font-medium text-yellow-300 mb-1">Legal Notice</h4>
<p class="text-sm text-yellow-200">
You must have the necessary rights to convert any model. By proceeding, you acknowledge that you have verified the model's license terms.
</p>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row justify-end space-y-3 sm:space-y-0 sm:space-x-4">
<button class="px-6 py-2.5 bg-gray-700 hover:bg-gray-600 rounded-lg font-medium flex items-center justify-center">
<i data-feather="save" class="mr-2"></i> Save Settings
</button>
<button id="convertBtn" class="px-6 py-2.5 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 rounded-lg font-medium flex items-center justify-center">
<i data-feather="zap" class="mr-2"></i> Convert Model
</button>
</div>
</div>
</div>
<!-- Progress Modal -->
<div id="progressModal" class="hidden fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4">
<div class="bg-gray-800 rounded-xl shadow-2xl w-full max-w-2xl">
<div class="p-6 border-b border-gray-700">
<h3 class="text-xl font-semibold flex items-center">
<i data-feather="rotate-cw" class="mr-2 animate-spin"></i> Converting Model
</h3>
</div>
<div class="p-6">
<div class="mb-4">
<div class="flex justify-between text-sm mb-1">
<span>Progress</span>
<span id="progressPercent">0%</span>
</div>
<div class="w-full bg-gray-700 rounded-full h-2.5">
<div id="progressBar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
</div>
<div class="bg-gray-700/50 rounded-lg p-4 h-48 overflow-y-auto">
<div id="progressLog" class="font-mono text-sm text-gray-300 space-y-1">
<div>> Initializing model conversion...</div>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-700 flex justify-end">
<button id="cancelBtn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg mr-3">
Cancel
</button>
<button id="hideModalBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg disabled:opacity-50" disabled>
Close
</button>
</div>
</div>
</div>
</div>
<script>
// Settings management
function loadSettings() {
try {
// In a real app, this would load from settings.json file
// const settingsData = fs.readFileSync(path.join(app.getPath('userData'), 'settings.json'), 'utf8');
// const settings = JSON.parse(settingsData);
const settings = JSON.parse(localStorage.getItem('ama_settings')) || {
outputDir: '',
createSubdir: true,
overwriteFiles: false,
saveSettings: true,
autoClose: true
};
document.getElementById('outputDir').value = settings.outputDir;
document.getElementById('createSubdir').checked = settings.createSubdir;
document.getElementById('overwriteFiles').checked = settings.overwriteFiles;
document.getElementById('saveSettings').checked = settings.saveSettings;
document.getElementById('autoClose').checked = settings.autoClose;
updateSettingsSummary();
return settings;
} catch (e) {
console.error('Error loading settings:', e);
return {
outputDir: '',
createSubdir: true,
overwriteFiles: false,
saveSettings: true,
autoClose: true
};
}
}
function saveSettings() {
const settings = {
outputDir: document.getElementById('outputDir').value,
createSubdir: document.getElementById('createSubdir').checked,
overwriteFiles: document.getElementById('overwriteFiles').checked,
saveSettings: document.getElementById('saveSettings').checked,
autoClose: document.getElementById('autoClose').checked
};
localStorage.setItem('ama_settings', JSON.stringify(settings));
// In a real app, this would save to settings.json file
// const settingsJSON = JSON.stringify(settings, null, 2);
// fs.writeFileSync(path.join(app.getPath('userData'), 'settings.json'), settingsJSON);
updateSettingsSummary();
return settings;
}
// Error handling for settings loading
function handleSettingsError(error) {
console.error('Settings error:', error);
const defaultSettings = {
outputDir: '',
createSubdir: true,
overwriteFiles: false,
saveSettings: true,
autoClose: true
};
localStorage.setItem('ama_settings', JSON.stringify(defaultSettings));
return defaultSettings;
}
function updateSettingsSummary() {
const settings = JSON.parse(localStorage.getItem('ama_settings')) || {};
document.getElementById('currentOutputDir').textContent = settings.outputDir || 'Not set';
document.getElementById('currentSubdir').textContent = settings.createSubdir ? 'Yes' : 'No';
document.getElementById('currentOverwrite').textContent = settings.overwriteFiles ? 'Yes' : 'No';
document.getElementById('currentSaveSettings').textContent = settings.saveSettings ? 'Yes' : 'No';
document.getElementById('currentAutoClose').textContent = settings.autoClose ? 'Yes' : 'No';
}
// Initialize Vanta.js background
VANTA.WAVES({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x1e293b,
shininess: 50.00,
waveHeight: 15.00,
waveSpeed: 0.75,
zoom: 0.85
});
// Initialize Feather Icons
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
// Tab switching
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContent = document.getElementById('tabContent');
const convertTab = document.getElementById('convertTab');
const settingsTab = document.getElementById('settingsTab');
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
tabBtns.forEach(b => b.classList.remove('active', 'text-blue-400', 'border-blue-400'));
tabBtns.forEach(b => b.classList.add('text-gray-400'));
btn.classList.add('active', 'text-blue-400', 'border-blue-400');
btn.classList.remove('text-gray-400');
if (btn.dataset.tab === 'settings') {
convertTab.style.display = 'none';
settingsTab.style.display = 'block';
} else {
settingsTab.style.display = 'none';
convertTab.style.display = 'block';
}
});
});
// Settings summary toggle
document.getElementById('settingsSummaryToggle').addEventListener('click', () => {
const summary = document.getElementById('settingsSummary');
const icon = document.getElementById('settingsSummaryToggle').querySelector('i');
if (summary.classList.contains('hidden')) {
summary.classList.remove('hidden');
icon.setAttribute('data-feather', 'chevron-up');
} else {
summary.classList.add('hidden');
icon.setAttribute('data-feather', 'chevron-down');
}
feather.replace();
});
// Load settings on startup
loadSettings();
// Save settings button
document.getElementById('saveSettingsBtn').addEventListener('click', saveSettings);
// Advanced options toggle
const advancedToggle = document.getElementById('advancedToggle');
const advancedOptions = document.getElementById('advancedOptions');
advancedToggle.addEventListener('click', () => {
const icon = advancedToggle.querySelector('i');
if (advancedOptions.classList.contains('hidden')) {
advancedOptions.classList.remove('hidden');
icon.setAttribute('data-feather', 'chevron-up');
} else {
advancedOptions.classList.add('hidden');
icon.setAttribute('data-feather', 'chevron-down');
}
feather.replace();
});
// File dropzone functionality
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const browseBtn = document.getElementById('browseBtn');
const selectedFiles = document.getElementById('selectedFiles');
const fileList = document.getElementById('fileList');
// Handle drag and drop
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropzone.classList.add('active');
}
function unhighlight() {
dropzone.classList.remove('active');
}
dropzone.addEventListener('drop', handleDrop, false);
browseBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', function() {
handleFiles(this.files);
});
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
function handleFiles(files) {
if (files.length > 0) {
selectedFiles.classList.remove('hidden');
fileList.innerHTML = '';
for (let i = 0; i < Math.min(files.length, 5); i++) {
const file = files[i];
const fileItem = document.createElement('div');
fileItem.className = 'flex items-center text-sm';
fileItem.innerHTML = `
<i data-feather="file" class="mr-2 text-gray-400"></i>
<span class="truncate flex-1">${file.name}</span>
<span class="text-gray-400 ml-2">${formatFileSize(file.size)}</span>
`;
fileList.appendChild(fileItem);
}
if (files.length > 5) {
const moreFiles = document.createElement('div');
moreFiles.className = 'text-sm text-gray-400';
moreFiles.textContent = `+ ${files.length - 5} more files`;
fileList.appendChild(moreFiles);
}
feather.replace();
}
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Conversion simulation
const convertBtn = document.getElementById('convertBtn');
const progressModal = document.getElementById('progressModal');
const progressBar = document.getElementById('progressBar');
const progressPercent = document.getElementById('progressPercent');
const progressLog = document.getElementById('progressLog');
const cancelBtn = document.getElementById('cancelBtn');
const hideModalBtn = document.getElementById('hideModalBtn');
convertBtn.addEventListener('click', () => {
progressModal.classList.remove('hidden');
simulateConversion();
});
cancelBtn.addEventListener('click', () => {
progressModal.classList.add('hidden');
resetProgress();
});
hideModalBtn.addEventListener('click', () => {
progressModal.classList.add('hidden');
});
function resetProgress() {
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
progressLog.innerHTML = '<div>> Initializing model conversion...</div>';
hideModalBtn.disabled = true;
}
function simulateConversion() {
let progress = 0;
const steps = [
{percent: 10, message: "> Detecting model type and components..."},
{percent: 25, message: "> Preparing model for ONNX export..."},
{percent: 40, message: "> Exporting text encoder to ONNX format..."},
{percent: 55, message: "> Exporting UNet to ONNX format..."},
{percent: 70, message: "> Exporting VAE to ONNX format..."},
{percent: 85, message: "> Running Olive optimization passes..."},
{percent: 95, message: "> Performing smoke test validation..."},
{percent: 100, message: "> Conversion complete! Output saved to models/exported_model"}
];
// Create logs directory if it doesn't exist
if (!localStorage.getItem('ama_logs')) {
localStorage.setItem('ama_logs', JSON.stringify([]));
}
const interval = setInterval(() => {
if (progress >= steps.length) {
clearInterval(interval);
hideModalBtn.disabled = false;
return;
}
const step = steps[progress];
progressBar.style.width = `${step.percent}%`;
progressPercent.textContent = `${step.percent}%`;
const logEntry = document.createElement('div');
logEntry.textContent = step.message;
progressLog.appendChild(logEntry);
progressLog.scrollTop = progressLog.scrollHeight;
// If this is the last step
if (progress === steps.length - 1) {
const settings = JSON.parse(localStorage.getItem('ama_settings')) || {};
// Save log
const logContent = Array.from(progressLog.children).map(el => el.textContent).join('\n');
const logs = JSON.parse(localStorage.getItem('ama_logs') || '[]');
logs.push({
timestamp: new Date().toISOString(),
content: logContent
});
localStorage.setItem('ama_logs', JSON.stringify(logs));
console.log('Log content saved:\n', logContent);
if (settings.autoClose) {
const completeMsg = document.createElement('div');
completeMsg.className = 'text-green-400';
completeMsg.textContent = '> Conversie voltooid — programma sluit nu automatisch.';
progressLog.appendChild(completeMsg);
setTimeout(() => {
progressModal.classList.add('hidden');
resetProgress();
// In a real app, this would close the window
// window.close();
}, 3000);
} else {
hideModalBtn.disabled = false;
}
}
progress++;
}, 1500);
}
});
</script>
</body>
</html>