1241 lines
46 KiB
HTML
1241 lines
46 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Workflow Fixer</title>
|
|
<style>
|
|
/* 添加主题变量 */
|
|
:root {
|
|
/* Light theme */
|
|
--bg-color: #ffffff;
|
|
--text-color: #333333;
|
|
--border-color: #dddddd;
|
|
--container-bg: #f5f5f5;
|
|
--error-color: #ff4444;
|
|
--success-color: #44bb44;
|
|
--hover-bg: #e8e8e8;
|
|
--button-bg: #4a90e2;
|
|
--button-text: #ffffff;
|
|
--button-hover: #357abd;
|
|
--dropzone-border: #cccccc;
|
|
--dropzone-bg: #ffffff;
|
|
--file-item-bg: #ffffff;
|
|
}
|
|
|
|
/* Dark theme */
|
|
[data-theme="dark"] {
|
|
--bg-color: #1a1a1a;
|
|
--text-color: #ffffff;
|
|
--border-color: #444444;
|
|
--container-bg: #2d2d2d;
|
|
--error-color: #ff6666;
|
|
--success-color: #66cc66;
|
|
--hover-bg: #3d3d3d;
|
|
--button-bg: #4a90e2;
|
|
--button-text: #ffffff;
|
|
--button-hover: #357abd;
|
|
--dropzone-border: #444444;
|
|
--dropzone-bg: #2d2d2d;
|
|
--file-item-bg: #363636;
|
|
}
|
|
|
|
/* 更新现有样式使用变量 */
|
|
body {
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
font-family: Arial, sans-serif;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
background-color: var(--container-bg);
|
|
color: var(--text-color);
|
|
padding: 20px;
|
|
border-radius: 0 0 8px 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
margin: 0 auto;
|
|
max-width: 800px;
|
|
}
|
|
.file-input {
|
|
margin: 20px 0;
|
|
}
|
|
.file-input input[type="file"] {
|
|
display: none;
|
|
}
|
|
.file-input label {
|
|
display: block;
|
|
margin-bottom: 10px;
|
|
color: var(--text-color);
|
|
}
|
|
.file-input label:hover {
|
|
background-color: var(--hover-bg);
|
|
}
|
|
button {
|
|
background-color: var(--button-bg);
|
|
color: var(--button-text);
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
margin: 5px;
|
|
}
|
|
button:hover:not(:disabled) {
|
|
background-color: var(--button-hover);
|
|
}
|
|
button:disabled {
|
|
background-color: var(--border-color);
|
|
cursor: not-allowed;
|
|
}
|
|
.error {
|
|
color: var(--error-color);
|
|
margin: 10px 0;
|
|
}
|
|
.success {
|
|
color: var(--success-color);
|
|
margin: 10px 0;
|
|
}
|
|
.file-name {
|
|
margin: 10px 0;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
/* 更新语言切换按钮样式 */
|
|
.lang-switch {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 100;
|
|
}
|
|
|
|
.lang-btn {
|
|
background-color: rgba(0, 0, 0, 0.6); /* 深色半透明背景 */
|
|
color: #FFFFFF;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 4px;
|
|
padding: 8px 16px;
|
|
font-size: 0.9em;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.lang-btn:hover {
|
|
background-color: rgba(0, 0, 0, 0.8); /* 悬停时更深的背景色 */
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* 悬停时增强阴影 */
|
|
}
|
|
|
|
/* 添加下拉菜单样式 */
|
|
.fix-type-select {
|
|
margin: 20px 0;
|
|
width: 100%;
|
|
}
|
|
|
|
select {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--border-color);
|
|
background-color: var(--dropzone-bg);
|
|
color: var(--text-color);
|
|
font-size: 16px;
|
|
}
|
|
|
|
select:focus {
|
|
outline: none;
|
|
border-color: var(--button-bg);
|
|
}
|
|
|
|
/* 添加路径转换选项样式 */
|
|
.path-options {
|
|
margin: 15px 0;
|
|
padding: 10px;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
display: none; /* 默认隐藏 */
|
|
background-color: var(--container-bg);
|
|
}
|
|
|
|
.path-options.show {
|
|
display: block;
|
|
}
|
|
|
|
.path-option {
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.path-option label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
margin-right: 20px;
|
|
}
|
|
|
|
.path-option input[type="radio"] {
|
|
margin-right: 8px;
|
|
}
|
|
|
|
/* 修改头图容器样式 */
|
|
.header-image {
|
|
text-align: center;
|
|
margin: 0 auto;
|
|
max-width: 800px;
|
|
padding: 8px; /* 添加6像素的填充 */
|
|
position: relative;
|
|
}
|
|
|
|
.header-image img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 8px 8px 0 0;
|
|
box-shadow: 0 -2px 4px rgba(0,0,0,0.1);
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.header-image a {
|
|
display: block;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.header-image a:hover img {
|
|
opacity: 0.9;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
/* 整体包装容器 */
|
|
.wrapper {
|
|
position: relative;
|
|
background-color: var(--container-bg);
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 0px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
/* 更新拖拽区域样式 */
|
|
.file-drop-zone {
|
|
border: 2px dashed #4CAF50; /* 绿色边框 */
|
|
border-radius: 4px;
|
|
padding: 30px;
|
|
text-align: center;
|
|
background-color: rgba(76, 175, 80, 0.1); /* 浅绿色背景 */
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.file-drop-zone:hover {
|
|
border-color: #45a049; /* 深一点的绿色 */
|
|
background-color: rgba(76, 175, 80, 0.2); /* 更深的浅绿色 */
|
|
}
|
|
|
|
.file-drop-zone.dragover {
|
|
border-color: #45a049;
|
|
background-color: rgba(76, 175, 80, 0.25); /* 拖拽时的背景色 */
|
|
transform: scale(1.01); /* 轻微放大效果 */
|
|
}
|
|
|
|
#dragText {
|
|
font-size: 16px;
|
|
color: var(--text-color);
|
|
margin: 0;
|
|
}
|
|
|
|
.selected-files {
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.file-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 8px;
|
|
margin: 5px 0;
|
|
background-color: var(--file-item-bg);
|
|
border-radius: 4px;
|
|
transition: all 0.3s ease;
|
|
color: var(--text-color);
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.file-item:hover {
|
|
background-color: var(--hover-bg);
|
|
}
|
|
|
|
.file-name {
|
|
flex-grow: 1;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.delete-btn {
|
|
cursor: pointer;
|
|
color: var(--text-color);
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.delete-btn:hover {
|
|
color: var(--error-color);
|
|
background-color: rgba(255, 68, 68, 0.1);
|
|
}
|
|
|
|
/* 添加平滑过渡效果 */
|
|
* {
|
|
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
|
}
|
|
|
|
/* 版本号基础样式 */
|
|
.version-tag {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
right: 40px;
|
|
color: #FFFFFF;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
font-style: italic;
|
|
font-weight: 300;
|
|
font-size: 1.8em;
|
|
letter-spacing: 1px;
|
|
opacity: 0.8;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
/* 添加移动端适配 */
|
|
@media screen and (max-width: 768px) {
|
|
.version-tag {
|
|
bottom: 16px; /* 仅上移10像素 */
|
|
right: 20px; /* 减小右侧间距 */
|
|
font-size: 1.2em; /* 减小字体大小 */
|
|
}
|
|
}
|
|
|
|
/* 适配更小屏幕的设备 */
|
|
@media screen and (max-width: 480px) {
|
|
.version-tag {
|
|
bottom: 16px; /* 保持与平板端相同的上移距离 */
|
|
right: 15px; /* 稍微减小右侧间距 */
|
|
font-size: 1em; /* 进一步减小字体大小 */
|
|
}
|
|
}
|
|
|
|
/* 更新勾选框容器样式 */
|
|
.zip-option {
|
|
display: inline-block;
|
|
margin-left: 10px;
|
|
vertical-align: middle; /* 垂直居中对齐 */
|
|
}
|
|
|
|
.zip-checkbox {
|
|
width: 16px;
|
|
height: 16px;
|
|
cursor: pointer;
|
|
vertical-align: middle; /* 垂直居中对齐 */
|
|
margin: 0; /* 移除默认边距 */
|
|
position: relative;
|
|
top: -1px; /* 微调位置 */
|
|
}
|
|
|
|
.zip-label {
|
|
color: var(--text-color);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
vertical-align: middle; /* 垂直居中对齐 */
|
|
margin-left: 4px; /* 添加文字间距 */
|
|
}
|
|
|
|
/* 添加清除按钮样式 */
|
|
.clear-btn {
|
|
background-color: var(--error-color);
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
margin: 5px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.clear-btn:hover {
|
|
background-color: #ff6666; /* 稍微亮一点的红色 */
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.clear-btn:disabled {
|
|
background-color: var(--border-color);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* 按钮容器样式 */
|
|
.button-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
/* 主题切换按钮样式 */
|
|
.theme-switch {
|
|
position: absolute;
|
|
top: 40px;
|
|
right: 85px; /* 位于语言按钮左侧 */
|
|
z-index: 100;
|
|
}
|
|
|
|
.theme-btn {
|
|
background-color: var(--button-bg);
|
|
color: var(--button-text);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
width: 36px;
|
|
height: 36px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.3s ease;
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.theme-btn:hover {
|
|
background-color: var(--button-hover);
|
|
border-color: var(--border-color);
|
|
}
|
|
|
|
.theme-btn svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.theme-btn:hover svg {
|
|
transform: rotate(15deg);
|
|
}
|
|
|
|
/* 移动端适配 */
|
|
@media screen and (max-width: 768px) {
|
|
.theme-switch {
|
|
top: 30px;
|
|
right: 70px;
|
|
}
|
|
|
|
.theme-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.theme-btn svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
}
|
|
|
|
/* 统一头部按钮样式 */
|
|
.header-controls {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
z-index: 100;
|
|
}
|
|
|
|
.header-btn {
|
|
background-color: rgba(0, 0, 0, 0.6);
|
|
color: #FFFFFF;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
}
|
|
|
|
/* 主题切换按钮特定样式 */
|
|
.theme-btn {
|
|
width: 36px;
|
|
height: 36px;
|
|
padding: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* 语言切换按钮特定样式 */
|
|
.lang-btn {
|
|
padding: 8px 16px;
|
|
height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
/* 统一悬停效果 */
|
|
.header-btn:hover {
|
|
background-color: rgba(0, 0, 0, 0.8);
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
/* 图标样式 */
|
|
.theme-icon {
|
|
width: 20px;
|
|
height: 20px;
|
|
stroke: currentColor;
|
|
stroke-width: 2;
|
|
fill: none;
|
|
display: none;
|
|
}
|
|
|
|
.theme-icon.active {
|
|
display: block;
|
|
}
|
|
|
|
/* 移动端适配 */
|
|
@media screen and (max-width: 768px) {
|
|
.header-controls {
|
|
top: 12px;
|
|
right: 12px;
|
|
gap: 8px;
|
|
}
|
|
|
|
.theme-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.lang-btn {
|
|
height: 32px;
|
|
padding: 6px 12px;
|
|
}
|
|
|
|
.theme-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
}
|
|
|
|
/* 更小屏幕的适配 */
|
|
@media screen and (max-width: 480px) {
|
|
.header-controls {
|
|
top: 20px; /* 保持与平板相同 */
|
|
right: 20px; /* 保持与平板相同 */
|
|
gap: 6px; /* 进一步减小间距 */
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrapper">
|
|
<!-- 头图容器 -->
|
|
<div class="header-image">
|
|
<a href="https://github.com/jammyfu/ComfyUI_PaintingCoderUtils" target="_blank" title="Visit PaintingCoder on GitHub">
|
|
<img src="../images/workflow_fix_top_banner01.jpg" alt="Painting Coder Logo">
|
|
</a>
|
|
<span class="version-tag">version 0.2.2</span>
|
|
<div class="header-controls">
|
|
<button class="header-btn theme-btn" onclick="toggleTheme()" title="Toggle theme">
|
|
<!-- 月亮图标 -->
|
|
<svg class="theme-icon moon-icon" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
|
</svg>
|
|
<!-- 太阳图标 -->
|
|
<svg class="theme-icon sun-icon" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
|
</svg>
|
|
</button>
|
|
<button class="header-btn lang-btn" onclick="toggleLanguage()" id="langBtn">中文</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主内容容器 -->
|
|
<div class="container">
|
|
<h1 id="title">PaintingCoder Workflow Fixer</h1>
|
|
<p id="description">This tool helps fix old workflow JSON files by adding the PaintingCoder namespace to node types.</p>
|
|
|
|
<!-- 添加功能选择下拉菜单 -->
|
|
<div class="fix-type-select">
|
|
<select id="fixType" onchange="updateUI()">
|
|
<option value="namespace">Fix PaintingCoder old version (<0.3.0) workflow</option>
|
|
<option value="path">Fix Path Separator</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- 添加路径转换选项 -->
|
|
<div id="pathOptions" class="path-options">
|
|
<div class="path-option">
|
|
<label>
|
|
<input type="radio" name="pathType" value="auto" checked>
|
|
<span id="autoLabel">Auto Detect</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="pathType" value="toWindows">
|
|
<span id="toWindowsLabel">To Windows Format</span>
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="pathType" value="toUnix">
|
|
<span id="toUnixLabel">To Unix Format (Linux/Mac)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="file-input">
|
|
<div id="dropZone" class="file-drop-zone">
|
|
<div id="dragText">Choose Workflow File or drag and drop file here</div>
|
|
<input type="file" id="fileInput" accept=".json" multiple onchange="handleFileSelect(event)">
|
|
</div>
|
|
<div id="selectedFiles" class="selected-files"></div>
|
|
</div>
|
|
|
|
<div class="button-container">
|
|
<button id="fixButton" onclick="fixAndDownload()" disabled>Fix & Download</button>
|
|
<button id="clearButton" onclick="clearFiles()" class="clear-btn" disabled>Clear All</button>
|
|
<div class="zip-option">
|
|
<input type="checkbox" id="zipDownload" class="zip-checkbox">
|
|
<label for="zipDownload" class="zip-label" id="zipLabel">Download as ZIP</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="error" class="error"></div>
|
|
<div id="success" class="success"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 页脚 -->
|
|
<footer style="text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid var(--border-color);">
|
|
<p style="margin-bottom: 5px;">© 2025 jammyfu. All rights reserved.</p>
|
|
</footer>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
|
<script>
|
|
// 在现有代码前添加
|
|
window.onload = function() {
|
|
// 获取URL参数
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const lang = urlParams.get('lang');
|
|
|
|
// 如果有语言参数,设置对应语言
|
|
if (lang && (lang === 'zh' || lang === 'en')) {
|
|
currentLanguage = lang;
|
|
updateLanguage();
|
|
} else {
|
|
// 如果没有语言参数,根据当前语言设置URL
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('lang', currentLanguage);
|
|
window.history.replaceState({}, '', url.toString());
|
|
}
|
|
}
|
|
|
|
// 语言配置
|
|
const translations = {
|
|
en: {
|
|
title: "PaintingCoder Workflow Fixer",
|
|
description: {
|
|
namespace: "This tool helps fix old workflow JSON files by adding the PaintingCoder namespace to node types.",
|
|
path: "This tool helps convert path separators between Windows (\\) and Unix-style (Linux/Mac) (/) formats."
|
|
},
|
|
dragText: "Choose Workflow File or drag and drop file here",
|
|
fixButton: "Fix & Download",
|
|
selectedFile: "Selected file: ",
|
|
errorSelectFile: "Please select a file first.",
|
|
successMessage: "File fixed and saved as ",
|
|
switchLang: "中文",
|
|
fixTypeNamespace: "Fix PaintingCoder old version (<0.3.0) workflow",
|
|
fixTypePath: "Fix Path Separator",
|
|
pathOptions: {
|
|
auto: "Auto Detect",
|
|
toWindows: "To Windows Format (\\)",
|
|
toUnix: "To Unix Format (Linux/Mac)"
|
|
},
|
|
errorJsonFile: "Please select a JSON file.",
|
|
selectedFiles: "Selected files:",
|
|
noFilesSelected: "No files selected",
|
|
removeFile: "Remove",
|
|
zipOption: "Download as ZIP",
|
|
processingFiles: "Processing {0}/{1} files...",
|
|
successMultipleFiles: "Successfully processed {0} files",
|
|
clearButton: "Clear All"
|
|
},
|
|
zh: {
|
|
title: "PaintingCoder 工作流修复工具",
|
|
description: {
|
|
namespace: "此工具可以通过添加 PaintingCoder 命名空间来修复旧的工作流 JSON 文件。",
|
|
path: "此工具可以在 Windows (\\) 和 Unix (Linux/Mac) (/) 格式之间转换路径分隔符。"
|
|
},
|
|
dragText: "选择工作流文件或将文件拖放到此处",
|
|
fixButton: "修复并下载",
|
|
selectedFile: "已选择文件:",
|
|
errorSelectFile: "请先选择一个文件。",
|
|
successMessage: "文件已修复并保存为 ",
|
|
switchLang: "English",
|
|
fixTypeNamespace: "修复PaintingCoder旧版本(<0.3.0)工作流",
|
|
fixTypePath: "修复路径分隔符",
|
|
pathOptions: {
|
|
auto: "自动检测",
|
|
toWindows: "转换为 Windows 格式 (\\)",
|
|
toUnix: "转换为 Unix 格式 (Linux/Mac) (/)"
|
|
},
|
|
errorJsonFile: "请选择JSON文件。",
|
|
selectedFiles: "已选择文件:",
|
|
noFilesSelected: "未选择文件",
|
|
removeFile: "移除",
|
|
zipOption: "以ZIP格式下载",
|
|
processingFiles: "正在处理 {0}/{1} 个文件...",
|
|
successMultipleFiles: "成功处理 {0} 个文件",
|
|
clearButton: "清除全部"
|
|
}
|
|
};
|
|
|
|
let currentLanguage = 'en';
|
|
|
|
function toggleLanguage() {
|
|
// 切换语言
|
|
currentLanguage = currentLanguage === 'en' ? 'zh' : 'en';
|
|
|
|
// 更新 URL 参数
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('lang', currentLanguage);
|
|
window.history.replaceState({}, '', url.toString());
|
|
|
|
// 更新页面语言
|
|
updateLanguage();
|
|
}
|
|
|
|
function updateLanguage() {
|
|
const t = translations[currentLanguage];
|
|
|
|
document.getElementById('title').textContent = t.title;
|
|
updateDescription();
|
|
document.getElementById('dragText').textContent = t.dragText;
|
|
document.getElementById('fixButton').textContent = t.fixButton;
|
|
document.getElementById('langBtn').textContent = t.switchLang;
|
|
|
|
// 更新下拉菜单选项
|
|
const select = document.getElementById('fixType');
|
|
select.options[0].text = t.fixTypeNamespace;
|
|
select.options[1].text = t.fixTypePath;
|
|
|
|
// 更新动态内容
|
|
if (currentFile) {
|
|
document.getElementById('fileName').textContent =
|
|
`${t.selectedFile}${currentFile.name}`;
|
|
}
|
|
|
|
// 更新错误和成功消息
|
|
const errorEl = document.getElementById('error');
|
|
if (errorEl.textContent === translations.en.errorSelectFile) {
|
|
errorEl.textContent = t.errorSelectFile;
|
|
} else if (errorEl.textContent === translations.en.errorJsonFile) {
|
|
errorEl.textContent = t.errorJsonFile;
|
|
}
|
|
|
|
const successEl = document.getElementById('success');
|
|
if (successEl.textContent.startsWith(translations.en.successMessage)) {
|
|
const fileName = successEl.textContent.replace(translations.en.successMessage, '');
|
|
successEl.textContent = t.successMessage + fileName;
|
|
}
|
|
|
|
// 更新路径选项文本
|
|
document.getElementById('autoLabel').textContent = t.pathOptions.auto;
|
|
document.getElementById('toWindowsLabel').textContent = t.pathOptions.toWindows;
|
|
document.getElementById('toUnixLabel').textContent = t.pathOptions.toUnix;
|
|
|
|
updateFileList(); // 更新文件列表的语言
|
|
|
|
// 更新头图
|
|
const headerImage = document.querySelector('.header-image img');
|
|
if (currentLanguage === 'zh') {
|
|
headerImage.src = "../images/workflow_fix_top_banner02.jpg";
|
|
} else {
|
|
headerImage.src = "../images/workflow_fix_top_banner01.jpg";
|
|
}
|
|
|
|
document.getElementById('zipLabel').textContent = t.zipOption;
|
|
document.getElementById('clearButton').textContent = t.clearButton;
|
|
}
|
|
|
|
// 更新节点映射
|
|
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",
|
|
|
|
};
|
|
|
|
let currentFile = null;
|
|
let selectedFiles = []; // 存储选中的文件
|
|
|
|
// 添加拖拽相关函数
|
|
const dropZone = document.getElementById('dropZone');
|
|
const dragText = document.getElementById('dragText');
|
|
|
|
// 添加拖拽事件处理
|
|
function handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
dropZone.classList.add('dragover');
|
|
}
|
|
|
|
function handleDragLeave(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
dropZone.classList.remove('dragover');
|
|
}
|
|
|
|
function handleDrop(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
dropZone.classList.remove('dragover');
|
|
|
|
const files = e.dataTransfer.files;
|
|
for (let file of files) {
|
|
if (file.name.endsWith('.json')) {
|
|
// 检查是否已存在相同文件
|
|
if (!selectedFiles.some(f => f.name === file.name)) {
|
|
selectedFiles.push(file);
|
|
}
|
|
} else {
|
|
const t = translations[currentLanguage];
|
|
document.getElementById('error').textContent = t.errorJsonFile;
|
|
}
|
|
}
|
|
updateFileList();
|
|
}
|
|
|
|
// 统一处理文件的函数
|
|
function handleFile(file) {
|
|
currentFile = file;
|
|
const t = translations[currentLanguage];
|
|
document.getElementById('fileName').textContent = `${t.selectedFile}${file.name}`;
|
|
document.getElementById('fixButton').disabled = false;
|
|
document.getElementById('error').textContent = '';
|
|
document.getElementById('success').textContent = '';
|
|
}
|
|
|
|
// 修改文件选择处理函数
|
|
function handleFileSelect(event) {
|
|
const files = event.target.files;
|
|
for (let file of files) {
|
|
if (file.name.endsWith('.json')) {
|
|
// 检查是否已存在相同文件
|
|
if (!selectedFiles.some(f => f.name === file.name)) {
|
|
selectedFiles.push(file);
|
|
}
|
|
} else {
|
|
const t = translations[currentLanguage];
|
|
document.getElementById('error').textContent = t.errorJsonFile;
|
|
}
|
|
}
|
|
updateFileList();
|
|
event.target.value = ''; // 清空input,允许重复选择相同文件
|
|
}
|
|
|
|
// 修改点击事件处理
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const dropZone = document.getElementById('dropZone');
|
|
const fileInput = document.getElementById('fileInput');
|
|
|
|
// 整个虚线框区域点击触发文件选择
|
|
dropZone.addEventListener('click', function() {
|
|
fileInput.click();
|
|
});
|
|
|
|
// 拖拽事件监听
|
|
dropZone.addEventListener('dragover', handleDragOver);
|
|
dropZone.addEventListener('dragleave', handleDragLeave);
|
|
dropZone.addEventListener('drop', handleDrop);
|
|
|
|
updateFileList();
|
|
});
|
|
|
|
function updateDescription() {
|
|
const fixType = document.getElementById('fixType').value;
|
|
const t = translations[currentLanguage];
|
|
document.getElementById('description').textContent = t.description[fixType];
|
|
}
|
|
|
|
function updateUI() {
|
|
const fixType = document.getElementById('fixType').value;
|
|
updateDescription();
|
|
|
|
// 显示/隐藏路径选项
|
|
const pathOptions = document.getElementById('pathOptions');
|
|
pathOptions.classList.toggle('show', fixType === 'path');
|
|
}
|
|
|
|
function updateFileList() {
|
|
const filesContainer = document.getElementById('selectedFiles');
|
|
const t = translations[currentLanguage];
|
|
const clearButton = document.getElementById('clearButton');
|
|
|
|
if (selectedFiles.length === 0) {
|
|
filesContainer.innerHTML = `<div class="file-item"><span class="file-name">${t.noFilesSelected}</span></div>`;
|
|
clearButton.disabled = true;
|
|
document.getElementById('fixButton').disabled = true;
|
|
return;
|
|
}
|
|
|
|
filesContainer.innerHTML = selectedFiles.map((file, index) => `
|
|
<div class="file-item">
|
|
<span class="file-name">${file.name}</span>
|
|
<span class="delete-btn" onclick="removeFile(${index})">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M18 6L6 18M6 6l12 12"/>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
`).join('');
|
|
|
|
clearButton.disabled = false;
|
|
document.getElementById('fixButton').disabled = false;
|
|
}
|
|
|
|
function removeFile(index) {
|
|
selectedFiles.splice(index, 1);
|
|
updateFileList();
|
|
}
|
|
|
|
// 添加清除功能
|
|
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 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",
|
|
"LoraLoader",
|
|
"DualCLIPLoader",
|
|
"DualUNETLoader",
|
|
"DualVAELoader",
|
|
"DualCLIPVisionLoader",
|
|
"DualStyleModelLoader",
|
|
"DualUpscaleModelLoader",
|
|
"LoraLoader|pysssss",
|
|
"LoraSave|pysssss",
|
|
"LoraSave",
|
|
"LoraList",
|
|
"LoRA Stacker",
|
|
"GridLoras",
|
|
"LoraSave|pysssss",
|
|
"LoraList|pysssss",
|
|
"LoRA Stacker|pysssss",
|
|
"GridLoras|pysssss"
|
|
]);
|
|
|
|
function isMarkdownOrBase64File(filename) {
|
|
return filename.endsWith('.md') ||
|
|
filename.endsWith('.markdown') ||
|
|
filename.includes('base64') ||
|
|
filename.includes('data:image');
|
|
}
|
|
|
|
function detectPathFormat(workflow) {
|
|
let unixCount = 0;
|
|
let windowsCount = 0;
|
|
|
|
Object.keys(workflow.nodes).forEach(nodeId => {
|
|
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 path = value.content;
|
|
const unixSlashes = (path.match(/\//g) || []).length;
|
|
const windowsSlashes = (path.match(/\\/g) || []).length;
|
|
|
|
if (unixSlashes > windowsSlashes) {
|
|
unixCount++;
|
|
} else if (windowsSlashes > unixSlashes) {
|
|
windowsCount++;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (unixCount > windowsCount) {
|
|
return 'unix';
|
|
} else if (windowsCount > unixCount) {
|
|
return 'windows';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function fixAndDownload() {
|
|
const t = translations[currentLanguage];
|
|
if (selectedFiles.length === 0) {
|
|
document.getElementById('error').textContent = t.errorSelectFile;
|
|
return;
|
|
}
|
|
|
|
const useZip = document.getElementById('zipDownload').checked;
|
|
const fixedFiles = [];
|
|
let processedCount = 0;
|
|
const totalFiles = selectedFiles.length;
|
|
|
|
// 处理进度提示
|
|
const successEl = document.getElementById('success');
|
|
const errorEl = document.getElementById('error');
|
|
successEl.textContent = `Processing 0/${totalFiles} files...`;
|
|
errorEl.textContent = '';
|
|
|
|
selectedFiles.forEach((file, index) => {
|
|
if (isMarkdownOrBase64File(file.name)) {
|
|
console.log(`Skipping file: ${file.name} (markdown or base64)`);
|
|
processedCount++;
|
|
successEl.textContent = `Processing ${processedCount}/${totalFiles} files...`;
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
try {
|
|
const workflow = JSON.parse(e.target.result);
|
|
const fixType = document.getElementById('fixType').value;
|
|
let conversionDirection = '';
|
|
|
|
if (fixType === 'path') {
|
|
const selectedPathType = document.querySelector('input[name="pathType"]:checked');
|
|
conversionDirection = selectedPathType ? selectedPathType.value : 'auto';
|
|
|
|
if (conversionDirection === 'auto') {
|
|
const currentFormat = detectPathFormat(workflow);
|
|
if (currentFormat === 'unix') {
|
|
conversionDirection = 'windows';
|
|
} else if (currentFormat === 'windows') {
|
|
conversionDirection = 'unix';
|
|
} else {
|
|
conversionDirection = 'unix';
|
|
}
|
|
} else if (conversionDirection === 'toWindows') {
|
|
conversionDirection = 'windows';
|
|
} else if (conversionDirection === 'toUnix') {
|
|
conversionDirection = 'unix';
|
|
}
|
|
}
|
|
|
|
const fixedWorkflow = fixWorkflow(workflow, fixType, conversionDirection);
|
|
|
|
let newFileName = file.name;
|
|
if (fixType === 'namespace') {
|
|
newFileName = file.name.replace('.json', '_fixed.json');
|
|
} else if (fixType === 'path') {
|
|
newFileName = file.name.replace('.json', `_${conversionDirection}.json`);
|
|
}
|
|
|
|
if (useZip) {
|
|
// 收集文件用于ZIP打包
|
|
fixedFiles.push({
|
|
name: newFileName,
|
|
content: JSON.stringify(fixedWorkflow, null, 2)
|
|
});
|
|
} else {
|
|
// 直接下载单个文件
|
|
const blob = new Blob([JSON.stringify(fixedWorkflow, null, 2)], {type: 'application/json'});
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = newFileName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
processedCount++;
|
|
successEl.textContent = `Processing ${processedCount}/${totalFiles} files...`;
|
|
|
|
// 当所有文件处理完成后
|
|
if (processedCount === totalFiles) {
|
|
if (useZip && fixedFiles.length > 0) {
|
|
createAndDownloadZip(fixedFiles);
|
|
successEl.textContent = t.successMessage + 'workflows.zip';
|
|
} else if (!useZip) {
|
|
successEl.textContent = t.successMessage +
|
|
(totalFiles > 1 ? `${totalFiles} files` : newFileName);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
errorEl.textContent = `Error processing ${file.name}: ${e.message}`;
|
|
console.error(e);
|
|
processedCount++;
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
});
|
|
}
|
|
|
|
// 添加ZIP创建和下载函数
|
|
async function createAndDownloadZip(files) {
|
|
// 动态加载 JSZip 库
|
|
if (typeof JSZip === 'undefined') {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
|
|
document.head.appendChild(script);
|
|
await new Promise(resolve => script.onload = resolve);
|
|
}
|
|
|
|
const zip = new JSZip();
|
|
files.forEach(file => {
|
|
zip.file(file.name, file.content);
|
|
});
|
|
|
|
const blob = await zip.generateAsync({type: 'blob'});
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'workflows.zip';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
// 主题切换功能
|
|
let currentTheme = localStorage.getItem('theme') || 'light';
|
|
|
|
function setTheme(theme) {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
localStorage.setItem('theme', theme);
|
|
|
|
// 更新图标显示
|
|
const moonIcon = document.querySelector('.moon-icon');
|
|
const sunIcon = document.querySelector('.sun-icon');
|
|
|
|
if (theme === 'dark') {
|
|
moonIcon.classList.remove('active');
|
|
sunIcon.classList.add('active');
|
|
} else {
|
|
moonIcon.classList.add('active');
|
|
sunIcon.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
function toggleTheme() {
|
|
currentTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
setTheme(currentTheme);
|
|
}
|
|
|
|
// 页面加载时初始化主题
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
setTheme(currentTheme);
|
|
});
|
|
|
|
function fixWorkflow(workflow, fixType, conversionDirection) {
|
|
if (fixType === 'namespace') {
|
|
// 深拷贝工作流
|
|
const fixedWorkflow = JSON.parse(JSON.stringify(workflow));
|
|
|
|
// 遍历所有节点
|
|
if (fixedWorkflow.nodes) {
|
|
fixedWorkflow.nodes.forEach(node => {
|
|
// 检查节点类型是否需要添加命名空间
|
|
if (node.type && nodeMapping[node.type]) {
|
|
// 检查是否已经有命名空间前缀
|
|
if (!node.type.startsWith('PaintingCoder::')) {
|
|
node.type = nodeMapping[node.type];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return fixedWorkflow;
|
|
} else if (fixType === 'path') {
|
|
Object.keys(workflow.nodes).forEach(nodeId => {
|
|
const node = workflow.nodes[nodeId];
|
|
const nodeType = node.type || node.class_type;
|
|
|
|
// 如果是需要处理的节点类型
|
|
if (PATH_CONVERSION_NODES.has(nodeType)) {
|
|
// 处理 widgets_values 数组中的所有值
|
|
if (node.widgets_values && Array.isArray(node.widgets_values)) {
|
|
node.widgets_values = node.widgets_values.map(value => {
|
|
if (typeof value === 'string' && !value.includes('base64')) {
|
|
// 转换路径分隔符
|
|
if (conversionDirection === 'unix') {
|
|
return value.replace(/\\/g, '/');
|
|
} else if (conversionDirection === 'windows') {
|
|
return value.replace(/\//g, '\\');
|
|
}
|
|
} else if (typeof value === 'object' && value?.content) {
|
|
// 处理 pysssss 节点的特殊格式
|
|
if (!value.content.includes('base64')) {
|
|
const newContent = conversionDirection === 'unix'
|
|
? value.content.replace(/\\/g, '/')
|
|
: value.content.replace(/\//g, '\\');
|
|
return { ...value, content: newContent };
|
|
}
|
|
}
|
|
return value;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return workflow;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |