750 lines
28 KiB
JavaScript
750 lines
28 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
||
import { api } from "../../scripts/api.js";
|
||
|
||
// 本地存储的 key
|
||
const COMFY_SERVER_REGISTRATION_KEY = "comfyui_server_registration";
|
||
|
||
// ComfyUI服务器注册配置
|
||
const DEFAULT_SERVER_CONFIG = {
|
||
name: "comfyui_node_1",
|
||
http_url: "http://127.0.0.1:8188",
|
||
workflow_service_host: "http://localhost:8000",
|
||
};
|
||
|
||
// 获取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) {
|
||
console.warn("未设置工作流服务地址,无法注册");
|
||
return false;
|
||
}
|
||
|
||
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 true;
|
||
} else {
|
||
const errorText = await response.text();
|
||
console.error("ComfyUI服务器注册失败:", response.status, errorText);
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
console.error("注册ComfyUI服务器时发生错误:", error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 注销服务器
|
||
async unregisterServer() {
|
||
if (!this.serverConfig.workflow_service_host || !this.isRegistered) {
|
||
return false;
|
||
}
|
||
|
||
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 true;
|
||
} else {
|
||
console.error("ComfyUI服务器注销失败:", response.status);
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
console.error("注销ComfyUI服务器时发生错误:", error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 发送心跳
|
||
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(() => {
|
||
setTimeout(() => this.registerServer(), 1000);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 设置工作流服务地址
|
||
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;
|
||
}
|
||
}
|
||
|
||
// 自定义 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() {
|
||
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>
|
||
</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);
|
||
}
|
||
|
||
// 绑定事件
|
||
const btnRegister = document.getElementById("btn-register");
|
||
const btnUnregister = document.getElementById("btn-unregister");
|
||
const btnRefreshStatus = document.getElementById("btn-refresh-status");
|
||
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 success = await serverManager.registerServer();
|
||
if (success) {
|
||
statusText.textContent = "已注册";
|
||
statusText.style.color = "var(--success-color)";
|
||
btnRegister.textContent = "已注册";
|
||
btnRegister.disabled = true;
|
||
btnUnregister.disabled = false;
|
||
} else {
|
||
statusText.textContent = "注册失败";
|
||
statusText.style.color = "var(--error-color)";
|
||
btnRegister.textContent = "注册服务器";
|
||
btnRegister.disabled = false;
|
||
}
|
||
};
|
||
|
||
// 注销服务器
|
||
btnUnregister.onclick = async () => {
|
||
btnUnregister.disabled = true;
|
||
btnUnregister.textContent = "注销中...";
|
||
|
||
const success = await serverManager.unregisterServer();
|
||
if (success) {
|
||
statusText.textContent = "已注销";
|
||
statusText.style.color = "orange";
|
||
btnRegister.disabled = false;
|
||
btnRegister.textContent = "注册服务器";
|
||
btnUnregister.disabled = true;
|
||
btnUnregister.textContent = "注销服务器";
|
||
} else {
|
||
statusText.textContent = "注销失败";
|
||
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();
|
||
if (status) {
|
||
statusText.textContent = `在线 - 最后心跳: ${new Date(
|
||
status.last_heartbeat
|
||
).toLocaleString()}`;
|
||
statusText.style.color = "var(--success-color)";
|
||
btnRegister.disabled = true;
|
||
btnRegister.textContent = "已注册";
|
||
btnUnregister.disabled = false;
|
||
} else {
|
||
statusText.textContent = "未注册";
|
||
statusText.style.color = "orange";
|
||
btnRegister.disabled = false;
|
||
btnRegister.textContent = "注册服务器";
|
||
btnUnregister.disabled = true;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 初始状态检查
|
||
if (settings.host) {
|
||
btnRefreshStatus.click();
|
||
}
|
||
} catch (error) {
|
||
console.error("显示ComfyUI服务器管理对话框失败:", error);
|
||
alert(`显示对话框失败: ${error.message}`);
|
||
}
|
||
}
|
||
},
|
||
});
|