Allex21/Lora-trainer-all
0
1<!DOCTYPE html>2<html lang="pt-BR">3<head>4 <meta charset="UTF-8">5 <link rel="icon" type="image/x-icon" href="/favicon.ico" />6 <meta name="viewport" content="width=device-width, initial-scale=1.0">7 <title>LoRA Trainer - Treinamento de Personagens Consistentes</title>8 <style>9 * {10 margin: 0;11 padding: 0;12 box-sizing: border-box;13 }14 15 body {16 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;17 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);18 min-height: 100vh;19 padding: 20px;20 }21 22 .container {23 max-width: 1200px;24 margin: 0 auto;25 background: white;26 border-radius: 15px;27 box-shadow: 0 20px 40px rgba(0,0,0,0.1);28 overflow: hidden;29 }30 31 .header {32 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);33 color: white;34 padding: 30px;35 text-align: center;36 }37 38 .header h1 {39 font-size: 2.5em;40 margin-bottom: 10px;41 }42 43 .header p {44 font-size: 1.2em;45 opacity: 0.9;46 }47 48 .content {49 padding: 40px;50 }51 52 .section {53 margin-bottom: 40px;54 padding: 30px;55 border: 2px solid #f0f0f0;56 border-radius: 10px;57 background: #fafafa;58 }59 60 .section h2 {61 color: #333;62 margin-bottom: 20px;63 font-size: 1.8em;64 border-bottom: 3px solid #667eea;65 padding-bottom: 10px;66 }67 68 .form-group {69 margin-bottom: 20px;70 }71 72 label {73 display: block;74 margin-bottom: 8px;75 font-weight: 600;76 color: #555;77 }78 79 input[type="text"], input[type="number"], input[type="file"], select, textarea {80 width: 100%;81 padding: 12px;82 border: 2px solid #ddd;83 border-radius: 8px;84 font-size: 16px;85 transition: border-color 0.3s;86 }87 88 input[type="text"]:focus, input[type="number"]:focus, input[type="file"]:focus, select:focus, textarea:focus {89 outline: none;90 border-color: #667eea;91 }92 93 .file-upload-area {94 border: 3px dashed #ddd;95 border-radius: 10px;96 padding: 40px;97 text-align: center;98 background: white;99 transition: all 0.3s;100 cursor: pointer;101 }102 103 .file-upload-area:hover {104 border-color: #667eea;105 background: #f8f9ff;106 }107 108 .file-upload-area.dragover {109 border-color: #667eea;110 background: #f0f4ff;111 }112 113 .upload-icon {114 font-size: 3em;115 color: #667eea;116 margin-bottom: 15px;117 }118 119 .btn {120 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);121 color: white;122 border: none;123 padding: 15px 30px;124 border-radius: 8px;125 font-size: 16px;126 font-weight: 600;127 cursor: pointer;128 transition: all 0.3s;129 margin-right: 10px;130 margin-bottom: 10px;131 }132 133 .btn:hover {134 transform: translateY(-2px);135 box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);136 }137 138 .btn:disabled {139 background: #ccc;140 cursor: not-allowed;141 transform: none;142 box-shadow: none;143 }144 145 .btn-secondary {146 background: #6c757d;147 }148 149 .btn-success {150 background: #28a745;151 }152 153 .btn-danger {154 background: #dc3545;155 }156 157 .progress-container {158 background: #f0f0f0;159 border-radius: 10px;160 padding: 20px;161 margin: 20px 0;162 display: none;163 }164 165 .progress-bar {166 width: 100%;167 height: 20px;168 background: #e0e0e0;169 border-radius: 10px;170 overflow: hidden;171 margin-bottom: 10px;172 }173 174 .progress-fill {175 height: 100%;176 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);177 width: 0%;178 transition: width 0.3s;179 }180 181 .log-container {182 background: #1e1e1e;183 color: #00ff00;184 padding: 20px;185 border-radius: 10px;186 font-family: 'Courier New', monospace;187 max-height: 300px;188 overflow-y: auto;189 margin: 20px 0;190 display: none;191 }192 193 .image-preview {194 display: flex;195 flex-wrap: wrap;196 gap: 15px;197 margin-top: 20px;198 }199 200 .image-item {201 position: relative;202 border-radius: 10px;203 overflow: hidden;204 box-shadow: 0 5px 15px rgba(0,0,0,0.1);205 }206 207 .image-item img {208 width: 150px;209 height: 150px;210 object-fit: cover;211 }212 213 .image-item .remove-btn {214 position: absolute;215 top: 5px;216 right: 5px;217 background: #dc3545;218 color: white;219 border: none;220 border-radius: 50%;221 width: 25px;222 height: 25px;223 cursor: pointer;224 font-size: 12px;225 }226 227 .parameters-grid {228 display: grid;229 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));230 gap: 20px;231 }232 233 .result-section {234 background: #e8f5e8;235 border: 2px solid #28a745;236 border-radius: 10px;237 padding: 20px;238 margin-top: 20px;239 display: none;240 }241 242 .download-links {243 display: flex;244 flex-wrap: wrap;245 gap: 10px;246 margin-top: 15px;247 }248 249 .alert {250 padding: 15px;251 border-radius: 8px;252 margin: 15px 0;253 display: none;254 }255 256 .alert-success {257 background: #d4edda;258 border: 1px solid #c3e6cb;259 color: #155724;260 }261 262 .alert-error {263 background: #f8d7da;264 border: 1px solid #f5c6cb;265 color: #721c24;266 }267 268 .alert-warning {269 background: #fff3cd;270 border: 1px solid #ffeaa7;271 color: #856404;272 }273 274 @media (max-width: 768px) {275 .container {276 margin: 10px;277 border-radius: 10px;278 }279 280 .content {281 padding: 20px;282 }283 284 .parameters-grid {285 grid-template-columns: 1fr;286 }287 }288 </style>289</head>290<body>291 <div class="container">292 <div class="header">293 <h1>🎨 LoRA Trainer</h1>294 <p>Ferramenta para Treinamento de Personagens Consistentes</p>295 </div>296 297 <div class="content">298 <!-- Seção de Upload de Imagens -->299 <div class="section">300 <h2>📸 Upload de Imagens</h2>301 <p style="margin-bottom: 20px; color: #666;">302 Faça upload de 5-20 imagens do personagem que deseja treinar. 303 Recomenda-se imagens de alta qualidade (512x512 ou superior) com o personagem em diferentes poses e ângulos.304 </p>305 306 <div class="file-upload-area" id="uploadArea">307 <div class="upload-icon">📁</div>308 <h3>Arraste e solte suas imagens aqui</h3>309 <p>ou clique para selecionar arquivos</p>310 <input type="file" id="imageFiles" multiple accept="image/*" style="display: none;">311 </div>312 313 <div class="image-preview" id="imagePreview"></div>314 315 <div class="alert alert-warning" id="imageAlert">316 <strong>Atenção:</strong> Você precisa fazer upload de pelo menos 5 imagens para treinar o LoRA.317 </div>318 </div>319 320 <!-- Seção de Configurações -->321 <div class="section">322 <h2>⚙️ Configurações de Treinamento</h2>323 324 <div class="parameters-grid">325 <div class="form-group">326 <label for="characterName">Nome do Personagem:</label>327 <input type="text" id="characterName" placeholder="Ex: meu_personagem" required>328 <small style="color: #666;">Use apenas letras, números e underscore</small>329 </div>330 331 <div class="form-group">332 <label for="triggerWord">Palavra-chave (Trigger):</label>333 <input type="text" id="triggerWord" placeholder="Ex: ohwx person" required>334 <small style="color: #666;">Palavra que ativará o personagem nas gerações</small>335 </div>336 337 <div class="form-group">338 <label for="resolution">Resolução de Treinamento:</label>339 <select id="resolution">340 <option value="512">512x512 (Recomendado)</option>341 <option value="768">768x768 (Melhor qualidade)</option>342 <option value="1024">1024x1024 (Alta qualidade)</option>343 </select>344 </div>345 346 <div class="form-group">347 <label for="learningRate">Taxa de Aprendizado:</label>348 <select id="learningRate">349 <option value="1e-4">1e-4 (Padrão)</option>350 <option value="5e-5">5e-5 (Conservador)</option>351 <option value="2e-4">2e-4 (Agressivo)</option>352 </select>353 </div>354 355 <div class="form-group">356 <label for="rank">Rank do LoRA:</label>357 <select id="rank">358 <option value="16">16 (Padrão)</option>359 <option value="32">32 (Mais detalhes)</option>360 <option value="64">64 (Máxima qualidade)</option>361 </select>362 </div>363 364 <div class="form-group">365 <label for="epochs">Número de Épocas:</label>366 <select id="epochs">367 <option value="10">10 (Rápido)</option>368 <option value="20">20 (Padrão)</option>369 <option value="30">30 (Detalhado)</option>370 </select>371 </div>372 </div>373 374 <div class="form-group">375 <label for="description">Descrição do Personagem (Opcional):</label>376 <textarea id="description" rows="3" placeholder="Descreva as características do personagem..."></textarea>377 </div>378 </div>379 380 <!-- Seção de Treinamento -->381 <div class="section">382 <h2>🚀 Iniciar Treinamento</h2>383 384 <button class="btn" id="startTraining" onclick="startTraining()">385 Iniciar Treinamento LoRA386 </button>387 388 <button class="btn btn-secondary" id="stopTraining" onclick="stopTraining()" style="display: none;">389 Parar Treinamento390 </button>391 392 <div class="progress-container" id="progressContainer">393 <h3>Progresso do Treinamento</h3>394 <div class="progress-bar">395 <div class="progress-fill" id="progressFill"></div>396 </div>397 <p id="progressText">Preparando treinamento...</p>398 </div>399 400 <div class="log-container" id="logContainer">401 <div id="trainingLogs"></div>402 </div>403 404 <div class="alert alert-success" id="successAlert">405 <strong>Sucesso!</strong> O treinamento foi concluído com sucesso.406 </div>407 408 <div class="alert alert-error" id="errorAlert">409 <strong>Erro!</strong> <span id="errorMessage"></span>410 </div>411 </div>412 413 <!-- Seção de Resultados -->414 <div class="section result-section" id="resultSection">415 <h2>✅ Treinamento Concluído</h2>416 <p>Seu modelo LoRA foi treinado com sucesso! Baixe os arquivos abaixo:</p>417 418 <div class="download-links" id="downloadLinks">419 <!-- Links de download serão inseridos aqui -->420 </div>421 422 <div style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">423 <h4>Como usar seu LoRA:</h4>424 <ol>425 <li>Baixe o arquivo <code>pytorch_lora_weights.safetensors</code></li>426 <li>Coloque o arquivo na pasta de LoRAs do seu software (ComfyUI, Automatic1111, etc.)</li>427 <li>Use a palavra-chave "<span id="resultTriggerWord"></span>" em seus prompts</li>428 <li>Ajuste o peso do LoRA entre 0.7 e 1.0 para melhores resultados</li>429 </ol>430 </div>431 </div>432 </div>433 </div>434 435 <script>436 let uploadedImages = [];437 let trainingInProgress = false;438 let trainingInterval = null;439 440 // Configuração da área de upload441 const uploadArea = document.getElementById('uploadArea');442 const fileInput = document.getElementById('imageFiles');443 const imagePreview = document.getElementById('imagePreview');444 445 uploadArea.addEventListener('click', () => fileInput.click());446 uploadArea.addEventListener('dragover', handleDragOver);447 uploadArea.addEventListener('drop', handleDrop);448 fileInput.addEventListener('change', handleFileSelect);449 450 function handleDragOver(e) {451 e.preventDefault();452 uploadArea.classList.add('dragover');453 }454 455 function handleDrop(e) {456 e.preventDefault();457 uploadArea.classList.remove('dragover');458 const files = Array.from(e.dataTransfer.files);459 processFiles(files);460 }461 462 function handleFileSelect(e) {463 const files = Array.from(e.target.files);464 processFiles(files);465 }466 467 function processFiles(files) {468 const imageFiles = files.filter(file => file.type.startsWith('image/'));469 470 imageFiles.forEach(file => {471 if (uploadedImages.length < 20) {472 const reader = new FileReader();473 reader.onload = (e) => {474 uploadedImages.push({475 file: file,476 url: e.target.result,477 name: file.name478 });479 updateImagePreview();480 updateImageAlert();481 };482 reader.readAsDataURL(file);483 }484 });485 }486 487 function updateImagePreview() {488 imagePreview.innerHTML = '';489 uploadedImages.forEach((image, index) => {490 const imageItem = document.createElement('div');491 imageItem.className = 'image-item';492 imageItem.innerHTML = `493 <img src="${image.url}" alt="${image.name}">494 <button class="remove-btn" onclick="removeImage(${index})">×</button>495 `;496 imagePreview.appendChild(imageItem);497 });498 }499 500 function removeImage(index) {501 uploadedImages.splice(index, 1);502 updateImagePreview();503 updateImageAlert();504 }505 506 function updateImageAlert() {507 const alert = document.getElementById('imageAlert');508 if (uploadedImages.length < 5) {509 alert.style.display = 'block';510 alert.className = 'alert alert-warning';511 alert.innerHTML = `<strong>Atenção:</strong> Você precisa fazer upload de pelo menos 5 imagens. Atualmente: ${uploadedImages.length} imagens.`;512 } else if (uploadedImages.length >= 5) {513 alert.style.display = 'block';514 alert.className = 'alert alert-success';515 alert.innerHTML = `<strong>Perfeito!</strong> ${uploadedImages.length} imagens carregadas. Pronto para treinar!`;516 }517 }518 519 async function startTraining() {520 // Validações521 if (uploadedImages.length < 5) {522 showError('Você precisa fazer upload de pelo menos 5 imagens.');523 return;524 }525 526 const characterName = document.getElementById('characterName').value.trim();527 const triggerWord = document.getElementById('triggerWord').value.trim();528 529 if (!characterName) {530 showError('Por favor, insira o nome do personagem.');531 return;532 }533 534 if (!triggerWord) {535 showError('Por favor, insira a palavra-chave (trigger).');536 return;537 }538 539 // Preparar dados para envio540 const formData = new FormData();541 uploadedImages.forEach((image, index) => {542 formData.append('images', image.file);543 });544 545 formData.append('character_name', characterName);546 formData.append('trigger_word', triggerWord);547 formData.append('resolution', document.getElementById('resolution').value);548 formData.append('learning_rate', document.getElementById('learningRate').value);549 formData.append('rank', document.getElementById('rank').value);550 formData.append('epochs', document.getElementById('epochs').value);551 formData.append('description', document.getElementById('description').value);552 553 // Iniciar treinamento554 trainingInProgress = true;555 updateTrainingUI();556 557 try {558 const response = await fetch('/api/train', {559 method: 'POST',560 body: formData561 });562 563 if (!response.ok) {564 throw new Error(`Erro HTTP: ${response.status}`);565 }566 567 const result = await response.json();568 569 if (result.success) {570 startProgressMonitoring(result.training_id);571 } else {572 throw new Error(result.message || 'Erro desconhecido');573 }574 575 } catch (error) {576 showError(`Erro ao iniciar treinamento: ${error.message}`);577 trainingInProgress = false;578 updateTrainingUI();579 }580 }581 582 function startProgressMonitoring(trainingId) {583 trainingInterval = setInterval(async () => {584 try {585 const response = await fetch(`/api/training-status/${trainingId}`);586 const status = await response.json();587 588 updateProgress(status.progress, status.message);589 updateLogs(status.logs);590 591 if (status.completed) {592 clearInterval(trainingInterval);593 trainingInProgress = false;594 updateTrainingUI();595 showTrainingComplete(status.download_links, status.trigger_word);596 } else if (status.error) {597 clearInterval(trainingInterval);598 trainingInProgress = false;599 updateTrainingUI();600 showError(status.error);601 }602 } catch (error) {603 console.error('Erro ao verificar status:', error);604 }605 }, 2000);606 }607 608 function stopTraining() {609 if (trainingInterval) {610 clearInterval(trainingInterval);611 }612 trainingInProgress = false;613 updateTrainingUI();614 showError('Treinamento interrompido pelo usuário.');615 }616 617 function updateTrainingUI() {618 const startBtn = document.getElementById('startTraining');619 const stopBtn = document.getElementById('stopTraining');620 const progressContainer = document.getElementById('progressContainer');621 const logContainer = document.getElementById('logContainer');622 623 if (trainingInProgress) {624 startBtn.style.display = 'none';625 stopBtn.style.display = 'inline-block';626 progressContainer.style.display = 'block';627 logContainer.style.display = 'block';628 } else {629 startBtn.style.display = 'inline-block';630 stopBtn.style.display = 'none';631 if (!document.getElementById('resultSection').style.display) {632 progressContainer.style.display = 'none';633 logContainer.style.display = 'none';634 }635 }636 }637 638 function updateProgress(progress, message) {639 const progressFill = document.getElementById('progressFill');640 const progressText = document.getElementById('progressText');641 642 progressFill.style.width = `${progress}%`;643 progressText.textContent = message || `Progresso: ${progress}%`;644 }645 646 function updateLogs(logs) {647 const logContainer = document.getElementById('trainingLogs');648 if (logs && logs.length > 0) {649 logContainer.innerHTML = logs.join('\n');650 logContainer.scrollTop = logContainer.scrollHeight;651 }652 }653 654 function showTrainingComplete(downloadLinks, triggerWord) {655 const resultSection = document.getElementById('resultSection');656 const downloadLinksContainer = document.getElementById('downloadLinks');657 const resultTriggerWord = document.getElementById('resultTriggerWord');658 659 resultSection.style.display = 'block';660 resultTriggerWord.textContent = triggerWord;661 662 downloadLinksContainer.innerHTML = '';663 downloadLinks.forEach(link => {664 const btn = document.createElement('a');665 btn.href = link.url;666 btn.className = 'btn btn-success';667 btn.textContent = `📥 ${link.name}`;668 btn.download = link.name;669 downloadLinksContainer.appendChild(btn);670 });671 672 document.getElementById('successAlert').style.display = 'block';673 674 // Scroll para a seção de resultados675 resultSection.scrollIntoView({ behavior: 'smooth' });676 }677 678 function showError(message) {679 const errorAlert = document.getElementById('errorAlert');680 const errorMessage = document.getElementById('errorMessage');681 682 errorMessage.textContent = message;683 errorAlert.style.display = 'block';684 685 setTimeout(() => {686 errorAlert.style.display = 'none';687 }, 5000);688 }689 690 // Inicialização691 updateImageAlert();692 </script>693</body>694</html>695 696 