Spaces:
Running
Running
<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> |