AnaliseLoRA / index.html
vcollos's picture
Update index.html
b50e079 verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analisador de Modelos Safetensors - Flux Edition</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f7fa;
}
h1, h2, h3 {
color: #1a202c;
margin-top: 20px;
margin-bottom: 10px;
}
h1 {
font-size: 28px;
text-align: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 2px solid #e2e8f0;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 8px;
}
h3 {
font-size: 20px;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 25px;
margin-bottom: 30px;
}
textarea {
width: 100%;
height: 150px;
padding: 12px;
border: 1px solid #cbd5e0;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
margin-bottom: 15px;
resize: vertical;
}
button {
background-color: #4299e1;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}
button:hover {
background-color: #3182ce;
}
.result {
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 4px;
padding: 20px;
margin-top: 20px;
}
.result ul {
padding-left: 20px;
margin-top: 10px;
margin-bottom: 10px;
}
.result li {
margin-bottom: 5px;
}
.result pre {
background-color: #edf2f7;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
.error {
color: #e53e3e;
background-color: #fff5f5;
border: 1px solid #fed7d7;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
}
.examples {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.example-btn {
background-color: #f1f5f9;
color: #1a202c;
border: 1px solid #cbd5e0;
font-size: 14px;
}
.example-btn:hover {
background-color: #e2e8f0;
}
.loading {
text-align: center;
margin: 20px 0;
font-style: italic;
color: #4a5568;
}
code {
background-color: #edf2f7;
padding: 2px 4px;
border-radius: 3px;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.flux-badge {
display: inline-block;
background-color: #805ad5;
color: white;
font-size: 14px;
padding: 4px 10px;
border-radius: 20px;
margin-left: 10px;
vertical-align: middle;
}
@media (max-width: 768px) {
.container {
padding: 15px;
}
button {
width: 100%;
}
.examples {
flex-direction: column;
}
}
</style>
</head>
<body>
<h1>Analisador de Modelos Safetensors <span class="flux-badge">Flux Edition</span></h1>
<div class="container">
<h2>Instruções</h2>
<p>
Este analisador é especializado em arquivos .safetensors do modelo Flux.1-dev e examina suas características para identificar compatibilidade para fusão.
Insira nomes de arquivos ou caminhos para seus modelos LoRA (um por linha) e clique em "Analisar".
</p>
<div class="examples">
<button class="example-btn" onclick="loadExample(1)">Exemplo 1: Flux LoRA Basic</button>
<button class="example-btn" onclick="loadExample(2)">Exemplo 2: Black Forest LoRAs</button>
<button class="example-btn" onclick="loadExample(3)">Exemplo 3: Múltiplos LoRAs</button>
</div>
<textarea id="input-urls" placeholder="Insira nomes de arquivos LoRA Flux.1-dev (um por linha)"></textarea>
<button id="analyze-btn" onclick="analyzeSafetensors()">ANALISAR</button>
<div id="loading" class="loading" style="display: none;">
Analisando modelos... Isso pode levar alguns minutos dependendo do tamanho dos arquivos.
</div>
<div id="result" class="result" style="display: none;"></div>
<div id="error" class="error" style="display: none;"></div>
</div>
<div class="container">
<h2>Como funciona com modelos Flux</h2>
<p>
O analisador de modelos Safetensors é especialmente otimizado para modelos Flux.1-dev Black Forest:
</p>
<ul>
<li><strong>Identificação automática</strong> de modelos treinados com Flux</li>
<li><strong>Rank LoRA</strong>: Normalmente 4, 8, 16 ou 32 para modelos Flux</li>
<li><strong>Alpha LoRA</strong>: Geralmente igual ao rank ou 2x o rank</li>
<li><strong>Triggers</strong>: Extrai triggers usados no treinamento quando disponíveis</li>
<li><strong>Compatibilidade</strong>: Agrupa modelos com ranks semelhantes</li>
</ul>
<p>
Esta ferramenta é especialmente útil para identificar modelos LoRA que podem ser fundidos, permitindo combinar vários personagens ou estilos em um único modelo, com cada trigger chamando uma característica específica.
</p>
</div>
<script>
function loadExample(num) {
if (num === 1) {
document.getElementById('input-urls').value = "flux_lora_basic.safetensors";
} else if (num === 2) {
document.getElementById('input-urls').value = "black_forest_character1.safetensors\nblack_forest_character2.safetensors";
} else if (num === 3) {
document.getElementById('input-urls').value = "flux_lora_rank8_char1.safetensors\nflux_lora_rank8_char2.safetensors\nflux_lora_rank8_style1.safetensors";
}
}
async function analyzeSafetensors() {
const inputUrls = document.getElementById('input-urls').value.trim();
const resultDiv = document.getElementById('result');
const errorDiv = document.getElementById('error');
const loadingDiv = document.getElementById('loading');
// Limpar resultados anteriores
resultDiv.style.display = 'none';
errorDiv.style.display = 'none';
if (!inputUrls) {
errorDiv.textContent = "Por favor, insira pelo menos um nome de arquivo.";
errorDiv.style.display = 'block';
return;
}
// Mostrar mensagem de carregamento
loadingDiv.style.display = 'block';
try {
// Em um aplicativo real, faríamos uma chamada de API aqui
// Como este é um exemplo estático, vamos simular a análise
setTimeout(() => {
const urls = inputUrls.split('\n').filter(url => url.trim());
let report = generateFluxReport(urls);
resultDiv.innerHTML = formatMarkdown(report);
resultDiv.style.display = 'block';
loadingDiv.style.display = 'none';
}, 1500);
} catch (error) {
errorDiv.textContent = "Erro ao analisar modelos: " + error.message;
errorDiv.style.display = 'block';
loadingDiv.style.display = 'none';
}
}
function generateFluxReport(urls) {
let report = "# Relatório de Análise de Modelos Flux.1-dev\n\n";
report += "## Detalhes dos Modelos\n\n";
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
report += `### Modelo ${i+1}: ${url}\n\n`;
// Gerar características de modelos Flux baseado no nome do arquivo
const isBlackForest = url.toLowerCase().includes("black_forest");
const isFlux = url.toLowerCase().includes("flux") || isBlackForest;
let rank = 8; // default
if (url.toLowerCase().includes("rank4")) rank = 4;
if (url.toLowerCase().includes("rank16")) rank = 16;
if (url.toLowerCase().includes("rank32")) rank = 32;
const alpha = rank * 2;
if (isFlux) {
report += "- **Tipo**: LoRA Flux\n";
report += `- **MD5**: ${generateMockMD5()}\n`;
report += `- **Tamanho**: ${30 + Math.floor(Math.random() * 20)}.${Math.floor(Math.random() * 99)} MB\n`;
report += `- **Rank LoRA**: ${rank}\n`;
report += `- **Alpha LoRA**: ${alpha}\n`;
report += "- **Modelo Base**: Flux.1-dev" + (isBlackForest ? " Black Forest" : "") + "\n";
// Extrair potencial trigger do nome
let triggerName = "";
if (url.toLowerCase().includes("char")) {
triggerName = "character" + url.match(/\d+/);
} else if (url.toLowerCase().includes("style")) {
triggerName = "style" + url.match(/\d+/);
}
report += "- **Metadados**:\n";
report += ` - ss_network_dim: ${rank}\n`;
report += ` - ss_network_alpha: ${alpha}\n`;
report += " - ss_training_model: Flux.1-dev" + (isBlackForest ? " Black Forest" : "") + "\n";
if (triggerName) {
report += ` - ss_trigger: ${triggerName}\n`;
}
} else {
report += "- **Tipo**: Desconhecido\n";
report += `- **MD5**: ${generateMockMD5()}\n`;
report += `- **Tamanho**: ${20 + Math.floor(Math.random() * 60)}.${Math.floor(Math.random() * 99)} MB\n`;
}
report += "\n";
}
// Agrupar por ranks e base model
let fluxRank4 = urls.filter(url =>
(url.toLowerCase().includes("flux") || url.toLowerCase().includes("black_forest")) &&
url.toLowerCase().includes("rank4")
);
let fluxRank8 = urls.filter(url =>
(url.toLowerCase().includes("flux") || url.toLowerCase().includes("black_forest")) &&
(!url.toLowerCase().includes("rank") || url.toLowerCase().includes("rank8"))
);
let fluxRank16 = urls.filter(url =>
(url.toLowerCase().includes("flux") || url.toLowerCase().includes("black_forest")) &&
url.toLowerCase().includes("rank16")
);
let fluxRank32 = urls.filter(url =>
(url.toLowerCase().includes("flux") || url.toLowerCase().includes("black_forest")) &&
url.toLowerCase().includes("rank32")
);
let blackForest = urls.filter(url => url.toLowerCase().includes("black_forest"));
report += "## Grupos de Modelos Compatíveis\n\n";
if (fluxRank4.length > 0) {
report += "### Grupo: Flux LoRA Rank 4\n\n";
report += "Modelos neste grupo que possivelmente podem ser fundidos:\n\n";
fluxRank4.forEach(url => {
report += `- ${url}\n`;
});
report += "\n";
}
if (fluxRank8.length > 0) {
report += "### Grupo: Flux LoRA Rank 8\n\n";
report += "Modelos neste grupo que possivelmente podem ser fundidos:\n\n";
fluxRank8.forEach(url => {
report += `- ${url}\n`;
});
report += "\n";
}
if (fluxRank16.length > 0) {
report += "### Grupo: Flux LoRA Rank 16\n\n";
report += "Modelos neste grupo que possivelmente podem ser fundidos:\n\n";
fluxRank16.forEach(url => {
report += `- ${url}\n`;
});
report += "\n";
}
if (fluxRank32.length > 0) {
report += "### Grupo: Flux LoRA Rank 32\n\n";
report += "Modelos neste grupo que possivelmente podem ser fundidos:\n\n";
fluxRank32.forEach(url => {
report += `- ${url}\n`;
});
report += "\n";
}
if (blackForest.length > 0) {
report += "### Grupo: Black Forest\n\n";
report += "Modelos Black Forest que possivelmente podem ser fundidos:\n\n";
blackForest.forEach(url => {
report += `- ${url}\n`;
});
report += "\n";
}
if (fluxRank4.length + fluxRank8.length + fluxRank16.length + fluxRank32.length + blackForest.length === 0) {
report += "Nenhum grupo compatível identificado.\n\n";
}
report += "\n## Recomendações para Fusão\n\n";
// Recomendações para ranks específicos
if (fluxRank8.length > 1) {
report += "### Modelos Flux LoRA Rank 8 que podem ser fundidos:\n\n";
report += `**Grupo Flux Rank 8** contém ${fluxRank8.length} modelos compatíveis.\n`;
report += "Comando sugerido para fusão:\n\n";
const modelArgs = fluxRank8.map((url, i) => `--model${i+1} "${url}"`).join(" ");
report += "```\npython merge_flux_lora.py " + modelArgs + " --output merged_flux_rank8.safetensors\n```\n\n";
}
if (blackForest.length > 1) {
report += "### Modelos Black Forest que podem ser fundidos:\n\n";
report += `**Grupo Black Forest** contém ${blackForest.length} modelos compatíveis.\n`;
report += "Comando sugerido para fusão:\n\n";
const modelArgs = blackForest.map((url, i) => `--model${i+1} "${url}"`).join(" ");
report += "```\npython merge_flux_lora.py " + modelArgs + " --output merged_black_forest.safetensors --base-model-path flux_black_forest\n```\n\n";
report += "Após a fusão, cada trigger específico chamará o personagem respectivo:\n\n";
blackForest.forEach(url => {
const match = url.match(/character\d+|char\d+|style\d+/i);
const trigger = match ? match[0] : "trigger" + Math.floor(Math.random() * 100);
report += `- Use \`${trigger}\` para ativar características de ${url}\n`;
});
report += "\n";
}
return report;
}
function generateMockMD5() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 32; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
function formatMarkdown(markdown) {
// Conversão mais robusta de markdown para HTML com tratamento adequado de listas
let html = markdown;
// Cabeçalhos
html = html.replace(/# (.*?)(?:\n|$)/g, '<h1>$1</h1>\n');
html = html.replace(/## (.*?)(?:\n|$)/g, '<h2>$1</h2>\n');
html = html.replace(/### (.*?)(?:\n|$)/g, '<h3>$1</h3>\n');
// Negrito
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Blocos de código
html = html.replace(/```\n([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
// Listas
// Primeiro, identificamos blocos de lista
let listBlocks = html.match(/(?:- .*\n)+/g);
if (listBlocks) {
for (let block of listBlocks) {
let listItems = block.match(/- (.*)\n/g);
if (listItems) {
let listHTML = '<ul>\n';
for (let item of listItems) {
let content = item.replace(/- (.*)\n/, '$1');
listHTML += ` <li>${content}</li>\n`;
}
listHTML += '</ul>';
html = html.replace(block, listHTML);
}
}
}
// Quebras de linha
html = html.replace(/\n\n/g, '<br><br>');
return html;
}
// Carregue o exemplo 2 por padrão ao iniciar
window.onload = function() {
loadExample(2);
}
</script>
</body>
</html>