diff --git a/docs/fix/js/main.js b/docs/fix/js/main.js
index 2c69886..96fe74f 100644
--- a/docs/fix/js/main.js
+++ b/docs/fix/js/main.js
@@ -24,9 +24,11 @@ const translations = {
noFilesSelected: "No files selected",
removeFile: "Remove",
zipOption: "Download as ZIP",
- processingFiles: "Processing {0}/{1} files...",
+ processingFiles: "Processing files: {0}/{1}",
successMultipleFiles: "Successfully processed {0} files",
- clearButton: "Clear All"
+ clearButton: "Clear All",
+ successSingleFile: "Successfully processed 1 file",
+ errorProcessing: "Error occurred while processing files"
},
zh: {
title: "ComfyUI 工作流修复工具",
@@ -52,9 +54,11 @@ const translations = {
noFilesSelected: "未选择文件",
removeFile: "移除",
zipOption: "以ZIP格式下载",
- processingFiles: "正在处理 {0}/{1} 个文件...",
- successMultipleFiles: "成功处理 {0} 个文件",
- clearButton: "清除全部"
+ processingFiles: "正在处理文件:{0}/{1}",
+ successMultipleFiles: "成功处理{0}个文件",
+ clearButton: "清除全部",
+ successSingleFile: "成功处理1个文件",
+ errorProcessing: "处理文件时发生错误"
}
};
@@ -70,6 +74,13 @@ document.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
updateHeaderImage();
initializePathOptions();
+
+ // 初始化按钮文本
+ const t = translations[currentLanguage];
+ const fixButton = document.getElementById('fixButton');
+ if (fixButton) {
+ fixButton.textContent = t.fixButton;
+ }
});
// 添加路径选项初始化函数
@@ -161,7 +172,13 @@ function updateLanguage() {
// 更新所有文本内容
document.getElementById('title').textContent = t.title;
document.getElementById('dragText').textContent = t.dragText;
- document.getElementById('fixButton').textContent = t.fixButton;
+
+ // 确保更新 Fix & Download 按钮文本
+ const fixButton = document.getElementById('fixButton');
+ if (fixButton) {
+ fixButton.textContent = t.fixButton;
+ }
+
document.getElementById('langBtn').textContent = t.switchLang;
document.getElementById('clearButton').textContent = t.clearButton;
document.getElementById('zipLabel').textContent = t.zipOption;
@@ -244,6 +261,10 @@ function updateFileList() {
clearButton.disabled = true;
document.getElementById('fixButton').disabled = true;
filesContainer.classList.add('no-scroll');
+
+ // 清除错误和成功消息
+ document.getElementById('error').textContent = '';
+ document.getElementById('success').textContent = '';
return;
}
@@ -274,8 +295,24 @@ function clearFiles() {
selectedFiles = [];
updateFileList();
document.getElementById('fileInput').value = '';
+
+ // 清除错误和成功消息
document.getElementById('error').textContent = '';
document.getElementById('success').textContent = '';
+
+ // 禁用相关按钮
+ document.getElementById('clearButton').disabled = true;
+ document.getElementById('fixButton').disabled = true;
+
+ // 重置文件拖放区域的状态
+ const dropZone = document.getElementById('dropZone');
+ dropZone.classList.remove('dragover');
+
+ // 更新文件列表显示
+ const t = translations[currentLanguage];
+ const filesContainer = document.getElementById('selectedFiles');
+ filesContainer.innerHTML = `
${t.noFilesSelected}
`;
+ filesContainer.classList.add('no-scroll');
}
// UI更新函数
@@ -289,4 +326,288 @@ function updateUI() {
const fixType = document.getElementById('fixType').value;
updateDescription();
document.getElementById('pathOptions').classList.toggle('show', fixType === 'path');
+}
+
+// 添加下载功能
+async function fixAndDownload() {
+ const t = translations[currentLanguage];
+ const fixType = document.getElementById('fixType').value;
+ const useZip = document.getElementById('zipDownload').checked;
+ const error = document.getElementById('error');
+ const success = document.getElementById('success');
+
+ error.textContent = '';
+ success.textContent = '';
+
+ if (selectedFiles.length === 0) {
+ error.textContent = t.errorSelectFile;
+ return;
+ }
+
+ try {
+ if (useZip) {
+ // ZIP下载逻辑保持不变
+ const zip = new JSZip();
+
+ for (let i = 0; i < selectedFiles.length; i++) {
+ const file = selectedFiles[i];
+ const content = await file.text();
+ const fixedContent = fixWorkflowContent(content, fixType);
+ const fileName = file.name.replace('.json', '_fixed.json');
+ zip.file(fileName, fixedContent);
+ }
+
+ const blob = await zip.generateAsync({type: 'blob'});
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'fixed_workflows.zip';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ success.textContent = t.successMultipleFiles.replace('{0}', selectedFiles.length);
+ } else {
+ // 修改后的单文件处理逻辑
+ let processedCount = 0;
+ const totalFiles = selectedFiles.length;
+ const fixedFiles = [];
+
+ // 首先处理所有文件
+ for (const file of selectedFiles) {
+ try {
+ const content = await file.text();
+ const fixedContent = fixWorkflowContent(content, fixType);
+ const fileName = file.name.replace('.json', '_fixed.json');
+ fixedFiles.push({
+ name: fileName,
+ content: fixedContent
+ });
+ processedCount++;
+ success.textContent = t.processingFiles
+ .replace('{0}', processedCount)
+ .replace('{1}', totalFiles);
+ } catch (err) {
+ console.error(`Error processing ${file.name}:`, err);
+ }
+ }
+
+ // 然后逐个下载处理好的文件
+ for (let i = 0; i < fixedFiles.length; i++) {
+ const file = fixedFiles[i];
+ const blob = new Blob([file.content], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+
+ await new Promise(resolve => {
+ setTimeout(() => {
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = file.name;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ resolve();
+ }, i * 800); // 增加间隔时间到800ms,确保文件能够正确下载
+ });
+ }
+
+ // 更新最终状态
+ if (processedCount > 0) {
+ success.textContent = processedCount === 1
+ ? t.successSingleFile
+ : t.successMultipleFiles.replace('{0}', processedCount);
+ }
+ }
+ } catch (err) {
+ error.textContent = t.errorProcessing;
+ console.error(err);
+ }
+}
+
+// 定义需要进行路径转换的节点类型
+const PATH_CONVERSION_NODES = new Set([
+ "CheckpointLoaderSimple",
+ "LoraLoader",
+ "ControlNetLoader",
+ "UNETLoader",
+ "VAELoader",
+ "CLIPLoader",
+ "DiffControlNetLoader",
+ "ModelLoader",
+ "LoadDiffusionModel",
+ "CheckpointLoader",
+ "DiffusersLoader",
+ "LoraLoaderModelOnly",
+ "VAEDecode",
+ "VAEEncode",
+ "CLIPVisionLoader",
+ "StyleModelLoader",
+ "UpscaleModelLoader",
+ "Load LoRA",
+ "Lora List",
+ "LoRA Stacker",
+ "GridLoras",
+ "CheckpointLoader|pysssss",
+ "LoraLoader|pysssss",
+ "LoraSave",
+ "DualCLIPLoader",
+ "DualUNETLoader",
+ "DualVAELoader",
+ "DualCLIPVisionLoader",
+ "DualStyleModelLoader",
+ "DualUpscaleModelLoader"
+]);
+
+function fixWorkflowContent(content, fixType) {
+ const workflow = JSON.parse(content);
+
+ if (fixType === 'namespace') {
+ // 遍历所有节点
+ for (const nodeId in workflow.nodes) {
+ const node = workflow.nodes[nodeId];
+ if (node && node.type) {
+ // 只修改 nodeMapping 中定义的节点
+ if (nodeMapping[node.type]) {
+ node.type = nodeMapping[node.type];
+ }
+ }
+ }
+ } else if (fixType === 'path') {
+ // 获取选中的路径类型
+ const pathType = document.querySelector('input[name="pathType"]:checked').value;
+ let conversionDirection = pathType;
+
+ // 处理自动检测
+ if (pathType === 'auto') {
+ const currentFormat = detectPathFormat(workflow);
+ conversionDirection = currentFormat === 'unix' ? 'toWindows' : 'toUnix';
+ }
+
+ // 遍历所有节点
+ for (const nodeId in workflow.nodes) {
+ const node = workflow.nodes[nodeId];
+ const nodeType = node.type || node.class_type;
+
+ // 如果是需要处理的节点类型
+ if (PATH_CONVERSION_NODES.has(nodeType) && node.widgets_values) {
+ node.widgets_values = node.widgets_values.map(value => {
+ if (typeof value === 'string' && !value.includes('base64')) {
+ // 转换路径分隔符
+ if (conversionDirection === 'toUnix') {
+ return value.replace(/\\/g, '/');
+ } else if (conversionDirection === 'toWindows') {
+ return value.replace(/\//g, '\\');
+ }
+ } else if (typeof value === 'object' && value?.content) {
+ // 处理特殊格式
+ if (!value.content.includes('base64')) {
+ const newContent = conversionDirection === 'toUnix'
+ ? value.content.replace(/\\/g, '/')
+ : value.content.replace(/\//g, '\\');
+ return { ...value, content: newContent };
+ }
+ }
+ return value;
+ });
+ }
+ }
+ }
+
+ return JSON.stringify(workflow, null, 2);
+}
+
+// 检测路径格式
+function detectPathFormat(workflow) {
+ let unixCount = 0;
+ let windowsCount = 0;
+
+ for (const nodeId in workflow.nodes) {
+ const node = workflow.nodes[nodeId];
+ const nodeType = node.type || node.class_type;
+
+ if (PATH_CONVERSION_NODES.has(nodeType) && node.widgets_values) {
+ node.widgets_values.forEach(value => {
+ if (typeof value === 'string' && !value.includes('base64')) {
+ const unixSlashes = (value.match(/\//g) || []).length;
+ const windowsSlashes = (value.match(/\\/g) || []).length;
+
+ if (unixSlashes > windowsSlashes) {
+ unixCount++;
+ } else if (windowsSlashes > unixSlashes) {
+ windowsCount++;
+ }
+ } else if (typeof value === 'object' && value?.content) {
+ if (!value.content.includes('base64')) {
+ const unixSlashes = (value.content.match(/\//g) || []).length;
+ const windowsSlashes = (value.content.match(/\\/g) || []).length;
+
+ if (unixSlashes > windowsSlashes) {
+ unixCount++;
+ } else if (windowsSlashes > unixSlashes) {
+ windowsCount++;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ return unixCount > windowsCount ? 'unix' : 'windows';
+}
+
+// 更新 nodeMapping 对象
+const nodeMapping = {
+ "MaskPreview": "PaintingCoder::MaskPreview",
+ "ImageSizeCreator": "PaintingCoder::ImageSizeCreator",
+ "ImageToBase64": "PaintingCoder::ImageToBase64",
+ "ImageLatentCreator": "PaintingCoder::ImageLatentCreator",
+ "DynamicImageCombiner": "PaintingCoder::DynamicImageCombiner",
+ "DynamicMaskCombiner": "PaintingCoder::DynamicMaskCombiner",
+ "ImageResolutionAdjuster": "PaintingCoder::ImageResolutionAdjuster",
+ "TextCombiner": "PaintingCoder::TextCombiner",
+ "ShowTextPlus": "PaintingCoder::ShowTextPlus",
+ "SimpleTextInput": "PaintingCoder::SimpleTextInput",
+ "MultilineTextInput": "PaintingCoder::MultilineTextInput",
+ "RemoveEmptyLinesAndLeadingSpaces": "PaintingCoder::RemoveEmptyLinesAndLeadingSpaces",
+ "ImageSwitch": "PaintingCoder::ImageSwitch",
+ "TextSwitch": "PaintingCoder::TextSwitch",
+ "MaskSwitch": "PaintingCoder::MaskSwitch",
+ "LatentSwitch": "PaintingCoder::LatentSwitch",
+ "WebImageLoader": "PaintingCoder::WebImageLoader",
+ "MaskPreview+": "PaintingCoder::MaskPreview"
+};
+
+function isPaintingCoderNode(type) {
+ // PaintingCoder 节点的类名前缀列表
+ const paintingCoderPrefixes = [
+ 'Prompt',
+ 'ControlNet',
+ 'Model',
+ 'VAE',
+ 'CLIP',
+ 'Lora',
+ 'Checkpoint',
+ 'UNET',
+ 'Save',
+ 'Preview',
+ 'KSampler',
+ 'EmptyLatent',
+ 'Conditioning',
+ 'Latent',
+ 'Image',
+ 'Text',
+ 'Mask',
+ 'Dynamic',
+ 'Simple',
+ 'Show',
+ 'Web'
+ ];
+
+ // 检查类名是否以任一前缀开头
+ return paintingCoderPrefixes.some(prefix =>
+ type.startsWith(prefix) ||
+ type.startsWith('PaintingCoder::' + prefix)
+ );
}
\ No newline at end of file
diff --git a/docs/fix/workflow_fixer.html b/docs/fix/workflow_fixer.html
index aaa4a98..ca84679 100644
--- a/docs/fix/workflow_fixer.html
+++ b/docs/fix/workflow_fixer.html
@@ -80,7 +80,7 @@
- Version 0.2.2
+ Version 0.2.3