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> |