Files
ComfyUI-WorkflowPublisher/js/publisher.js

1007 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
// 检测ComfyUI服务端口的函数
async function detectComfyUIPort(host) {
try {
const currentPort = window.location.port;
if (currentPort) {
console.log(`从当前页面URL获取端口: ${currentPort}`);
return parseInt(currentPort);
}
} catch (error) {
console.warn("无法从当前页面获取端口:", error);
}
// 默认返回8188
console.warn("无法检测到ComfyUI端口使用默认端口8188");
return 8188;
}
// 获取本地IP地址的函数
async function getLocalIPAddress() {
try {
// 尝试通过WebRTC获取本地IP地址
const pc = new RTCPeerConnection({
iceServers: [],
});
pc.createDataChannel("");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
return new Promise((resolve) => {
pc.onicecandidate = (event) => {
if (event.candidate) {
const ipMatch = event.candidate.candidate.match(
/([0-9]{1,3}(\.[0-9]{1,3}){3})/
);
if (ipMatch) {
const localIP = ipMatch[1];
// 保留有效的内网IP地址
if (
localIP.startsWith("192.168.") ||
localIP.startsWith("10.") ||
localIP.startsWith("172.") ||
localIP === "127.0.0.1"
) {
pc.close();
resolve(localIP);
}
}
}
};
// 超时处理
setTimeout(() => {
pc.close();
resolve("127.0.0.1"); // 如果获取失败,使用默认地址
}, 3000);
});
} catch (error) {
console.warn("无法获取本地IP地址使用默认地址:", error);
return "127.0.0.1";
}
}
// 本地存储的 key
const COMFY_SERVER_REGISTRATION_KEY = "comfyui_server_registration";
// ComfyUI服务器注册配置 - 使用动态IP地址
let DEFAULT_SERVER_CONFIG = {
name: "comfyui_node_1",
http_url: "http://127.0.0.1:8188", // 将在初始化时更新为实际IP
workflow_service_host: "http://localhost:8000",
};
// 初始化默认配置
async function initializeDefaultConfig() {
try {
const localIP = await getLocalIPAddress();
const port = await detectComfyUIPort(localIP);
DEFAULT_SERVER_CONFIG.http_url = `http://${localIP}:${port}`;
console.log("自动获取本地IP地址和端口:", localIP, port);
} catch (error) {
console.warn("初始化IP地址和端口失败使用默认地址:", error);
}
}
// 获取ComfyUI服务器注册配置
function getComfyServerConfig() {
try {
const stored = localStorage.getItem(COMFY_SERVER_REGISTRATION_KEY);
return stored ? JSON.parse(stored) : { ...DEFAULT_SERVER_CONFIG };
} catch (error) {
console.error("读取ComfyUI服务器配置失败:", error);
return { ...DEFAULT_SERVER_CONFIG };
}
}
// 保存ComfyUI服务器注册配置
function saveComfyServerConfig(config) {
try {
localStorage.setItem(COMFY_SERVER_REGISTRATION_KEY, JSON.stringify(config));
} catch (error) {
console.error("保存ComfyUI服务器配置失败:", error);
}
}
// 获取本地设置
function getLocalSettings() {
try {
const stored = localStorage.getItem("workflow_publisher_settings");
return stored ? JSON.parse(stored) : { host: "" };
} catch (error) {
console.error("读取本地设置失败:", error);
return { host: "" };
}
}
// 保存本地设置
function saveLocalSettings(settings) {
try {
localStorage.setItem(
"workflow_publisher_settings",
JSON.stringify(settings)
);
} catch (error) {
console.error("保存本地设置失败:", error);
}
}
// ComfyUI服务器注册管理器
class ComfyServerManager {
constructor() {
this.registrationInterval = null;
this.heartbeatInterval = null;
this.isRegistered = false;
this.serverConfig = getComfyServerConfig();
}
// 注册服务器到工作流服务
async registerServer() {
if (!this.serverConfig.workflow_service_host) {
return { success: false, error: "未设置工作流服务地址" };
}
try {
const response = await fetch(
`${this.serverConfig.workflow_service_host}/api/comfy/register`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.serverConfig.name,
http_url: this.serverConfig.http_url,
ws_url: this.getWebSocketUrl(),
}),
}
);
if (response.ok) {
const result = await response.json();
console.log("ComfyUI服务器注册成功:", result);
this.isRegistered = true;
this.startHeartbeat();
return { success: true };
} else {
const errorText = await response.text();
const errorMessage = `HTTP ${response.status}: ${
errorText || "服务器响应错误"
}`;
console.error("ComfyUI服务器注册失败:", errorMessage);
return { success: false, error: errorMessage };
}
} catch (error) {
const errorMessage = `网络错误: ${error.message}`;
console.error("注册ComfyUI服务器时发生错误:", errorMessage);
return { success: false, error: errorMessage };
}
}
// 注销服务器
async unregisterServer() {
if (!this.serverConfig.workflow_service_host || !this.isRegistered) {
return { success: false, error: "未设置工作流服务地址或服务器未注册" };
}
try {
const response = await fetch(
`${this.serverConfig.workflow_service_host}/api/comfy/unregister`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: this.serverConfig.name }),
}
);
if (response.ok) {
console.log("ComfyUI服务器注销成功");
this.isRegistered = false;
this.stopHeartbeat();
return { success: true };
} else {
const errorText = await response.text();
const errorMessage = `HTTP ${response.status}: ${
errorText || "服务器响应错误"
}`;
console.error("ComfyUI服务器注销失败:", errorMessage);
return { success: false, error: errorMessage };
}
} catch (error) {
const errorMessage = `网络错误: ${error.message}`;
console.error("注销ComfyUI服务器时发生错误:", errorMessage);
return { success: false, error: errorMessage };
}
}
// 发送心跳
async sendHeartbeat() {
if (!this.serverConfig.workflow_service_host || !this.isRegistered) {
return false;
}
try {
const response = await fetch(
`${this.serverConfig.workflow_service_host}/api/comfy/heartbeat`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: this.serverConfig.name }),
}
);
if (response.ok) {
console.debug("心跳发送成功");
return true;
} else {
console.warn("心跳发送失败:", response.status);
// 如果心跳失败,尝试重新注册
if (response.status === 404) {
console.log("服务器不存在,尝试重新注册...");
this.isRegistered = false;
await this.registerServer();
}
return false;
}
} catch (error) {
console.error("发送心跳时发生错误:", error);
// 如果网络错误,尝试重新注册
this.isRegistered = false;
await this.registerServer();
return false;
}
}
// 启动心跳
startHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
// 每30秒发送一次心跳
this.heartbeatInterval = setInterval(() => {
this.sendHeartbeat();
}, 30000);
console.log("ComfyUI服务器心跳已启动");
}
// 停止心跳
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
console.log("ComfyUI服务器心跳已停止");
}
// 更新服务器配置
updateConfig(newConfig) {
this.serverConfig = { ...this.serverConfig, ...newConfig };
saveComfyServerConfig(this.serverConfig);
// 如果已注册,需要重新注册以更新配置
if (this.isRegistered) {
this.unregisterServer().then((result) => {
if (result.success) {
setTimeout(() => this.registerServer(), 1000);
} else {
console.warn("更新配置时注销失败:", result.error);
}
});
}
}
// 设置工作流服务地址
setWorkflowServiceHost(host) {
this.serverConfig.workflow_service_host = host;
saveComfyServerConfig(this.serverConfig);
}
// 获取WebSocket URL自动从HTTP URL生成
getWebSocketUrl() {
if (!this.serverConfig.http_url) return "";
const url = new URL(this.serverConfig.http_url);
// 根据协议自动选择ws或wss
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${url.host}/ws`;
}
// 获取服务器状态
async getServerStatus() {
if (!this.serverConfig.workflow_service_host) {
return null;
}
try {
const response = await fetch(
`${this.serverConfig.workflow_service_host}/api/comfy/status/${this.serverConfig.name}`
);
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error("获取服务器状态失败:", error);
}
return null;
}
// 获取系统健康状态
async getSystemHealth() {
if (!this.serverConfig.workflow_service_host) {
return null;
}
try {
const response = await fetch(
`${this.serverConfig.workflow_service_host}/api/comfy/health`
);
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error("获取系统健康状态失败:", error);
}
return null;
}
// 动态更新IP地址和端口
async updateLocalIPAddress() {
try {
const localIP = await getLocalIPAddress();
const port = await detectComfyUIPort(localIP);
const newHttpUrl = `http://${localIP}:${port}`;
if (this.serverConfig.http_url !== newHttpUrl) {
this.serverConfig.http_url = newHttpUrl;
saveComfyServerConfig(this.serverConfig);
console.log("已更新本地IP地址和端口:", localIP, port);
return { ip: localIP, port: port };
}
return null;
} catch (error) {
console.warn("更新IP地址和端口失败:", error);
return null;
}
}
// 同步注册状态
syncRegistrationStatus() {
if (this.serverConfig.workflow_service_host) {
this.getServerStatus()
.then((status) => {
if (status) {
this.isRegistered = true;
console.log("同步注册状态: 已注册");
} else {
this.isRegistered = false;
console.log("同步注册状态: 未注册");
}
})
.catch((error) => {
console.warn("同步注册状态失败:", error);
this.isRegistered = false;
});
}
}
}
// 自定义 fetchApi 函数使用ComfyUI服务器配置中的工作流服务地址
async function customFetchApi(endpoint, options = {}) {
try {
// 从ComfyUI服务器配置获取工作流服务地址
const serverConfig = getComfyServerConfig();
// 如果没有配置工作流服务地址,回退到默认的 api.fetchApi
if (!serverConfig.workflow_service_host) {
console.warn("未配置工作流服务地址,使用默认的 ComfyUI API");
return await api.fetchApi(endpoint, options);
}
// 构建完整的 URL
const host = serverConfig.workflow_service_host.replace(/\/$/, ""); // 移除末尾的斜杠
const fullUrl = `${host}${endpoint}`;
console.log(`使用工作流服务地址请求: ${fullUrl}`);
// 使用 fetch 发送请求
const response = await fetch(fullUrl, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
return response;
} catch (error) {
console.error("自定义 fetchApi 失败,回退到默认 API:", error);
// 如果自定义请求失败,回退到默认的 api.fetchApi
return await api.fetchApi(endpoint, options);
}
}
function parseJson(text) {
try {
return JSON.parse(text);
} catch (error) {
console.error("JSON解析失败:", error, "原始数据:", text);
return null;
}
}
function createElement(tagName, props) {
const element = document.createElement(tagName);
if (props.style) element.style = props.style;
if (props.className) element.className = props.className;
if (props.id) element.id = props.id;
if (props.textContent) element.textContent = props.textContent;
if (props.innerHTML) element.innerHTML = props.innerHTML;
if (props.onclick) element.onclick = props.onclick;
if (props.children) {
if (Array.isArray(props.children)) {
props.children.forEach((child) => {
element.appendChild(child);
});
} else {
element.appendChild(props.children);
}
}
return element;
}
// Modal class (保持不变)
class Modal {
constructor(id, title, full_width = false) {
this.id = id;
this.element = document.createElement("div");
this.element.id = this.id;
this.element.style.cssText = `position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--comfy-menu-bg); color: var(--fg-color); padding: 20px; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.5); z-index: 1001; display: none; flex-direction: column; gap: 15px; min-width: ${
full_width ? "500px" : "350px"
}; max-width: 600px; max-height: 80vh;`;
this.element.innerHTML = `<h3 style="margin: 0; padding-bottom: 10px; border-bottom: 1px solid var(--border-color);">${title}</h3><div class="content" style="overflow-y: auto; padding-right: 5px;"></div><div class="buttons" style="display: flex; justify-content: flex-end; gap: 10px;"></div>`;
document.body.appendChild(this.element);
}
show() {
this.element.style.display = "flex";
}
hide() {
this.element.style.display = "none";
}
setContent(html) {
this.element.querySelector(".content").innerHTML = html;
}
addButtons(buttons) {
const container = this.element.querySelector(".buttons");
container.innerHTML = "";
buttons.forEach((btn) => {
const button = document.createElement("button");
button.textContent = btn.label;
button.onclick = btn.callback;
container.appendChild(button);
});
}
}
const createButton = (innerHTML, title, { onClick }) => {
const btn = document.createElement("button");
btn.className = "jags-button";
btn.innerHTML = innerHTML;
btn.title = title;
btn.onclick = onClick;
return btn;
};
app.registerExtension({
name: "Comfy.WorkflowPublisher",
async setup() {
// 初始化默认配置获取本地IP地址
await initializeDefaultConfig();
const addCustomStyles = () => {
const styleId = "jags-publisher-styles";
if (document.getElementById(styleId)) return;
const style = document.createElement("style");
style.id = styleId;
style.innerHTML = `
.jags-button { background-color: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); color: var(--fg-color); border-radius: 8px; padding: 5px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease-in-out; outline: none; position: relative; }
.jags-button:hover { background-color: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.2); transform: translateY(-1px); box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.2); }
.jags-button:active { background-color: rgba(255, 255, 255, 0.15); transform: translateY(0px) scale(0.98); box-shadow: none; }
.jags-accordion-header { background-color: var(--comfy-input-bg); width: 100%; border: 1px solid var(--border-color); padding: 10px; text-align: left; cursor: pointer; transition: background-color 0.2s; display: flex; justify-content: space-between; align-items: center; border-radius: 4px; }
.jags-accordion-header:hover { background-color: var(--comfy-menu-bg); }
.jags-accordion-panel { padding-left: 15px; margin-top: 5px; border-left: 2px solid var(--border-color); max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out, margin-top 0.3s ease-out; }
.jags-version-item { display: flex; justify-content: space-between; align-items: center; padding: 2px 5px; border-radius: 3px; cursor: pointer; }
.jags-version-item:hover { background-color: var(--comfy-input-bg); }
.jags-version-item-label { flex-grow: 1; padding: 6px 0; }
.jags-delete-btn { background: transparent; border: none; color: var(--error-color); cursor: pointer; font-size: 1.2em; padding: 4px 8px; border-radius: 50%; line-height: 1; }
.jags-delete-btn:hover { background-color: var(--error-color); color: var(--comfy-menu-bg); }
`;
document.head.appendChild(style);
};
addCustomStyles();
const waitForElement = (selector, callback, timeout = 10000) => {
const startTime = Date.now();
const interval = setInterval(() => {
const element = document.getElementsByClassName(selector)[0];
if (element) {
clearInterval(interval);
callback(element);
} else if (Date.now() - startTime > timeout) {
clearInterval(interval);
console.error(
`Workflow Publisher: Timed out waiting for element: "${selector}"`
);
}
}, 100);
};
waitForElement("queue-button-group flex", (menu) => {
const container = document.createElement("div");
container.id = "jags-publisher-container";
container.style.display = "flex";
container.style.alignItems = "center";
container.style.gap = "8px";
const iconGet = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>`;
const iconPublish = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" /><path d="M7 9l5-5 5 5" /><path d="M12 4v12" /></svg>`;
const btnGet = createButton(iconGet, "从服务器获取工作流", {
onClick: () => showWorkflowListDialog(),
});
const btnPublish = createButton(iconPublish, "发布当前工作流", {
onClick: () => showPublishDialog(),
});
const btnComfyServer = createButton(`🖥️`, "ComfyUI服务器管理", {
onClick: () => showComfyServerDialog(),
});
const separator = document.createElement("div");
separator.style.cssText =
"border-left: 1px solid var(--border-color); height: 22px; margin: 0 4px;";
container.append(btnGet, btnPublish, btnComfyServer, separator);
menu.insertBefore(container, menu.firstChild);
});
const publishDialog = new Modal(
"workflow-publisher-publish-dialog",
"发布工作流"
);
const workflowListDialog = new Modal(
"workflow-publisher-list-dialog",
"工作流库",
true
);
const comfyServerDialog = new Modal(
"comfy-server-dialog",
"ComfyUI服务器管理",
true
);
async function showWorkflowListDialog() {
workflowListDialog.setContent(
'<p style="text-align:center;">正在从服务器加载工作流...</p>'
);
workflowListDialog.addButtons([
{ label: "关闭", callback: () => workflowListDialog.hide() },
]);
workflowListDialog.show();
try {
const response = await customFetchApi("/api/workflow");
if (!response.ok) {
throw new Error(
`服务器错误: ${response.status} ${response.statusText}`
);
}
const text = await response.text();
const workflows = parseJson(text);
if (workflows.length === 0) {
workflowListDialog.setContent(
'<p style="text-align:center;">服务器上没有找到任何工作流。</p>'
);
return;
}
const accordionContainer = document.createElement("div");
accordionContainer.style.cssText =
"display: flex; flex-direction: column; gap: 8px;";
workflows.forEach((workflow) => {
const workflowLabel = document.createElement("span");
workflowLabel.textContent = workflow.name;
workflowLabel.onclick = () => {
if (
confirm(
`确定要加载工作流 "${workflow.name}" 吗?\n这将覆盖当前画布。`
)
) {
const file = new File(
[JSON.stringify(workflow.workflow)],
workflow.name,
{ type: "application/json" }
);
app.handleFile(file);
}
};
accordionContainer.appendChild(workflowLabel);
});
workflowListDialog.setContent("");
workflowListDialog.element
.querySelector(".content")
.appendChild(accordionContainer);
} catch (error) {
console.error("加载工作流失败:", error);
workflowListDialog.setContent(
`<p style="color:var(--error-color); text-align:center;">加载失败: ${error.message}</p>`
);
}
}
async function publishWorkflow() {
const flowName = document
.getElementById("publisher-workflow-name")
.value.trim();
if (!flowName) {
alert("请输入工作流的基础名称。");
return;
}
const statusEl = document.getElementById("publisher-status");
statusEl.textContent = "正在准备发布...";
statusEl.style.color = "orange";
const versionString = new Date().toISOString();
try {
const workflow = await app.graph.serialize();
const body = { name: flowName, workflow: workflow };
statusEl.textContent = `正在发布为 "${flowName}",版本号: ${versionString}...`;
const response = await customFetchApi("/api/workflow", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`服务器返回错误: ${response.status} ${response.statusText}${
errorText ? ` - ${errorText}` : ""
}`
);
}
const text = await response.text();
let result;
try {
result = text ? JSON.parse(text) : {};
} catch (parseError) {
console.error("JSON解析失败:", parseError, "原始响应:", text);
result = { message: "发布成功,但服务器返回的数据格式无效" };
}
statusEl.textContent = `发布成功! ${result.message || ""}`;
statusEl.style.color = "var(--success-color)";
setTimeout(() => publishDialog.hide(), 2000);
} catch (error) {
statusEl.textContent = `发布失败: ${error.message}`;
statusEl.style.color = "var(--error-color)";
console.error("发布失败:", error);
}
}
async function showPublishDialog() {
try {
publishDialog.setContent(
`<label for="publisher-workflow-name" style="display: block; margin-bottom: 5px;">工作流基础名称:</label><input id="publisher-workflow-name" type="text" placeholder="例如: SDXL高级出图" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 5px; border-radius: 4px;"><p id="publisher-status" style="margin-top: 10px; color: var(--success-color); min-height: 1.2em;"></p>`
);
publishDialog.addButtons([
{ label: "取消", callback: () => publishDialog.hide() },
{ label: "确认发布", callback: publishWorkflow },
]);
publishDialog.show();
} catch (error) {
console.error("显示发布对话框失败:", error);
alert(`显示对话框失败: ${error.message}`);
}
}
// ComfyUI 服务器管理对话框
async function showComfyServerDialog() {
try {
const serverConfig = getComfyServerConfig();
const settings = getLocalSettings();
// 创建服务器配置表单
const configForm = `
<div style="display: flex; flex-direction: column; gap: 15px;">
<div>
<label for="server-name" style="display: block; margin-bottom: 5px;">当前服务节点名称:</label>
<input id="server-name" type="text" value="${
serverConfig.name
}" placeholder="例如: comfyui_node_1" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
</div>
<div>
<label for="server-http-url" style="display: block; margin-bottom: 5px;">当前服务节点地址:</label>
<input id="server-http-url" type="text" value="${
serverConfig.http_url
}" placeholder="例如: http://127.0.0.1:8188" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
<small style="color: var(--border-color); font-size: 0.9em;">WebSocket地址将自动生成为: <span id="ws-url-preview">${
serverConfig.http_url
? serverConfig.http_url.replace(/^https?:\/\//, "ws://") +
"/ws"
: ""
}</span></small>
</div>
<div>
<label for="workflow-service-host" style="display: block; margin-bottom: 5px;">工作流服务地址 (统一管理地址):</label>
<input id="workflow-service-host" type="text" value="${
settings.host
}" placeholder="例如: http://localhost:8000" style="width: 100%; box-sizing: border-box; background: var(--comfy-input-bg); color: var(--fg-color); border: 1px solid var(--border-color); padding: 8px; border-radius: 4px;">
</div>
<div id="server-status" style="padding: 10px; border-radius: 4px; background: var(--comfy-input-bg); border: 1px solid var(--border-color);">
<strong>服务器状态:</strong> <span id="status-text">未连接</span>
</div>
<div id="server-actions" style="display: flex; gap: 10px; flex-wrap: wrap;">
<button id="btn-register" class="jags-button" style="flex: 1; min-width: 120px;">注册服务器</button>
<button id="btn-unregister" class="jags-button" style="flex: 1; min-width: 120px;">注销服务器</button>
<button id="btn-refresh-status" class="jags-button" style="flex: 1; min-width: 120px;">刷新状态</button>
<button id="btn-refresh-ip" class="jags-button" style="flex: 1; min-width: 120px;">刷新IP和端口</button>
</div>
</div>
`;
comfyServerDialog.setContent(configForm);
comfyServerDialog.addButtons([
{ label: "关闭", callback: () => comfyServerDialog.hide() },
]);
comfyServerDialog.show();
// 初始化 ComfyServerManager
const serverManager = new ComfyServerManager();
if (settings.host) {
serverManager.setWorkflowServiceHost(settings.host);
// 同步注册状态
serverManager.syncRegistrationStatus();
}
// 绑定事件
const btnRegister = document.getElementById("btn-register");
const btnUnregister = document.getElementById("btn-unregister");
const btnRefreshStatus = document.getElementById("btn-refresh-status");
const btnRefreshIP = document.getElementById("btn-refresh-ip");
const statusText = document.getElementById("status-text");
// 添加HTTP地址输入框的实时预览功能
const httpUrlInput = document.getElementById("server-http-url");
const wsUrlPreview = document.getElementById("ws-url-preview");
httpUrlInput.addEventListener("input", function () {
const httpUrl = this.value;
if (httpUrl) {
try {
const url = new URL(httpUrl);
// 根据协议自动选择ws或wss
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${url.host}/ws`;
wsUrlPreview.textContent = wsUrl;
} catch (error) {
wsUrlPreview.textContent = "无效的URL格式";
}
} else {
wsUrlPreview.textContent = "";
}
});
// 注册服务器
btnRegister.onclick = async () => {
const newConfig = {
name: document.getElementById("server-name").value,
http_url: document.getElementById("server-http-url").value,
};
const workflowHost = document.getElementById(
"workflow-service-host"
).value;
if (workflowHost) {
serverManager.setWorkflowServiceHost(workflowHost);
// 保存到本地设置
saveLocalSettings({ host: workflowHost });
}
serverManager.updateConfig(newConfig);
btnRegister.disabled = true;
btnRegister.textContent = "注册中...";
const result = await serverManager.registerServer();
if (result.success) {
statusText.textContent = "已注册";
statusText.style.color = "var(--success-color)";
btnRegister.textContent = "已注册";
btnRegister.disabled = true;
btnUnregister.disabled = false;
} else {
const errorMessage = result.error || "未知错误";
statusText.textContent = `注册失败: ${errorMessage}`;
statusText.style.color = "var(--error-color)";
btnRegister.textContent = "注册服务器";
btnRegister.disabled = false;
}
};
// 注销服务器
btnUnregister.onclick = async () => {
btnUnregister.disabled = true;
btnUnregister.textContent = "注销中...";
const result = await serverManager.unregisterServer();
if (result.success) {
statusText.textContent = "已注销";
statusText.style.color = "orange";
btnRegister.disabled = false;
btnRegister.textContent = "注册服务器";
btnUnregister.disabled = true;
btnUnregister.textContent = "注销服务器";
} else {
const errorMessage = result.error || "未知错误";
statusText.textContent = `注销失败: ${errorMessage}`;
statusText.style.color = "var(--error-color)";
btnUnregister.disabled = false;
btnUnregister.textContent = "注销服务器";
}
};
// 刷新状态
btnRefreshStatus.onclick = async () => {
if (settings.host) {
serverManager.setWorkflowServiceHost(settings.host);
const status = await serverManager.getServerStatus();
// 调试信息
console.log("服务器状态:", status);
if (status && status.last_heartbeat) {
console.log("最后心跳时间原始值:", status.last_heartbeat);
console.log("时间类型:", typeof status.last_heartbeat);
}
if (status) {
// 安全地处理时间显示
let heartbeatTime = "未知";
if (status.last_heartbeat) {
try {
// 尝试解析ISO格式的时间字符串
const date = new Date(status.last_heartbeat);
if (!isNaN(date.getTime())) {
heartbeatTime = date.toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
} else {
heartbeatTime = "时间格式错误";
}
} catch (error) {
console.warn(
"时间解析失败:",
error,
"原始时间:",
status.last_heartbeat
);
heartbeatTime = "时间解析失败";
}
}
statusText.textContent = `在线 - 最后心跳: ${heartbeatTime}`;
statusText.style.color = "var(--success-color)";
// 同步更新本地注册状态
serverManager.isRegistered = true;
btnRegister.disabled = true;
btnRegister.textContent = "已注册";
btnUnregister.disabled = false;
} else {
statusText.textContent = "未注册";
statusText.style.color = "orange";
// 同步更新本地注册状态
serverManager.isRegistered = false;
btnRegister.disabled = false;
btnRegister.textContent = "注册服务器";
btnUnregister.disabled = true;
}
}
};
// 刷新IP地址和端口
btnRefreshIP.onclick = async () => {
btnRefreshIP.disabled = true;
btnRefreshIP.textContent = "刷新中...";
try {
const result = await serverManager.updateLocalIPAddress();
if (result) {
// 更新输入框显示
document.getElementById(
"server-http-url"
).value = `http://${result.ip}:${result.port}`;
// 更新WebSocket预览
const wsUrlPreview = document.getElementById("ws-url-preview");
wsUrlPreview.textContent = `ws://${result.ip}:${result.port}/ws`;
statusText.textContent = `IP地址和端口已更新为: ${result.ip}:${result.port}`;
statusText.style.color = "var(--success-color)";
} else {
statusText.textContent = "IP地址和端口无需更新";
statusText.style.color = "orange";
}
} catch (error) {
statusText.textContent = `IP地址和端口更新失败: ${error.message}`;
statusText.style.color = "var(--error-color)";
} finally {
btnRefreshIP.disabled = false;
btnRefreshIP.textContent = "刷新IP和端口";
}
};
// 初始状态检查
if (settings.host) {
btnRefreshStatus.click();
} else {
// 如果没有设置工作流服务地址,确保按钮状态正确
btnRegister.disabled = false;
btnUnregister.disabled = true;
}
// 自动刷新IP地址和端口
setTimeout(async () => {
try {
const result = await serverManager.updateLocalIPAddress();
if (result) {
// 更新输入框显示
document.getElementById(
"server-http-url"
).value = `http://${result.ip}:${result.port}`;
// 更新WebSocket预览
const wsUrlPreview = document.getElementById("ws-url-preview");
wsUrlPreview.textContent = `ws://${result.ip}:${result.port}/ws`;
console.log(
"对话框显示时自动更新IP地址和端口:",
result.ip,
result.port
);
}
} catch (error) {
console.warn("自动更新IP地址和端口失败:", error);
}
}, 500);
// 延迟同步按钮状态,确保与内部状态一致
setTimeout(() => {
if (serverManager.isRegistered) {
btnRegister.disabled = true;
btnRegister.textContent = "已注册";
btnUnregister.disabled = false;
} else {
btnRegister.disabled = false;
btnRegister.textContent = "注册服务器";
btnUnregister.disabled = true;
}
}, 1000);
} catch (error) {
console.error("显示ComfyUI服务器管理对话框失败:", error);
alert(`显示对话框失败: ${error.message}`);
}
}
},
});